新版 Server酱免费额度低,使用Python写部署麻烦,于是试着用Java写一个。
刚学Java,记录一下各步骤。

本篇为v0.0.1版编写笔记
GitHub:1bit-cc/smschan: 企业微信消息推送服务,用于...... (github.com)

第一版计划

环境

  • 项目名为smschan
  • Java版本为1.8
  • 构建工具使用Maven
  • 使用VSCode编写

设计

  • 配置文件全部用txt储存,方便更改。
  • 只支持单人使用
  • 只支持企业微信

代码 - 配置文件部分

读写文件

用来读配置文件
  • 代码
fileclass.java
用来放文件读写函数的类
读取是一次性读文件全部内容,写入是覆写
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;

public class fileclass {
    // 读取指定文件
    public String readFileString(String filepath) throws IOException {
        File tokenFile = new File(filepath);
        byte[] bytesToken = new byte[1024];
        StringBuffer sb = new StringBuffer();
        FileInputStream in = new FileInputStream(tokenFile);
        int len;
        while ((len = in.read(bytesToken)) != -1) {
            sb.append(new String(bytesToken, 0, len));
        }
        in.close();
        String rDat = sb.toString();
        return rDat;
    }

    // 写入指定文件
    public void writeFileString(String filepath, String writeData) throws IOException {
        FileWriter writeFile = new FileWriter(filepath);
        writeFile.write(writeData);
        writeFile.close();
    }
}
test.java
主函数
import java.io.IOException;

class test {
    public static void main(String[] args) throws IOException {
        fileclass fileOperation = new fileclass();
        // 读取
        System.out.print("读取结果:");
        System.out.println(fileOperation.readFileString("test.txt"));
        // 写入
        fileOperation.writeFileString("test.txt", "abcabc,123123");
        System.out.println("写入:abcabc,123123");
        // 再读取
        System.out.print("读取结果:");
        System.out.println(fileOperation.readFileString("test.txt"));
    }
}
  • 运行结果
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
读取结果:abcd1234,qwer1234
写入:abcabc,123123
读取结果:abcabc,123123

字符串分割成字符数组

部分配置文件有多个参数,例如企业微信API调用凭证就有 corpidcorpsecret两个参数。
  • 代码
class test {
    public static void main(String[] args) {
        String wxData = "abcd1234,qwer1234";
        String[] wxDataArray = wxData.split(",");
        System.out.println(wxDataArray[0]);
        System.out.println(wxDataArray[1]);
    }
}
  • 运行结果
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
abcd1234
qwer1234

代码 - 企业微信API使用部分

发送GET/POST请求

给企业微信服务器发送请求要用到
  • 代码
HttpURLConnectionWX.java
发送请求用
参考来源:Java代码发送Http的GET和POST请求 - itprobie-菜鸟程序员 - 博客园 (cnblogs.com)
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * Created by chengxia on 2018/12/4.
 */
