新版 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调用凭证就有corpid
和corpsecret
两个参数。
- 代码
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.html
、500.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);
}
}