回调管理

This commit is contained in:
六如
2025-11-03 07:56:35 +08:00
parent 9d8a04b702
commit 5001809fef
75 changed files with 2828 additions and 56 deletions

25
sop-example/example-notify/.gitignore vendored Executable file
View File

@@ -0,0 +1,25 @@
target/
!.mvn/wrapper/maven-wrapper.jar
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
.nb-gradle/
/local-config/

View File

@@ -0,0 +1,148 @@
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.15</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gitee.sop</groupId>
<artifactId>example-notify</artifactId>
<version>5.0.0-SNAPSHOT</version>
<name>example-notify</name>
<properties>
<java.version>1.8</java.version>
<!-- dubbo版本 -->
<dubbo.version>3.2.16</dubbo.version>
</properties>
<dependencies>
<!-- 回调处理 -->
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-notify-api</artifactId>
<version>5.0.0-SNAPSHOT</version>
</dependency>
<!-- sop接入依赖 -->
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-spring-boot-starter</artifactId>
<version>5.0.0-SNAPSHOT</version>
</dependency>
<!-- nacos注册中心 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-nacos-spring-boot-starter</artifactId>
</dependency>
<!-- zookeeper注册中心 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-zookeeper-curator5-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sdk-java</artifactId>
<version>5.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-bom</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!-- 打包时跳过测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<!-- 文档推送 -->
<plugin>
<groupId>com.ly.smart-doc</groupId>
<artifactId>smart-doc-maven-plugin</artifactId>
<version>3.0.9</version>
<configuration>
<!--指定生成文档的使用的配置文件-->
<configFile>./src/main/resources/smart-doc.json</configFile>
<!--指定项目名称-->
<projectName>${project.artifactId}</projectName>
</configuration>
<dependencies>
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-support</artifactId>
<version>5.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
<repository>
<id>maven_central</id>
<name>Maven Central</name>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
</repositories>
</project>

View File

@@ -0,0 +1 @@
mvn -Dfile.encoding=UTF-8 -Dcheckstyle.skip=true smart-doc:torna-rpc

View File

@@ -0,0 +1,16 @@
package com.gitee.sop.notifyexample;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDubbo
public class ExampleNotifyApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleNotifyApplication.class, args);
}
}

View File

@@ -0,0 +1,71 @@
package com.gitee.sop.notifyexample.controller;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.gitee.sop.sdk.sign.SignUtil;
import com.gitee.sop.sdk.sign.SopSignException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* 处理回调
*
* @author 六如
*/
@RestController
@Slf4j
public class DemoCallbackController {
// 平台下发的公钥
String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj0CaMfudpfsrzgT7014aIGQPiEHvk5JPMlHH7YI5JYk+yAgePntojJ8/q1nmeHAauJqEYuCZHfqcjxzLM2hVvttrXtiacTMlr/ea9CGJtx4m20ltrsPOIXPXXZUToxXgO7X1FNvgXgeBBPcWLrsmJUgAQbM1KG/bo9QdNp/cFf5tBuo+1fXB9qXlZnSCbvQwrhfDGAF7NmEYkvkoQeys9YkASAl+zeEOXdBkPQjKDd9USyb/tIkrgLmeo0EOp+PytmEOAsMPSeIEdRcwrgg16X9BvMvnPKLTetQxXILG7r6kkkLj1pVA8EGinRDFu0jwp/Wu+wwUvRlpDRvUbyWEOQIDAQAB";
/**
* 模拟客户端处理接口回调
* 回调接口http://127.0.0.1:7074/notify/callback
* 返回状态200表示成功收到请求
* 返回非200表示处理失败平台会进行重试重试机制见com.gitee.sop.notify.service.NotifyBizService#timeLevel
* @param content
* @return
*/
@PostMapping("notify/callback")
public ResponseEntity<String> callback(@RequestBody String content) {
log.info("收到回调通知, content={}", content);
JSONObject jsonObject = JSON.parseObject(content);
// 签名校验
if (!checkSign(jsonObject)) {
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body("ERR");
}
log.info("签名验证通过,处理业务逻辑");
String method = jsonObject.getString("method");
// 判断业务类型,处理不同业务
switch (method) {
// 处理订单创建回调
case "shop.order.create": {
JSONObject bizContent = jsonObject.getJSONObject("biz_content");
log.info("业务参数bizContent={}", bizContent);
break;
}
case "shop.order.close": {
// 处理订单关闭回调
break;
}
default:{}
}
// 返回200状态即可
return ResponseEntity.ok("success");
}
private boolean checkSign(JSONObject jsonObject) {
try {
return SignUtil.rsaCheckV2(jsonObject, publicKey, "UTF-8", "RSA2");
} catch (SopSignException e) {
log.error("签名校验错误, jsonObject={}", jsonObject, e);
return false;
}
}
}