public class HttpURLConnectionWX {
    public String doPost(String URL, String jsonStr) {
        OutputStreamWriter out = null;
        BufferedReader in = null;
        StringBuilder result = new StringBuilder();
        HttpURLConnection conn = null;
        try {
            URL url = new URL(URL);
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            // 发送POST请求必须设置为true
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 设置连接超时时间和读取超时时间
            conn.setConnectTimeout(30000);
            conn.setReadTimeout(10000);
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setRequestProperty("Accept", "application/json");
            // 获取输出流
            out = new OutputStreamWriter(conn.getOutputStream());
            out.write(jsonStr);
            out.flush();
            out.close();
            // 取得输入流,并使用Reader读取
            if (200 == conn.getResponseCode()) {
                in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
                String line;
                while ((line = in.readLine()) != null) {
                    result.append(line);
                    System.out.println(line);
                }
            } else {
                System.out.println("ResponseCode is an error code:" + conn.getResponseCode());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
        return result.toString();
    }

    public String doGet(String URL) {
        HttpURLConnection conn = null;
        InputStream is = null;
        BufferedReader br = null;
        StringBuilder result = new StringBuilder();
        try {
            // 创建远程url连接对象
            URL url = new URL(URL);
            // 通过远程url连接对象打开一个连接,强转成HTTPURLConnection类
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            // 设置连接超时时间和读取超时时间
            conn.setConnectTimeout(15000);
            conn.setReadTimeout(60000);
            conn.setRequestProperty("Accept", "application/json");
            // 发送请求
            conn.connect();
            // 通过conn取得输入流,并使用Reader读取
            if (200 == conn.getResponseCode()) {
                is = conn.getInputStream();
                br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                String line;
                while ((line = br.readLine()) != null) {
                    result.append(line);
                    System.out.println(line);
                }
            } else {
                System.out.println("ResponseCode is an error code:" + conn.getResponseCode());
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
                if (is != null) {
                    is.close();
                }
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
            conn.disconnect();
        }
        return result.toString();
    }
}
test.java
主函数
import java.io.IOException;

class test {
    public static void main(String[] args) throws IOException {
        HttpURLConnectionWX httpclass = new HttpURLConnectionWX();
        String URL = "http://127.0.0.1?name=abc123";
        String jsonStr = "{\"age\":\"23\"}";
        String getData = httpclass.doGet(URL);
        String postData = httpclass.doPost(URL, jsonStr);
        System.out.println("GET返回数据:" + getData);
        System.out.println("POST返回数据:" + postData);
    }
}
ser.py
用来测试的python服务端,基于Flask
import json
import charset_normalizer
from flask import Flask, request

app = Flask(__name__)

@app.route('/', methods=['GET'])
def registerGET():
    byu = request.stream.read()
    # print(request.headers)
    print(charset_normalizer.detect(byu))
    print(byu)
    return 'is GET,name = ' + request.args.get('name')

@app.route('/', methods=['POST'])
def registerPOST():
    byu = request.stream.read()
    # print(request.headers)
    print(charset_normalizer.detect(byu))
    print(byu)
    return 'is POST,name = ' + request.args.get('name') + ',Age is ' + json.loads(str(byu,encoding='utf-8'))['age']

if __name__ == '__main__':
    app.run(port=5000, debug=True)
  • 运行结果
java
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
is GET,name = abc123
is POST,name = abc123,Age is 23
GET返回数据:is GET,name = abc123
POST返回数据:is POST,name = abc123,Age is 23
Python
* Serving Flask app 'ser' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 112-135-227
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
{'encoding': 'utf-8', 'language': '', 'confidence': 1.0}
b''
127.0.0.1 - - [14/May/2022 14:36:41] "GET /?name=abc123 HTTP/1.1" 200 -
{'encoding': 'ascii', 'language': 'English', 'confidence': 1.0}
b'{"age":"23"}'
127.0.0.1 - - [14/May/2022 14:36:41] "POST /?name=abc123 HTTP/1.1" 200 -

JSON数据解析

用来解析企业微信API返回的数据
  • 情况

需要导入阿里的包 com.alibaba.fastjson.JSONObject
使用了Maven工具构建。

  • Maven的 pom.xml修改
加入如下包应用
<!-- 省略 -->
<dependencies>
    <!-- 省略 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.72</version>
    </dependency>
</dependencies>
<!-- 省略 -->
  • 示例JSON
{
    "name": "abc",
    "dic": {
        "a": "1",
        "b": "2"
    },
    "list": [
        "1",
        "2"
    ]
}
  • 代码
package com;

import com.alibaba.fastjson.JSONObject;

class atest {
    public static void main(String[] args) {
        // 模拟API返回的JSON数据
        String jsonString = "{\"name\":\"abc\",\"dic\":{\"a\":\"1\",\"b\":\"2\"},\"list\":[\"1\",\"2\"]}";
        JSONObject JSONrAccessTokenHttp = JSONObject.parseObject(jsonString);
        System.out.println(JSONrAccessTokenHttp.getString("name"));
        System.out.println(JSONrAccessTokenHttp.getString("dic"));
        System.out.println(JSONrAccessTokenHttp.getJSONObject("dic").getString("a"));
        System.out.println(JSONrAccessTokenHttp.getString("list"));
        System.out.println(JSONrAccessTokenHttp.getJSONArray("list").get(0));
    }
}
  • 运行结果
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
abc
{"a":"1","b":"2"}
1
["1","2"]
1

Spring Boot错误处理

直接根据HTTP状态码返回对应网页 - 已弃用

  • 新建不同错误码返回的html文件
新建 404.html500.html等html文件,放在 .\static\文件夹
<html>

<head>
    <meta charset="UTF-8">
    <title>400</title>
    <link rel="icon" sizes="any" mask="" href="https://img.alicdn.com/tfs/TB1qNVdXlGw3KVjSZFDXXXWEpXa-620-620.png">
    <style type="text/css">
        body {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            color: #58889f;
            display: table;
            font-weight: 100;
            font-family: 'Lato';
        }

        .container {
            text-align: center;
            display: table-cell;
            vertical-align: middle;
        }

        .content {
            text-align: center;
            display: inline-block;
        }

        .title {
            font-size: 42px;
            margin-bottom: 40px;
        }
    </style>
</head>

<body>
    <div class="container">
        <div class="content">
            <div class="title">
                <p>400 错误请求</p>
            </div>
        </div>
    </div>
</body>

</html>
  • 在代码中增加错误转跳
// 上面的省略
@RestController
public class SmachanController {
    // 上面的省略

    @Bean
    public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer() {
        return (factory -> {
            ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND,
                    "/404.html");
            ErrorPage errorPage400 = new ErrorPage(HttpStatus.BAD_REQUEST,
                    "/400.html");
            ErrorPage errorPage403 = new ErrorPage(HttpStatus.FORBIDDEN,
                    "/403.html");
            ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR,
                    "/500.html");
            factory.addErrorPages(errorPage404, errorPage400, errorPage401,
                    errorPage500);
        });
    }
}

根据不同错误返回不同的JSON数据,附带错误说明

看别人示例可以设计成根据客户端类型返回text/html数据或者json数据。
但后来想既然是api干脆全部返回json数据算了。
  • 新建一个类,专门用来返回json数据
路径 src\main\java\com\example\smschan\error\ErrorPage.java
errormsg`为传入的错误说明
package com.example.smschan.error;

import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ErrorPage {
    public ResponseEntity<Object> Page200(String msg) {
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);

        return new ResponseEntity<Object>("{\"errorcode\":\"0\",\"code\":\"200\",\"msg\":\"" + msg + "\"}", headers,
                HttpStatus.OK);
    }

    public ResponseEntity<Object> Page404(String errormsg) {
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);

        return new ResponseEntity<Object>("{\"errorcode\":\"404\",\"errormsg\":\"" + errormsg + "\"}", headers,
                HttpStatus.NOT_FOUND);
    }

    public ResponseEntity<Object> Page400(String errormsg) {
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);

        return new ResponseEntity<Object>("{\"errorcode\":\"400\",\"errormsg\":\"" + errormsg + "\"}", headers,
                HttpStatus.BAD_REQUEST);
    }

    public ResponseEntity<Object> Page403(String errormsg) {
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);

        return new ResponseEntity<Object>("{\"errorcode\":\"403\",\"errormsg\":\"" + errormsg + "\"}", headers,
                HttpStatus.FORBIDDEN);
    }

    public ResponseEntity<Object> Page500(String errormsg) {
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);

        return new ResponseEntity<Object>("{\"errorcode\":\"500\",\"errormsg\":\"" + errormsg + "\"}", headers,
                HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
  • 一个类用来根据不同错误转跳到不同的返回函数中
根据不同错误调用ErrorPage类中不同的函数
路径 src\main\java\com\example\smschan\error\MyErrorController.java
package com.example.smschan.error;

import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * <pre>
 * 出错页面控制器
 * Created by Binary Wang on 2018/8/25.
 * </pre>
 *
 * @author <a href="https://github.com/binarywang">Binary Wang</a>
 */
@Controller
public class MyErrorController implements ErrorController {

  // 将/error转到404页
  @GetMapping(value = "/error")
  public String errorPage() {
    return "404";
  }

  @GetMapping(value = "/error_404")
  public ResponseEntity<Object> error404() {
    return new ErrorPage().Page404("Not Found");
  }

  @GetMapping(value = "/error_403")
  public ResponseEntity<Object> error403(String errormsg) {
    return new ErrorPage().Page403("Forbidden");
  }

  @GetMapping(value = "/error_400")
  public ResponseEntity<Object> error400() {
    return new ErrorPage().Page400("Bad Request");
  }

  @GetMapping(value = "/error_500")
  public ResponseEntity<Object> error500() {
    return new ErrorPage().Page500("Service Unavailable");
  }

  @Override
  public String getErrorPath() {
    // TODO Auto-generated method stub
    return null;
  }

}
  • 一个用来配置错误跳转路径
发生错误后会跳到 MyErrorController类中进行下一步处理
路径 src\main\java\com\example\smschan\error\MyErrorPageRegister.java
package com.example.smschan.error;

import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

/**
 * <pre>
 * 配置错误状态与对应访问路径
 * Created by Binary Wang on 2018/8/25.
 * </pre>
 *
 * @author <a href="https://github.com/binarywang">Binary Wang</a>
 */
@Component
public class MyErrorPageRegister implements ErrorPageRegistrar {
  @Override
  public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
    errorPageRegistry.addErrorPages(
        new ErrorPage(HttpStatus.NOT_FOUND, "/error_404"),
        new ErrorPage(HttpStatus.FORBIDDEN, "/error_403"),
        new ErrorPage(HttpStatus.BAD_REQUEST, "/error_400"),
        new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error_500"));
  }

}
  • 一个用来启动的类(大概)
路径 src\main\java\com\example\smschan\error\MyErrorPageRegisterConfigration.java
package com.example.smschan.error;

import org.springframework.context.annotation.Bean;

public class MyErrorPageRegisterConfigration {
    @Bean
    public MyErrorPageRegister errorPageRegister(){
        return new MyErrorPageRegister();
    }
}
  • 示例:在业务逻辑中调用
例如API Token不正确时返回403错误提示,并附带 Invalid Token信息
@RequestMapping("/qwt")
    public ResponseEntity<Object> qywx_send_text_msg(@RequestParam String msg, @RequestParam String token)
            throws IOException {
        // 省略

        // token是待验证的API Token,sb是正确的API Token
        if (token.equals(sb.toString())) {
            // API Token正确,省略
        } else {
            return new ErrorPage().Page403("Invalid Token");
        }
    }
  • 其他资料 - HttpStatus状态
常用HttpStatus状态:
HttpStatus.OK = 200;
HttpStatus.BAD_REQUEST = 400;
HttpStatus.FORBIDDEN = 403;
HttpStatus.NOT_FOUND = 404;
HttpStatus.REQUEST_TIMEOUT = 408;
HttpStatus.SERVICE_UNAVAILABLE =500;
HttpStatus状态码详解
HttpStatus = {  
        //Informational 1xx  信息
        '100' : 'Continue',  //继续
        '101' : 'Switching Protocols',  //交换协议
 
        //Successful 2xx  成功
        '200' : 'OK',  //OK
        '201' : 'Created',  //创建
        '202' : 'Accepted',  //已接受
        '203' : 'Non-Authoritative Information',  //非权威信息
        '204' : 'No Content',  //没有内容
        '205' : 'Reset Content',  //重置内容
        '206' : 'Partial Content',  //部分内容
 
        //Redirection 3xx  重定向
        '300' : 'Multiple Choices',  //多种选择
        '301' : 'Moved Permanently',  //永久移动
        '302' : 'Found',  //找到
        '303' : 'See Other',  //参见其他
        '304' : 'Not Modified',  //未修改
        '305' : 'Use Proxy',  //使用代理
        '306' : 'Unused',  //未使用
        '307' : 'Temporary Redirect',  //暂时重定向
 
        //Client Error 4xx  客户端错误
        '400' : 'Bad Request',  //错误的请求
        '401' : 'Unauthorized',  //未经授权
        '402' : 'Payment Required',  //付费请求
        '403' : 'Forbidden',  //禁止
        '404' : 'Not Found',  //没有找到
        '405' : 'Method Not Allowed',  //方法不允许
        '406' : 'Not Acceptable',  //不可接受
        '407' : 'Proxy Authentication Required',  //需要代理身份验证
        '408' : 'Request Timeout',  //请求超时
        '409' : 'Conflict',  //指令冲突
        '410' : 'Gone',  //文档永久地离开了指定的位置
        '411' : 'Length Required',  //需要Content-Length头请求
        '412' : 'Precondition Failed',  //前提条件失败
        '413' : 'Request Entity Too Large',  //请求实体太大
        '414' : 'Request-URI Too Long',  //请求URI太长
        '415' : 'Unsupported Media Type',  //不支持的媒体类型
        '416' : 'Requested Range Not Satisfiable',  //请求的范围不可满足
        '417' : 'Expectation Failed',  //期望失败
 
        //Server Error 5xx  服务器错误
        '500' : 'Internal Server Error',  //内部服务器错误
        '501' : 'Not Implemented',  //未实现
        '502' : 'Bad Gateway',  //错误的网关
        '503' : 'Service Unavailable',  //服务不可用
        '504' : 'Gateway Timeout',  //网关超时
        '505' : 'HTTP Version Not Supported'  //HTTP版本不支持
};

问题解决

找不到或无法加载主类

环境:使用了Maven。

路径不对,类文件需要放在 .\src\main\java\内。

  • 例如有一个主类,路径是 .\src\main\java\com\abc.java,则需要在开头加上 package语句:
package com;

import com.alibaba.fastjson.JSONObject;

class abc {
    public static void main(String[] args){
        String tString = "abc";
        System.out.println(tString);
    }
}
最后修改:2024 年 02 月 10 日
如果觉得我的文章对你有用,请随意赞赏