6
0
Fork 0
master
nishengli 2 years ago
parent 6654ab9b76
commit c25e59a991
  1. 4
      .gitignore
  2. 116
      pom.xml
  3. 265
      src/main/java/com/tsl3060/open/extend/core/ApiClient.java
  4. 107
      src/main/java/com/tsl3060/open/extend/core/ApiRequest.java
  5. 125
      src/main/java/com/tsl3060/open/extend/core/ApiResponse.java
  6. 113
      src/main/java/com/tsl3060/open/extend/core/Config.java
  7. 11
      src/main/java/com/tsl3060/open/extend/core/IApiRequest.java
  8. 14
      src/main/java/com/tsl3060/open/extend/core/INotifyListener.java
  9. 84
      src/main/java/com/tsl3060/open/extend/core/NotifyAnswerResponse.java
  10. 150
      src/main/java/com/tsl3060/open/extend/core/NotifyRequest.java
  11. 11
      src/main/java/com/tsl3060/open/extend/core/exception/ApiException.java
  12. 21
      src/main/java/com/tsl3060/open/extend/core/exception/BadResourceException.java
  13. 110
      src/main/java/com/tsl3060/open/extend/core/notify/CarbonOrderNotify.java
  14. 20
      src/main/java/com/tsl3060/open/extend/core/notify/CarbonOrderNotifyAnswer.java
  15. 4
      src/main/java/com/tsl3060/open/extend/core/notify/IAnswer.java
  16. 113
      src/main/java/com/tsl3060/open/extend/core/payload/CarbonOrderPayload.java
  17. 13
      src/main/java/com/tsl3060/open/extend/core/payload/RequestPayload.java
  18. 35
      src/main/java/com/tsl3060/open/extend/core/payload/UserLoginPayload.java
  19. 40
      src/main/java/com/tsl3060/open/extend/core/payload/UserRegisterRequestPayload.java
  20. 22
      src/main/java/com/tsl3060/open/extend/core/payload/WalletQueryPayload.java
  21. 31
      src/main/java/com/tsl3060/open/extend/core/response/CaptchaResponse.java
  22. 52
      src/main/java/com/tsl3060/open/extend/core/response/CarbonOrderResponse.java
  23. 44
      src/main/java/com/tsl3060/open/extend/core/response/SmsSendResponse.java
  24. 35
      src/main/java/com/tsl3060/open/extend/core/response/UserLoginResponse.java
  25. 42
      src/main/java/com/tsl3060/open/extend/core/response/UserRegisterResponse.java
  26. 32
      src/main/java/com/tsl3060/open/extend/core/response/WalletQueryResponse.java
  27. 10
      src/main/java/com/tsl3060/open/extend/core/router/INotifyRouter.java
  28. 30
      src/main/java/com/tsl3060/open/extend/core/router/NotifyMapRouter.java
  29. 29
      src/main/java/com/tsl3060/open/extend/core/router/notify/CarbonOrderNotifyRouter.java
  30. 47
      src/main/java/com/tsl3060/open/extend/core/secure/ISecure.java
  31. 220
      src/main/java/com/tsl3060/open/extend/core/secure/RSASecure.java
  32. 24
      src/main/java/com/tsl3060/open/extend/core/secure/SecureTool.java
  33. 76
      src/main/resources/logback.xml

4
.gitignore vendored

@ -16,4 +16,6 @@ buildNumber.properties
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
.idea
src/test
*.iml

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tsl3060.open.extend</groupId>
<artifactId>OpenExtendDemo</artifactId>
<version>0.3.6-wanshun</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hutool.version>5.8.19</hutool.version>
<!-- 日志-->
<logback.version>1.4.7</logback.version>
</properties>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-log</artifactId>
</dependency>
<!-- 日志库-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.69</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.33</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-bom</artifactId>
<version>${hutool.version}</version>
<type>pom</type>
<!-- 注意这里是import -->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>8</source>
<target>8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- <plugin>-->
<!-- <artifactId>maven-assembly-plugin</artifactId>-->
<!-- <configuration>-->
<!-- <descriptorRefs>-->
<!-- <descriptorRef>jar-with-dependencies</descriptorRef>-->
<!-- </descriptorRefs>-->
<!-- <archive>-->
<!-- <manifest>-->
<!-- <mainClass></mainClass>-->
<!-- </manifest>-->
<!-- </archive>-->
<!-- </configuration>-->
<!-- <executions>-->
<!-- <execution>-->
<!-- <id>make-assembly</id>-->
<!-- <phase>package</phase>-->
<!-- <goals>-->
<!-- <goal>single</goal>-->
<!-- </goals>-->
<!-- </execution>-->
<!-- </executions>-->
<!-- </plugin>-->
</plugins>
</build>
</project>