View File

@@ -0,0 +1,23 @@
package com.gitee.sop.notifyexample.open;
import com.gitee.sop.notifyexample.open.req.CreateOrderRequest;
import com.gitee.sop.notifyexample.open.resp.CreateOrderResponse;
import com.gitee.sop.support.annotation.Open;
/**
* 回调接口
*
* @author 六如
*/
public interface OpenNotify {
/**
* 下单-回调
*
* @apiNote 演示回调
*/
@Open("shop.order.create")
CreateOrderResponse createOrder(CreateOrderRequest request);
}

View File

@@ -0,0 +1,31 @@
package com.gitee.sop.notifyexample.open.impl;
import com.gitee.sop.notifyexample.open.OpenNotify;
import com.gitee.sop.notifyexample.open.req.CreateOrderRequest;
import com.gitee.sop.notifyexample.open.resp.CreateOrderResponse;
import com.gitee.sop.notifyexample.service.OrderService;
import com.gitee.sop.support.context.OpenContext;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 开放接口实现
*
* @author 六如
*/
@DubboService(validation = "true")
public class OpenNotifyImpl implements OpenNotify {
@Autowired
private OrderService orderService;
@Override
public CreateOrderResponse createOrder(CreateOrderRequest request) {
OpenContext openContext = OpenContext.current();
CreateOrderResponse response = new CreateOrderResponse();
String bizNumber = orderService.createOrder(request, openContext);
response.setOrderNo(bizNumber);
return response;
}
}

View File

@@ -0,0 +1,47 @@
package com.gitee.sop.notifyexample.open.req;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
/**
* 创建订单
*
* @author 六如
* https://opendocs.alipay.com/open/29ae8cb6_alipay.trade.wap.pay?pathHash=1ef587fd&ref=api&scene=21
*/
@Data
public class CreateOrderRequest {
/**
* 商户网站唯一订单号
*
* @mock 70501111111S001111119
*/
@Length(max = 64)
@NotBlank(message = "商户网站唯一订单号必填")
private String outTradeNo;
/**
* 订单总金额.单位为元,精确到小数点后两位,取值范围:[0.01,100000000]
*
* @mock 9.00
*/
@NotNull(message = "订单总金额不能为空")
private BigDecimal totalAmount;
/**
* 订单标题。注意:不可使用特殊字符,如 /=& 等。
*
* @mock 大乐透
*/
@Length(max = 256)
@NotBlank(message = "订单标题不能为空")
private String subject;
}

View File

@@ -0,0 +1,21 @@
package com.gitee.sop.notifyexample.open.resp;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author 六如
*/
@Data
public class CreateOrderResponse {
/**
* 订单号
*
* @mock 111
*/
@NotNull
private String orderNo;
}

View File

@@ -0,0 +1,77 @@
package com.gitee.sop.notifyexample.service;
import com.gitee.sop.notify.api.NotifyService;
import com.gitee.sop.notify.api.req.NotifyRequest;
import com.gitee.sop.notify.api.resp.NotifyResponse;
import com.gitee.sop.notifyexample.open.req.CreateOrderRequest;
import com.gitee.sop.support.context.OpenContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 订单服务,回调处理
*
* @author 六如
*/
@Service
@Slf4j
public class OrderService {
@DubboReference
private NotifyService notifyService;
/**
* 下单成功
*
* @param request 入参
* @param openContext 开放平台上下文
* @return 返回订单编号
*/
public String createOrder(CreateOrderRequest request, OpenContext openContext) {
// 处理业务,回调客户端
// 生成一个业务编号
String orderNo = UUID.randomUUID().toString();
log.info("生成订单,编号:{}", orderNo);
// 模拟业务处理耗时
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 发送回调
this.sendNotifyTask(request, openContext, orderNo);
return orderNo;
}
private void sendNotifyTask(CreateOrderRequest request, OpenContext openContext, String orderNo) {
// 回调
NotifyRequest notifyRequest = new NotifyRequest();
notifyRequest.setAppId(openContext.getAppId());
notifyRequest.setApiName(openContext.getApiName());
notifyRequest.setVersion(openContext.getVersion());
notifyRequest.setClientIp(openContext.getClientIp());
notifyRequest.setNotifyUrl(openContext.getNotifyUrl());
notifyRequest.setCharset(openContext.getCharset());
Map<String, Object> bizParams = new HashMap<>();
bizParams.put("orderNo", orderNo);
bizParams.put("msg", "success");
bizParams.put("price", "100");
notifyRequest.setBizParams(bizParams);
notifyRequest.setRemark("下单回调");
// 发送回调任务
NotifyResponse notifyResponse = notifyService.notify(notifyRequest);
log.info("回调返回,notifyResponse={}", notifyResponse);
if (notifyResponse.getSuccess()) {
log.info("保存notifyId到数据库, notifyId={}", notifyResponse.getNotifyId());
}
}
}

