From 5001809fef1ea4029dbbb018d75ba34579215caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AD=E5=A6=82?= Date: Mon, 3 Nov 2025 07:56:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9B=9E=E8=B0=83=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- changelog.md | 1 + pom.xml | 3 +- .../admin/common/enums/NotifyStatusEnum.java | 23 ++ .../gitee/sop/admin/dao/entity/IsvInfo.java | 5 + .../sop/admin/dao/entity/NotifyInfo.java | 99 +++++ .../admin/dao/mapper/NotifyInfoMapper.java | 13 + .../sop-admin-backend/admin-service/pom.xml | 5 + .../sop/admin/service/isv/IsvInfoService.java | 12 +- .../admin/service/isv/dto/IsvInfoAddDTO.java | 5 + .../sop/admin/service/isv/dto/IsvInfoDTO.java | 5 + .../service/serve/NotifyInfoService.java | 61 +++ .../controller/isv/param/IsvInfoAddParam.java | 1 + .../admin/controller/isv/vo/IsvInfoVO.java | 5 + .../serve/{ => api}/ApiInfoController.java | 6 +- .../{ => api}/param/ApiInfoPageParam.java | 2 +- .../serve/{ => api}/param/ApiInfoParam.java | 2 +- .../serve/{ => api}/vo/ApiInfoVO.java | 2 +- .../serve/notify/NotifyInfoController.java | 68 ++++ .../notify/param/NotifyInfoAddParam.java | 72 ++++ .../notify/param/NotifyInfoSearchParam.java | 106 +++++ .../notify/param/NotifyInfoUpdateParam.java | 24 ++ .../serve/notify/param/NotifyPushParam.java | 11 + .../serve/notify/vo/NotifyInfoVO.java | 96 +++++ .../sop-admin-frontend/src/api/notifyInfo.ts | 36 ++ .../src/model/enums/index.ts | 7 + .../src/views/isv/list/index.ts | 8 +- .../src/views/serve/notify/index.ts | 382 ++++++++++++++++++ .../src/views/serve/notify/index.vue | 73 ++++ sop-example/example-notify/.gitignore | 25 ++ sop-example/example-notify/pom.xml | 148 +++++++ sop-example/example-notify/push-doc.sh | 1 + .../ExampleNotifyApplication.java | 16 + .../controller/DemoCallbackController.java | 71 ++++ .../sop/notifyexample/open/OpenNotify.java | 23 ++ .../open/impl/OpenNotifyImpl.java | 31 ++ .../open/req/CreateOrderRequest.java | 47 +++ .../open/resp/CreateOrderResponse.java | 21 + .../notifyexample/service/OrderService.java | 77 ++++ .../main/resources/application-dev.properties | 1 + .../resources/application-test.properties | 2 + .../src/main/resources/application.properties | 10 + .../src/main/resources/doc.json | 27 ++ .../resources/i18n/isp/bizerror_en.properties | 10 + .../i18n/isp/bizerror_zh_CN.properties | 14 + .../i18n/isp/goods_error_en.properties | 2 + .../i18n/isp/goods_error_zh_CN.properties | 5 + .../src/main/resources/smart-doc.json | 13 + .../gitee/sop/notifyexample/DocPushTest.java | 14 + sop-example/pom.xml | 1 + sop-notify/pom.xml | 24 ++ sop-notify/sop-notify-api/pom.xml | 31 ++ .../gitee/sop/notify/api/NotifyService.java | 29 ++ .../sop/notify/api/req/NotifyRequest.java | 66 +++ .../sop/notify/api/resp/NotifyResponse.java | 39 ++ sop-notify/sop-notify-service/pom.xml | 154 +++++++ .../sop/notify/SopNotifyApplication.java | 18 + .../sop/notify/dao/entity/NotifyInfo.java | 99 +++++ .../sop/notify/dao/mapper/IsvMapper.java | 14 + .../notify/dao/mapper/NotifyInfoMapper.java | 13 + .../sop/notify/dubbo/NotifyServiceImpl.java | 48 +++ .../sop/notify/enums/NotifyStatusEnum.java | 22 + .../sop/notify/schedule/NotifySchedule.java | 27 ++ .../gitee/sop/notify/service/IsvService.java | 24 ++ .../sop/notify/service/NotifyBizService.java | 243 +++++++++++ .../gitee/sop/notify/service/bo/NotifyBO.java | 64 +++ .../main/resources/application-dev.properties | 7 + .../resources/application-test.properties | 9 + .../src/main/resources/application.properties | 47 +++ .../resources/mybatis/mapper/IsvMapper.xml | 19 + .../main/resources/mybatis/mybatisConfig.xml | 26 ++ .../com/gitee/sop/support/dto/NotifyInfo.java | 42 -- .../java/com/gitee/sop/test/NotifyTest.java | 65 +++ sop.sql | 31 +- upgrade/sop-20251101.sql | 29 ++ 75 files changed, 2828 insertions(+), 56 deletions(-) create mode 100644 sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/NotifyStatusEnum.java create mode 100644 sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/NotifyInfo.java create mode 100644 sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/NotifyInfoMapper.java create mode 100644 sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/serve/NotifyInfoService.java rename sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/{ => api}/ApiInfoController.java (93%) rename sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/{ => api}/param/ApiInfoPageParam.java (90%) rename sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/{ => api}/param/ApiInfoParam.java (84%) rename sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/{ => api}/vo/ApiInfoVO.java (96%) create mode 100644 sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/NotifyInfoController.java create mode 100644 sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyInfoAddParam.java create mode 100644 sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyInfoSearchParam.java create mode 100644 sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyInfoUpdateParam.java create mode 100644 sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyPushParam.java create mode 100644 sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/vo/NotifyInfoVO.java create mode 100644 sop-admin/sop-admin-frontend/src/api/notifyInfo.ts create mode 100644 sop-admin/sop-admin-frontend/src/views/serve/notify/index.ts create mode 100644 sop-admin/sop-admin-frontend/src/views/serve/notify/index.vue create mode 100755 sop-example/example-notify/.gitignore create mode 100755 sop-example/example-notify/pom.xml create mode 100644 sop-example/example-notify/push-doc.sh create mode 100755 sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/ExampleNotifyApplication.java create mode 100644 sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/controller/DemoCallbackController.java create mode 100755 sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/OpenNotify.java create mode 100755 sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/impl/OpenNotifyImpl.java create mode 100755 sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/req/CreateOrderRequest.java create mode 100755 sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/resp/CreateOrderResponse.java create mode 100644 sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/service/OrderService.java create mode 100755 sop-example/example-notify/src/main/resources/application-dev.properties create mode 100755 sop-example/example-notify/src/main/resources/application-test.properties create mode 100755 sop-example/example-notify/src/main/resources/application.properties create mode 100755 sop-example/example-notify/src/main/resources/doc.json create mode 100755 sop-example/example-notify/src/main/resources/i18n/isp/bizerror_en.properties create mode 100755 sop-example/example-notify/src/main/resources/i18n/isp/bizerror_zh_CN.properties create mode 100755 sop-example/example-notify/src/main/resources/i18n/isp/goods_error_en.properties create mode 100755 sop-example/example-notify/src/main/resources/i18n/isp/goods_error_zh_CN.properties create mode 100644 sop-example/example-notify/src/main/resources/smart-doc.json create mode 100755 sop-example/example-notify/src/test/java/com/gitee/sop/notifyexample/DocPushTest.java create mode 100644 sop-notify/pom.xml create mode 100644 sop-notify/sop-notify-api/pom.xml create mode 100644 sop-notify/sop-notify-api/src/main/java/com/gitee/sop/notify/api/NotifyService.java create mode 100644 sop-notify/sop-notify-api/src/main/java/com/gitee/sop/notify/api/req/NotifyRequest.java create mode 100644 sop-notify/sop-notify-api/src/main/java/com/gitee/sop/notify/api/resp/NotifyResponse.java create mode 100644 sop-notify/sop-notify-service/pom.xml create mode 100755 sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/SopNotifyApplication.java create mode 100644 sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dao/entity/NotifyInfo.java create mode 100755 sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dao/mapper/IsvMapper.java create mode 100644 sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dao/mapper/NotifyInfoMapper.java create mode 100644 sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dubbo/NotifyServiceImpl.java create mode 100644 sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/enums/NotifyStatusEnum.java create mode 100644 sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/schedule/NotifySchedule.java create mode 100644 sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/service/IsvService.java create mode 100644 sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/service/NotifyBizService.java create mode 100644 sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/service/bo/NotifyBO.java create mode 100755 sop-notify/sop-notify-service/src/main/resources/application-dev.properties create mode 100755 sop-notify/sop-notify-service/src/main/resources/application-test.properties create mode 100755 sop-notify/sop-notify-service/src/main/resources/application.properties create mode 100644 sop-notify/sop-notify-service/src/main/resources/mybatis/mapper/IsvMapper.xml create mode 100755 sop-notify/sop-notify-service/src/main/resources/mybatis/mybatisConfig.xml delete mode 100755 sop-support/sop-service-support/src/main/java/com/gitee/sop/support/dto/NotifyInfo.java create mode 100644 sop-test/src/test/java/com/gitee/sop/test/NotifyTest.java create mode 100644 upgrade/sop-20251101.sql diff --git a/README.md b/README.md index 4f4c802e..8f8d1ac8 100755 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ 通过简单的配置后,你的项目就具备了和支付宝开放平台的一样的接口提供能力。 -SOP封装了开放平台大部分功能包括:签名验证、统一异常处理、统一返回内容 、业务参数验证(JSR-303)、秘钥管理等,未来还会实现更多功能。 +SOP封装了开放平台大部分功能包括:签名验证、统一异常处理、统一返回内容 、业务参数验证(JSR-303)、秘钥管理、接口回调等。 ## 项目特点 diff --git a/changelog.md b/changelog.md index ab004f29..6af1bc99 100755 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,7 @@ ## 日常更新 +- 2025-11-01:添加回调处理。有升级SQL,见:[sop-20251101.sql](./upgrade/sop-20251101.sql) - 2025-09-12:修复推送文档报找不到@Open注解问题 - 2025-08-29:smart-doc升级到3.1.1 - 2025-08-17:admin后台可关联商户;fastmybatis升级到3.1.7。有升级SQL,见:[sop-20250817.sql](./upgrade/sop-20250817.sql) diff --git a/pom.xml b/pom.xml index 3c57eaeb..393d4f70 100755 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ sop-gateway sop-registry sop-support + sop-notify @@ -127,7 +128,7 @@ com.alibaba transmittable-thread-local - 2.14.5 + 2.14.2 com.google.guava diff --git a/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/NotifyStatusEnum.java b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/NotifyStatusEnum.java new file mode 100644 index 00000000..2b9af6e8 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-common/src/main/java/com/gitee/sop/admin/common/enums/NotifyStatusEnum.java @@ -0,0 +1,23 @@ +package com.gitee.sop.admin.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author 六如 + */ +@AllArgsConstructor +@Getter +public enum NotifyStatusEnum implements IntEnum { + + // 状态,1-发送成功,2-发送失败,3-重试结束 + SEND_SUCCESS(1, "发送成功"), + SEND_FAIL(2, "发送失败"), + RETRY_OVER(3, "重试结束"), + END(4, "手动结束"); + + private final Integer value; + + private final String description; + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/IsvInfo.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/IsvInfo.java index e76d2aa5..9b672bc9 100755 --- a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/IsvInfo.java +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/IsvInfo.java @@ -38,6 +38,11 @@ public class IsvInfo { */ private String remark; + /** + * 回调接口 + */ + private String notifyUrl; + /** * 添加时间 */ diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/NotifyInfo.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/NotifyInfo.java new file mode 100644 index 00000000..0bc182c3 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/entity/NotifyInfo.java @@ -0,0 +1,99 @@ +package com.gitee.sop.admin.dao.entity; + +import java.time.LocalDateTime; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; + +import lombok.Data; + + +/** + * 表名:notify_info + * 备注:回调信息 + * + * @author 六如 + */ +@Table(name = "notify_info", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class NotifyInfo { + + private Long id; + + /** + * app_id + */ + private String appId; + + /** + * api_name + */ + private String apiName; + + /** + * api_version + */ + private String apiVersion; + + /** + * 最近一次发送时间 + */ + private LocalDateTime lastSendTime; + + /** + * 下一次发送时间 + */ + private LocalDateTime nextSendTime; + + /** + * 已发送次数 + */ + private Integer sendCnt; + + /** + * 发送内容 + */ + private String content; + + /** + * 状态,1-发送成功,2-发送失败,3-重试结束 + */ + private Integer notifyStatus; + + /** + * 失败原因 + */ + private String errorMsg; + + /** + * 返回结果 + */ + private String resultContent; + + /** + * 备注 + */ + private String remark; + + /** + * 回调url + */ + private String notifyUrl; + + private LocalDateTime addTime; + + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/NotifyInfoMapper.java b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/NotifyInfoMapper.java new file mode 100644 index 00000000..6ace3dd2 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-dao/src/main/java/com/gitee/sop/admin/dao/mapper/NotifyInfoMapper.java @@ -0,0 +1,13 @@ +package com.gitee.sop.admin.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.admin.dao.entity.NotifyInfo; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface NotifyInfoMapper extends BaseMapper { + +} diff --git a/sop-admin/sop-admin-backend/admin-service/pom.xml b/sop-admin/sop-admin-backend/admin-service/pom.xml index 4489abd1..a17e46c3 100755 --- a/sop-admin/sop-admin-backend/admin-service/pom.xml +++ b/sop-admin/sop-admin-backend/admin-service/pom.xml @@ -28,6 +28,11 @@ admin-dao 5.0.0-SNAPSHOT + + com.gitee.sop + sop-notify-api + 5.0.0-SNAPSHOT + org.apache.dubbo dubbo diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/IsvInfoService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/IsvInfoService.java index a53588c8..0629f205 100755 --- a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/IsvInfoService.java +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/IsvInfoService.java @@ -119,10 +119,7 @@ public class IsvInfoService implements ServiceSupport { @Transactional(rollbackFor = Exception.class) public int update(IsvInfoUpdateDTO isvInfoUpdateDTO) { - IsvInfo isvInfo = new IsvInfo(); - isvInfo.setId(isvInfoUpdateDTO.getId()); - isvInfo.setStatus(isvInfoUpdateDTO.getStatus()); - isvInfo.setRemark(isvInfoUpdateDTO.getRemark()); + IsvInfo isvInfo = CopyUtil.copyBean(isvInfoUpdateDTO, IsvInfo::new); int cnt = this.update(isvInfo); sendChangeEvent(isvInfoUpdateDTO.getId()); return cnt; @@ -185,4 +182,11 @@ public class IsvInfoService implements ServiceSupport { sendChangeEvent(isvId); return i; } + + public Integer setNotifyUrl(Long id, String url) { + return this.query() + .eq(IsvInfo::getId, id) + .set(IsvInfo::getNotifyUrl, url) + .update(); + } } diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoAddDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoAddDTO.java index b7935343..2517b77e 100755 --- a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoAddDTO.java +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoAddDTO.java @@ -24,4 +24,9 @@ public class IsvInfoAddDTO { @Length(max = 500) private String remark; + /** + * 回调接口 + */ + private String notifyUrl; + } diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoDTO.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoDTO.java index 2fed0ed3..3cbff588 100755 --- a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoDTO.java +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/isv/dto/IsvInfoDTO.java @@ -58,4 +58,9 @@ public class IsvInfoDTO { */ private String merchantCode; + /** + * 回调接口 + */ + private String notifyUrl; + } diff --git a/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/serve/NotifyInfoService.java b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/serve/NotifyInfoService.java new file mode 100644 index 00000000..2ec99829 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-service/src/main/java/com/gitee/sop/admin/service/serve/NotifyInfoService.java @@ -0,0 +1,61 @@ +package com.gitee.sop.admin.service.serve; + +import com.gitee.fastmybatis.core.PageInfo; +import com.gitee.fastmybatis.core.query.LambdaQuery; +import com.gitee.fastmybatis.core.support.LambdaService; +import com.gitee.sop.admin.common.enums.NotifyStatusEnum; +import com.gitee.sop.admin.common.exception.BizException; +import com.gitee.sop.admin.dao.entity.NotifyInfo; +import com.gitee.sop.admin.dao.mapper.NotifyInfoMapper; +import com.gitee.sop.notify.api.NotifyService; +import com.gitee.sop.notify.api.resp.NotifyResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.stereotype.Service; + + +/** + * @author thc + */ +@Slf4j +@Service +public class NotifyInfoService implements LambdaService { + + @DubboReference + private NotifyService notifyService; + + public PageInfo doPage(LambdaQuery query) { + query.orderByDesc(NotifyInfo::getId); + PageInfo page = this.page(query); + + // 格式转换 + return page.convert(isvInfo -> { + + return isvInfo; + }); + } + + public int push(Long id, String url) { + if (StringUtils.isNotBlank(url)) { + this.query() + .eq(NotifyInfo::getId, id) + .set(NotifyInfo::getNotifyUrl, url) + .update(); + } + NotifyResponse notifyResponse = notifyService.notifyImmediately(id); + log.info("重新推送结果, notifyResponse={}", notifyResponse); + if (!notifyResponse.getSuccess()) { + throw new BizException(notifyResponse.getMsg()); + } + return 1; + } + + public int end(Long id) { + return this.query() + .eq(NotifyInfo::getId, id) + .eq(NotifyInfo::getNotifyStatus, NotifyStatusEnum.SEND_FAIL.getValue()) + .set(NotifyInfo::getNotifyStatus, NotifyStatusEnum.END.getValue()) + .update(); + } +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvInfoAddParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvInfoAddParam.java index 306a97ea..969ec36c 100755 --- a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvInfoAddParam.java +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/param/IsvInfoAddParam.java @@ -24,4 +24,5 @@ public class IsvInfoAddParam { @Length(max = 500) private String remark; + private String notifyUrl; } diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/vo/IsvInfoVO.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/vo/IsvInfoVO.java index eb1b449a..ddb25071 100755 --- a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/vo/IsvInfoVO.java +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/isv/vo/IsvInfoVO.java @@ -58,4 +58,9 @@ public class IsvInfoVO { */ private String merchantCode; + /** + * 回调接口 + */ + private String notifyUrl; + } diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/ApiInfoController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/api/ApiInfoController.java similarity index 93% rename from sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/ApiInfoController.java rename to sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/api/ApiInfoController.java index c256286c..51d4febe 100755 --- a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/ApiInfoController.java +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/api/ApiInfoController.java @@ -1,4 +1,4 @@ -package com.gitee.sop.admin.controller.serve; +package com.gitee.sop.admin.controller.serve.api; import com.gitee.fastmybatis.core.PageInfo; import com.gitee.fastmybatis.core.query.Query; @@ -6,8 +6,8 @@ import com.gitee.sop.admin.common.dto.StatusUpdateDTO; import com.gitee.sop.admin.common.req.StatusUpdateParam; import com.gitee.sop.admin.common.resp.Result; import com.gitee.sop.admin.common.util.CopyUtil; -import com.gitee.sop.admin.controller.serve.param.ApiInfoPageParam; -import com.gitee.sop.admin.controller.serve.vo.ApiInfoVO; +import com.gitee.sop.admin.controller.serve.api.param.ApiInfoPageParam; +import com.gitee.sop.admin.controller.serve.api.vo.ApiInfoVO; import com.gitee.sop.admin.dao.entity.ApiInfo; import com.gitee.sop.admin.service.serve.ApiInfoService; import org.springframework.beans.factory.annotation.Autowired; diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/param/ApiInfoPageParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/api/param/ApiInfoPageParam.java similarity index 90% rename from sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/param/ApiInfoPageParam.java rename to sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/api/param/ApiInfoPageParam.java index 793fdd03..813990a9 100755 --- a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/param/ApiInfoPageParam.java +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/api/param/ApiInfoPageParam.java @@ -1,4 +1,4 @@ -package com.gitee.sop.admin.controller.serve.param; +package com.gitee.sop.admin.controller.serve.api.param; import com.gitee.fastmybatis.core.query.Operator; import com.gitee.fastmybatis.core.query.annotation.Condition; diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/param/ApiInfoParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/api/param/ApiInfoParam.java similarity index 84% rename from sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/param/ApiInfoParam.java rename to sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/api/param/ApiInfoParam.java index 14e98ed0..52825f5c 100755 --- a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/param/ApiInfoParam.java +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/api/param/ApiInfoParam.java @@ -1,4 +1,4 @@ -package com.gitee.sop.admin.controller.serve.param; +package com.gitee.sop.admin.controller.serve.api.param; import com.gitee.fastmybatis.core.query.Operator; import com.gitee.fastmybatis.core.query.annotation.Condition; diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/vo/ApiInfoVO.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/api/vo/ApiInfoVO.java similarity index 96% rename from sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/vo/ApiInfoVO.java rename to sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/api/vo/ApiInfoVO.java index 2a7ce9c9..f265798a 100755 --- a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/vo/ApiInfoVO.java +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/api/vo/ApiInfoVO.java @@ -1,4 +1,4 @@ -package com.gitee.sop.admin.controller.serve.vo; +package com.gitee.sop.admin.controller.serve.api.vo; import com.gitee.sop.admin.service.jackson.convert.annotation.UserFormat; import lombok.Data; diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/NotifyInfoController.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/NotifyInfoController.java new file mode 100644 index 00000000..c4b0f76d --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/NotifyInfoController.java @@ -0,0 +1,68 @@ +package com.gitee.sop.admin.controller.serve.notify; + +import com.gitee.fastmybatis.core.PageInfo; +import com.gitee.fastmybatis.core.query.LambdaQuery; +import com.gitee.sop.admin.common.req.IdParam; +import com.gitee.sop.admin.common.resp.Result; +import com.gitee.sop.admin.common.util.CopyUtil; +import com.gitee.sop.admin.controller.serve.notify.param.NotifyInfoSearchParam; +import com.gitee.sop.admin.controller.serve.notify.param.NotifyPushParam; +import com.gitee.sop.admin.controller.serve.notify.vo.NotifyInfoVO; +import com.gitee.sop.admin.dao.entity.NotifyInfo; +import com.gitee.sop.admin.service.serve.NotifyInfoService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +/** + * @author thc + */ +@RestController +@RequestMapping("serve/notify") +public class NotifyInfoController { + + @Autowired + private NotifyInfoService notifyInfoService; + + /** + * 分页查询 + * + * @param param 查询参数 + * @return 返回分页结果 + */ + @GetMapping("/page") + public Result> page(NotifyInfoSearchParam param) { + LambdaQuery query = param.toLambdaQuery(NotifyInfo.class); + PageInfo pageInfo = notifyInfoService.doPage(query) + .convert(data -> CopyUtil.copyBean(data, NotifyInfoVO::new)); + return Result.ok(pageInfo); + } + + /** + * 重新推送 + * + * @param param 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/push") + public Result update(@Validated @RequestBody NotifyPushParam param) { + return Result.ok(notifyInfoService.push(param.getId(), param.getUrl())); + } + + /** + * 结束重试 + * + * @param param 表单数据 + * @return 返回影响行数 + */ + @PostMapping("/end") + public Result end(@Validated @RequestBody IdParam param) { + return Result.ok(notifyInfoService.end(param.getId())); + } + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyInfoAddParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyInfoAddParam.java new file mode 100644 index 00000000..3fee3848 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyInfoAddParam.java @@ -0,0 +1,72 @@ +package com.gitee.sop.admin.controller.serve.notify.param; + +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * 新增表单 + * + * @author thc + */ +@Data +public class NotifyInfoAddParam { + + /** + * app_id + */ + private String appId; + + /** + * api_name + */ + private String apiName; + + /** + * api_version + */ + private String apiVersion; + + /** + * 最近一次发送时间 + */ + private LocalDateTime lastSendTime; + + /** + * 下一次发送时间 + */ + private LocalDateTime nextSendTime; + + /** + * 最大发送次数 + */ + private Integer sendMax; + + /** + * 已发送次数 + */ + private Integer sendCnt; + + /** + * 发送内容 + */ + private String content; + + /** + * 状态,1-发送成功,2-发送失败,3-重试结束 + */ + private Integer notifyStatus; + + /** + * 失败原因 + */ + private String errorMsg; + + /** + * 备注 + */ + private String remark; + + +} \ No newline at end of file diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyInfoSearchParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyInfoSearchParam.java new file mode 100644 index 00000000..70e5b284 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyInfoSearchParam.java @@ -0,0 +1,106 @@ +package com.gitee.sop.admin.controller.serve.notify.param; + +import com.gitee.fastmybatis.core.query.Operator; +import com.gitee.fastmybatis.core.query.annotation.Condition; +import com.gitee.fastmybatis.core.query.param.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 查询表单 + * + * @author thc + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class NotifyInfoSearchParam extends PageParam { + private static final long serialVersionUID = 1L; + + /** + * app_id + */ + @Condition(operator = Operator.like) + private String appId; + + /** + * api_name + */ + @Condition(operator = Operator.like) + private String apiName; + + /** + * api_version + */ + @Condition(operator = Operator.like) + private String apiVersion; + + /** + * 最近一次发送时间 + */ + @Condition + private LocalDateTime lastSendTime; + + /** + * 下一次发送时间 + */ + @Condition + private LocalDateTime nextSendTime; + + /** + * 最大发送次数 + */ + @Condition + private Integer sendMax; + + /** + * 已发送次数 + */ + @Condition + private Integer sendCnt; + + /** + * 发送内容 + */ + @Condition(operator = Operator.like) + private String content; + + /** + * 状态,1-发送成功,2-发送失败,3-重试结束 + */ + @Condition + private Integer notifyStatus; + + /** + * 失败原因 + */ + @Condition(operator = Operator.like) + private String errorMsg; + + /** + * 备注 + */ + @Condition(operator = Operator.like) + private String remark; + + @Condition + private LocalDateTime addTime; + + @Condition + private LocalDateTime updateTime; + + /** + * 创建人id + */ + @Condition + private Long addBy; + + /** + * 修改人id + */ + @Condition + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyInfoUpdateParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyInfoUpdateParam.java new file mode 100644 index 00000000..d142b676 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyInfoUpdateParam.java @@ -0,0 +1,24 @@ +package com.gitee.sop.admin.controller.serve.notify.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotNull; + + +/** + * 修改表单 + * + * @author thc + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class NotifyInfoUpdateParam extends NotifyInfoAddParam { + + /** + * id + */ + @NotNull(message = "id必填") + private Long id; + +} \ No newline at end of file diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyPushParam.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyPushParam.java new file mode 100644 index 00000000..8bf7a849 --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/param/NotifyPushParam.java @@ -0,0 +1,11 @@ +package com.gitee.sop.admin.controller.serve.notify.param; + +import com.gitee.sop.admin.common.req.IdParam; +import lombok.Data; + +@Data +public class NotifyPushParam extends IdParam { + + private String url; + +} diff --git a/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/vo/NotifyInfoVO.java b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/vo/NotifyInfoVO.java new file mode 100644 index 00000000..e2184b3f --- /dev/null +++ b/sop-admin/sop-admin-backend/admin-web/src/main/java/com/gitee/sop/admin/controller/serve/notify/vo/NotifyInfoVO.java @@ -0,0 +1,96 @@ +package com.gitee.sop.admin.controller.serve.notify.vo; + +import com.gitee.sop.admin.service.jackson.convert.annotation.UserFormat; +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * 返回结果 + * + * @author thc + */ +@Data +public class NotifyInfoVO { + + private Long id; + + /** + * app_id + */ + private String appId; + + /** + * api_name + */ + private String apiName; + + /** + * api_version + */ + private String apiVersion; + + /** + * 最近一次发送时间 + */ + private LocalDateTime lastSendTime; + + /** + * 下一次发送时间 + */ + private LocalDateTime nextSendTime; + + /** + * 最大发送次数 + */ + private Integer sendMax; + + /** + * 已发送次数 + */ + private Integer sendCnt; + + /** + * 发送内容 + */ + private String content; + + /** + * 状态,1-发送成功,2-发送失败,3-重试结束 + */ + private Integer notifyStatus; + + /** + * 失败原因 + */ + private String errorMsg; + + /** + * 返回结果 + */ + private String resultContent; + + /** + * 备注 + */ + private String remark; + + private LocalDateTime addTime; + + private LocalDateTime updateTime; + + /** + * 创建人id + */ + @UserFormat + private Long addBy; + + /** + * 修改人id + */ + @UserFormat + private Long updateBy; + + +} diff --git a/sop-admin/sop-admin-frontend/src/api/notifyInfo.ts b/sop-admin/sop-admin-frontend/src/api/notifyInfo.ts new file mode 100644 index 00000000..f4b9ad9a --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/api/notifyInfo.ts @@ -0,0 +1,36 @@ +import { createUrl, http } from "@/utils/http"; +import type { PageResult, Result } from "@/model"; + +// 后端请求接口 +const apiUrl: any = createUrl({ + page: "/serve/notify/page", + push: "/serve/notify/push", + end: "/serve/notify/end" +}); + +/** + * 接口管理 + */ +export const api: any = { + /** + * 分页查询 + * @param params 查询参数 + */ + page(params: object): Promise { + return http.get(apiUrl.page, { params }); + }, + /** + * 推送 + * @param data 表单内容 + */ + push(data: object) { + return http.post, any>(apiUrl.push, { data }); + }, + /** + * 关闭 + * @param data 表单内容 + */ + end(data: object) { + return http.post, any>(apiUrl.end, { data }); + } +}; diff --git a/sop-admin/sop-admin-frontend/src/model/enums/index.ts b/sop-admin/sop-admin-frontend/src/model/enums/index.ts index c2466c8f..3f30be96 100755 --- a/sop-admin/sop-admin-frontend/src/model/enums/index.ts +++ b/sop-admin/sop-admin-frontend/src/model/enums/index.ts @@ -17,3 +17,10 @@ export enum RegSource { SYS = 1, CUSTOM = 2 } + +export enum NotifyStatusEnum { + SUCCESS = 1, + FAIL = 2, + END = 3, + STOP = 4 +} diff --git a/sop-admin/sop-admin-frontend/src/views/isv/list/index.ts b/sop-admin/sop-admin-frontend/src/views/isv/list/index.ts index d3dfba48..fd35ede8 100755 --- a/sop-admin/sop-admin-frontend/src/views/isv/list/index.ts +++ b/sop-admin/sop-admin-frontend/src/views/isv/list/index.ts @@ -239,7 +239,8 @@ export function useIsvList() { id: 0, status: StatusEnum.ENABLE, keyFormat: KeyFormatEnum.PKCS8, - remark: "" + remark: "", + notifyUrl: "" }; }; const editFormData = ref(editFormDataGen()); @@ -280,6 +281,11 @@ export function useIsvList() { showWordLimit: true, autosize: { minRows: 2, maxRows: 4 } } + }, + { + label: "回调接口", + prop: "notifyUrl", + valueType: "input" } ]; diff --git a/sop-admin/sop-admin-frontend/src/views/serve/notify/index.ts b/sop-admin/sop-admin-frontend/src/views/serve/notify/index.ts new file mode 100644 index 00000000..73be7470 --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/views/serve/notify/index.ts @@ -0,0 +1,382 @@ +import { onMounted, ref } from "vue"; +import { + type ButtonsCallBackParams, + type PageInfo, + type PlusColumn, + useTable +} from "plus-pro-components"; +import { ElMessage, ElMessageBox } from "element-plus"; +import { api } from "@/api/notifyInfo"; +import { WarnTriangleFilled } from "@element-plus/icons-vue"; +import { NotifyStatusEnum } from "@/model/enums"; + +export function useEntNotifyInfo() { + const isAdd = ref(false); + + // ========= search form ========= + + // 查询表单对象 + const searchFormData = ref({ + appId: "", + apiName: "", + apiVersion: "", + notifyStatus: "", + pageIndex: 1, + pageSize: 10 + }); + + // 查询表单字段定义 + const searchFormColumns: PlusColumn[] = [ + { + label: "AppId", + prop: "appId" + }, + { + label: "接口名称", + prop: "apiName" + }, + { + label: "接口版本", + prop: "apiVersion" + }, + { + label: "状态", + prop: "notifyStatus", + width: 80, + valueType: "select", + options: [ + { + label: "成功", + value: NotifyStatusEnum.SUCCESS, + color: "green" + }, + { + label: "失败", + value: NotifyStatusEnum.FAIL, + color: "red" + }, + { + label: "重试结束", + value: NotifyStatusEnum.END, + color: "gray" + } + ] + } + ]; + + // ========= table ========= + + // 表格对象 + const { + tableData, + total, + pageInfo, + buttons: actionButtons + } = useTable(); + // 默认每页条数,默认10 + pageInfo.value.pageSize = 10; + + // 表格字段定义 + const tableColumns: PlusColumn[] = [ + { + label: "AppId", + prop: "appId" + }, + { + label: "接口名称", + prop: "apiName" + }, + { + label: "接口版本", + prop: "apiVersion" + }, + { + width: 120, + label: "最近一次发送时间", + prop: "lastSendTime" + }, + { + width: 120, + label: "下一次发送时间", + prop: "nextSendTime" + }, + { + label: "已发送次数", + prop: "sendCnt" + }, + { + label: "发送内容", + prop: "content" + }, + { + label: "状态", + prop: "notifyStatus", + width: 100, + valueType: "select", + options: [ + { + label: "成功", + value: NotifyStatusEnum.SUCCESS, + color: "green" + }, + { + label: "失败", + value: NotifyStatusEnum.FAIL, + color: "red" + }, + { + label: "重试结束", + value: NotifyStatusEnum.END, + color: "gray" + }, + { + label: "重试结束", + value: NotifyStatusEnum.STOP, + color: "gray" + } + ] + }, + { + label: "失败原因", + prop: "errorMsg" + }, + { + label: "返回结果", + prop: "resultContent", + width: 120 + }, + { + label: "备注", + prop: "remark" + }, + { + width: 120, + label: "添加时间", + prop: "addTime" + }, + { + width: 120, + label: "修改时间", + prop: "updateTime" + } + ]; + // 表格按钮定义 + actionButtons.value = [ + { + text: "推送", + props: { + type: "primary" + }, + onClick(params: ButtonsCallBackParams) { + ElMessageBox.prompt("回调URL", "推送", { + confirmButtonText: "确定", + cancelButtonText: "取消", + inputPlaceholder: "【选填】不填使用默认URL" + }) + .then(({ value }) => { + api + .push({ + id: params.row.id, + url: value + }) + .then(() => { + ElMessage.success("操作成功"); + search(); + }); + }) + .catch(() => {}); + } + }, + { + text: "结束重试", + show: (row: any) => row.notifyStatus === 2, + props: { + type: "danger" + }, + confirm: { + // @ts-ignore + popconfirmProps: { + width: 150, + icon: WarnTriangleFilled, + iconColor: "red" + }, + message: _ => `确定要结束重试吗?` + }, + onConfirm(params: ButtonsCallBackParams) { + api.end(params.row).then(() => { + ElMessage.success("操作成功"); + search(); + }); + } + } + ]; + + // ========= dialog form ========= + + // 弹窗显示 + const dlgShow = ref(false); + const dlgTitle = ref(""); + // 表单值 + const editFormDataGen = () => { + return { + appId: "", + apiName: "", + apiVersion: "", + lastSendTime: "", + nextSendTime: "", + sendMax: "", + sendCnt: "", + content: "", + notifyStatus: "", + errorMsg: "", + remark: "" + }; + }; + const editFormData = ref(editFormDataGen()); + const editFormRules = { + appId: [{ required: true, message: "请输入app_id" }], + apiName: [{ required: true, message: "请输入api_name" }], + apiVersion: [{ required: true, message: "请输入api_version" }] + }; + + // 表单内容 + const editFormColumns: PlusColumn[] = [ + { + label: "app_id", + prop: "appId", + valueType: "input" + }, + { + label: "api_name", + prop: "apiName", + valueType: "input" + }, + { + label: "api_version", + prop: "apiVersion", + valueType: "input" + }, + { + label: "最近一次发送时间", + prop: "lastSendTime", + valueType: "input" + }, + { + label: "下一次发送时间", + prop: "nextSendTime", + valueType: "input" + }, + { + label: "已发送次数", + prop: "sendCnt", + valueType: "input" + }, + { + label: "发送内容", + prop: "content", + valueType: "input" + }, + { + label: "状态,1-发送成功,2-发送失败,3-重试结束", + prop: "notifyStatus", + valueType: "input" + }, + { + label: "失败原因", + prop: "errorMsg", + valueType: "input" + }, + { + label: "备注", + prop: "remark", + valueType: "input" + } + ]; + + // ========= event ========= + + // 添加按钮事件 + const handleAdd = () => { + isAdd.value = true; + editFormData.value = editFormDataGen(); + dlgTitle.value = "新增"; + dlgShow.value = true; + }; + + // 保存按钮事件,校验成功后触发 + const handleSave = () => { + const postData = editFormData.value; + const pms = isAdd.value ? api.add(postData) : api.update(postData); + pms.then(() => { + ElMessage.success("保存成功"); + dlgShow.value = false; + search(); + }); + }; + + // 点击查询按钮 + const handleSearch = () => { + pageInfo.value.page = 1; + search(); + }; + + // 分页事件 + const handlePaginationChange = (_pageInfo: PageInfo): void => { + pageInfo.value = _pageInfo; + search(); + }; + + // 查询 + const search = async () => { + try { + const { data } = await doSearch(); + tableData.value = data.list; + total.value = data.total; + } catch (error) {} + }; + // 请求接口 + const doSearch = async () => { + // 查询参数 + const data = searchFormData.value; + // 添加分页参数 + data.pageIndex = pageInfo.value.page; + data.pageSize = pageInfo.value.pageSize; + + return api.page(data); + }; + + onMounted(() => { + // 页面加载 + search(); + }); + + function viewContent(row: any) { + ElMessageBox.alert( + ``, + "发送内容", + { + dangerouslyUseHTMLString: true + } + ); + } + + return { + actionButtons, + dlgShow, + dlgTitle, + editFormColumns, + editFormData, + editFormRules, + handleAdd, + handlePaginationChange, + handleSave, + handleSearch, + pageInfo, + searchFormColumns, + searchFormData, + tableColumns, + tableData, + total, + viewContent + }; +} diff --git a/sop-admin/sop-admin-frontend/src/views/serve/notify/index.vue b/sop-admin/sop-admin-frontend/src/views/serve/notify/index.vue new file mode 100644 index 00000000..d362368a --- /dev/null +++ b/sop-admin/sop-admin-frontend/src/views/serve/notify/index.vue @@ -0,0 +1,73 @@ + + diff --git a/sop-example/example-notify/.gitignore b/sop-example/example-notify/.gitignore new file mode 100755 index 00000000..18ccf36e --- /dev/null +++ b/sop-example/example-notify/.gitignore @@ -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/ diff --git a/sop-example/example-notify/pom.xml b/sop-example/example-notify/pom.xml new file mode 100755 index 00000000..88075122 --- /dev/null +++ b/sop-example/example-notify/pom.xml @@ -0,0 +1,148 @@ + + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.6.15 + + + + com.gitee.sop + example-notify + 5.0.0-SNAPSHOT + example-notify + + + 1.8 + + 3.2.16 + + + + + + com.gitee.sop + sop-notify-api + 5.0.0-SNAPSHOT + + + + + com.gitee.sop + sop-spring-boot-starter + 5.0.0-SNAPSHOT + + + + org.apache.dubbo + dubbo-nacos-spring-boot-starter + + + + org.apache.dubbo + dubbo-zookeeper-curator5-spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-web + + + com.gitee.sop + sdk-java + 5.0.0-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + 1.18.34 + provided + + + + + + + org.apache.dubbo + dubbo-bom + ${dubbo.version} + pom + import + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12.4 + + true + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + ${java.version} + ${java.version} + UTF-8 + + -parameters + + + + + + com.ly.smart-doc + smart-doc-maven-plugin + 3.0.9 + + + ./src/main/resources/smart-doc.json + + ${project.artifactId} + + + + com.gitee.sop + sop-service-support + 5.0.0-SNAPSHOT + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + maven_central + Maven Central + https://repo.maven.apache.org/maven2/ + + + + diff --git a/sop-example/example-notify/push-doc.sh b/sop-example/example-notify/push-doc.sh new file mode 100644 index 00000000..7068d908 --- /dev/null +++ b/sop-example/example-notify/push-doc.sh @@ -0,0 +1 @@ +mvn -Dfile.encoding=UTF-8 -Dcheckstyle.skip=true smart-doc:torna-rpc diff --git a/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/ExampleNotifyApplication.java b/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/ExampleNotifyApplication.java new file mode 100755 index 00000000..e35044e1 --- /dev/null +++ b/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/ExampleNotifyApplication.java @@ -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); + } + +} + diff --git a/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/controller/DemoCallbackController.java b/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/controller/DemoCallbackController.java new file mode 100644 index 00000000..7f118584 --- /dev/null +++ b/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/controller/DemoCallbackController.java @@ -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 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; + } + } +} diff --git a/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/OpenNotify.java b/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/OpenNotify.java new file mode 100755 index 00000000..0bdc7570 --- /dev/null +++ b/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/OpenNotify.java @@ -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); + + +} diff --git a/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/impl/OpenNotifyImpl.java b/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/impl/OpenNotifyImpl.java new file mode 100755 index 00000000..59044ac3 --- /dev/null +++ b/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/impl/OpenNotifyImpl.java @@ -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; + } +} diff --git a/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/req/CreateOrderRequest.java b/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/req/CreateOrderRequest.java new file mode 100755 index 00000000..37788ff6 --- /dev/null +++ b/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/req/CreateOrderRequest.java @@ -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; + + +} diff --git a/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/resp/CreateOrderResponse.java b/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/resp/CreateOrderResponse.java new file mode 100755 index 00000000..35fc33a5 --- /dev/null +++ b/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/open/resp/CreateOrderResponse.java @@ -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; + +} diff --git a/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/service/OrderService.java b/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/service/OrderService.java new file mode 100644 index 00000000..e1570324 --- /dev/null +++ b/sop-example/example-notify/src/main/java/com/gitee/sop/notifyexample/service/OrderService.java @@ -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 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()); + } + } + +} diff --git a/sop-example/example-notify/src/main/resources/application-dev.properties b/sop-example/example-notify/src/main/resources/application-dev.properties new file mode 100755 index 00000000..bfead63f --- /dev/null +++ b/sop-example/example-notify/src/main/resources/application-dev.properties @@ -0,0 +1 @@ +dubbo.registry.address=zookeeper://localhost:2181 diff --git a/sop-example/example-notify/src/main/resources/application-test.properties b/sop-example/example-notify/src/main/resources/application-test.properties new file mode 100755 index 00000000..53e5d0bc --- /dev/null +++ b/sop-example/example-notify/src/main/resources/application-test.properties @@ -0,0 +1,2 @@ + +dubbo.registry.address=nacos://localhost:8848 diff --git a/sop-example/example-notify/src/main/resources/application.properties b/sop-example/example-notify/src/main/resources/application.properties new file mode 100755 index 00000000..c350dca2 --- /dev/null +++ b/sop-example/example-notify/src/main/resources/application.properties @@ -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 diff --git a/sop-example/example-notify/src/main/resources/doc.json b/sop-example/example-notify/src/main/resources/doc.json new file mode 100755 index 00000000..2e1ba445 --- /dev/null +++ b/sop-example/example-notify/src/main/resources/doc.json @@ -0,0 +1,27 @@ +{ + // 开启推送 + "enable": true, + // 扫描package,多个用;隔开 + "basePackage": "com.gitee.sop.payment.open", + // 推送URL,IP端口对应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 } + } + } +} diff --git a/sop-example/example-notify/src/main/resources/i18n/isp/bizerror_en.properties b/sop-example/example-notify/src/main/resources/i18n/isp/bizerror_en.properties new file mode 100755 index 00000000..48d134c8 --- /dev/null +++ b/sop-example/example-notify/src/main/resources/i18n/isp/bizerror_en.properties @@ -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} \ No newline at end of file diff --git a/sop-example/example-notify/src/main/resources/i18n/isp/bizerror_zh_CN.properties b/sop-example/example-notify/src/main/resources/i18n/isp/bizerror_zh_CN.properties new file mode 100755 index 00000000..4ac48428 --- /dev/null +++ b/sop-example/example-notify/src/main/resources/i18n/isp/bizerror_zh_CN.properties @@ -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 + diff --git a/sop-example/example-notify/src/main/resources/i18n/isp/goods_error_en.properties b/sop-example/example-notify/src/main/resources/i18n/isp/goods_error_en.properties new file mode 100755 index 00000000..195c7d2b --- /dev/null +++ b/sop-example/example-notify/src/main/resources/i18n/isp/goods_error_en.properties @@ -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} \ No newline at end of file diff --git a/sop-example/example-notify/src/main/resources/i18n/isp/goods_error_zh_CN.properties b/sop-example/example-notify/src/main/resources/i18n/isp/goods_error_zh_CN.properties new file mode 100755 index 00000000..af1a43bc --- /dev/null +++ b/sop-example/example-notify/src/main/resources/i18n/isp/goods_error_zh_CN.properties @@ -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 \ No newline at end of file diff --git a/sop-example/example-notify/src/main/resources/smart-doc.json b/sop-example/example-notify/src/main/resources/smart-doc.json new file mode 100644 index 00000000..29e2956a --- /dev/null +++ b/sop-example/example-notify/src/main/resources/smart-doc.json @@ -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 +} diff --git a/sop-example/example-notify/src/test/java/com/gitee/sop/notifyexample/DocPushTest.java b/sop-example/example-notify/src/test/java/com/gitee/sop/notifyexample/DocPushTest.java new file mode 100755 index 00000000..5c9dbc3d --- /dev/null +++ b/sop-example/example-notify/src/test/java/com/gitee/sop/notifyexample/DocPushTest.java @@ -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(); + } +} diff --git a/sop-example/pom.xml b/sop-example/pom.xml index fd4d2854..d10d430b 100755 --- a/sop-example/pom.xml +++ b/sop-example/pom.xml @@ -13,5 +13,6 @@ example-payment example-product example-rest + example-notify diff --git a/sop-notify/pom.xml b/sop-notify/pom.xml new file mode 100644 index 00000000..fd10953d --- /dev/null +++ b/sop-notify/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + com.gitee.sop + sop-parent + 5.0.0-SNAPSHOT + + + sop-notify + pom + + + UTF-8 + + + + sop-notify-api + sop-notify-service + + + diff --git a/sop-notify/sop-notify-api/pom.xml b/sop-notify/sop-notify-api/pom.xml new file mode 100644 index 00000000..57c81843 --- /dev/null +++ b/sop-notify/sop-notify-api/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + com.gitee.sop + sop-notify-api + 5.0.0-SNAPSHOT + + + 8 + 8 + UTF-8 + + + + + javax.validation + validation-api + 2.0.1.Final + + + + org.projectlombok + lombok + 1.18.36 + true + + + + diff --git a/sop-notify/sop-notify-api/src/main/java/com/gitee/sop/notify/api/NotifyService.java b/sop-notify/sop-notify-api/src/main/java/com/gitee/sop/notify/api/NotifyService.java new file mode 100644 index 00000000..13f8cbd3 --- /dev/null +++ b/sop-notify/sop-notify-api/src/main/java/com/gitee/sop/notify/api/NotifyService.java @@ -0,0 +1,29 @@ +package com.gitee.sop.notify.api; + +import com.gitee.sop.notify.api.req.NotifyRequest; +import com.gitee.sop.notify.api.resp.NotifyResponse; + +/** + * 回调接口 + * + * @author 六如 + */ +public interface NotifyService { + + /** + * 回调 + * + * @param request 参数 + * @return 返回结果 + */ + NotifyResponse notify(NotifyRequest request); + + /** + * 回调立即发送 + * + * @param notifyId notifyId + * @return 返回结果 + */ + NotifyResponse notifyImmediately(Long notifyId); + +} diff --git a/sop-notify/sop-notify-api/src/main/java/com/gitee/sop/notify/api/req/NotifyRequest.java b/sop-notify/sop-notify-api/src/main/java/com/gitee/sop/notify/api/req/NotifyRequest.java new file mode 100644 index 00000000..dcf7c6a4 --- /dev/null +++ b/sop-notify/sop-notify-api/src/main/java/com/gitee/sop/notify/api/req/NotifyRequest.java @@ -0,0 +1,66 @@ +package com.gitee.sop.notify.api.req; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.util.Map; + +/** + * @author 六如 + */ +@Data +public class NotifyRequest implements Serializable { + private static final long serialVersionUID = -4018307141661725928L; + + /** + * appId + */ + @NotBlank(message = "appId必填") + private String appId; + + /** + * apiName + */ + @NotBlank(message = "apiName必填") + private String apiName; + + /** + * version + */ + @NotBlank(message = "version必填") + private String version; + + /** + * 编码 + */ + @NotBlank(message = "charset必填") + private String charset; + + /** + * token,没有返回null + */ + private String appAuthToken; + + /** + * 客户端ip + */ + private String clientIp; + + /** + * 回调地址 + */ + private String notifyUrl; + + /** + * 业务参数 + */ + @NotNull(message = "bizParams必填") + private Map bizParams; + + /** + * 备注 + */ + private String remark; +} diff --git a/sop-notify/sop-notify-api/src/main/java/com/gitee/sop/notify/api/resp/NotifyResponse.java b/sop-notify/sop-notify-api/src/main/java/com/gitee/sop/notify/api/resp/NotifyResponse.java new file mode 100644 index 00000000..3d7f35ce --- /dev/null +++ b/sop-notify/sop-notify-api/src/main/java/com/gitee/sop/notify/api/resp/NotifyResponse.java @@ -0,0 +1,39 @@ +package com.gitee.sop.notify.api.resp; + +import lombok.Data; + +import java.io.Serializable; + + +/** + * @author 六如 + */ +@Data +public class NotifyResponse implements Serializable { + private static final long serialVersionUID = 5813802354743928430L; + + /** + * 返回请求id + */ + private Long notifyId; + + private Boolean success = true; + + private String msg; + + public static NotifyResponse success(Long notifyId) { + NotifyResponse notifyResponse = new NotifyResponse(); + notifyResponse.setNotifyId(notifyId); + notifyResponse.setSuccess(true); + notifyResponse.setMsg(""); + return notifyResponse; + } + + public static NotifyResponse error(String msg) { + NotifyResponse notifyResponse = new NotifyResponse(); + notifyResponse.setNotifyId(null); + notifyResponse.setSuccess(false); + notifyResponse.setMsg(msg); + return notifyResponse; + } +} diff --git a/sop-notify/sop-notify-service/pom.xml b/sop-notify/sop-notify-service/pom.xml new file mode 100644 index 00000000..df65c040 --- /dev/null +++ b/sop-notify/sop-notify-service/pom.xml @@ -0,0 +1,154 @@ + + + 4.0.0 + + com.gitee.sop + sop-notify + 5.0.0-SNAPSHOT + + + sop-notify-service + 5.0.0-SNAPSHOT + + + 1.8 + + 3.2.16 + + + + + com.gitee.sop + sop-notify-api + 5.0.0-SNAPSHOT + + + com.gitee.sop + sdk-java + 5.0.0-SNAPSHOT + + + io.gitee.durcframework + fastmybatis-spring-boot-starter + + + net.oschina.durcframework + http-helper + + + + com.gitee.sop + sop-spring-boot-starter + 5.0.0-SNAPSHOT + + + + org.apache.dubbo + dubbo-nacos-spring-boot-starter + + + + org.apache.dubbo + dubbo-zookeeper-curator5-spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-jdbc + + + mysql + mysql-connector-java + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + 1.18.34 + provided + + + + + + + org.apache.dubbo + dubbo-bom + ${dubbo.version} + pom + import + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12.4 + + true + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + ${java.version} + ${java.version} + UTF-8 + + -parameters + + + + + + com.ly.smart-doc + smart-doc-maven-plugin + 3.0.9 + + + ./src/main/resources/smart-doc.json + + ${project.artifactId} + + + + com.gitee.sop + sop-service-support + 5.0.0-SNAPSHOT + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + maven_central + Maven Central + https://repo.maven.apache.org/maven2/ + + + diff --git a/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/SopNotifyApplication.java b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/SopNotifyApplication.java new file mode 100755 index 00000000..1ed38198 --- /dev/null +++ b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/SopNotifyApplication.java @@ -0,0 +1,18 @@ +package com.gitee.sop.notify; + +import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication +@EnableDubbo +@EnableScheduling +public class SopNotifyApplication { + + public static void main(String[] args) { + SpringApplication.run(SopNotifyApplication.class, args); + } + +} + diff --git a/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dao/entity/NotifyInfo.java b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dao/entity/NotifyInfo.java new file mode 100644 index 00000000..3608c99c --- /dev/null +++ b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dao/entity/NotifyInfo.java @@ -0,0 +1,99 @@ +package com.gitee.sop.notify.dao.entity; + +import java.time.LocalDateTime; + +import com.gitee.fastmybatis.annotation.Pk; +import com.gitee.fastmybatis.annotation.PkStrategy; +import com.gitee.fastmybatis.annotation.Table; + +import lombok.Data; + + +/** + * 表名:notify_info + * 备注:回调信息 + * + * @author 六如 + */ +@Table(name = "notify_info", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT)) +@Data +public class NotifyInfo { + + private Long id; + + /** + * app_id + */ + private String appId; + + /** + * api_name + */ + private String apiName; + + /** + * api_version + */ + private String apiVersion; + + /** + * 回调url + */ + private String notifyUrl; + + /** + * 最近一次发送时间 + */ + private LocalDateTime lastSendTime; + + /** + * 下一次发送时间 + */ + private LocalDateTime nextSendTime; + + /** + * 已发送次数 + */ + private Integer sendCnt; + + /** + * 发送内容 + */ + private String content; + + /** + * 状态,1-发送成功,2-发送失败,3-重试结束 + */ + private Integer notifyStatus; + + /** + * 失败原因 + */ + private String errorMsg; + + /** + * 返回结果 + */ + private String resultContent; + + /** + * 备注 + */ + private String remark; + + private LocalDateTime addTime; + + private LocalDateTime updateTime; + + /** + * 创建人id + */ + private Long addBy; + + /** + * 修改人id + */ + private Long updateBy; + + +} diff --git a/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dao/mapper/IsvMapper.java b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dao/mapper/IsvMapper.java new file mode 100755 index 00000000..81ac1c85 --- /dev/null +++ b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dao/mapper/IsvMapper.java @@ -0,0 +1,14 @@ +package com.gitee.sop.notify.dao.mapper; + +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface IsvMapper { + + String getPrivatePlatformKey(String appId); + + String getNotifyUrl(String appId); +} diff --git a/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dao/mapper/NotifyInfoMapper.java b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dao/mapper/NotifyInfoMapper.java new file mode 100644 index 00000000..cce15029 --- /dev/null +++ b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dao/mapper/NotifyInfoMapper.java @@ -0,0 +1,13 @@ +package com.gitee.sop.notify.dao.mapper; + +import com.gitee.fastmybatis.core.mapper.BaseMapper; +import com.gitee.sop.notify.dao.entity.NotifyInfo; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author 六如 + */ +@Mapper +public interface NotifyInfoMapper extends BaseMapper { + +} diff --git a/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dubbo/NotifyServiceImpl.java b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dubbo/NotifyServiceImpl.java new file mode 100644 index 00000000..0a4821b6 --- /dev/null +++ b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/dubbo/NotifyServiceImpl.java @@ -0,0 +1,48 @@ +package com.gitee.sop.notify.dubbo; + +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.notify.service.NotifyBizService; +import com.gitee.sop.notify.service.bo.NotifyBO; +import com.gitee.sop.sdk.sign.SopSignException; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboService; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; + + +/** + * @author 六如 + */ +@DubboService(validation = "true") +@Slf4j +public class NotifyServiceImpl implements NotifyService { + + @Autowired + private NotifyBizService notifyBizService; + + @Override + public NotifyResponse notify(NotifyRequest request) { + NotifyBO notifyBO = new NotifyBO(); + BeanUtils.copyProperties(request, notifyBO); + try { + Long notifyId = notifyBizService.notify(notifyBO); + return NotifyResponse.success(notifyId); + } catch (SopSignException e) { + log.error("回调异常,服务端签名失败, request={}", request, e); + return NotifyResponse.error(e.getErrMsg()); + } + } + + @Override + public NotifyResponse notifyImmediately(Long notifyId) { + try { + notifyBizService.notifyImmediately(notifyId); + return NotifyResponse.success(notifyId); + } catch (Exception e) { + log.error("回调异常, notifyId={}", notifyId, e); + return NotifyResponse.error(e.getMessage()); + } + } +} diff --git a/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/enums/NotifyStatusEnum.java b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/enums/NotifyStatusEnum.java new file mode 100644 index 00000000..2c9c81a3 --- /dev/null +++ b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/enums/NotifyStatusEnum.java @@ -0,0 +1,22 @@ +package com.gitee.sop.notify.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author 六如 + */ +@AllArgsConstructor +@Getter +public enum NotifyStatusEnum { + + // 状态,1-发送成功,2-发送失败,3-重试结束 + SEND_SUCCESS(1, "发送成功"), + SEND_FAIL(2, "发送失败"), + RETRY_OVER(3, "重试结束"); + + private final Integer value; + + private final String description; + +} diff --git a/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/schedule/NotifySchedule.java b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/schedule/NotifySchedule.java new file mode 100644 index 00000000..099b6834 --- /dev/null +++ b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/schedule/NotifySchedule.java @@ -0,0 +1,27 @@ +package com.gitee.sop.notify.schedule; + +import com.gitee.sop.notify.service.NotifyBizService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * @author 六如 + */ +@Component +public class NotifySchedule { + + @Autowired + private NotifyBizService notifyBizService; + + /** + * 每分钟执行一次 + */ + @Scheduled(cron = "0 0/1 * * * ?") + public void run() { + notifyBizService.retry(LocalDateTime.now()); + } + +} diff --git a/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/service/IsvService.java b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/service/IsvService.java new file mode 100644 index 00000000..96f24c6f --- /dev/null +++ b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/service/IsvService.java @@ -0,0 +1,24 @@ +package com.gitee.sop.notify.service; + +import com.gitee.sop.notify.dao.mapper.IsvMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author 六如 + */ +@Service +public class IsvService { + + @Autowired + private IsvMapper isvMapper; + + public String getPrivatePlatformKey(String appId) { + return isvMapper.getPrivatePlatformKey(appId); + } + + public String getNotifyUrl(String appId) { + return isvMapper.getNotifyUrl(appId); + } + +} diff --git a/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/service/NotifyBizService.java b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/service/NotifyBizService.java new file mode 100644 index 00000000..9916fda7 --- /dev/null +++ b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/service/NotifyBizService.java @@ -0,0 +1,243 @@ +package com.gitee.sop.notify.service; + +import com.alibaba.fastjson.JSON; +import com.gitee.fastmybatis.core.support.LambdaService; +import com.gitee.httphelper.HttpHelper; +import com.gitee.httphelper.result.ResponseResult; +import com.gitee.sop.notify.dao.entity.NotifyInfo; +import com.gitee.sop.notify.dao.mapper.NotifyInfoMapper; +import com.gitee.sop.notify.enums.NotifyStatusEnum; +import com.gitee.sop.notify.service.bo.NotifyBO; +import com.gitee.sop.sdk.sign.SignUtil; +import com.gitee.sop.sdk.sign.SopSignException; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.http.HttpStatus; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + + +/** + * 回调业务逻辑处理 + * + * @author 六如 + */ +@Service +@Slf4j +public class NotifyBizService implements LambdaService { + + // 对应第1,2,3...次尝试 + // 即1分钟后进行第一次尝试,如果失败,5分钟后进行第二次尝试 + @Value("${sop.notify.time-level:1m,5m,10m,30m,1h,2h,5h}") + private String timeLevel; + + @Autowired + private IsvService isvService; + + /** + * 第一次发送 + * + * @param notifyBO 回调内容 + * @return 返回回调id + * @throws SopSignException 异常处理 + */ + public Long notify(NotifyBO notifyBO) throws SopSignException { + NotifyInfo notifyInfo = buildRecord(notifyBO); + return doNotify(notifyBO, notifyInfo); + } + + public void notifyImmediately(Long notifyId) throws SopSignException { + NotifyInfo notifyInfo = this.getById(notifyId); + String content = notifyInfo.getContent(); + NotifyBO notifyBO = JSON.parseObject(content, NotifyBO.class); + // 发送请求 + doNotify(notifyBO, notifyInfo); + } + + + /** + * 重试 + * + * @param now 当前时间 + */ + public void retry(LocalDateTime now) { + LocalDateTime nextTime = now.withSecond(0).withNano(0); + List list = this.query() + .eq(NotifyInfo::getNextSendTime, nextTime) + .eq(NotifyInfo::getNotifyStatus, NotifyStatusEnum.SEND_FAIL.getValue()) + .list(); + if (list.isEmpty()) { + log.info("[notify]无重试记录"); + return; + } + + for (NotifyInfo notifyInfo : list) { + retry(notifyInfo); + } + } + + private void retry(NotifyInfo notifyInfo) { + String content = notifyInfo.getContent(); + NotifyBO notifyBO = JSON.parseObject(content, NotifyBO.class); + try { + log.info("[notify]开始重试, notifyId={}", notifyInfo.getId()); + if (Objects.equals(notifyInfo.getNotifyStatus(), NotifyStatusEnum.RETRY_OVER.getValue())) { + log.warn("重试次数已用尽, notifyId={}", notifyInfo.getId()); + return; + } + // 发送请求 + doNotify(notifyBO, notifyInfo); + } catch (SopSignException e) { + log.error("[notify]重试签名错误,notifyId={}", notifyInfo.getId(), e); + throw new RuntimeException("重试失败,签名错误"); + } + } + + /** + * 构建下一次重试时间 + * + * @param currentSendCnt 当前发送次数 + * @return 返回null表示重试次数用完 + */ + private LocalDateTime buildNextSendTime(Integer currentSendCnt) { + String[] split = timeLevel.split(","); + if (currentSendCnt >= split.length) { + return null; + } + // 1m + String exp = split[currentSendCnt - 1]; + // 秒,毫秒归零f + LocalDateTime time = LocalDateTime.now().withSecond(0).withNano(0); + // 最后一个字符,如:m,h,d + char ch = exp.charAt(exp.length() - 1); + int value = NumberUtils.toInt(exp.substring(0, exp.length() - 1)); + switch (String.valueOf(ch).toLowerCase()) { + case "m": + return time.plusMinutes(value); + case "h": + return time.plusHours(value); + case "d": + return time.plusDays(value); + default: + return null; + } + } + + + private Long doNotify(NotifyBO notifyBO, NotifyInfo notifyInfo) throws SopSignException { + notifyInfo.setSendCnt(notifyInfo.getSendCnt() + 1); + notifyInfo.setLastSendTime(LocalDateTime.now()); + notifyInfo.setNotifyUrl(buildNotifyUrl(notifyBO, notifyInfo)); + + String notifyUrl = notifyInfo.getNotifyUrl(); + // 构建请求参数 + Map params = buildParams(notifyBO); + try { + if (StringUtils.isBlank(notifyUrl)) { + throw new RuntimeException("回调接口不能为空"); + } + + String json = JSON.toJSONString(params); + log.info("发送回调请求,notifyUrl={}, content={}", notifyUrl, json); + ResponseResult responseResult = HttpHelper.postJson(notifyUrl, json) + .execute(); + + // 这里判断收到200认为请求成功 + int status = responseResult.getStatus(); + String resultContent = responseResult.asString(); + notifyInfo.setResultContent(resultContent); + if (status == HttpStatus.SC_OK) { + // 更新状态 + notifyInfo.setNotifyStatus(NotifyStatusEnum.SEND_SUCCESS.getValue()); + notifyInfo.setErrorMsg(""); + } else { + // 回调失败 + log.error("回调状态非200:{}, result={}", status, resultContent); + throw new RuntimeException(resultContent); + } + } catch (Exception e) { + log.error("回调请求失败, notifyUrl={}, params={}, notifyBO={}", notifyUrl, params, notifyBO, e); + notifyInfo.setNotifyStatus(NotifyStatusEnum.SEND_FAIL.getValue()); + notifyInfo.setErrorMsg(e.getMessage()); + + LocalDateTime nextSendTime = buildNextSendTime(notifyInfo.getSendCnt()); + notifyInfo.setNextSendTime(nextSendTime); + + if (nextSendTime == null) { + log.error("回调请求次数达到上线, notifyUrl={}, params={}", notifyUrl, params); + notifyInfo.setNotifyStatus(NotifyStatusEnum.RETRY_OVER.getValue()); + } + } + + this.saveOrUpdate(notifyInfo); + + return notifyInfo.getId(); + } + + private Map buildParams(NotifyBO notifyBO) throws SopSignException { + // 公共请求参数 + Map params = new HashMap<>(); + String appId = notifyBO.getAppId(); + params.put("app_id", appId); + params.put("method", notifyBO.getApiName()); + params.put("format", "json"); + params.put("charset", notifyBO.getCharset()); + params.put("sign_type", "RSA2"); + params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); + params.put("version", notifyBO.getVersion()); + + // 业务参数 + Map bizContent = notifyBO.getBizParams(); + + params.put("biz_content", JSON.toJSONString(bizContent)); + String content = SignUtil.getSignContent(params); + + String privateKey = isvService.getPrivatePlatformKey(appId); + String sign = SignUtil.rsa256Sign(content, privateKey, notifyBO.getCharset()); + params.put("sign", sign); + + return params; + } + + private String buildNotifyUrl(NotifyBO notifyBO, NotifyInfo notifyInfo) { + String savedUrl = notifyInfo.getNotifyUrl(); + if (StringUtils.isNotBlank(savedUrl)) { + return savedUrl; + } + String notifyUrl = notifyBO.getNotifyUrl(); + if (StringUtils.isBlank(notifyUrl)) { + notifyUrl = isvService.getNotifyUrl(notifyBO.getAppId()); + } + return notifyUrl; + } + + private NotifyInfo buildRecord(NotifyBO notifyBO) { + NotifyInfo notifyInfo = new NotifyInfo(); + notifyInfo.setAppId(notifyBO.getAppId()); + notifyInfo.setApiName(notifyBO.getApiName()); + notifyInfo.setApiVersion(notifyBO.getVersion()); + notifyInfo.setSendCnt(0); + notifyInfo.setContent(JSON.toJSONString(notifyBO)); + notifyInfo.setNotifyStatus(0); + notifyInfo.setErrorMsg(""); + notifyInfo.setRemark(notifyBO.getRemark()); + notifyInfo.setAddTime(LocalDateTime.now()); + notifyInfo.setUpdateTime(LocalDateTime.now()); + notifyInfo.setAddBy(0L); + notifyInfo.setUpdateBy(0L); + + return notifyInfo; + } + + +} diff --git a/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/service/bo/NotifyBO.java b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/service/bo/NotifyBO.java new file mode 100644 index 00000000..687a9edd --- /dev/null +++ b/sop-notify/sop-notify-service/src/main/java/com/gitee/sop/notify/service/bo/NotifyBO.java @@ -0,0 +1,64 @@ +package com.gitee.sop.notify.service.bo; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.Map; + +/** + * @author 六如 + */ +@Data +public class NotifyBO { + + /** + * appId + */ + @NotBlank(message = "appId必填") + private String appId; + + /** + * apiName + */ + @NotBlank(message = "apiName必填") + private String apiName; + + /** + * version + */ + @NotBlank(message = "version必填") + private String version; + + /** + * token,没有返回null + */ + private String appAuthToken; + + /** + * 客户端ip + */ + private String clientIp; + + /** + * 回调地址 + */ + @NotBlank(message = "notifyUrl必填") + private String notifyUrl; + + /** + * 编码 + */ + private String charset; + + /** + * 业务参数 + */ + @NotBlank(message = "bizParams必填") + private Map bizParams; + + /** + * 备注 + */ + private String remark; + +} diff --git a/sop-notify/sop-notify-service/src/main/resources/application-dev.properties b/sop-notify/sop-notify-service/src/main/resources/application-dev.properties new file mode 100755 index 00000000..1c6bbfdf --- /dev/null +++ b/sop-notify/sop-notify-service/src/main/resources/application-dev.properties @@ -0,0 +1,7 @@ +mybatis.print-sql=true + +# mysql config +mysql.host=127.0.0.1:3306 +mysql.db=sop +mysql.username=root +mysql.password=12345678 diff --git a/sop-notify/sop-notify-service/src/main/resources/application-test.properties b/sop-notify/sop-notify-service/src/main/resources/application-test.properties new file mode 100755 index 00000000..30f45aa6 --- /dev/null +++ b/sop-notify/sop-notify-service/src/main/resources/application-test.properties @@ -0,0 +1,9 @@ +dubbo.registry.address=nacos://localhost:8848 + +mybatis.print-sql=true + +# mysql config +mysql.host=127.0.0.1:3306 +mysql.db=sop +mysql.username=root +mysql.password=root diff --git a/sop-notify/sop-notify-service/src/main/resources/application.properties b/sop-notify/sop-notify-service/src/main/resources/application.properties new file mode 100755 index 00000000..3a5c59e9 --- /dev/null +++ b/sop-notify/sop-notify-service/src/main/resources/application.properties @@ -0,0 +1,47 @@ +server.port=8085 +spring.profiles.active=dev + +spring.application.name=sop-notify + +dubbo.protocol.name=dubbo +dubbo.protocol.port=-1 +dubbo.application.qos-enable=false +dubbo.consumer.check=false +# ### register config see:https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/registry/overview/ +# ------ +# nacos://localhost:8848 Cluster config:nacos://localhost:8848?backup=localshot:8846,localshot:8847 +# zookeeper://localhost:2181 Cluster config:zookeeper://10.20.153.10:2181?backup=10.20.153.11:2181,10.20.153.12:2181 +# redis://localhost:6379 Cluster config:redis://10.20.153.10:6379?backup=10.20.153.11:6379,10.20.153.12:6379 +# ------ +dubbo.registry.address=zookeeper://localhost:2181 + +####### mysql config ####### +mysql.host=127.0.0.1:3306 +mysql.db=sop +mysql.username= +mysql.password= + +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://${mysql.host}/${mysql.db}?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai +spring.datasource.username=${mysql.username} +spring.datasource.password=${mysql.password} + +####### mybatis config ####### +mybatis.fill.com.gitee.fastmybatis.core.support.LocalDateTimeFillInsert=add_time +mybatis.fill.com.gitee.fastmybatis.core.support.LocalDateTimeFillUpdate=update_time +# mybatis config file +mybatis.config-location=classpath:mybatis/mybatisConfig.xml +mybatis.mapper-locations=classpath:mybatis/mapper/*.xml + +# log level +logging.level.com.gitee.sop=info +# log path +logging.file.name=logs/sop-notify.log +# print SQL +logging.level.com.gitee.sop.notify.dao=error +logging.level.com.gitee.fastmybatis=info +mybatis.print-sql=false + +# \u5BF9\u5E94\u7B2C1\uFF0C2\uFF0C3...\u6B21\u5C1D\u8BD5 +# \u53731\u5206\u949F\u540E\u8FDB\u884C\u7B2C\u4E00\u6B21\u5C1D\u8BD5\uFF0C\u5982\u679C\u5931\u8D25\uFF0C5\u5206\u949F\u540E\u8FDB\u884C\u7B2C\u4E8C\u6B21\u5C1D\u8BD5 +sop.notify.time-level:1m,5m,10m,30m,1h,2h,5h \ No newline at end of file diff --git a/sop-notify/sop-notify-service/src/main/resources/mybatis/mapper/IsvMapper.xml b/sop-notify/sop-notify-service/src/main/resources/mybatis/mapper/IsvMapper.xml new file mode 100644 index 00000000..3cd6ed82 --- /dev/null +++ b/sop-notify/sop-notify-service/src/main/resources/mybatis/mapper/IsvMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/sop-notify/sop-notify-service/src/main/resources/mybatis/mybatisConfig.xml b/sop-notify/sop-notify-service/src/main/resources/mybatis/mybatisConfig.xml new file mode 100755 index 00000000..3c4b234f --- /dev/null +++ b/sop-notify/sop-notify-service/src/main/resources/mybatis/mybatisConfig.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/dto/NotifyInfo.java b/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/dto/NotifyInfo.java deleted file mode 100755 index e6d38615..00000000 --- a/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/dto/NotifyInfo.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.gitee.sop.support.dto; - -import lombok.Data; - -import java.io.Serializable; -import java.util.Map; - -/** - * 回调信息 - * - * @author 六如 - */ -@Data -public class NotifyInfo implements Serializable { - private static final long serialVersionUID = -2492336644456313771L; - - /** - * 链路id - */ - private String traceId; - - /** - * 回调接口 - */ - private String url; - - /** - * 跟在url后面的参数 - */ - private Map query; - - /** - * 请求头 - */ - private Map header; - - /** - * 请求体 - */ - private Map body; - -} diff --git a/sop-test/src/test/java/com/gitee/sop/test/NotifyTest.java b/sop-test/src/test/java/com/gitee/sop/test/NotifyTest.java new file mode 100644 index 00000000..37ddd92c --- /dev/null +++ b/sop-test/src/test/java/com/gitee/sop/test/NotifyTest.java @@ -0,0 +1,65 @@ +package com.gitee.sop.test; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author 六如 + */ +public class NotifyTest extends TestBase { + + String url = "http://localhost:8081/api"; + String appId = "2019032617262200001"; + String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ="; + + private final Client client = new Client(url, appId, privateKey, AllInOneTest::assertResult); + + /** + * 以get方式提交 + */ + public void testGet() { + // 参见:com.gitee.sop.notifyexample.open.req.CreateOrderRequest + Map bizContent = new LinkedHashMap<>(); + bizContent.put("outTradeNo", "1111"); + bizContent.put("totalAmount", "100"); + bizContent.put("subject", "话费"); + + Client.RequestBuilder requestBuilder = new Client.RequestBuilder() + .method("shop.order.create") + .version("1.0") + // 回调地址,见:com.gitee.sop.notifyexample.controller.DemoCallbackController + .notifyUrl("http://127.0.0.1:7074/notify/callback") + .bizContent(bizContent) + .httpMethod(HttpTool.HTTPMethod.GET) + .callback((requestInfo, responseData) -> { + System.out.println(responseData); + }); + + client.execute(requestBuilder); + } + + /** + * 不填notifyUrl + */ + public void testGet2() { + // 参见:com.gitee.sop.notifyexample.open.req.CreateOrderRequest + Map bizContent = new LinkedHashMap<>(); + bizContent.put("outTradeNo", "1111"); + bizContent.put("totalAmount", "100"); + bizContent.put("subject", "话费"); + + Client.RequestBuilder requestBuilder = new Client.RequestBuilder() + .method("shop.order.create") + .version("1.0") + // 回调地址,见:com.gitee.sop.notifyexample.controller.DemoCallbackController + //.notifyUrl("http://127.0.0.1:7074/notify/callback") + .bizContent(bizContent) + .httpMethod(HttpTool.HTTPMethod.POST) + .callback((requestInfo, responseData) -> { + System.out.println(responseData); + }); + + client.execute(requestBuilder); + } + +} diff --git a/sop.sql b/sop.sql index f322edea..ea123a8e 100755 --- a/sop.sql +++ b/sop.sql @@ -493,7 +493,6 @@ CREATE TABLE `help_doc` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB COMMENT='帮助内容表'; - -- 2025-08-17 CREATE TABLE `isv_merchant` ( @@ -507,3 +506,33 @@ CREATE TABLE `isv_merchant` PRIMARY KEY (`id`), UNIQUE KEY `uk_appid_merchant` (`app_id`, merchant_code) ) ENGINE=InnoDB COMMENT='isv商户关系表'; + +-- 2025-11-01 +ALTER TABLE `isv_info` + ADD COLUMN `notify_url` varchar(256) NULL COMMENT '回调接口'; + +CREATE TABLE `notify_info` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `app_id` varchar(64) NOT NULL COMMENT 'app_id', + `api_name` varchar(64) NOT NULL COMMENT 'api_name', + `api_version` varchar(16) NOT NULL COMMENT 'api_version', + `notify_url` varchar(255) DEFAULT '' COMMENT '回调url', + `last_send_time` datetime DEFAULT NULL COMMENT '最近一次发送时间', + `next_send_time` datetime DEFAULT NULL COMMENT '下一次发送时间', + `send_max` int(11) DEFAULT NULL COMMENT '最大发送次数', + `send_cnt` int(11) DEFAULT NULL COMMENT '已发送次数', + `content` text COMMENT '发送内容', + `notify_status` tinyint(4) DEFAULT '1' COMMENT '状态,1-发送成功,2-发送失败,3-重试结束', + `error_msg` text COMMENT '失败原因', + `result_content` text COMMENT '返回结果', + `remark` varchar(256) DEFAULT '' COMMENT '备注', + `add_time` datetime DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `add_by` bigint(20) DEFAULT '0' COMMENT '创建人id', + `update_by` bigint(20) DEFAULT '0' COMMENT '修改人id', + PRIMARY KEY (`id`), + KEY `idx_app_id` (`app_id`) USING BTREE +) ENGINE=InnoDB COMMENT='回调信息'; + +INSERT INTO `sys_resource` ( `menu_type`, `title`, `name`, `path`, `component`, `rank`, `redirect`, `icon`, `extra_icon`, `enter_transition`, `leave_transition`, `active_path`, `auths`, `frame_src`, `frame_loading`, `keep_alive`, `hidden_tag`, `fixed_tag`, `show_link`, `show_parent`, `parent_id`, `is_deleted`, `add_time`, `update_time`, `add_by`, `update_by`) VALUES + (0, '回调管理', 'NotifyMgr', '/serve/notify', '', 99, '', 'ri:align-vertically', '', '', '', '', '', '', 0, 0, 0, 0, 1, 0, 1, 0, '2025-11-01 20:37:08', '2025-11-01 20:37:08', 1, 1); diff --git a/upgrade/sop-20251101.sql b/upgrade/sop-20251101.sql new file mode 100644 index 00000000..75f7ab73 --- /dev/null +++ b/upgrade/sop-20251101.sql @@ -0,0 +1,29 @@ +ALTER TABLE `isv_info` + ADD COLUMN `notify_url` varchar(256) NULL COMMENT '回调接口'; + +CREATE TABLE `notify_info` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `app_id` varchar(64) NOT NULL COMMENT 'app_id', + `api_name` varchar(64) NOT NULL COMMENT 'api_name', + `api_version` varchar(16) NOT NULL COMMENT 'api_version', + `notify_url` varchar(255) DEFAULT '' COMMENT '回调url', + `last_send_time` datetime DEFAULT NULL COMMENT '最近一次发送时间', + `next_send_time` datetime DEFAULT NULL COMMENT '下一次发送时间', + `send_max` int(11) DEFAULT NULL COMMENT '最大发送次数', + `send_cnt` int(11) DEFAULT NULL COMMENT '已发送次数', + `content` text COMMENT '发送内容', + `notify_status` tinyint(4) DEFAULT '1' COMMENT '状态,1-发送成功,2-发送失败,3-重试结束', + `error_msg` text COMMENT '失败原因', + `result_content` text COMMENT '返回结果', + `remark` varchar(256) DEFAULT '' COMMENT '备注', + `add_time` datetime DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `add_by` bigint(20) DEFAULT '0' COMMENT '创建人id', + `update_by` bigint(20) DEFAULT '0' COMMENT '修改人id', + PRIMARY KEY (`id`), + KEY `idx_app_id` (`app_id`) USING BTREE +) ENGINE=InnoDB COMMENT='回调信息'; + +INSERT INTO `sys_resource` ( `menu_type`, `title`, `name`, `path`, `component`, `rank`, `redirect`, `icon`, `extra_icon`, `enter_transition`, `leave_transition`, `active_path`, `auths`, `frame_src`, `frame_loading`, `keep_alive`, `hidden_tag`, `fixed_tag`, `show_link`, `show_parent`, `parent_id`, `is_deleted`, `add_time`, `update_time`, `add_by`, `update_by`) VALUES + (0, '回调管理', 'NotifyMgr', '/serve/notify', '', 99, '', 'ri:align-vertically', '', '', '', '', '', '', 0, 0, 0, 0, 1, 0, 1, 0, '2025-11-01 20:37:08', '2025-11-01 20:37:08', 1, 1);