@ -0,0 +1,265 @@
package com.tsl3060.open.extend.core;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.tsl3060.open.extend.core.exception.ApiException;
import com.tsl3060.open.extend.core.exception.BadResourceException;
import com.tsl3060.open.extend.core.notify.IAnswer;
import com.tsl3060.open.extend.core.router.INotifyRouter;
import com.tsl3060.open.extend.core.router.NotifyMapRouter;
import com.tsl3060.open.extend.core.secure.ISecure;
import com.tsl3060.open.extend.core.secure.SecureTool;
import okhttp3.*;
import org.w3c.dom.Document;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
public class ApiClient {
private static final int ERROR_OK = 1000;
private static final int SUB_OK = 2000;
/**
* 请求内容格式
*/
private final MediaType contentType = MediaType.parse("application/json;charset=utf-8");
/**
* 返回内容格式
*/
private final MediaType acceptType = MediaType.parse("application/json");
private SecureTool secureTool;
private Config config;
private final Log log = LogFactory.get();
private SecureTool getSecureTool() {
if (secureTool == null) {
secureTool = new SecureTool(getConfig());
}
return secureTool;
}
public Config getConfig() {
return config;
}
public void setConfig(Config config) {
this.config = config;
}
public <T> T request(IApiRequest request, Class<T> tClass) throws Exception {
ApiResponse apiResponse = this.request(request);
if (apiResponse == null) {
return null;
}
if (apiResponse.getErrCode() != ERROR_OK) {
//系统级错误
throw new ApiException(apiResponse.getDescription() + "(" + apiResponse.getErrCode() + ")");
}
if (apiResponse.getSubErr() != SUB_OK) {
//业务级错误
throw new ApiException(apiResponse.getDescription() + "(" + apiResponse.getSubErr() + ")");
}
Object o = apiResponse.getPayload();
if (o != null) {
return JSON.to(tClass, o);
}
return null;
}
/**
* 接口请求
*
* @param request
* @return
*/
public ApiResponse request(IApiRequest request) throws Exception {
ApiRequest apiRequest = pack(request.path(), request);
ApiResponse apiResponse = this.request(apiRequest);
if (apiResponse == null) {
throw new Exception("返回结果为空");
}
return apiResponse;
}
/**
* 打包请求
*
* @param path
* @param data
* @return
*/
private ApiRequest pack(String path, Object data) {
ApiRequest apiRequest = new ApiRequest();
apiRequest.setPath(path);
apiRequest.setCharset(this.config.getCharset());
DateFormat dateFormat = new SimpleDateFormat(this.config.getDataFormat());
apiRequest.setTime(dateFormat.format(new Date()));
apiRequest.setSignType(this.config.getSignType());
apiRequest.setPayload(data);
apiRequest.setAccessToken("");
apiRequest.setAppId(this.config.getAppid());
return apiRequest;
}
/**
* 执行请求
*
* @param apiRequest
* @return
*/
public ApiResponse request(ApiRequest apiRequest) {
ISecure iSecure = getSecureTool().getSecure(apiRequest.getSignType());
String signStr = iSecure.requestSign(apiRequest);
apiRequest.setSign(signStr);
String fBody = JSON.toJSONString(apiRequest);
log.debug(">>> {}", fBody);
RequestBody requestBody = RequestBody.create(fBody, contentType);
/**
* 通讯主机地址
*/
String host = this.config.getHost();
Request request = new Request.Builder()
.url(String.format("%s%s", host, apiRequest.getPath()))
.addHeader("ACCEPT", acceptType.toString())
.post(requestBody)
.build();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
OkHttpClient okHttpClient = builder.build();
try {
Call call = okHttpClient.newCall(request);
try (Response response = call.execute()) {
ResponseBody responseBody = response.body();
ApiResponse apiResponse = null;
if (responseBody != null) {
String bStr = responseBody.string();
log.debug("<<< {}", bStr);
apiResponse = JSON.parseObject(bStr, ApiResponse.class);
}
if (apiResponse == null) {
return null;
}
//验证签名
if (this.verifyResponse(apiResponse)) {
return apiResponse;
}
return null;
}
} catch (IOException e) {
log.error(e);
}
return null;
}
/**
* 验证反馈数据
*
* @param response
* @return
*/
public boolean verifyResponse(ApiResponse response) {
ISecure iSecure = this.secureTool.getSecure(response.getSignType());
if (iSecure == null) {
log.debug("没有找到签名类型 %s", response.getSignType());
return false;
}
return iSecure.verifyResponse(response);
}
private INotifyListener notifyListener;
public INotifyListener getNotifyListener() {
return notifyListener;
}
public void setNotifyListener(INotifyListener notifyListener) {
this.notifyListener = notifyListener;
}
private NotifyMapRouter notifyMapRouter;
/**
* 验证回调数据
*
* @return
*/
public String notifyRun(String raw, String contentType, String accept) throws BadResourceException {
if (notifyMapRouter == null) {
notifyMapRouter = new NotifyMapRouter(this.notifyListener);
}
if (StrUtil.isEmpty(raw)) {
throw new BadResourceException("通知内容为空");
}
NotifyRequest notifyRequest;
if (contentType.contains("application/json")) {
notifyRequest = JSON.parseObject(raw, NotifyRequest.class);
} else if (contentType.contains("application/xml")) {
//XML格式
Document document = XmlUtil.parseXml(raw);
//TODO 对XML解析
throw new BadResourceException("暂不支持的格式");
} else {
throw new BadResourceException("不支持的数据格式");
}
//验证通知
ISecure iSecure = getSecureTool().getSecure(notifyRequest.getSignType());
if (!iSecure.verifyNotify(notifyRequest)) {
throw new BadResourceException("验签未通过");
}
INotifyRouter notifyRouter = this.notifyMapRouter.getRouter(notifyRequest.getModule());
if (notifyRouter == null) {
throw new BadResourceException("未知的通知");
}
//组装
NotifyAnswerResponse notifyAnswerResponse = new NotifyAnswerResponse();
notifyAnswerResponse.setAnswerId(notifyRequest.getNotifyId());
notifyAnswerResponse.setAppId(this.config.getAppid());
notifyAnswerResponse.setTime(DateTime.now().toString(this.config.getDataFormat()));
notifyAnswerResponse.setSignType(this.config.getSignType());
notifyAnswerResponse.setCharset(this.config.getCharset());
//通知
try {
IAnswer o = notifyRouter.makeBody(notifyRequest);
notifyAnswerResponse.setPayload(o);
notifyAnswerResponse.setResult("ok");
} catch (Exception e) {
e.printStackTrace();
notifyAnswerResponse.setResult("fail");
}
//生成签名
ISecure makeSecure = getSecureTool().getSecure(notifyAnswerResponse.getSignType());
String signStr = makeSecure.answerSign(notifyAnswerResponse);
notifyAnswerResponse.setSign(signStr);
//解析完成
if (accept.contains("application/json")) {
return JSON.toJSONString(notifyAnswerResponse);
} else if (accept.contains("application/xml")) {
//返回XML格式
return "";
} else {
//未知的格式
return "";
}
}
}