View File

@@ -0,0 +1 @@
dubbo.registry.address=zookeeper://localhost:2181

View File

@@ -0,0 +1,2 @@
dubbo.registry.address=nacos://localhost:8848

View File

@@ -0,0 +1,10 @@
spring.profiles.active=dev
server.port=7074
spring.application.name=example-notify
dubbo.protocol.name=dubbo
dubbo.protocol.port=-1
dubbo.application.qos-enable=false
dubbo.consumer.check=false
dubbo.registry.address=zookeeper://localhost:2181

View File

@@ -0,0 +1,27 @@
{
// 开启推送
"enable": true,
// 扫描package多个用;隔开
"basePackage": "com.gitee.sop.payment.open",
// 推送URLIP端口对应Torna服务器
"url": "http://localhost:7700/api",
// 模块token
"token": "34ff76952462413982d21219cf099d46",
// 推送人
"author": "Jim",
// 打开调试:true/false
"debug": true,
// 是否替换文档true替换false不替换追加。默认true
"isReplace": true,
// 第三方jar中的class配置
"jarClass": {
"com.xx.Page": {
"records": { "value": "查询数据列表", "example": "" },
"total": { "value": "总数", "example": "100" },
"size": { "value": "页数", "example": "10" },
"current": { "value": "当前页", "example": "1" },
"countId": { "hidden": true },
"orders": { "hidden": true }
}
}
}

View File

@@ -0,0 +1,10 @@
# 错误配置
# 系统配置
isp.error_isv.common-error=The system is busy.
isp.error_isv.invalid-parameter=Invalid parameter, {0}
# ==== 参数配置 ====
goods.remark.notNull=The goods_remark can not be null
goods.comment.length=The goods_comment length must >= {0} and <= {1}

View File

@@ -0,0 +1,14 @@
# 错误配置
# 系统繁忙
isp.error_isv.common-error=\u7cfb\u7edf\u7e41\u5fd9
# 参数无效
isp.error_isv.invalid-parameter=\u53c2\u6570\u65e0\u6548, {0}
# ==== 参数配置 ====
# 商品备注不能为空
goods.remark.notNull=\u5546\u54c1\u5907\u6ce8\u4e0d\u80fd\u4e3a\u7a7a
# 商品评论长度必须在{0}和{1}之间
goods.comment.length=\u5546\u54c1\u8bc4\u8bba\u957f\u5ea6\u5fc5\u987b\u5728{0}\u548c{1}\u4e4b\u95f4

View File

@@ -0,0 +1,2 @@
isp.goods_error_100=the goods_name can NOT be null
isp.goods_error_101=the goods_name must bigger than {0}

View File

@@ -0,0 +1,5 @@
# 商品名字不能为空
isp.goods_error_100=\u5546\u54C1\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
# 商品名称太短,不能小于{0}个字
isp.goods_error_101=\u5546\u54C1\u540D\u79F0\u592A\u77ED\uFF0C\u4E0D\u80FD\u5C0F\u4E8E{0}\u4E2A\u5B57

View File

@@ -0,0 +1,13 @@
{
"framework": "sop",
"outPath": "target/doc",
"projectName": "项目",
"packageFilters": "com.gitee.sop.payment.open.*",
"openUrl": "http://localhost:7700/api", // torna服务器地址
"appToken": "34ff76952462413982d21219cf099d46", // torna应用token
"debugEnvName":"本地环境",
"debugEnvUrl":"http://127.0.0.1:8081",
"tornaDebug": true,
"replace": true,
"showValidation": false
}

View File

@@ -0,0 +1,14 @@
package com.gitee.sop.notifyexample;
import cn.torna.swaggerplugin.SwaggerPlugin;
/**
* 推送swagger文档
* @author thc
*/
public class DocPushTest {
public static void main(String[] args) {
SwaggerPlugin.pushDoc();
}
}