@ -0,0 +1,107 @@
package com.tsl3060.open.extend.core;
import com.alibaba.fastjson2.annotation.JSONField;
public class ApiRequest {
@JSONField(name = "app_id")
private String appId;
/**
* 请求路径
*/
private String path;
/**
* 访问TOKEN
*/
@JSONField(name = "access_token")
private String accessToken;
/**
* 签名数据
*/
private String sign;
/**
* 签名类型
*/
@JSONField(name = "sign_type")
private String signType;
/**
* 数据编码
*/
private String charset;
/**
* 时间
*/
private String time;
/**
* 载体
*/
private Object payload;
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getSignType() {
return signType;
}
public void setSignType(String signType) {
this.signType = signType;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public Object getPayload() {
return payload;
}
public void setPayload(Object payload) {
this.payload = payload;
}
}

@ -0,0 +1,125 @@
package com.tsl3060.open.extend.core;
import com.alibaba.fastjson2.annotation.JSONField;
public class ApiResponse {
private String time = "";
private String sign = "";
private String charset = "UTF-8";
@JSONField(name = "response_id")
private String responseId = "";
@JSONField(name = "err_code")
private int errCode;
@JSONField(name = "err_msg")
private String errMsg = "";
@JSONField(name = "sub_err")
private int subErr;
@JSONField(name = "sub_msg")
private String subMsg = "";
@JSONField(name = "sign_type")
private String signType = "";
private Object payload;
@JSONField(name = "open_id")
private String openId = "";
private String description="";
public String getOpenId() {
return openId;
}
public void setOpenId(String openId) {
this.openId = openId;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public String getResponseId() {
return responseId;
}
public void setResponseId(String responseId) {
this.responseId = responseId;
}
public int getErrCode() {
return errCode;
}
public void setErrCode(int errCode) {
this.errCode = errCode;
}
public String getErrMsg() {
return errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
public int getSubErr() {
return subErr;
}
public void setSubErr(int subErr) {
this.subErr = subErr;
}
public String getSubMsg() {
return subMsg;
}
public void setSubMsg(String subMsg) {
this.subMsg = subMsg;
}
public String getSignType() {
return signType;
}
public void setSignType(String signType) {
this.signType = signType;
}
public Object getPayload() {
return payload;
}
public void setPayload(Object payload) {
this.payload = payload;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

@ -0,0 +1,113 @@
package com.tsl3060.open.extend.core;
import com.tsl3060.open.extend.core.secure.RSASecure;
public class Config {
private String privateKey = "";
private String publicKey = "";
/**
* API 平台公钥
*/
private String apiPublicKey = "";
private String appid = "";
private String signType = RSASecure.NAME;
private String charset = "utf-8";
private String host = "https://open.tsl3060.com";
private String dataFormat = "yyyy-MM-dd HH:mm:ss";
public String getPrivateKey() {
return privateKey;
}
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
if (this.privateKey != null) {
this.privateKey = this.privateKey
.replace("\r", "")
.replace("\n", "")
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "");
}
}
public String getPublicKey() {
return publicKey;
}
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
if (this.publicKey != null) {
this.publicKey = this.publicKey
.replace("\r", "")
.replace("\n", "")
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "");
}
}
public String getApiPublicKey() {
return apiPublicKey;
}
public void setApiPublicKey(String apiPublicKey) {
this.apiPublicKey = apiPublicKey;
if (this.apiPublicKey != null) {
this.apiPublicKey = this.apiPublicKey
.replace("\r", "")
.replace("\n", "")
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "");
}
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getSignType() {
return signType;
}
public void setSignType(String signType) {
this.signType = signType;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getDataFormat() {
return dataFormat;
}
public void setDataFormat(String dataFormat) {
this.dataFormat = dataFormat;
}
public void useSandBox() {
this.setHost("https://opendev.tsl3060.com");
}
}

@ -0,0 +1,11 @@
package com.tsl3060.open.extend.core;
public interface IApiRequest {
/**
* 请求路径
*
* @return
*/
String path();
}

@ -0,0 +1,14 @@
package com.tsl3060.open.extend.core;
import com.tsl3060.open.extend.core.notify.CarbonOrderNotify;
import com.tsl3060.open.extend.core.notify.CarbonOrderNotifyAnswer;
public interface INotifyListener {
/**
* 低碳积分通知
*
* @param notify
* @return
*/
CarbonOrderNotifyAnswer carbon(CarbonOrderNotify notify);
}

@ -0,0 +1,84 @@
package com.tsl3060.open.extend.core;
import com.alibaba.fastjson2.annotation.JSONField;
public class NotifyAnswerResponse {
@JSONField(name = "answer_id")
private String answerId;
@JSONField(name = "app_id")
private String appId;
private String time;
private Object payload;
private String sign;
@JSONField(name = "sign_type")
private String signType;
private String charset;
private String result;
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public String getAnswerId() {
return answerId;
}
public void setAnswerId(String answerId) {
this.answerId = answerId;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public Object getPayload() {
return payload;
}
public void setPayload(Object payload) {
this.payload = payload;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getSignType() {
return signType;
}
public void setSignType(String signType) {
this.signType = signType;
}
}

@ -0,0 +1,150 @@
package com.tsl3060.open.extend.core;
import com.alibaba.fastjson2.annotation.JSONField;
public class NotifyRequest {
@JSONField(name = "notify_id")
private String notifyId;
@JSONField(name = "app_id")
private String appId;
private String module = "";
@JSONField(name = "source_id")
private String sourceId = "";
@JSONField(name = "err_code")
private int errCode;
@JSONField(name = "err_msg")
private String errMsg;
@JSONField(name = "sub_err")
private int subErr;
@JSONField(name = "sub_msg")
private String subMsg;
@JSONField(name = "sign_type")
private String signType;
private Object payload;
@JSONField(name = "open_id")
private String openId;
private String description;
private String charset = "UTF-8";
private String time;
private String sign;
public String getNotifyId() {
return notifyId;
}
public void setNotifyId(String notifyId) {
this.notifyId = notifyId;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getModule() {
return module;
}
public void setModule(String module) {
this.module = module;
}
public String getSourceId() {
return sourceId;
}
public void setSourceId(String sourceId) {
this.sourceId = sourceId;
}
public int getErrCode() {
return errCode;
}
public void setErrCode(int errCode) {
this.errCode = errCode;
}
public String getErrMsg() {
return errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
public int getSubErr() {
return subErr;
}
public void setSubErr(int subErr) {
this.subErr = subErr;
}
public String getSubMsg() {
return subMsg;
}
public void setSubMsg(String subMsg) {
this.subMsg = subMsg;
}
public String getSignType() {
return signType;
}
public void setSignType(String signType) {
this.signType = signType;
}
public Object getPayload() {
return payload;
}
public void setPayload(Object payload) {
this.payload = payload;
}
public String getOpenId() {
return openId;
}
public void setOpenId(String openId) {
this.openId = openId;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
}

@ -0,0 +1,11 @@
package com.tsl3060.open.extend.core.exception;
public class ApiException extends Exception {
public ApiException() {
}
public ApiException(String message) {
super(message);
}
}

@ -0,0 +1,21 @@
package com.tsl3060.open.extend.core.exception;
import java.io.IOException;
public class BadResourceException extends IOException {
public BadResourceException() {
super();
}
public BadResourceException(String message) {
super(message);
}
public BadResourceException(String message, Throwable cause) {
super(message, cause);
}
public BadResourceException(Throwable cause) {
super(cause);
}
}

@ -0,0 +1,110 @@
package com.tsl3060.open.extend.core.notify;
import com.alibaba.fastjson2.annotation.JSONField;
public class CarbonOrderNotify {
/**
* 用户OpenID
*/
private String openid;
/**
* 订单号
*/
@JSONField(name = "order_no")
private String orderNo;
/**
* 低碳订单号
*/
@JSONField(name = "carbon_no")
private String carbonNo;
/**
* 用户低碳积分
*/
@JSONField(name = "carbon")
private double carbon;
/**
* 本次订单新增积分
*/
@JSONField(name = "amount")
private double amount;
/**
* 订单时间
*/
@JSONField(name = "order_time")
private String orderTime;
/**
* 低碳积分订单完成时间
*/
@JSONField(name = "complete_time")
private String completeTime;
/**
* 订单类型
*/
private String type;
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
public String getCarbonNo() {
return carbonNo;
}
public void setCarbonNo(String carbonNo) {
this.carbonNo = carbonNo;
}
public double getCarbon() {
return carbon;
}
public void setCarbon(double carbon) {
this.carbon = carbon;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public String getOrderTime() {
return orderTime;
}
public void setOrderTime(String orderTime) {
this.orderTime = orderTime;
}
public String getCompleteTime() {
return completeTime;
}
public void setCompleteTime(String completeTime) {
this.completeTime = completeTime;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

@ -0,0 +1,20 @@
package com.tsl3060.open.extend.core.notify;
public class CarbonOrderNotifyAnswer implements IAnswer {
private boolean answer;
public CarbonOrderNotifyAnswer() {
}
public CarbonOrderNotifyAnswer(boolean answer) {
this.answer = answer;
}
public boolean isAnswer() {
return answer;
}
public void setAnswer(boolean answer) {
this.answer = answer;
}
}

@ -0,0 +1,4 @@
package com.tsl3060.open.extend.core.notify;
public interface IAnswer {
}

@ -0,0 +1,113 @@
package com.tsl3060.open.extend.core.payload;
import com.alibaba.fastjson2.annotation.JSONField;
import com.tsl3060.open.extend.core.IApiRequest;
public class CarbonOrderPayload extends RequestPayload implements IApiRequest {
@Override
public String path() {
return "/v1/wanshun/wallet/carbon";
}
private String openid;
@JSONField(name = "order_no")
private String orderNo;
private double mileage;
@JSONField(name = "order_time")
private String orderTime;
@JSONField(name = "order_state")
private String orderState;
@JSONField(name = "vehicle_model")
private String vehicleModel = "";
@JSONField(name = "new_energy")
private boolean newEnergy = false;
@JSONField(name = "complete_time")
private String completeTime = "";
@JSONField(name = "order_pay")
private double orderPay = 0;
private String behavior = "";
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
public double getMileage() {
return mileage;
}
public void setMileage(double mileage) {
this.mileage = mileage;
}
public String getOrderTime() {
return orderTime;
}
public void setOrderTime(String orderTime) {
this.orderTime = orderTime;
}
public boolean isNewEnergy() {
return newEnergy;
}
public void setNewEnergy(boolean newEnergy) {
this.newEnergy = newEnergy;
}
public String getOrderState() {
return orderState;
}
public void setOrderState(String orderState) {
this.orderState = orderState;
}
public String getVehicleModel() {
return vehicleModel;
}
public void setVehicleModel(String vehicleModel) {
this.vehicleModel = vehicleModel;
}
public String getCompleteTime() {
return completeTime;
}
public void setCompleteTime(String completeTime) {
this.completeTime = completeTime;
}
public double getOrderPay() {
return orderPay;
}
public void setOrderPay(double orderPay) {
this.orderPay = orderPay;
}
public String getBehavior() {
return behavior;
}
public void setBehavior(String behavior) {
this.behavior = behavior;
}
}

@ -0,0 +1,13 @@
package com.tsl3060.open.extend.core.payload;
public abstract class RequestPayload {
private String source;
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
}

@ -0,0 +1,35 @@
package com.tsl3060.open.extend.core.payload;
import com.alibaba.fastjson2.annotation.JSONField;
import com.tsl3060.open.extend.core.IApiRequest;
/**
* 用户登录载体
*/
public class UserLoginPayload extends RequestPayload implements IApiRequest {
private String device;
private String openid;
public String getDevice() {
return device;
}
public void setDevice(String device) {
this.device = device;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
@Override
public String path() {
return "/v1/wanshun/account/login";
}
}

@ -0,0 +1,40 @@
package com.tsl3060.open.extend.core.payload;
import com.tsl3060.open.extend.core.IApiRequest;
import com.tsl3060.open.extend.core.payload.RequestPayload;
public class UserRegisterRequestPayload extends RequestPayload implements IApiRequest {
@Override
public String path() {
return "/v1/wanshun/account/register";
}
private String phone;
private String uid;
private String type;
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

@ -0,0 +1,22 @@
package com.tsl3060.open.extend.core.payload;
import com.tsl3060.open.extend.core.IApiRequest;
public class WalletQueryPayload extends RequestPayload implements IApiRequest {
private String openid;
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
@Override
public String path() {
return "/v1/wanshun/wallet/query";
}
}

@ -0,0 +1,31 @@
package com.tsl3060.open.extend.core.response;
/**
* 验证码载体
*/
public class CaptchaResponse {
/**
* 验证码内容
*/
private String image;
/**
* 验证码ID
*/
private String id;
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}

@ -0,0 +1,52 @@
package com.tsl3060.open.extend.core.response;
import com.alibaba.fastjson2.annotation.JSONField;
public class CarbonOrderResponse {
private String openid;
private String carbon;
private double total;
private String record;
@JSONField(name = "record_time")
private String recordTime;
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getCarbon() {
return carbon;
}
public void setCarbon(String carbon) {
this.carbon = carbon;
}
public double getTotal() {
return total;
}
public void setTotal(double total) {
this.total = total;
}
public String getRecord() {
return record;
}
public void setRecord(String record) {
this.record = record;
}
public String getRecordTime() {
return recordTime;
}
public void setRecordTime(String recordTime) {
this.recordTime = recordTime;
}
}

@ -0,0 +1,44 @@
package com.tsl3060.open.extend.core.response;
import com.alibaba.fastjson2.annotation.JSONField;
public class SmsSendResponse {
private String phone;
private String send;
private int time;
@JSONField(name = "send_time")
private String sendTime;
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getSend() {
return send;
}
public void setSend(String send) {
this.send = send;
}
public int getTime() {
return time;
}
public void setTime(int time) {
this.time = time;
}
public String getSendTime() {
return sendTime;
}
public void setSendTime(String sendTime) {
this.sendTime = sendTime;
}
}

@ -0,0 +1,35 @@
package com.tsl3060.open.extend.core.response;
import com.alibaba.fastjson2.annotation.JSONField;
public class UserLoginResponse {
@JSONField(name = "expire_at")
private String expireAt;
private String openid;
private String token;
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getExpireAt() {
return expireAt;
}
public void setExpireAt(String expireAt) {
this.expireAt = expireAt;
}
}

@ -0,0 +1,42 @@
package com.tsl3060.open.extend.core.response;
public class UserRegisterResponse {
private String nickname;
private String avatar;
private String openid;
private String appid;
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
}

@ -0,0 +1,32 @@
package com.tsl3060.open.extend.core.response;
public class WalletQueryResponse {
private String openid;
private String carbon;
private String query;
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getCarbon() {
return carbon;
}
public void setCarbon(String carbon) {
this.carbon = carbon;
}
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
}

@ -0,0 +1,10 @@
package com.tsl3060.open.extend.core.router;
import com.tsl3060.open.extend.core.NotifyRequest;
import com.tsl3060.open.extend.core.notify.IAnswer;
public interface INotifyRouter {
String path();
IAnswer makeBody(NotifyRequest notifyRequest) throws Exception;
}

@ -0,0 +1,30 @@
package com.tsl3060.open.extend.core.router;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import com.tsl3060.open.extend.core.INotifyListener;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class NotifyMapRouter {
private Map<String, INotifyRouter> map = new HashMap<>();
public NotifyMapRouter(INotifyListener notifyListener) {
Set<Class<?>> classes = ClassUtil.scanPackage("com.tsl3060.open.extend.core.router.notify");
for (Class<?> c : classes) {
INotifyRouter notifyRouter = (INotifyRouter) ReflectUtil.newInstance(c,notifyListener);
map.put(notifyRouter.path(), notifyRouter);
}
}
public INotifyRouter getRouter(String module) {
return map.get(module);
}
}

@ -0,0 +1,29 @@
package com.tsl3060.open.extend.core.router.notify;
import com.alibaba.fastjson2.JSON;
import com.tsl3060.open.extend.core.INotifyListener;
import com.tsl3060.open.extend.core.NotifyRequest;
import com.tsl3060.open.extend.core.notify.CarbonOrderNotify;
import com.tsl3060.open.extend.core.notify.IAnswer;
import com.tsl3060.open.extend.core.router.INotifyRouter;
public class CarbonOrderNotifyRouter implements INotifyRouter {
private final INotifyListener notifyListener;
public CarbonOrderNotifyRouter(INotifyListener notifyListener) {
this.notifyListener = notifyListener;
}
@Override
public String path() {
return "/v1/wanshun/notify/carbon";
}
@Override
public IAnswer makeBody(NotifyRequest notifyRequest) throws Exception {
CarbonOrderNotify carbonOrderNotify = JSON.to(CarbonOrderNotify.class, notifyRequest.getPayload());
return this.notifyListener.carbon(carbonOrderNotify);
}
}

@ -0,0 +1,47 @@
package com.tsl3060.open.extend.core.secure;
import com.tsl3060.open.extend.core.*;
public interface ISecure {
/**
* 参数设置
*
* @param config
*/
void setConfig(Config config);
/**
* 安全模块名称
*
* @return 名称
*/
String name();
/**
* 对请求进行签名
*
* @param apiRequest 请求
* @return 签名字符串
*/
String requestSign(ApiRequest apiRequest);
/**
* 同步返回验签
*
* @param apiResponse 同步返回
* @return 是否正确
*/
boolean verifyResponse(ApiResponse apiResponse);
/**
* 异步通知验签
*
* @param notifyRequest 异步通知
* @return 是否正确
*/
boolean verifyNotify(NotifyRequest notifyRequest);
String answerSign(NotifyAnswerResponse response);
}

@ -0,0 +1,220 @@
package com.tsl3060.open.extend.core.secure;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.alibaba.fastjson2.JSONObject;
import com.tsl3060.open.extend.core.*;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
import java.util.stream.Collectors;
public class RSASecure implements ISecure {
private final Log log = LogFactory.get();
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
private static final String DIGEST_ALGORITHM = "SHA-256";
public static final String NAME = "RSA";
private Config config;
@Override
public void setConfig(Config config) {
this.config = config;
}
@Override
public String name() {
return NAME;
}
private String object2LinkStr(Object ob) {
List<String> payloadStr = new ArrayList<>();
if (ob != null) {
JSONObject j = JSONObject.from(ob);
//排序
Set<String> _keys = j.keySet();
List<String> keys = _keys.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList());
for (String key : keys) {
if (j.get(key) != null) {
Object v = j.get(key);
String vx;
if (v instanceof Double) {
vx = String.format("%.2f", v);
} else if (v instanceof Float) {
vx = String.format("%.2f", v);
} else if (v instanceof Boolean) {
vx = String.format("%s", v);
} else if (v instanceof BigDecimal) {
vx = String.format("%.2f", v);
} else {
vx = String.valueOf(v);
}
payloadStr.add(String.format("%s=%s", key, vx));
}
}
}
return CollectionUtil.join(payloadStr, "&");
}
public String sign(String content, String charset) {
log.debug("req: {}", content);
try {
byte[] bytesContent = content.getBytes(charset);
MessageDigest messageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM);
byte[] digestData = messageDigest.digest(bytesContent);
//签名工具
byte[] pKeyData = Base64.getDecoder().decode(this.config.getPrivateKey());
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(pKeyData);
KeyFactory keyFactory = KeyFactory.getInstance(NAME);
PrivateKey pkey = keyFactory.generatePrivate(spec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(pkey);
signature.update(digestData);
byte[] s = signature.sign();
return HexUtil.encodeHexStr(s);
} catch (UnsupportedEncodingException | NoSuchAlgorithmException | InvalidKeySpecException |
SignatureException |
InvalidKeyException e) {
throw new RuntimeException(e);
}
}
/**
* 对请求生成签名
*
* @param request 请求
* @return
*/
@Override
public String requestSign(ApiRequest request) {
Object ob = request.getPayload();
String waitStr = this.object2LinkStr(ob);
String content = String.format("%s&%s&%s&%s&%s&%s&%s",
request.getPath(),
request.getAppId(),
request.getAccessToken(),
request.getSignType(),
request.getCharset(),
request.getTime(),
waitStr
);
log.debug("req: {}", content);
return this.sign(content, request.getCharset());
}
private boolean responseServerVerify(byte[] content, String sign) {
try {
MessageDigest messageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM);
byte[] digestData = messageDigest.digest(content);
byte[] pubKeyData = Base64.getDecoder().decode(this.config.getApiPublicKey());
X509EncodedKeySpec spec = new X509EncodedKeySpec(pubKeyData);
KeyFactory keyFactory = KeyFactory.getInstance(NAME);
PublicKey pubKey = keyFactory.generatePublic(spec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(pubKey);
signature.update(digestData);
byte[] bodySign = HexUtil.decodeHex(sign);
return signature.verify(bodySign);
} catch (NoSuchAlgorithmException | InvalidKeySpecException | SignatureException | InvalidKeyException e) {
log.error(e);
return false;
}
}
@Override
public boolean verifyResponse(ApiResponse response) {
String waitStr = this.object2LinkStr(response.getPayload());
String formatStr = String.format(
"%s&%s&%s&%s&%s&%s&%s&%s&%s&%s&%s",
response.getResponseId(),
response.getErrCode(),
response.getErrMsg(),
response.getSubErr(),
response.getSubMsg(),
response.getTime(),
response.getOpenId(),
response.getSignType(),
response.getCharset(),
response.getDescription(),
waitStr
);
log.debug("反馈内容 {}", formatStr);
log.debug("签名内容 {}", response.getSign());
Charset bodyCharset = Charset.forName(response.getCharset());
byte[] bytesContent = formatStr.getBytes(bodyCharset);
return this.responseServerVerify(bytesContent, response.getSign());
}
/**
* 验证通知
*
* @param response 异步通知
* @return
*/
@Override
public boolean verifyNotify(NotifyRequest response) {
String waitStr = this.object2LinkStr(response.getPayload());
String formatStr = String.format(
"%s&%s&%s&%s&%s&%s&%s&%s&%s&%s&%s&%s&%s",
response.getNotifyId(),
response.getSourceId(),
response.getAppId(),
response.getErrCode(),
response.getErrMsg(),
response.getSubErr(),
response.getSubMsg(),
response.getTime(),
response.getOpenId(),
response.getSignType(),
response.getCharset(),
response.getDescription(),
waitStr
);
log.debug("通知内容 {}", formatStr);
log.debug("通知签名内容 {}", response.getSign());
Charset bodyCharset = Charset.forName(response.getCharset());
byte[] bytesContent = formatStr.getBytes(bodyCharset);
return this.responseServerVerify(bytesContent, response.getSign());
}
/**
* 通知反馈签名
*
* @param response
* @return
*/
@Override
public String answerSign(NotifyAnswerResponse response) {
Object ob = response.getPayload();
String waitStr = this.object2LinkStr(ob);
String content = String.format("%s&%s&%s&%s&%s&%s&%s",
response.getAnswerId(),
response.getAppId(),
response.getResult(),
response.getSignType(),
response.getCharset(),
response.getTime(),
waitStr
);
return this.sign(content, response.getCharset());
}
}

@ -0,0 +1,24 @@
package com.tsl3060.open.extend.core.secure;
import com.tsl3060.open.extend.core.Config;
import java.util.HashMap;
/**
*
*/
public class SecureTool {
private final HashMap<String, ISecure> secureHashMap = new HashMap<>();
public SecureTool(Config config) {
RSASecure rsaSecure = new RSASecure();
rsaSecure.setConfig(config);
secureHashMap.put(rsaSecure.name(), rsaSecure);
}
public ISecure getSecure(String name) {
return secureHashMap.get(name);
}
}

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- 从高到地低 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 、 ALL -->
<!-- 日志输出规则 根据当前ROOT 级别,日志输出时,级别高于root默认的级别时 会输出 -->
<!-- 以下 每个配置的 filter 是过滤掉输出文件里面,会出现高级别文件,依然出现低级别的日志信息,通过filter 过滤只记录本级别的日志-->
<!-- 属性描述 scan:性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,
默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 定义日志文件 输入位置 -->
<property name="logPath" value="logs"/>
<!-- 日志最大的历史 30天 -->
<property name="maxHistory" value="3"/>
<!-- 配置项, 通过此节点配置日志输出位置(控制台、文件、数据库)、输出格式等-->
<!-- ConsoleAppender代表输出到控制台 -->
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<!-- layout代表输出格式 -->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger -%msg%n</pattern>
</layout>
</appender>
<!-- 日志输出文件 -->
<appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger -%msg%n</pattern>
</encoder>
<!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 RollingFileAppender-->
<!-- 滚动策略,它根据时间来制定滚动策略.既负责滚动也负责触发滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 输出路径 -->
<fileNamePattern>${logPath}/info/%d.log</fileNamePattern>
<!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件假设设置每个月滚动,且<maxHistory>是6,
则只保存最近6个月的文件,删除之前的旧文件。注意,删除旧文件是,那些为了归档而创建的目录也会被删除-->
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<!-- 按照固定窗口模式生成日志文件,当文件大于20MB时,生成新的日志文件。窗口大小是1到3,当保存了3个归档文件后,将覆盖最早的日志。
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>${logPath}/%d{yyyy-MM-dd}/.log.zip</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>3</maxIndex>
</rollingPolicy> -->
<!-- 查看当前活动文件的大小,如果超过指定大小会告知RollingFileAppender 触发当前活动文件滚动
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>5MB</maxFileSize>
</triggeringPolicy> -->
</appender>
<!-- 特殊记录Error日志 -->
<appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 只记录ERROR级别日志,添加范围过滤,可以将该类型的日志特殊记录到某个位置 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger -%msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logPath}/error/%d.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>3</maxHistory>
</rollingPolicy>
</appender>
<logger name="io.lettuce" level="OFF"/>
<logger name="io.undertow.request" level="OFF"/>
<logger name="com.zaxxer" level="ERROR"/>
<logger name="io.netty" level="OFF"/>
<logger name="org.tio.core.task" level="OFF"/>
<!-- 根节点,表名基本的日志级别,里面可以由多个appender规则 -->
<!-- level="info"代表基础日志级别为info -->
<root level="debug">
<!-- 引入控制台输出规则 -->
<appender-ref ref="consoleLog"/>
<!-- <appender-ref ref="fileInfoLog"/>-->
<!-- <appender-ref ref="fileErrorLog"/>-->
</root>
</configuration>
Loading…
Cancel
Save