From df1eeef0d02020d019418f4c07f099b41b1bf565 Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Tue, 7 Jul 2026 17:05:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=9E=81=E5=85=89=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/1000pjcloudx_thirdparty.sql | 136 +++++++-- .../core/constant/ServiceNameConstants.java | 5 + .../constant/push/ThirdPushBizTypeEnum.java | 20 ++ .../push/ThirdPushInformTypeEnum.java | 22 ++ .../pjcloud/thirdparty/dto/jg/JgPusDto.java | 2 + .../entity/ThirdPushConfEntity.java | 100 +++++++ .../entity/ThirdPushRecordEntity.java | 100 +++++++ .../entity/ThirdPushRecordUserEntity.java | 85 ++++++ ...mework.cloud.openfeign.FeignClient.imports | 2 +- .../pjcloud-thirdparty-biz/pom.xml | 5 + .../push/ThirdPushConfController.java | 117 ++++++++ .../thirdparty/job/ThirdAppJgPushJob.java | 21 +- .../mapper/push/ThirdPushConfMapper.java | 10 + .../mapper/push/ThirdPushRecordMapper.java | 10 + .../push/ThirdPushRecordUserMapper.java | 10 + .../impl/jg/ThirdThirdJgPushServiceImpl.java | 18 +- .../impl/push/ThirdPushConfServiceImpl.java | 282 ++++++++++++++++++ .../push/ThirdPushExecuteServiceImpl.java | 224 ++++++++++++++ .../impl/push/ThirdPushRecordServiceImpl.java | 78 +++++ .../push/ThirdPushRecordUserServiceImpl.java | 115 +++++++ .../service/push/ThirdPushConfService.java | 22 ++ .../service/push/ThirdPushExecuteService.java | 16 + .../service/push/ThirdPushRecordService.java | 31 ++ .../push/ThirdPushRecordUserService.java | 43 +++ .../push/ThirdPushExecuteServiceImplTest.java | 171 +++++++++++ .../ThirdPushRecordUserServiceImplTest.java | 43 +++ .../pjcloud-daemon-quartz-api/pom.xml | 30 ++ .../daemon/quartz/api/dto/SysJobDTO.java | 96 ++++++ .../quartz/api/feign/RemoteSysJobService.java | 56 ++++ ...mework.cloud.openfeign.FeignClient.imports | 1 + pjcloud-visual/pjcloud-daemon-quartz/pom.xml | 5 + .../quartz/config/PjcloudInitQuartzJob.java | 29 +- .../quartz/controller/SysJobController.java | 68 +++-- .../controller/SysJobInnerController.java | 143 +++++++++ .../quartz/util/JavaClassTaskInvok.java | 5 +- .../quartz/util/SpringBeanTaskInvok.java | 5 +- .../daemon/quartz/util/TaskInvokUtil.java | 17 +- .../pjcloud/daemon/quartz/util/TaskUtil.java | 25 +- pjcloud-visual/pom.xml | 1 + .../wallet/api/feign/RemoteMchService.java | 4 + .../controller/mch/MchManageController.java | 27 +- .../mapper/mch/view/MchDeptViewMapper.java | 2 + .../wallet/service/MchManageService.java | 7 +- .../impl/mch/MchManageServiceImpl.java | 28 ++ .../mapper/mch/view/MchDeptViewMapper.xml | 10 + 45 files changed, 2157 insertions(+), 90 deletions(-) create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/constant/push/ThirdPushBizTypeEnum.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/constant/push/ThirdPushInformTypeEnum.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPushConfEntity.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPushRecordEntity.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPushRecordUserEntity.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/push/ThirdPushConfController.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/push/ThirdPushConfMapper.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/push/ThirdPushRecordMapper.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/push/ThirdPushRecordUserMapper.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushConfServiceImpl.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushExecuteServiceImpl.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushRecordServiceImpl.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushRecordUserServiceImpl.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushConfService.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushExecuteService.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushRecordService.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushRecordUserService.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushExecuteServiceImplTest.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushRecordUserServiceImplTest.java create mode 100644 pjcloud-visual/pjcloud-daemon-quartz-api/pom.xml create mode 100644 pjcloud-visual/pjcloud-daemon-quartz-api/src/main/java/com/bsd/pjcloud/daemon/quartz/api/dto/SysJobDTO.java create mode 100644 pjcloud-visual/pjcloud-daemon-quartz-api/src/main/java/com/bsd/pjcloud/daemon/quartz/api/feign/RemoteSysJobService.java create mode 100644 pjcloud-visual/pjcloud-daemon-quartz-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports create mode 100644 pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/controller/SysJobInnerController.java diff --git a/db/1000pjcloudx_thirdparty.sql b/db/1000pjcloudx_thirdparty.sql index 7873160e..dd42a7c2 100644 --- a/db/1000pjcloudx_thirdparty.sql +++ b/db/1000pjcloudx_thirdparty.sql @@ -175,27 +175,125 @@ CREATE TABLE `third_pay_order` DROP TABLE IF EXISTS `third_fz_order`; CREATE TABLE `third_fz_order` ( - `id` bigint NOT NULL COMMENT '主键', - `pay_no` varchar(128) DEFAULT NULL COMMENT '支付订单号', - `fz_no` varchar(128) DEFAULT NULL COMMENT '分账订单号', - `create_req` text DEFAULT NULL COMMENT '发起分账req', - `create_res` text DEFAULT NULL COMMENT '发起分账res', - `create_error` text DEFAULT NULL COMMENT '发起分账error', - `status` varchar(128) DEFAULT NULL COMMENT '支付状态', - `fz_time` datetime DEFAULT NULL COMMENT '分账时间', - `fz_refund_no` varchar(128) DEFAULT NULL COMMENT '分账退款订单号', - `fz_refund_time` datetime DEFAULT NULL COMMENT '分账退款时间', - `fz_refund_req` text DEFAULT NULL COMMENT '分账退款req', - `fz_refund_res` text DEFAULT NULL COMMENT '分账退款res', - `fz_refund_error` text DEFAULT NULL COMMENT '分账退款error', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `create_by` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人', - `update_by` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '更新人', - `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '删除时间', - `tenant_id` bigint(20) DEFAULT NULL, + `id` bigint NOT NULL COMMENT '主键', + `pay_no` varchar(128) DEFAULT NULL COMMENT '支付订单号', + `fz_no` varchar(128) DEFAULT NULL COMMENT '分账订单号', + `create_req` text DEFAULT NULL COMMENT '发起分账req', + `create_res` text DEFAULT NULL COMMENT '发起分账res', + `create_error` text DEFAULT NULL COMMENT '发起分账error', + `status` varchar(128) DEFAULT NULL COMMENT '支付状态', + `fz_time` datetime DEFAULT NULL COMMENT '分账时间', + `fz_refund_no` varchar(128) DEFAULT NULL COMMENT '分账退款订单号', + `fz_refund_time` datetime DEFAULT NULL COMMENT '分账退款时间', + `fz_refund_req` text DEFAULT NULL COMMENT '分账退款req', + `fz_refund_res` text DEFAULT NULL COMMENT '分账退款res', + `fz_refund_error` text DEFAULT NULL COMMENT '分账退款error', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `create_by` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人', + `update_by` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '更新人', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '删除时间', + `tenant_id` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC COMMENT ='第三方支付分账'; + +-- ---------------------------- +-- Table structure for third_push_conf +-- ---------------------------- +DROP TABLE IF EXISTS third_push_conf; +CREATE TABLE third_push_conf +( + id bigint NOT NULL COMMENT '主键', + biz_type varchar(32) NOT NULL COMMENT '业务类型', + biz_id varchar(256) NOT NULL COMMENT '业务id', + title varchar(64) NOT NULL COMMENT '通知标题', + content varchar(256) NOT NULL COMMENT '通知内容', + enable varchar(32) NOT NULL COMMENT '是否开启', + inform_type int NOT NULL COMMENT '每天、每月', + inform_time time NOT NULL COMMENT '通知时间', + create_by varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人', + create_time datetime DEFAULT NULL COMMENT '创建时间', + update_by varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '修改人', + update_time datetime DEFAULT NULL COMMENT '修改时间', + del_flag char(1) COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '删除标记', + tenant_id bigint DEFAULT NULL COMMENT '租户ID', + PRIMARY KEY (id), + KEY idx_third_push_conf_biz (tenant_id, biz_type, biz_id, del_flag), + KEY idx_third_push_conf_inform (tenant_id, enable, inform_type, inform_time, del_flag) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci COMMENT ='推送配置'; + +-- ---------------------------- +-- Table structure for third_push_record +-- ---------------------------- +DROP TABLE IF EXISTS `third_push_record`; +CREATE TABLE `third_push_record` +( + `id` bigint NOT NULL COMMENT '主键', + `conf_id` bigint NOT NULL COMMENT '推送配置id', + `batch_no` varchar(64) NOT NULL COMMENT '推送批次号', + `biz_type` varchar(32) NOT NULL COMMENT '业务类型', + `biz_id` varchar(256) NOT NULL COMMENT '业务id', + `title` varchar(64) NOT NULL COMMENT '通知标题', + `content` varchar(256) NOT NULL COMMENT '通知内容', + `inform_type` int NOT NULL COMMENT '每天、每月', + `inform_time` time NOT NULL COMMENT '通知时间', + `receiver_count` int DEFAULT 0 COMMENT '接收总人数', + `success_count` int DEFAULT 0 COMMENT '成功人数', + `fail_count` int DEFAULT 0 COMMENT '失败人数', + `send_time` datetime DEFAULT NULL COMMENT '发送时间', + `status` varchar(32) NOT NULL COMMENT '批次状态', + `push_result` text DEFAULT NULL COMMENT '推送结果摘要', + `error_msg` varchar(512) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '失败原因', + `create_by` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + `del_flag` char(1) COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '删除标记', + `tenant_id` bigint DEFAULT NULL COMMENT '租户ID', + PRIMARY KEY (`id`) USING BTREE, + KEY idx_third_push_record_conf_id (`conf_id`), + KEY idx_third_push_record_batch_no (`batch_no`), + KEY idx_third_push_record_biz (`tenant_id`, `biz_type`, `biz_id`, `del_flag`), + KEY idx_third_push_record_send_time (`tenant_id`, `send_time`), + KEY idx_third_push_record_status_time (`tenant_id`, `status`, `send_time`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci COMMENT ='推送通知批次记录'; + +-- ---------------------------- +-- Table structure for third_push_record_user +-- ---------------------------- +DROP TABLE IF EXISTS `third_push_record_user`; +CREATE TABLE `third_push_record_user` +( + `id` bigint NOT NULL COMMENT '主键', + `record_id` bigint NOT NULL COMMENT '推送批次记录id', + `conf_id` bigint NOT NULL COMMENT '推送配置id', + `batch_no` varchar(64) NOT NULL COMMENT '推送批次号', + `user_id` bigint NOT NULL COMMENT '接收用户id', + `biz_type` varchar(32) NOT NULL COMMENT '业务类型', + `biz_id` varchar(256) NOT NULL COMMENT '业务id', + `send_time` datetime DEFAULT NULL COMMENT '发送时间', + `status` varchar(32) NOT NULL COMMENT '发送状态', + `error_msg` varchar(512) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '失败原因', + `read_time` datetime DEFAULT NULL COMMENT '已读时间', + `create_by` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + `del_flag` char(1) COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '删除标记', + `tenant_id` bigint DEFAULT NULL COMMENT '租户ID', + PRIMARY KEY (`id`) USING BTREE, + KEY idx_third_push_record_user_record_id (`record_id`), + KEY idx_third_push_record_user_batch_no (`batch_no`), + KEY idx_third_push_record_user_user_time (`tenant_id`, `user_id`, `send_time`), + KEY idx_third_push_record_user_status_time (`tenant_id`, `status`, `send_time`), + KEY idx_third_push_record_user_biz (`tenant_id`, `biz_type`, `biz_id`, `del_flag`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci COMMENT ='推送通知用户明细记录'; diff --git a/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/constant/ServiceNameConstants.java b/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/constant/ServiceNameConstants.java index e5c4a1a3..08d8f4c9 100644 --- a/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/constant/ServiceNameConstants.java +++ b/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/constant/ServiceNameConstants.java @@ -96,4 +96,9 @@ public interface ServiceNameConstants { * 综合服务 */ String INTEGRATED_SERVICE = "pjcloud-integrated-biz"; + + /** + * Quartz 定时任务服务 + */ + String DAEMON_QUARTZ_SERVICE = "pjcloud-daemon-quartz"; } diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/constant/push/ThirdPushBizTypeEnum.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/constant/push/ThirdPushBizTypeEnum.java new file mode 100644 index 00000000..75da5635 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/constant/push/ThirdPushBizTypeEnum.java @@ -0,0 +1,20 @@ +package com.bsd.pjcloud.thirdparty.constant.push; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 推送通知类型 + */ +@AllArgsConstructor +@Getter +public enum ThirdPushBizTypeEnum { + MCH_PUSH("1", "商户用餐提醒推送"), + ; + + @EnumValue + private final String type; + + private final String description; +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/constant/push/ThirdPushInformTypeEnum.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/constant/push/ThirdPushInformTypeEnum.java new file mode 100644 index 00000000..2863cb1a --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/constant/push/ThirdPushInformTypeEnum.java @@ -0,0 +1,22 @@ +package com.bsd.pjcloud.thirdparty.constant.push; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 推送通知类型 + */ +@AllArgsConstructor +@Getter +public enum ThirdPushInformTypeEnum { + + DAY(1, "每天"), + MONTH(2, "每月"); + + @EnumValue + private final Integer type; + + private final String description; +} + diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/jg/JgPusDto.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/jg/JgPusDto.java index 0486432a..7442c8a9 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/jg/JgPusDto.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/jg/JgPusDto.java @@ -13,4 +13,6 @@ public class JgPusDto { private List userIds; + private List tagList; + } diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPushConfEntity.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPushConfEntity.java new file mode 100644 index 00000000..39d85289 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPushConfEntity.java @@ -0,0 +1,100 @@ +package com.bsd.pjcloud.thirdparty.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import com.bsd.pjcloud.common.core.util.TenantTable; +import com.bsd.pjcloud.thirdparty.constant.push.ThirdPushInformTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; +import java.time.LocalTime; + +/** + * 推送配置 + */ +@Data +@TenantTable +@TableName("third_push_conf") +@EqualsAndHashCode(callSuper = true) +@Schema(description = "推送配置") +public class ThirdPushConfEntity extends Model { + + @TableId(type = IdType.ASSIGN_ID) + @Schema(description = "主键") + private Long id; + + @Schema(description = "业务类型") + private String bizType; + + @Schema(description = "业务id") + private String bizId; + + @Schema(description = "通知标题") + private String title; + + @Schema(description = "通知内容") + private String content; + + @Schema(description = "是否开启") + private String enable; + + @Schema(description = "通知类型:每天、每月") + private Integer informType; + + @Schema(description = "通知时间") + private LocalTime informTime; + + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建人") + private String createBy; + + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + @Schema(description = "修改人") + private String updateBy; + + @TableField(fill = FieldFill.INSERT_UPDATE) + @Schema(description = "修改时间") + private LocalDateTime updateTime; + + @TableLogic + @TableField(fill = FieldFill.INSERT) + @Schema(description = "删除标记") + private String delFlag; + + @Schema(description = "租户ID") + private Long tenantId; + + /** + * 生成对应的 cron 表达式(Quartz) + *

+ * informType=DAY:每天指定时间触发 + * informType=MONTH:每月 1 号指定时间触发 + */ + public String buildCron() { + if (informType == null || informTime == null) { + return null; + } + int second = informTime.getSecond(); + int minute = informTime.getMinute(); + int hour = informTime.getHour(); + + if (ThirdPushInformTypeEnum.DAY.getType().equals(informType)) { + return String.format("%d %d %d * * ?", second, minute, hour); + } + if (ThirdPushInformTypeEnum.MONTH.getType().equals(informType)) { + return String.format("%d %d %d 1 * ?", second, minute, hour); + } + return null; + } +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPushRecordEntity.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPushRecordEntity.java new file mode 100644 index 00000000..1fec20e0 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPushRecordEntity.java @@ -0,0 +1,100 @@ +package com.bsd.pjcloud.thirdparty.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import com.bsd.pjcloud.common.core.util.TenantTable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; +import java.time.LocalTime; + +/** + * 推送通知批次记录 + */ +@Data +@TenantTable +@TableName("third_push_record") +@EqualsAndHashCode(callSuper = true) +@Schema(description = "推送通知批次记录") +public class ThirdPushRecordEntity extends Model { + + @TableId(type = IdType.ASSIGN_ID) + @Schema(description = "主键") + private Long id; + + @Schema(description = "推送配置id") + private Long confId; + + @Schema(description = "推送批次号") + private String batchNo; + + @Schema(description = "业务类型") + private String bizType; + + @Schema(description = "业务id") + private String bizId; + + @Schema(description = "通知标题") + private String title; + + @Schema(description = "通知内容") + private String content; + + @Schema(description = "通知类型:每天、每月") + private Integer informType; + + @Schema(description = "通知时间") + private LocalTime informTime; + + @Schema(description = "接收总人数") + private Integer receiverCount; + + @Schema(description = "成功人数") + private Integer successCount; + + @Schema(description = "失败人数") + private Integer failCount; + + @Schema(description = "发送时间") + private LocalDateTime sendTime; + + @Schema(description = "批次状态") + private String status; + + @Schema(description = "推送结果摘要") + private String pushResult; + + @Schema(description = "失败原因") + private String errorMsg; + + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建人") + private String createBy; + + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + @Schema(description = "修改人") + private String updateBy; + + @TableField(fill = FieldFill.INSERT_UPDATE) + @Schema(description = "修改时间") + private LocalDateTime updateTime; + + @TableLogic + @TableField(fill = FieldFill.INSERT) + @Schema(description = "删除标记") + private String delFlag; + + @Schema(description = "租户ID") + private Long tenantId; +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPushRecordUserEntity.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPushRecordUserEntity.java new file mode 100644 index 00000000..91d25e7e --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/entity/ThirdPushRecordUserEntity.java @@ -0,0 +1,85 @@ +package com.bsd.pjcloud.thirdparty.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import com.bsd.pjcloud.common.core.util.TenantTable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 推送通知用户明细记录 + */ +@Data +@TenantTable +@TableName("third_push_record_user") +@EqualsAndHashCode(callSuper = true) +@Schema(description = "推送通知用户明细记录") +public class ThirdPushRecordUserEntity extends Model { + + @TableId(type = IdType.ASSIGN_ID) + @Schema(description = "主键") + private Long id; + + @Schema(description = "推送批次记录id") + private Long recordId; + + @Schema(description = "推送配置id") + private Long confId; + + @Schema(description = "推送批次号") + private String batchNo; + + @Schema(description = "接收用户id") + private Long userId; + + @Schema(description = "业务类型") + private String bizType; + + @Schema(description = "业务id") + private String bizId; + + @Schema(description = "发送时间") + private LocalDateTime sendTime; + + @Schema(description = "发送状态") + private String status; + + @Schema(description = "失败原因") + private String errorMsg; + + @Schema(description = "已读时间") + private LocalDateTime readTime; + + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建人") + private String createBy; + + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + @Schema(description = "修改人") + private String updateBy; + + @TableField(fill = FieldFill.INSERT_UPDATE) + @Schema(description = "修改时间") + private LocalDateTime updateTime; + + @TableLogic + @TableField(fill = FieldFill.INSERT) + @Schema(description = "删除标记") + private String delFlag; + + @Schema(description = "租户ID") + private Long tenantId; +} + diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports index 3565f606..6151312e 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports @@ -3,9 +3,9 @@ com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdKsService com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdDoorApiService com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdLbApiService com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdPartyAuthService +com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdPushConfService com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdZyService com.bsd.pjcloud.thirdparty.api.feign.RemoterVisitorService com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdPartySmsService - diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml b/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml index ff550308..8778e507 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml @@ -196,6 +196,11 @@ 5.7.0 compile + + com.bsd + pjcloud-daemon-quartz-api + 5.7.0 + diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/push/ThirdPushConfController.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/push/ThirdPushConfController.java new file mode 100644 index 00000000..7024ce2a --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/push/ThirdPushConfController.java @@ -0,0 +1,117 @@ +package com.bsd.pjcloud.thirdparty.controller.push; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.common.security.annotation.Inner; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushConfEntity; +import com.bsd.pjcloud.thirdparty.service.push.ThirdPushConfService; +import com.bsd.pjcloud.thirdparty.service.push.ThirdPushExecuteService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +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; + +import java.util.List; + +/** + * 推送配置 + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/push-conf") +@Tag(description = "push-conf", name = "推送配置") +@SecurityRequirement(name = HttpHeaders.AUTHORIZATION) +public class ThirdPushConfController { + + private final ThirdPushConfService thirdPushConfService; + private final ThirdPushExecuteService thirdPushExecuteService; + + /** + * 新增推送配置 + */ + @Operation(summary = "新增推送配置") + @PostMapping("/create") + public R create(@RequestBody ThirdPushConfEntity dto) { + if (ObjectUtil.isEmpty(dto)) { + return R.ok(false); + } + return R.ok(thirdPushConfService.create(dto)); + } + + /** + * 修改推送配置 + */ + @Operation(summary = "修改推送配置") + @PostMapping("/edit") + public R edit(@RequestBody ThirdPushConfEntity dto) { + if (ObjectUtil.isEmpty(dto) || ObjectUtil.isEmpty(dto.getId())) { + return R.ok(false); + } + return R.ok(thirdPushConfService.edit(dto)); + } + + /** + * 删除推送配置 + */ + @Operation(summary = "删除推送配置") + @PostMapping("/remove/{id}") + public R remove(@PathVariable Long id) { + if (ObjectUtil.isEmpty(id)) { + return R.ok(false); + } + return R.ok(thirdPushConfService.removeById(id)); + } + + /** + * 查询详情 + */ + @Operation(summary = "查询详情") + @GetMapping("/detail/{id}") + public R detail(@PathVariable Long id) { + return R.ok(thirdPushConfService.getById(id)); + } + + /** + * 查询推送配置列表 + */ + @Operation(summary = "查询推送配置列表") + @PostMapping("/list") + public R> list(@RequestBody(required = false) ThirdPushConfEntity dto) { + ThirdPushConfEntity query = ObjectUtil.defaultIfNull(dto, new ThirdPushConfEntity()); + + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() + .eq(ObjectUtil.isNotEmpty(query.getBizType()), ThirdPushConfEntity::getBizType, query.getBizType()) + .eq(ObjectUtil.isNotEmpty(query.getBizId()), ThirdPushConfEntity::getBizId, query.getBizId()) + .like(ObjectUtil.isNotEmpty(query.getTitle()), ThirdPushConfEntity::getTitle, query.getTitle()) + .eq(ObjectUtil.isNotEmpty(query.getEnable()), ThirdPushConfEntity::getEnable, query.getEnable()) + .eq(ObjectUtil.isNotEmpty(query.getInformType()), ThirdPushConfEntity::getInformType, query.getInformType()) + .eq(ObjectUtil.isNotEmpty(query.getInformTime()), ThirdPushConfEntity::getInformTime, query.getInformTime()) + .orderByDesc(ThirdPushConfEntity::getCreateTime) + .orderByDesc(ThirdPushConfEntity::getId); + + return R.ok(thirdPushConfService.list(wrapper)); + } + + /** + * 提供给 Quartz 服务的内部执行入口 + */ + @Inner(value = false) + @GetMapping("/inner/execute/{id}") + public R execute(@PathVariable("id") Long id) { + if (ObjectUtil.isEmpty(id)) { + return R.failed("推送配置ID不能为空"); + } + return R.ok(thirdPushExecuteService.executeByConfId(String.valueOf(id))); + } +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/job/ThirdAppJgPushJob.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/job/ThirdAppJgPushJob.java index 1798a814..453f9376 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/job/ThirdAppJgPushJob.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/job/ThirdAppJgPushJob.java @@ -3,6 +3,7 @@ package com.bsd.pjcloud.thirdparty.job; import com.bsd.pjcloud.admin.api.feign.RemoteUserService; import com.bsd.pjcloud.thirdparty.dto.jg.JgPusDto; import com.bsd.pjcloud.thirdparty.service.ThirdJgPushService; +import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; @@ -21,22 +22,12 @@ public class ThirdAppJgPushJob { private final List testUserIdList = List.of(2029755089492066305L, 2034824566572941314L); - // 早饭提醒(每天早上6点7点) - @Scheduled(cron = "0 0 6,7 * * ?") - public void breakfastReminder() { + + @PostConstruct + public void test() { pushToAllUsers("早餐提醒", "早餐时间就要到了,请尽快预约哦!!"); - } - - // 午饭提醒(每天中午10点11点) - @Scheduled(cron = "0 0 10,11 * * ?") - public void lunchReminder() { - pushToAllUsers("午餐提醒", "午餐间就要到了,请尽快预约哦!!"); - } - - // 晚餐提醒(每天下午5点6点) - @Scheduled(cron = "0 0 17,18 * * ?") - public void dinnerReminder() { - pushToAllUsers("晚餐提醒", "晚餐时间就要到了,请尽快预约哦!!"); +// pushToAllUsers("午餐提醒", "午餐间就要到了,请尽快预约哦!!"); +// pushToAllUsers("晚餐提醒", "晚餐时间就要到了,请尽快预约哦!!"); } private void pushToAllUsers(String title, String subtitle) { diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/push/ThirdPushConfMapper.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/push/ThirdPushConfMapper.java new file mode 100644 index 00000000..93e81bf5 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/push/ThirdPushConfMapper.java @@ -0,0 +1,10 @@ +package com.bsd.pjcloud.thirdparty.mapper.push; + +import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushConfEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ThirdPushConfMapper extends PjcloudBaseMapper { +} + diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/push/ThirdPushRecordMapper.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/push/ThirdPushRecordMapper.java new file mode 100644 index 00000000..40278d54 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/push/ThirdPushRecordMapper.java @@ -0,0 +1,10 @@ +package com.bsd.pjcloud.thirdparty.mapper.push; + +import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushRecordEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ThirdPushRecordMapper extends PjcloudBaseMapper { +} + diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/push/ThirdPushRecordUserMapper.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/push/ThirdPushRecordUserMapper.java new file mode 100644 index 00000000..4ad92168 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/mapper/push/ThirdPushRecordUserMapper.java @@ -0,0 +1,10 @@ +package com.bsd.pjcloud.thirdparty.mapper.push; + +import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushRecordUserEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ThirdPushRecordUserMapper extends PjcloudBaseMapper { +} + diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/jg/ThirdThirdJgPushServiceImpl.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/jg/ThirdThirdJgPushServiceImpl.java index 5e98d4c2..07a9f776 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/jg/ThirdThirdJgPushServiceImpl.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/jg/ThirdThirdJgPushServiceImpl.java @@ -1,10 +1,12 @@ package com.bsd.pjcloud.thirdparty.service.impl.jg; +import cn.hutool.core.util.ObjectUtil; import cn.jiguang.sdk.api.PushApi; import cn.jiguang.sdk.bean.push.PushSendParam; import cn.jiguang.sdk.bean.push.PushSendResult; import cn.jiguang.sdk.bean.push.audience.Audience; import cn.jiguang.sdk.bean.push.message.notification.NotificationMessage; +import cn.jiguang.sdk.bean.push.options.Options; import cn.jiguang.sdk.constants.ApiConstants; import com.bsd.pjcloud.thirdparty.config.JgConfig; import com.bsd.pjcloud.thirdparty.dto.jg.JgPusDto; @@ -60,8 +62,22 @@ public class ThirdThirdJgPushServiceImpl implements ThirdJgPushService { notificationMessage.setAndroid(android); notificationMessage.setIos(iOS); + // start 通知人 ios start Audience audience = new Audience(); - audience.setAliasList(jgPusDto.getUserIds().stream().map(String::valueOf).toList()); + if (ObjectUtil.isNotEmpty(jgPusDto.getUserIds())) { + audience.setAliasList(jgPusDto.getUserIds().stream().map(String::valueOf).toList()); + } + if (ObjectUtil.isNotEmpty(jgPusDto.getTagList())) { + audience.setTagAndList(jgPusDto.getTagList()); + } + // end 通知人 ios end + + // start 通知options ios start + Options options = new Options(); + // true:表示推送生产环境,false:表示推送开发环境 + options.setApnsProduction(false); + param.setOptions(options); + // end 通知options ios end param.setNotification(notificationMessage); param.setAudience(audience); diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushConfServiceImpl.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushConfServiceImpl.java new file mode 100644 index 00000000..e87efc84 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushConfServiceImpl.java @@ -0,0 +1,282 @@ +package com.bsd.pjcloud.thirdparty.service.impl.push; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bsd.pjcloud.common.core.constant.ServiceNameConstants; +import com.bsd.pjcloud.common.core.constant.enums.YesNoEnum; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.daemon.quartz.api.dto.SysJobDTO; +import com.bsd.pjcloud.daemon.quartz.api.feign.RemoteSysJobService; +import com.bsd.pjcloud.thirdparty.constant.push.ThirdPushBizTypeEnum; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushConfEntity; +import com.bsd.pjcloud.thirdparty.mapper.push.ThirdPushConfMapper; +import com.bsd.pjcloud.thirdparty.service.push.ThirdPushConfService; +import com.bsd.pjcloud.wallet.api.feign.RemoteMchService; +import com.bsd.pjcloud.wallet.api.vo.MchManageVo; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.io.Serializable; + +/** + * 推送配置 + */ +@Service +@Validated +@Slf4j +@RequiredArgsConstructor +public class ThirdPushConfServiceImpl extends ServiceImpl implements ThirdPushConfService { + + private static final String JOB_GROUP = "third_push"; + private static final String JOB_TYPE_REST = "3"; + private static final String JOB_TENANT_TYPE = "1"; + private static final String JOB_ORDER = "5"; + private static final String MISFIRE_POLICY_DEFAULT = "0"; + private static final String JOB_STATUS_SUCCESS = "0"; + private static final String JOB_STATUS_RUNNING = "2"; + private static final String JOB_STATUS_NOT_RUNNING = "3"; + private static final String EXECUTE_URL_TEMPLATE = "http://127.0.0.1:9999/third/push-conf/inner/execute/%s"; + + private final RemoteSysJobService remoteSysJobService; + private final RemoteMchService remoteMchService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean create(ThirdPushConfEntity entity) { + if (entity == null) { + return false; + } + if (existTitle(entity)) { + throw new RuntimeException("推送标题不能重复"); + } + validateCron(entity); + boolean saved = save(entity); + if (!saved || entity.getId() == null) { + return false; + } + try { + saveRemoteQuartzJob(entity); + return true; + } catch (Exception e) { + removeRemoteQuartzJobQuietly(entity); + throw new RuntimeException("创建推送配置并同步定时任务失败", e); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean edit(ThirdPushConfEntity entity) { + if (entity == null || entity.getId() == null) { + return false; + } + if (existTitle(entity)) { + throw new RuntimeException("推送标题不能重复"); + } + ThirdPushConfEntity oldEntity = getById(entity.getId()); + if (oldEntity == null) { + return false; + } + mergeEditFields(entity, oldEntity); + validateCron(entity); + boolean updated = updateById(entity); + if (!updated) { + return false; + } + ThirdPushConfEntity latest = getById(entity.getId()); + try { + upsertRemoteQuartzJob(latest); + return true; + } catch (Exception e) { + // 尽最大努力恢复旧任务,避免编辑失败后任务丢失 + try { + upsertRemoteQuartzJob(oldEntity); + } catch (Exception restoreException) { + log.error("恢复旧 Quartz 任务失败 pushConfId={}", oldEntity.getId(), restoreException); + } + throw new RuntimeException("修改推送配置并同步定时任务失败", e); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean removeById(Serializable id) { + if (!(id instanceof Long pushConfId)) { + return false; + } + ThirdPushConfEntity entity = getById(pushConfId); + if (entity == null) { + return false; + } + removeRemoteQuartzJob(entity); + return super.removeById(id); + } + + /** + * 标题不能重复(同 bizType + bizId 下) + */ + private boolean existTitle(ThirdPushConfEntity dto) { + Long count = baseMapper.selectCount(new LambdaQueryWrapper() + .eq(ObjectUtil.isNotEmpty(dto.getTitle()), ThirdPushConfEntity::getTitle, dto.getTitle()) + .eq(ObjectUtil.isNotEmpty(dto.getBizType()), ThirdPushConfEntity::getBizType, dto.getBizType()) + .eq(ObjectUtil.isNotEmpty(dto.getBizId()), ThirdPushConfEntity::getBizId, dto.getBizId()) + .ne(ObjectUtil.isNotEmpty(dto.getId()), ThirdPushConfEntity::getId, dto.getId()) + ); + if (ObjectUtil.isNotEmpty(dto.getId())) { + return count >= 1; + } + return count > 0; + } + + /** + * 校验 cron 表达式是否合法 + */ + private void validateCron(ThirdPushConfEntity entity) { + String cron = entity.buildCron(); + if (ObjectUtil.isEmpty(cron)) { + throw new RuntimeException("推送配置生成的定时表达式不合法"); + } + R result = remoteSysJobService.validateCron(cron); + if (result == null || !result.isOk() || Boolean.FALSE.equals(result.getData())) { + throw new RuntimeException(result == null || ObjectUtil.isEmpty(result.getMsg()) + ? "推送配置生成的定时表达式不合法" + : result.getMsg()); + } + } + + /** + * 新增远程 Quartz 任务 + */ + private void saveRemoteQuartzJob(ThirdPushConfEntity entity) { + SysJobDTO sysJob = buildSysJob(entity); + R result = remoteSysJobService.saveInner(sysJob); + assertRemoteSuccess(result, "新增 Quartz 任务失败"); + } + + /** + * 远程新增或更新 Quartz 任务 + */ + private void upsertRemoteQuartzJob(ThirdPushConfEntity entity) { + SysJobDTO sysJob = buildSysJob(entity); + SysJobDTO exists = getRemoteQuartzJob(entity); + if (exists == null) { + saveRemoteQuartzJob(entity); + return; + } + sysJob.setJobId(exists.getJobId()); + R result = remoteSysJobService.updateInner(sysJob); + assertRemoteSuccess(result, "修改 Quartz 任务失败"); + } + + /** + * 删除远程 Quartz 任务 + */ + private void removeRemoteQuartzJob(ThirdPushConfEntity pushConfEntity) { + SysJobDTO exists = getRemoteQuartzJob(pushConfEntity); + if (exists == null || exists.getJobId() == null) { + return; + } + R result = remoteSysJobService.removeInnerById(exists.getJobId()); + assertRemoteSuccess(result, "删除 Quartz 任务失败"); + } + + private void removeRemoteQuartzJobQuietly(ThirdPushConfEntity pushConfEntity) { + try { + SysJobDTO exists = getRemoteQuartzJob(pushConfEntity); + if (exists != null && exists.getJobId() != null) { + remoteSysJobService.removeInnerById(exists.getJobId()); + } + } catch (Exception e) { + log.error("移除 Quartz 任务失败 pushConfId={}", pushConfEntity.getId(), e); + } + } + + private SysJobDTO getRemoteQuartzJob(ThirdPushConfEntity pushConfEntity) { + R result = remoteSysJobService.getByJobName(buildJobName(pushConfEntity), JOB_GROUP); + if (result == null || !result.isOk()) { + throw new RuntimeException(result == null ? "查询 Quartz 任务失败" : result.getMsg()); + } + return result.getData(); + } + + /** + * 构建 sys_job 元数据,便于统一在任务中心查看 + */ + private SysJobDTO buildSysJob(ThirdPushConfEntity entity) { + String jobName = buildJobName(entity); + SysJobDTO sysJob = new SysJobDTO(); + sysJob.setJobName(jobName); + sysJob.setJobGroup(JOB_GROUP); + sysJob.setJobOrder(JOB_ORDER); + sysJob.setJobType(JOB_TYPE_REST); + sysJob.setExecutePath(buildExecutePath(entity.getId())); + sysJob.setCronExpression(entity.buildCron()); + sysJob.setMisfirePolicy(MISFIRE_POLICY_DEFAULT); + sysJob.setJobTenantType(JOB_TENANT_TYPE); + sysJob.setJobStatus(isEnable(entity) ? JOB_STATUS_RUNNING : JOB_STATUS_NOT_RUNNING); + sysJob.setJobExecuteStatus(JOB_STATUS_SUCCESS); + sysJob.setTenantId(entity.getTenantId()); + sysJob.setRemark(String.format("pushConfId=%s,bizType=%s,bizId=%s,title=%s", entity.getId(), entity.getBizType(), entity.getBizId(), entity.getTitle())); + return sysJob; + } + + private boolean isEnable(ThirdPushConfEntity entity) { + return ObjectUtil.equals(entity.getEnable(), YesNoEnum.YES.getCode()); + } + + /** + * 编辑时补全未传的关键字段,避免校验与任务同步拿到空值 + */ + private void mergeEditFields(ThirdPushConfEntity entity, ThirdPushConfEntity oldEntity) { + if (ObjectUtil.isEmpty(entity.getBizType())) { + entity.setBizType(oldEntity.getBizType()); + } + if (ObjectUtil.isEmpty(entity.getBizId())) { + entity.setBizId(oldEntity.getBizId()); + } + if (ObjectUtil.isEmpty(entity.getTitle())) { + entity.setTitle(oldEntity.getTitle()); + } + if (ObjectUtil.isEmpty(entity.getContent())) { + entity.setContent(oldEntity.getContent()); + } + if (ObjectUtil.isEmpty(entity.getEnable())) { + entity.setEnable(oldEntity.getEnable()); + } + if (entity.getInformType() == null) { + entity.setInformType(oldEntity.getInformType()); + } + if (entity.getInformTime() == null) { + entity.setInformTime(oldEntity.getInformTime()); + } + if (entity.getTenantId() == null) { + entity.setTenantId(oldEntity.getTenantId()); + } + } + + private String buildJobName(ThirdPushConfEntity pushConfEntity) { + String jobName = "第三方推送(自动创建,请勿删除或停止)"; + if (ObjectUtil.equals(pushConfEntity.getBizType(), ThirdPushBizTypeEnum.MCH_PUSH.getType())) { + R mchRes = remoteMchService.getById(Long.valueOf(pushConfEntity.getBizId())); + if (!mchRes.isOk() || ObjectUtil.isEmpty(mchRes)) { + throw new RuntimeException("添加定时任务错误,无效的店铺id"); + } + jobName += ",店铺" + mchRes.getData().getName() + "定时推送"; + } + jobName += ",推送配置id:" + pushConfEntity.getId(); + return jobName; + } + + private String buildExecutePath(Long pushConfId) { + return String.format(EXECUTE_URL_TEMPLATE, pushConfId); + } + + private void assertRemoteSuccess(R result, String defaultMessage) { + if (result == null || !result.isOk() || Boolean.FALSE.equals(result.getData())) { + throw new RuntimeException(result == null || ObjectUtil.isEmpty(result.getMsg()) ? defaultMessage : result.getMsg()); + } + } +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushExecuteServiceImpl.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushExecuteServiceImpl.java new file mode 100644 index 00000000..cd5d5dec --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushExecuteServiceImpl.java @@ -0,0 +1,224 @@ +package com.bsd.pjcloud.thirdparty.service.impl.push; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import com.bsd.pjcloud.admin.api.entity.SysUser; +import com.bsd.pjcloud.admin.api.feign.RemoteUserService; +import com.bsd.pjcloud.common.core.constant.enums.YesNoEnum; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.thirdparty.constant.push.ThirdPushBizTypeEnum; +import com.bsd.pjcloud.thirdparty.dto.jg.JgPusDto; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushConfEntity; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushRecordEntity; +import com.bsd.pjcloud.thirdparty.service.ThirdJgPushService; +import com.bsd.pjcloud.thirdparty.service.push.ThirdPushConfService; +import com.bsd.pjcloud.thirdparty.service.push.ThirdPushExecuteService; +import com.bsd.pjcloud.thirdparty.service.push.ThirdPushRecordService; +import com.bsd.pjcloud.thirdparty.service.push.ThirdPushRecordUserService; +import com.bsd.pjcloud.wallet.api.feign.RemoteMchService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 推送任务统一执行入口 + *

+ * Quartz 触发时统一走这里,避免调度层直接依赖极光推送细节。 + */ +@Slf4j +@Service("thirdPushExecuteService") +@RequiredArgsConstructor +public class ThirdPushExecuteServiceImpl implements ThirdPushExecuteService { + + private static final String JOB_LOG_STATUS_SUCCESS = "0"; + private static final String JOB_LOG_STATUS_FAIL = "1"; + private static final int PUSH_BATCH_SIZE = 500; + private static final int PUSH_MAX_RETRY_TIMES = 3; + private static final long PUSH_RETRY_INTERVAL_MILLIS = 1000L; + + private final ThirdPushConfService thirdPushConfService; + private final ThirdPushRecordService thirdPushRecordService; + private final ThirdPushRecordUserService thirdPushRecordUserService; + private final ThirdJgPushService thirdJgPushService; + private final RemoteUserService remoteUserService; + private final RemoteMchService remoteMchService; + ; + + @Override + public String executeByConfId(String confId) { + Long pushConfId = parseConfId(confId); + if (pushConfId == null) { + return JOB_LOG_STATUS_FAIL; + } + ThirdPushConfEntity conf = thirdPushConfService.getById(pushConfId); + if (conf == null) { + log.warn("推送配置不存在 confId={}", pushConfId); + return JOB_LOG_STATUS_FAIL; + } + if (ObjectUtil.notEqual(conf.getEnable(), YesNoEnum.YES.getCode())) { + log.info("推送配置未启用,跳过执行 confId={}", pushConfId); + return JOB_LOG_STATUS_SUCCESS; + } + List targetDeptIds = listTargetDeptIds(conf); + if (CollUtil.isEmpty(targetDeptIds)) { + log.info("推送目标为空,跳过执行 confId={}", pushConfId); + return JOB_LOG_STATUS_SUCCESS; + } + List targetUserIds = listTargetUserIds(targetDeptIds); + if (CollUtil.isEmpty(targetUserIds)) { + log.info("推送目标部门下无合法用户,跳过执行 confId={} deptIds={}", pushConfId, targetDeptIds); + return JOB_LOG_STATUS_SUCCESS; + } + String batchNo = IdUtil.fastUUID(); + ThirdPushRecordEntity record = thirdPushRecordService.createBatchRecord(conf, batchNo, targetUserIds.size()); + if (record == null || record.getId() == null) { + log.error("创建推送批次记录失败 confId={}", pushConfId); + return JOB_LOG_STATUS_FAIL; + } + boolean savedUsers = thirdPushRecordUserService.saveUsersByUserIds(record.getId(), conf.getId(), batchNo, targetUserIds, conf.getBizType(), conf.getBizId(), "INIT"); + if (!savedUsers) { + log.error("写入推送用户明细失败 confId={} batchNo={} userIds={}", pushConfId, batchNo, targetUserIds); + thirdPushRecordService.markBatchFail(record.getId(), "写入推送用户明细失败", "推送失败"); + return JOB_LOG_STATUS_FAIL; + } + try { + pushByUserIdsInBatches(pushConfId, batchNo, conf, targetUserIds); + thirdPushRecordService.markBatchSuccess(record.getId(), targetUserIds.size(), 0, "推送成功"); + thirdPushRecordUserService.updateStatusByRecordId(record.getId(), "SUCCESS", null); + return JOB_LOG_STATUS_SUCCESS; + } catch (Exception e) { + log.error("执行推送任务失败 confId={} batchNo={}", pushConfId, batchNo, e); + thirdPushRecordService.markBatchFail(record.getId(), e.getMessage(), "推送失败"); + thirdPushRecordUserService.updateStatusByRecordId(record.getId(), "FAIL", e.getMessage()); + return JOB_LOG_STATUS_FAIL; + } + } + + /** + * 查询需要推送的部门标签 + */ + private List listTargetDeptIds(ThirdPushConfEntity conf) { + if (ObjectUtil.equals(conf.getBizType(), ThirdPushBizTypeEnum.MCH_PUSH.getType())) { + Long mchId = Long.valueOf(conf.getBizId()); + R> apiRes = remoteMchService.getSelfCanteenUserDeptIdList(mchId); + if (apiRes == null || !apiRes.isOk() || CollUtil.isEmpty(apiRes.getData())) { + return List.of(); + } + return apiRes.getData().stream() + .filter(ObjectUtil::isNotEmpty) + .distinct() + .toList(); + } + return List.of(); + } + + /** + * 根据部门解析实际推送用户 + */ + private List listTargetUserIds(List deptIds) { + if (CollUtil.isEmpty(deptIds)) { + return List.of(); + } + R> result = remoteUserService.getUserIdListByDeptIdList(deptIds); + if (result == null || !result.isOk() || CollUtil.isEmpty(result.getData())) { + return List.of(); + } + return result.getData().stream() + .map(SysUser::getUserId) + .filter(ObjectUtil::isNotEmpty) + .distinct() + .toList(); + } + + /** + * 按用户ID分组分批推送 + */ + private void pushByUserIdsInBatches(Long pushConfId, String batchNo, ThirdPushConfEntity conf, List targetUserIds) { + List> userIdGroups = splitList(targetUserIds, PUSH_BATCH_SIZE); + int totalDeviceCount = targetUserIds.size(); + int totalGroupCount = userIdGroups.size(); + int successDeviceCount = 0; + + log.info("开始分组推送 confId={} batchNo={} totalDeviceCount={} totalGroupCount={} batchSize={}", + pushConfId, batchNo, totalDeviceCount, totalGroupCount, PUSH_BATCH_SIZE); + + for (int index = 0; index < totalGroupCount; index++) { + List userIdGroup = userIdGroups.get(index); + int groupNo = index + 1; + pushSingleGroupWithRetry(pushConfId, batchNo, conf, userIdGroup, groupNo, totalGroupCount); + successDeviceCount += userIdGroup.size(); + } + + log.info("分组推送完成 confId={} batchNo={} totalDeviceCount={} totalGroupCount={} successDeviceCount={} failDeviceCount={}", + pushConfId, batchNo, totalDeviceCount, totalGroupCount, successDeviceCount, totalDeviceCount - successDeviceCount); + } + + /** + * 推送单个分组,失败后自动重试 + */ + private void pushSingleGroupWithRetry(Long pushConfId, String batchNo, ThirdPushConfEntity conf, List userIdGroup, + int groupNo, int totalGroupCount) { + for (int attempt = 1; attempt <= PUSH_MAX_RETRY_TIMES; attempt++) { + try { + JgPusDto dto = new JgPusDto(); + dto.setTitle(conf.getTitle()); + dto.setSubtitle(conf.getContent()); + dto.setUserIds(userIdGroup); + + log.info("开始推送分组 confId={} batchNo={} groupNo={}/{} groupDeviceCount={} attempt={}", + pushConfId, batchNo, groupNo, totalGroupCount, userIdGroup.size(), attempt); + thirdJgPushService.push(dto); + log.info("推送分组成功 confId={} batchNo={} groupNo={}/{} groupDeviceCount={} attempt={}", + pushConfId, batchNo, groupNo, totalGroupCount, userIdGroup.size(), attempt); + return; + } catch (Exception e) { + log.error("推送分组失败 confId={} batchNo={} groupNo={}/{} groupDeviceCount={} attempt={} maxRetry={}", + pushConfId, batchNo, groupNo, totalGroupCount, userIdGroup.size(), attempt, PUSH_MAX_RETRY_TIMES, e); + if (attempt >= PUSH_MAX_RETRY_TIMES) { + throw new RuntimeException(String.format("分组推送失败 groupNo=%s totalGroupCount=%s", groupNo, totalGroupCount), e); + } + sleepBeforeRetry(); + } + } + } + + /** + * 列表按固定大小分片 + */ + private List> splitList(List sourceList, int batchSize) { + if (CollUtil.isEmpty(sourceList) || batchSize <= 0) { + return List.of(); + } + java.util.ArrayList> result = new java.util.ArrayList<>(); + for (int start = 0; start < sourceList.size(); start += batchSize) { + int end = Math.min(start + batchSize, sourceList.size()); + result.add(sourceList.subList(start, end)); + } + return result; + } + + /** + * 分组失败后的重试间隔 + */ + protected void sleepBeforeRetry() { + try { + Thread.sleep(PUSH_RETRY_INTERVAL_MILLIS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("分组推送重试等待被中断", e); + } + } + + + private Long parseConfId(String confId) { + try { + return Long.valueOf(confId); + } catch (Exception e) { + log.error("解析推送配置ID失败 confId={}", confId, e); + return null; + } + } +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushRecordServiceImpl.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushRecordServiceImpl.java new file mode 100644 index 00000000..edf61d2a --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushRecordServiceImpl.java @@ -0,0 +1,78 @@ +package com.bsd.pjcloud.thirdparty.service.impl.push; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushConfEntity; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushRecordEntity; +import com.bsd.pjcloud.thirdparty.mapper.push.ThirdPushRecordMapper; +import com.bsd.pjcloud.thirdparty.service.push.ThirdPushRecordService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; + +/** + * 推送通知批次记录 + */ +@Service +@Validated +@Slf4j +@RequiredArgsConstructor +public class ThirdPushRecordServiceImpl extends ServiceImpl implements ThirdPushRecordService { + + @Override + public ThirdPushRecordEntity createBatchRecord(ThirdPushConfEntity conf, String batchNo, Integer receiverCount) { + if (conf == null || conf.getId() == null) { + return null; + } + + ThirdPushRecordEntity record = new ThirdPushRecordEntity(); + record.setConfId(conf.getId()); + record.setBatchNo(ObjectUtil.defaultIfEmpty(batchNo, IdUtil.fastUUID())); + record.setBizType(conf.getBizType()); + record.setBizId(conf.getBizId()); + record.setTitle(conf.getTitle()); + record.setContent(conf.getContent()); + record.setInformType(conf.getInformType()); + record.setInformTime(conf.getInformTime()); + record.setReceiverCount(ObjectUtil.defaultIfNull(receiverCount, 0)); + record.setSuccessCount(0); + record.setFailCount(0); + record.setStatus("INIT"); + record.setSendTime(LocalDateTime.now()); + + boolean saved = save(record); + return saved ? record : null; + } + + @Override + public Boolean markBatchSuccess(Long recordId, Integer successCount, Integer failCount, String pushResult) { + if (recordId == null) { + return false; + } + ThirdPushRecordEntity update = new ThirdPushRecordEntity(); + update.setId(recordId); + update.setSuccessCount(ObjectUtil.defaultIfNull(successCount, 0)); + update.setFailCount(ObjectUtil.defaultIfNull(failCount, 0)); + update.setStatus("SUCCESS"); + update.setPushResult(pushResult); + return updateById(update); + } + + @Override + public Boolean markBatchFail(Long recordId, String errorMsg, String pushResult) { + if (recordId == null) { + return false; + } + ThirdPushRecordEntity update = new ThirdPushRecordEntity(); + update.setId(recordId); + update.setStatus("FAIL"); + update.setErrorMsg(errorMsg); + update.setPushResult(pushResult); + return updateById(update); + } +} + diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushRecordUserServiceImpl.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushRecordUserServiceImpl.java new file mode 100644 index 00000000..b8530c01 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushRecordUserServiceImpl.java @@ -0,0 +1,115 @@ +package com.bsd.pjcloud.thirdparty.service.impl.push; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bsd.pjcloud.admin.api.entity.SysUser; +import com.bsd.pjcloud.admin.api.feign.RemoteUserService; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushRecordUserEntity; +import com.bsd.pjcloud.thirdparty.mapper.push.ThirdPushRecordUserMapper; +import com.bsd.pjcloud.thirdparty.service.push.ThirdPushRecordUserService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 推送通知用户明细记录 + */ +@Service +@Validated +@Slf4j +@RequiredArgsConstructor +public class ThirdPushRecordUserServiceImpl extends ServiceImpl implements ThirdPushRecordUserService { + + private final RemoteUserService remoteUserService; + + @Override + public Boolean saveUsers(Long recordId, Long confId, String batchNo, List deptIds, String bizType, String bizId, String status) { + if (recordId == null || confId == null || ObjectUtil.isEmpty(batchNo) || CollUtil.isEmpty(deptIds)) { + return false; + } + List userIds = listUserIdsByDeptIds(deptIds); + if (CollUtil.isEmpty(userIds)) { + log.warn("根据部门查询推送用户为空 recordId={} confId={} deptIds={}", recordId, confId, deptIds); + return false; + } + return saveUsersByUserIds(recordId, confId, batchNo, userIds, bizType, bizId, status); + } + + @Override + public Boolean saveUsersByUserIds(Long recordId, Long confId, String batchNo, List userIds, String bizType, String bizId, String status) { + if (recordId == null || confId == null || ObjectUtil.isEmpty(batchNo) || CollUtil.isEmpty(userIds)) { + return false; + } + List list = buildRecordUsers(recordId, confId, batchNo, userIds, bizType, bizId, status); + if (CollUtil.isEmpty(list)) { + return false; + } + return saveBatch(list); + } + + @Override + public Boolean updateStatusByRecordId(Long recordId, String status, String errorMsg) { + if (recordId == null || ObjectUtil.isEmpty(status)) { + return false; + } + return update(new LambdaUpdateWrapper() + .eq(ThirdPushRecordUserEntity::getRecordId, recordId) + .set(ThirdPushRecordUserEntity::getStatus, status) + .set(ObjectUtil.isNotEmpty(errorMsg), ThirdPushRecordUserEntity::getErrorMsg, errorMsg) + .set(ObjectUtil.isEmpty(errorMsg), ThirdPushRecordUserEntity::getErrorMsg, null)); + } + + /** + * 根据部门列表解析目标用户 + */ + private List listUserIdsByDeptIds(List deptIds) { + List validDeptIds = deptIds.stream() + .filter(ObjectUtil::isNotEmpty) + .distinct() + .toList(); + if (CollUtil.isEmpty(validDeptIds)) { + return List.of(); + } + R> result = remoteUserService.getUserIdListByDeptIdList(validDeptIds); + if (result == null || !result.isOk() || CollUtil.isEmpty(result.getData())) { + return List.of(); + } + return result.getData().stream() + .map(SysUser::getUserId) + .filter(ObjectUtil::isNotEmpty) + .distinct() + .toList(); + } + + /** + * 构建推送用户明细 + */ + private List buildRecordUsers(Long recordId, Long confId, String batchNo, List userIds, + String bizType, String bizId, String status) { + String finalStatus = ObjectUtil.defaultIfEmpty(status, "INIT"); + LocalDateTime now = LocalDateTime.now(); + return userIds.stream() + .filter(ObjectUtil::isNotEmpty) + .distinct() + .map(userId -> { + ThirdPushRecordUserEntity entity = new ThirdPushRecordUserEntity(); + entity.setRecordId(recordId); + entity.setConfId(confId); + entity.setBatchNo(batchNo); + entity.setUserId(userId); + entity.setBizType(bizType); + entity.setBizId(bizId); + entity.setStatus(finalStatus); + entity.setSendTime(now); + return entity; + }) + .toList(); + } +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushConfService.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushConfService.java new file mode 100644 index 00000000..4c7c0484 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushConfService.java @@ -0,0 +1,22 @@ +package com.bsd.pjcloud.thirdparty.service.push; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushConfEntity; + +/** + * 推送配置 + */ +public interface ThirdPushConfService extends IService { + + /** + * 创建推送配置 + */ + Boolean create(ThirdPushConfEntity entity); + + /** + * 修改推送配置 + */ + Boolean edit(ThirdPushConfEntity entity); + +} + diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushExecuteService.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushExecuteService.java new file mode 100644 index 00000000..195b43da --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushExecuteService.java @@ -0,0 +1,16 @@ +package com.bsd.pjcloud.thirdparty.service.push; + +/** + * 推送任务执行服务 + */ +public interface ThirdPushExecuteService { + + /** + * 根据推送配置 ID 执行一次推送 + * + * @param confId 推送配置 ID + * @return Quartz 约定返回值,0 表示成功,1 表示失败 + */ + String executeByConfId(String confId); +} + diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushRecordService.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushRecordService.java new file mode 100644 index 00000000..53b73846 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushRecordService.java @@ -0,0 +1,31 @@ +package com.bsd.pjcloud.thirdparty.service.push; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushConfEntity; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushRecordEntity; + +/** + * 推送通知批次记录 + */ +public interface ThirdPushRecordService extends IService { + + /** + * 创建推送批次记录 + * + * @param conf 推送配置 + * @param batchNo 批次号(外部传入或内部生成) + * @param receiverCount 接收人数 + */ + ThirdPushRecordEntity createBatchRecord(ThirdPushConfEntity conf, String batchNo, Integer receiverCount); + + /** + * 批次发送成功后回写统计信息 + */ + Boolean markBatchSuccess(Long recordId, Integer successCount, Integer failCount, String pushResult); + + /** + * 批次发送失败 + */ + Boolean markBatchFail(Long recordId, String errorMsg, String pushResult); +} + diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushRecordUserService.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushRecordUserService.java new file mode 100644 index 00000000..2169292f --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/push/ThirdPushRecordUserService.java @@ -0,0 +1,43 @@ +package com.bsd.pjcloud.thirdparty.service.push; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushRecordUserEntity; + +import java.util.List; + +/** + * 推送通知用户明细记录 + */ +public interface ThirdPushRecordUserService extends IService { + + /** + * 根据部门批量写入用户明细 + * + * @param recordId 推送批次记录id + * @param confId 推送配置id + * @param batchNo 批次号 + * @param deptIds 部门id列表 + * @param bizType 业务类型 + * @param bizId 业务id + * @param status 初始状态 + */ + Boolean saveUsers(Long recordId, Long confId, String batchNo, List deptIds, String bizType, String bizId, String status); + + /** + * 根据用户ID批量写入用户明细 + * + * @param recordId 推送批次记录id + * @param confId 推送配置id + * @param batchNo 批次号 + * @param userIds 用户id列表 + * @param bizType 业务类型 + * @param bizId 业务id + * @param status 初始状态 + */ + Boolean saveUsersByUserIds(Long recordId, Long confId, String batchNo, List userIds, String bizType, String bizId, String status); + + /** + * 按批次记录更新用户明细状态 + */ + Boolean updateStatusByRecordId(Long recordId, String status, String errorMsg); +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushExecuteServiceImplTest.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushExecuteServiceImplTest.java new file mode 100644 index 00000000..e0c260ca --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushExecuteServiceImplTest.java @@ -0,0 +1,171 @@ +package com.bsd.pjcloud.thirdparty.service.impl.push; + +import com.bsd.pjcloud.admin.api.entity.SysUser; +import com.bsd.pjcloud.admin.api.feign.RemoteUserService; +import com.bsd.pjcloud.common.core.constant.enums.YesNoEnum; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.thirdparty.constant.push.ThirdPushBizTypeEnum; +import com.bsd.pjcloud.thirdparty.dto.jg.JgPusDto; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushConfEntity; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushRecordEntity; +import com.bsd.pjcloud.thirdparty.service.ThirdJgPushService; +import com.bsd.pjcloud.thirdparty.service.push.ThirdPushConfService; +import com.bsd.pjcloud.thirdparty.service.push.ThirdPushRecordService; +import com.bsd.pjcloud.thirdparty.service.push.ThirdPushRecordUserService; +import com.bsd.pjcloud.wallet.api.feign.RemoteMchService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ThirdPushExecuteServiceImplTest { + + @Mock + private ThirdPushConfService thirdPushConfService; + + @Mock + private ThirdPushRecordService thirdPushRecordService; + + @Mock + private ThirdPushRecordUserService thirdPushRecordUserService; + + @Mock + private ThirdJgPushService thirdJgPushService; + + @Mock + private RemoteUserService remoteUserService; + + @Mock + private RemoteMchService remoteMchService; + + @Test + void executeByConfIdShouldPushByUserIdsAndKeepDeptLogicAsFallbackSource() { + ThirdPushExecuteServiceImpl thirdPushExecuteService = newTestService(); + List pushDtos = executeAndCapturePushDtos(thirdPushExecuteService, 2, 0); + + Assertions.assertEquals(1, pushDtos.size()); + Assertions.assertEquals(List.of(1L, 2L), pushDtos.get(0).getUserIds()); + Assertions.assertTrue(pushDtos.get(0).getTagList() == null || pushDtos.get(0).getTagList().isEmpty()); + verify(thirdPushRecordUserService).saveUsersByUserIds(anyLong(), anyLong(), anyString(), anyList(), anyString(), anyString(), anyString()); + verify(thirdPushRecordUserService, never()).saveUsers(anyLong(), anyLong(), anyString(), anyList(), anyString(), anyString(), anyString()); + } + + @Test + void executeByConfIdShouldSplitHundredUsersIntoOneGroup() { + ThirdPushExecuteServiceImpl thirdPushExecuteService = newTestService(); + List pushDtos = executeAndCapturePushDtos(thirdPushExecuteService, 100, 0); + + Assertions.assertEquals(1, pushDtos.size()); + Assertions.assertEquals(100, pushDtos.get(0).getUserIds().size()); + } + + @Test + void executeByConfIdShouldSplitThousandUsersIntoTwoGroups() { + ThirdPushExecuteServiceImpl thirdPushExecuteService = newTestService(); + List pushDtos = executeAndCapturePushDtos(thirdPushExecuteService, 1000, 0); + + Assertions.assertEquals(2, pushDtos.size()); + Assertions.assertTrue(pushDtos.stream().allMatch(item -> item.getUserIds().size() <= 500)); + Assertions.assertEquals(1000, pushDtos.stream().mapToInt(item -> item.getUserIds().size()).sum()); + } + + @Test + void executeByConfIdShouldSplitTenThousandUsersIntoTwentyGroups() { + ThirdPushExecuteServiceImpl thirdPushExecuteService = newTestService(); + List pushDtos = executeAndCapturePushDtos(thirdPushExecuteService, 10000, 0); + + Assertions.assertEquals(20, pushDtos.size()); + Assertions.assertTrue(pushDtos.stream().allMatch(item -> item.getUserIds().size() <= 500)); + Assertions.assertEquals(10000, pushDtos.stream().mapToInt(item -> item.getUserIds().size()).sum()); + } + + @Test + void executeByConfIdShouldRetryFailedGroupAndThenSucceed() { + ThirdPushExecuteServiceImpl thirdPushExecuteService = newTestService(); + List pushDtos = executeAndCapturePushDtos(thirdPushExecuteService, 1000, 2); + + Assertions.assertEquals(2, pushDtos.size()); + } + + private ThirdPushExecuteServiceImpl newTestService() { + return new ThirdPushExecuteServiceImpl( + thirdPushConfService, + thirdPushRecordService, + thirdPushRecordUserService, + thirdJgPushService, + remoteUserService, + remoteMchService + ) { + @Override + protected void sleepBeforeRetry() { + // 测试场景跳过真实等待,避免单元测试耗时过长 + } + }; + } + + private List executeAndCapturePushDtos(ThirdPushExecuteServiceImpl thirdPushExecuteService, int userCount, int failTimes) { + ThirdPushConfEntity conf = new ThirdPushConfEntity(); + conf.setId(10L); + conf.setTitle("测试标题"); + conf.setContent("测试内容"); + conf.setBizType(ThirdPushBizTypeEnum.MCH_PUSH.getType()); + conf.setBizId("66"); + conf.setEnable(YesNoEnum.YES.getCode()); + + ThirdPushRecordEntity record = new ThirdPushRecordEntity(); + record.setId(99L); + + when(thirdPushConfService.getById(10L)).thenReturn(conf); + when(remoteMchService.getSelfCanteenUserDeptIdList(66L)).thenReturn(R.ok(List.of(10001L, 10002L))); + when(remoteUserService.getUserIdListByDeptIdList(List.of(10001L, 10002L))).thenReturn(R.ok(buildUsers(userCount))); + when(thirdPushRecordService.createBatchRecord(any(), anyString(), anyInt())).thenReturn(record); + when(thirdPushRecordUserService.saveUsersByUserIds(anyLong(), anyLong(), anyString(), anyList(), anyString(), anyString(), anyString())) + .thenReturn(true); + when(thirdPushRecordService.markBatchSuccess(99L, userCount, 0, "推送成功")).thenReturn(true); + when(thirdPushRecordUserService.updateStatusByRecordId(99L, "SUCCESS", null)).thenReturn(true); + + List pushDtos = new ArrayList<>(); + AtomicInteger counter = new AtomicInteger(); + doAnswer(invocation -> { + JgPusDto dto = invocation.getArgument(0); + int current = counter.getAndIncrement(); + if (current < failTimes) { + throw new RuntimeException("模拟推送失败"); + } + pushDtos.add(dto); + return null; + }).when(thirdJgPushService).push(any(JgPusDto.class)); + + String result = thirdPushExecuteService.executeByConfId("10"); + + Assertions.assertEquals("0", result); + return pushDtos; + } + + private List buildUsers(int count) { + List users = new ArrayList<>(); + for (int i = 1; i <= count; i++) { + SysUser user = new SysUser(); + user.setUserId((long) i); + users.add(user); + } + return users; + } +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushRecordUserServiceImplTest.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushRecordUserServiceImplTest.java new file mode 100644 index 00000000..370b10cf --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/push/ThirdPushRecordUserServiceImplTest.java @@ -0,0 +1,43 @@ +package com.bsd.pjcloud.thirdparty.service.impl.push; + +import com.bsd.pjcloud.admin.api.feign.RemoteUserService; +import com.bsd.pjcloud.thirdparty.entity.ThirdPushRecordUserEntity; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +class ThirdPushRecordUserServiceImplTest { + + @Test + void saveUsersByUserIdsShouldSaveDistinctUsers() { + TestableThirdPushRecordUserServiceImpl service = new TestableThirdPushRecordUserServiceImpl(); + + boolean result = service.saveUsersByUserIds(1L, 2L, "batch-1", + Arrays.asList(1001L, 1002L, 1001L, null), "MCH_PUSH", "88", "INIT"); + + Assertions.assertTrue(result); + Assertions.assertEquals(2, service.savedEntities.size()); + Assertions.assertEquals(List.of(1001L, 1002L), service.savedEntities.stream().map(ThirdPushRecordUserEntity::getUserId).toList()); + Assertions.assertTrue(service.savedEntities.stream().allMatch(item -> "INIT".equals(item.getStatus()))); + } + + private static class TestableThirdPushRecordUserServiceImpl extends ThirdPushRecordUserServiceImpl { + + private final List savedEntities = new ArrayList<>(); + + private TestableThirdPushRecordUserServiceImpl() { + super(null); + } + + @Override + public boolean saveBatch(Collection entityList) { + this.savedEntities.clear(); + this.savedEntities.addAll(entityList); + return true; + } + } +} diff --git a/pjcloud-visual/pjcloud-daemon-quartz-api/pom.xml b/pjcloud-visual/pjcloud-daemon-quartz-api/pom.xml new file mode 100644 index 00000000..cd6d6c38 --- /dev/null +++ b/pjcloud-visual/pjcloud-daemon-quartz-api/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + com.bsd + pjcloud-visual + 5.7.0 + + + pjcloud-daemon-quartz-api + + + + com.bsd + pjcloud-common-core + + + com.bsd + pjcloud-common-feign + + + org.projectlombok + lombok + provided + + + diff --git a/pjcloud-visual/pjcloud-daemon-quartz-api/src/main/java/com/bsd/pjcloud/daemon/quartz/api/dto/SysJobDTO.java b/pjcloud-visual/pjcloud-daemon-quartz-api/src/main/java/com/bsd/pjcloud/daemon/quartz/api/dto/SysJobDTO.java new file mode 100644 index 00000000..4bf9b233 --- /dev/null +++ b/pjcloud-visual/pjcloud-daemon-quartz-api/src/main/java/com/bsd/pjcloud/daemon/quartz/api/dto/SysJobDTO.java @@ -0,0 +1,96 @@ +package com.bsd.pjcloud.daemon.quartz.api.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 定时任务远程传输对象 + */ +@Data +public class SysJobDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 任务主键ID + */ + private Long jobId; + + /** + * 任务名称 + */ + private String jobName; + + /** + * 任务分组 + */ + private String jobGroup; + + /** + * 任务排序值 + */ + private String jobOrder; + + /** + * 任务类型 + */ + private String jobType; + + /** + * REST任务执行地址 + */ + private String executePath; + + /** + * Java类任务的类名 + */ + private String className; + + /** + * Java类任务的执行方法名 + */ + private String methodName; + + /** + * Java类任务的方法参数值 + */ + private String methodParamsValue; + + /** + * Cron表达式 + */ + private String cronExpression; + + /** + * 误触发策略 + */ + private String misfirePolicy; + + /** + * 租户任务类型 + */ + private String jobTenantType; + + /** + * 任务状态 + */ + private String jobStatus; + + /** + * 最近一次执行状态 + */ + private String jobExecuteStatus; + + /** + * 所属租户ID + */ + private Long tenantId; + + /** + * 任务备注 + */ + private String remark; +} diff --git a/pjcloud-visual/pjcloud-daemon-quartz-api/src/main/java/com/bsd/pjcloud/daemon/quartz/api/feign/RemoteSysJobService.java b/pjcloud-visual/pjcloud-daemon-quartz-api/src/main/java/com/bsd/pjcloud/daemon/quartz/api/feign/RemoteSysJobService.java new file mode 100644 index 00000000..749839d7 --- /dev/null +++ b/pjcloud-visual/pjcloud-daemon-quartz-api/src/main/java/com/bsd/pjcloud/daemon/quartz/api/feign/RemoteSysJobService.java @@ -0,0 +1,56 @@ +package com.bsd.pjcloud.daemon.quartz.api.feign; + +import com.bsd.pjcloud.common.core.constant.ServiceNameConstants; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.common.feign.annotation.NoToken; +import com.bsd.pjcloud.daemon.quartz.api.dto.SysJobDTO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * Quartz 任务远程服务 + */ +@FeignClient(contextId = "remoteSysJobService", value = ServiceNameConstants.DAEMON_QUARTZ_SERVICE) +public interface RemoteSysJobService { + + /** + * 根据任务名称和任务组查询任务 + */ + @NoToken + @GetMapping("/sys-job/inner/by-name") + R getByJobName(@RequestParam("jobName") String jobName, @RequestParam("jobGroup") String jobGroup); + + /** + * 校验 cron 表达式 + */ + @NoToken + @GetMapping("/sys-job/inner/validate-cron") + R validateCron(@RequestParam("cron") String cron); + + /** + * 新增定时任务 + */ + @NoToken + @PostMapping("/sys-job/inner") + R saveInner(@RequestBody SysJobDTO sysJob); + + /** + * 修改定时任务 + */ + @NoToken + @PutMapping("/sys-job/inner") + R updateInner(@RequestBody SysJobDTO sysJob); + + /** + * 删除定时任务 + */ + @NoToken + @DeleteMapping("/sys-job/inner/{id}") + R removeInnerById(@PathVariable("id") Long id); +} diff --git a/pjcloud-visual/pjcloud-daemon-quartz-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports b/pjcloud-visual/pjcloud-daemon-quartz-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports new file mode 100644 index 00000000..a102dc13 --- /dev/null +++ b/pjcloud-visual/pjcloud-daemon-quartz-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports @@ -0,0 +1 @@ +com.bsd.pjcloud.daemon.quartz.api.feign.RemoteSysJobService diff --git a/pjcloud-visual/pjcloud-daemon-quartz/pom.xml b/pjcloud-visual/pjcloud-daemon-quartz/pom.xml index 518865d5..d7f33fbf 100644 --- a/pjcloud-visual/pjcloud-daemon-quartz/pom.xml +++ b/pjcloud-visual/pjcloud-daemon-quartz/pom.xml @@ -15,6 +15,11 @@ 基于quartz后台跑批定时任务模块 + + com.bsd + pjcloud-daemon-quartz-api + 5.7.0 + com.alibaba.cloud diff --git a/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/config/PjcloudInitQuartzJob.java b/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/config/PjcloudInitQuartzJob.java index b7abf37c..68883f6d 100644 --- a/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/config/PjcloudInitQuartzJob.java +++ b/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/config/PjcloudInitQuartzJob.java @@ -46,17 +46,24 @@ public class PjcloudInitQuartzJob implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { sysJobService.list().forEach(sysjob -> { - if (PjcloudQuartzEnum.JOB_STATUS_RELEASE.getType().equals(sysjob.getJobStatus())) { - taskUtil.removeJob(sysjob, scheduler); - } - else if (PjcloudQuartzEnum.JOB_STATUS_RUNNING.getType().equals(sysjob.getJobStatus())) { - taskUtil.resumeJob(sysjob, scheduler); - } - else if (PjcloudQuartzEnum.JOB_STATUS_NOT_RUNNING.getType().equals(sysjob.getJobStatus())) { - taskUtil.pauseJob(sysjob, scheduler); - } - else { - taskUtil.removeJob(sysjob, scheduler); + try { + if (PjcloudQuartzEnum.JOB_STATUS_RELEASE.getType().equals(sysjob.getJobStatus())) { + taskUtil.removeJob(sysjob, scheduler); + } + else if (PjcloudQuartzEnum.JOB_STATUS_RUNNING.getType().equals(sysjob.getJobStatus())) { + taskUtil.addOrUpateJob(sysjob, scheduler); + } + else if (PjcloudQuartzEnum.JOB_STATUS_NOT_RUNNING.getType().equals(sysjob.getJobStatus())) { + taskUtil.addOrUpateJob(sysjob, scheduler); + } + else { + taskUtil.removeJob(sysjob, scheduler); + } + } catch (Exception e) { + log.error("初始化定时任务失败 jobId={}, jobName={}, jobGroup={}, jobStatus={}, cron={}, jobType={}, className={}, methodName={}, methodParams={}", + sysjob.getJobId(), sysjob.getJobName(), sysjob.getJobGroup(), sysjob.getJobStatus(), + sysjob.getCronExpression(), sysjob.getJobType(), sysjob.getClassName(), sysjob.getMethodName(), + sysjob.getMethodParamsValue(), e); } }); } diff --git a/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/controller/SysJobController.java b/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/controller/SysJobController.java index 9ece9560..9004dead 100644 --- a/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/controller/SysJobController.java +++ b/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/controller/SysJobController.java @@ -76,7 +76,7 @@ public class SysJobController { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() .like(StrUtil.isNotBlank(sysJob.getJobName()), SysJob::getJobName, sysJob.getJobName()) .like(StrUtil.isNotBlank(sysJob.getJobGroup()), SysJob::getJobGroup, sysJob.getJobGroup()) - .eq(StrUtil.isNotBlank(sysJob.getJobStatus()), SysJob::getJobStatus, sysJob.getJobGroup()) + .eq(StrUtil.isNotBlank(sysJob.getJobStatus()), SysJob::getJobStatus, sysJob.getJobStatus()) .eq(StrUtil.isNotBlank(sysJob.getJobExecuteStatus()), SysJob::getJobExecuteStatus, sysJob.getJobExecuteStatus()); return R.ok(sysJobService.page(page, wrapper)); @@ -103,10 +103,19 @@ public class SysJobController { @HasPermission("job_sys_job_add") @Operation(description = "新增定时任务") public R save(@RequestBody SysJob sysJob) { + if (!taskUtil.isValidCron(sysJob.getCronExpression())) { + return R.failed("cron表达式不合法"); + } + if (sysJobService.count(Wrappers.query(SysJob.builder() + .jobName(sysJob.getJobName()) + .jobGroup(sysJob.getJobGroup()) + .build())) > 0) { + return R.failed("任务重复,请检查此组内是否已包含同名任务"); + } + sysJob.setJobStatus(PjcloudQuartzEnum.JOB_STATUS_RUNNING.getType()); + sysJob.setCreateBy(SecurityUtils.getUser().getUsername()); // 初始化任务 taskUtil.addOrUpateJob(sysJob, scheduler); - sysJob.setJobStatus(PjcloudQuartzEnum.JOB_STATUS_RELEASE.getType()); - sysJob.setCreateBy(SecurityUtils.getUser().getUsername()); return R.ok(sysJobService.save(sysJob)); } @@ -120,15 +129,31 @@ public class SysJobController { @HasPermission("job_sys_job_edit") @Operation(description = "修改定时任务") public R updateById(@RequestBody SysJob sysJob) { - sysJob.setUpdateBy(SecurityUtils.getUser().getUsername()); SysJob querySysJob = this.sysJobService.getById(sysJob.getJobId()); - if (PjcloudQuartzEnum.JOB_STATUS_NOT_RUNNING.getType().equals(querySysJob.getJobStatus())) { + if (querySysJob == null) { + return R.failed("任务不存在"); + } + if (!taskUtil.isValidCron(sysJob.getCronExpression())) { + return R.failed("cron表达式不合法"); + } + + sysJob.setUpdateBy(SecurityUtils.getUser().getUsername()); + sysJob.setJobStatus(querySysJob.getJobStatus()); + sysJob.setJobExecuteStatus(querySysJob.getJobExecuteStatus()); + + boolean jobKeyChanged = !StrUtil.equals(querySysJob.getJobName(), sysJob.getJobName()) + || !StrUtil.equals(querySysJob.getJobGroup(), sysJob.getJobGroup()); + if (jobKeyChanged) { + this.taskUtil.removeJob(querySysJob, scheduler); + } + + if (PjcloudQuartzEnum.JOB_STATUS_RUNNING.getType().equals(sysJob.getJobStatus()) + || PjcloudQuartzEnum.JOB_STATUS_NOT_RUNNING.getType().equals(sysJob.getJobStatus())) { this.taskUtil.addOrUpateJob(sysJob, scheduler); - sysJobService.updateById(sysJob); - } - else if (PjcloudQuartzEnum.JOB_STATUS_RELEASE.getType().equals(querySysJob.getJobStatus())) { - sysJobService.updateById(sysJob); + } else { + this.taskUtil.removeJob(sysJob, scheduler); } + sysJobService.updateById(sysJob); return R.ok(); } @@ -143,13 +168,11 @@ public class SysJobController { @Operation(description = "唯一标识查询定时任务,暂停任务才能删除") public R removeById(@PathVariable Long id) { SysJob querySysJob = this.sysJobService.getById(id); - if (PjcloudQuartzEnum.JOB_STATUS_NOT_RUNNING.getType().equals(querySysJob.getJobStatus())) { - this.taskUtil.removeJob(querySysJob, scheduler); - this.sysJobService.removeById(id); - } - else if (PjcloudQuartzEnum.JOB_STATUS_RELEASE.getType().equals(querySysJob.getJobStatus())) { - this.sysJobService.removeById(id); + if (querySysJob == null) { + return R.failed("任务不存在"); } + this.taskUtil.removeJob(querySysJob, scheduler); + this.sysJobService.removeById(id); return R.ok(); } @@ -231,12 +254,11 @@ public class SysJobController { @Operation(description = "启动定时任务") public R startJob(@PathVariable("id") Long jobId) { SysJob querySysJob = this.sysJobService.getById(jobId); - if (querySysJob != null && PjcloudQuartzEnum.JOB_LOG_STATUS_FAIL.getType().equals(querySysJob.getJobStatus())) { - taskUtil.addOrUpateJob(querySysJob, scheduler); - } - else { - taskUtil.resumeJob(querySysJob, scheduler); + if (querySysJob == null) { + return R.failed("任务不存在"); } + querySysJob.setJobStatus(PjcloudQuartzEnum.JOB_STATUS_RUNNING.getType()); + taskUtil.addOrUpateJob(querySysJob, scheduler); // 更新定时任务状态条件,暂停状态3更新为运行状态2 this.sysJobService .updateById(SysJob.builder().jobId(jobId).jobStatus(PjcloudQuartzEnum.JOB_STATUS_RUNNING.getType()).build()); @@ -267,12 +289,16 @@ public class SysJobController { @Operation(description = "暂停定时任务") public R shutdownJob(@PathVariable("id") Long id) { SysJob querySysJob = this.sysJobService.getById(id); + if (querySysJob == null) { + return R.failed("任务不存在"); + } // 更新定时任务状态条件,运行状态2更新为暂停状态3 this.sysJobService.updateById(SysJob.builder() .jobId(querySysJob.getJobId()) .jobStatus(PjcloudQuartzEnum.JOB_STATUS_NOT_RUNNING.getType()) .build()); - taskUtil.pauseJob(querySysJob, scheduler); + querySysJob.setJobStatus(PjcloudQuartzEnum.JOB_STATUS_NOT_RUNNING.getType()); + taskUtil.addOrUpateJob(querySysJob, scheduler); return R.ok(); } diff --git a/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/controller/SysJobInnerController.java b/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/controller/SysJobInnerController.java new file mode 100644 index 00000000..e062d482 --- /dev/null +++ b/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/controller/SysJobInnerController.java @@ -0,0 +1,143 @@ +package com.bsd.pjcloud.daemon.quartz.controller; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.common.security.annotation.Inner; +import com.bsd.pjcloud.daemon.quartz.api.dto.SysJobDTO; +import com.bsd.pjcloud.daemon.quartz.constants.PjcloudQuartzEnum; +import com.bsd.pjcloud.daemon.quartz.entity.SysJob; +import com.bsd.pjcloud.daemon.quartz.service.SysJobService; +import com.bsd.pjcloud.daemon.quartz.util.TaskUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.quartz.Scheduler; + +/** + * Quartz 内部远程接口 + */ +@Inner +@RestController +@RequiredArgsConstructor +@RequestMapping("/sys-job/inner") +public class SysJobInnerController { + + private final SysJobService sysJobService; + + private final TaskUtil taskUtil; + + private final Scheduler scheduler; + + /** + * 根据任务名称和分组查询任务 + */ + @GetMapping("/by-name") + public R getByJobName(@RequestParam("jobName") String jobName, @RequestParam("jobGroup") String jobGroup) { + SysJob sysJob = sysJobService.getOne(Wrappers.lambdaQuery() + .eq(SysJob::getJobName, jobName) + .eq(SysJob::getJobGroup, jobGroup), false); + if (sysJob == null) { + return R.ok(); + } + return R.ok(toDto(sysJob)); + } + + /** + * 校验 cron 表达式 + */ + @GetMapping("/validate-cron") + public R validateCron(@RequestParam("cron") String cron) { + return taskUtil.isValidCron(cron) ? R.ok(Boolean.TRUE) : R.failed(Boolean.FALSE, "cron表达式不合法"); + } + + /** + * 新增定时任务 + */ + @PostMapping + public R save(@RequestBody SysJobDTO sysJobDto) { + SysJob sysJob = toEntity(sysJobDto); + if (!taskUtil.isValidCron(sysJob.getCronExpression())) { + return R.failed("cron表达式不合法"); + } + if (sysJobService.count(Wrappers.query(SysJob.builder() + .jobName(sysJob.getJobName()) + .jobGroup(sysJob.getJobGroup()) + .build())) > 0) { + return R.failed("任务重复,请检查此组内是否已包含同名任务"); + } + if (StrUtil.isBlank(sysJob.getJobStatus())) { + sysJob.setJobStatus(PjcloudQuartzEnum.JOB_STATUS_RUNNING.getType()); + } + taskUtil.addOrUpateJob(sysJob, scheduler); + return R.ok(sysJobService.save(sysJob)); + } + + /** + * 修改定时任务 + */ + @PutMapping + public R update(@RequestBody SysJobDTO sysJobDto) { + if (sysJobDto.getJobId() == null) { + return R.failed("任务ID不能为空"); + } + SysJob querySysJob = sysJobService.getById(sysJobDto.getJobId()); + if (querySysJob == null) { + return R.failed("任务不存在"); + } + SysJob sysJob = toEntity(sysJobDto); + if (!taskUtil.isValidCron(sysJob.getCronExpression())) { + return R.failed("cron表达式不合法"); + } + + sysJob.setJobStatus(querySysJob.getJobStatus()); + sysJob.setJobExecuteStatus(querySysJob.getJobExecuteStatus()); + + boolean jobKeyChanged = !StrUtil.equals(querySysJob.getJobName(), sysJob.getJobName()) + || !StrUtil.equals(querySysJob.getJobGroup(), sysJob.getJobGroup()); + if (jobKeyChanged) { + taskUtil.removeJob(querySysJob, scheduler); + } + + if (PjcloudQuartzEnum.JOB_STATUS_RUNNING.getType().equals(sysJob.getJobStatus()) + || PjcloudQuartzEnum.JOB_STATUS_NOT_RUNNING.getType().equals(sysJob.getJobStatus())) { + taskUtil.addOrUpateJob(sysJob, scheduler); + } else { + taskUtil.removeJob(sysJob, scheduler); + } + return R.ok(sysJobService.updateById(sysJob)); + } + + /** + * 删除定时任务 + */ + @DeleteMapping("/{id}") + public R removeById(@PathVariable("id") Long id) { + SysJob querySysJob = sysJobService.getById(id); + if (querySysJob == null) { + return R.ok(Boolean.TRUE); + } + taskUtil.removeJob(querySysJob, scheduler); + return R.ok(sysJobService.removeById(id)); + } + + private SysJobDTO toDto(SysJob sysJob) { + SysJobDTO dto = new SysJobDTO(); + BeanUtils.copyProperties(sysJob, dto); + return dto; + } + + private SysJob toEntity(SysJobDTO dto) { + SysJob sysJob = new SysJob(); + BeanUtils.copyProperties(dto, sysJob); + return sysJob; + } +} diff --git a/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/JavaClassTaskInvok.java b/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/JavaClassTaskInvok.java index 7486a5c7..6716ea0e 100644 --- a/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/JavaClassTaskInvok.java +++ b/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/JavaClassTaskInvok.java @@ -55,8 +55,9 @@ public class JavaClassTaskInvok implements ITaskInvok { method = clazz.getDeclaredMethod(sysJob.getMethodName()); returnValue = method.invoke(obj); } - if (StrUtil.isEmpty(returnValue.toString()) - || PjcloudQuartzEnum.JOB_LOG_STATUS_FAIL.getType().equals(returnValue.toString())) { + if (returnValue != null + && (StrUtil.isEmpty(returnValue.toString()) + || PjcloudQuartzEnum.JOB_LOG_STATUS_FAIL.getType().equals(returnValue.toString()))) { log.error("定时任务javaClassTaskInvok异常,执行任务:{}", sysJob.getClassName()); throw new TaskException("定时任务javaClassTaskInvok业务执行失败,任务:" + sysJob.getClassName()); } diff --git a/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/SpringBeanTaskInvok.java b/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/SpringBeanTaskInvok.java index 243bf0e6..02d9afbc 100644 --- a/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/SpringBeanTaskInvok.java +++ b/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/SpringBeanTaskInvok.java @@ -56,8 +56,9 @@ public class SpringBeanTaskInvok implements ITaskInvok { ReflectionUtils.makeAccessible(method); returnValue = method.invoke(target); } - if (StrUtil.isEmpty(returnValue.toString()) - || PjcloudQuartzEnum.JOB_LOG_STATUS_FAIL.getType().equals(returnValue.toString())) { + if (returnValue != null + && (StrUtil.isEmpty(returnValue.toString()) + || PjcloudQuartzEnum.JOB_LOG_STATUS_FAIL.getType().equals(returnValue.toString()))) { log.error("定时任务springBeanTaskInvok异常,执行任务:{}", sysJob.getClassName()); throw new TaskException("定时任务springBeanTaskInvok业务执行失败,任务:" + sysJob.getClassName()); } diff --git a/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/TaskInvokUtil.java b/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/TaskInvokUtil.java index a9164554..e5362664 100644 --- a/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/TaskInvokUtil.java +++ b/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/TaskInvokUtil.java @@ -99,12 +99,17 @@ public class TaskInvokUtil { finally { // 记录执行时间 立刻执行使用的是simpleTeigger if (trigger instanceof CronTrigger) { - updateSysjob - .setStartTime(trigger.getStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()); - updateSysjob.setPreviousTime( - trigger.getPreviousFireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()); - updateSysjob.setNextTime( - trigger.getNextFireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()); + if (trigger.getStartTime() != null) { + updateSysjob.setStartTime(trigger.getStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()); + } + if (trigger.getPreviousFireTime() != null) { + updateSysjob.setPreviousTime( + trigger.getPreviousFireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()); + } + if (trigger.getNextFireTime() != null) { + updateSysjob.setNextTime( + trigger.getNextFireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()); + } } // 记录执行时长 endTime = System.currentTimeMillis(); diff --git a/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/TaskUtil.java b/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/TaskUtil.java index ccb58990..2e5f4bb1 100644 --- a/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/TaskUtil.java +++ b/pjcloud-visual/pjcloud-daemon-quartz/src/main/java/com/bsd/pjcloud/daemon/quartz/util/TaskUtil.java @@ -59,10 +59,27 @@ public class TaskUtil { public void addOrUpateJob(SysJob sysjob, Scheduler scheduler) { CronTrigger trigger = null; try { + if (!isValidCron(sysjob.getCronExpression())) { + throw new IllegalArgumentException("cron表达式不合法"); + } JobKey jobKey = getJobKey(sysjob); // 获得触发器 TriggerKey triggerKey = getTriggerKey(sysjob); + boolean jobExists = scheduler.checkExists(jobKey); trigger = (CronTrigger) scheduler.getTrigger(triggerKey); + // JDBC 持久化场景下,可能出现只残留 Job 或 Trigger 的脏数据,这里先做自愈。 + if (jobExists && trigger == null) { + log.warn("检测到 Quartz 残留 Job,准备重建任务 jobId={}, jobName={}, jobGroup={}", + sysjob.getJobId(), sysjob.getJobName(), sysjob.getJobGroup()); + scheduler.deleteJob(jobKey); + jobExists = false; + } + if (!jobExists && trigger != null) { + log.warn("检测到 Quartz 残留 Trigger,准备重建任务 jobId={}, jobName={}, jobGroup={}", + sysjob.getJobId(), sysjob.getJobName(), sysjob.getJobGroup()); + scheduler.unscheduleJob(triggerKey); + trigger = null; + } // 判断触发器是否存在(如果存在说明之前运行过但是在当前被禁用了,如果不存在说明一次都没运行过) if (trigger == null) { // 新建一个工作任务 指定任务类型为串接进行的 @@ -99,7 +116,10 @@ public class TaskUtil { } } catch (SchedulerException e) { - log.error("添加或更新定时任务,失败信息:{}", e.getMessage()); + log.error("添加或更新定时任务失败 jobId={}, jobName={}, jobGroup={}, cron={}, jobType={}, className={}, methodName={}, methodParams={}", + sysjob.getJobId(), sysjob.getJobName(), sysjob.getJobGroup(), sysjob.getCronExpression(), + sysjob.getJobType(), sysjob.getClassName(), sysjob.getMethodName(), sysjob.getMethodParamsValue(), e); + throw new IllegalStateException("添加或更新定时任务失败", e); } } @@ -135,6 +155,7 @@ public class TaskUtil { } catch (SchedulerException e) { log.error("暂停任务失败,失败信息:{}", e.getMessage()); + throw new IllegalStateException("暂停任务失败", e); } } @@ -152,6 +173,7 @@ public class TaskUtil { } catch (SchedulerException e) { log.error("恢复任务失败,失败信息:{}", e.getMessage()); + throw new IllegalStateException("恢复任务失败", e); } } @@ -174,6 +196,7 @@ public class TaskUtil { } catch (Exception e) { log.error("移除定时任务失败,失败信息:{}", e.getMessage()); + throw new IllegalStateException("移除定时任务失败", e); } } diff --git a/pjcloud-visual/pom.xml b/pjcloud-visual/pom.xml index 1d82835c..ff116b5c 100644 --- a/pjcloud-visual/pom.xml +++ b/pjcloud-visual/pom.xml @@ -35,6 +35,7 @@ pjcloud-report-platform pjcloud-codegen pjcloud-daemon-elastic-job + pjcloud-daemon-quartz-api pjcloud-daemon-quartz pjcloud-jimu-platform pjcloud-pay-platform diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteMchService.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteMchService.java index 158ef653..a31f5697 100644 --- a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteMchService.java +++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteMchService.java @@ -63,4 +63,8 @@ public interface RemoteMchService { @NoToken @GetMapping("/mch-manage/get-views-mch-by-dept-id") R getViewsMchByDeptId(@RequestParam Long deptId, @RequestParam List mchTypeList); + + @NoToken + @GetMapping("/mch-manage/get-self-canteen-user-dept-id-list") + R> getSelfCanteenUserDeptIdList(@RequestParam Long mchId); } diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/mch/MchManageController.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/mch/MchManageController.java index bdd0d0a1..481b2c56 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/mch/MchManageController.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/mch/MchManageController.java @@ -163,12 +163,22 @@ public class MchManageController { if (ObjectUtil.isEmpty(mchManage)) return R.failed("商户不存在"); return R.ok(mchManage); } + + @Inner + @GetMapping("/get-self-canteen-user-dept-id-list") + public R> getSelfCanteenUserDeptIdList(@RequestParam Long mchId) { + List userDeptIdList = mchManageService.getSelfCanteenUserDeptIdList(mchId); + if (ObjectUtil.isEmpty(userDeptIdList)) return R.failed("商户不存在"); + return R.ok(userDeptIdList); + } + /* ************************************* end 远程调用 end ************************************* */ /* ************************************* start app start ************************************* */ @Operation(summary = "查询用户的直属商户", description = "查询用户的直属商户") @ApiCryptoAes @GetMapping("/app/getZsMch") + public R appZsMch() { PjcloudUser user = SecurityUtils.getUser(); UserInfo userInfo = remoteUserService.getByUserId(user.getId()).getData(); @@ -256,16 +266,13 @@ public class MchManageController { @GetMapping("/select") public R getSelect(@ParameterObject Page page, @ParameterObject MchManageDto dto) { DataScope dataScope = null; - if (ObjectUtil.isNotEmpty(dto.getParentDeptId())) { - List deptIdList = remoteDeptService.getDescendantList(dto.getParentDeptId()).getData(); - if (ObjectUtil.isNotEmpty(deptIdList)) { - List deptIds = dto.getDeptIds(); - if (ObjectUtil.isEmpty(deptIds)) { - deptIds = new ArrayList<>(); - } - deptIds.addAll(deptIdList.stream().map(SysDept::getDeptId).toList()); - dto.setDeptIds(deptIds); - } + PjcloudUser user = SecurityUtils.getUser(); + List selfMchIdList = mchManageService.getSelfCanteenMchIdList(user.getId(), List.of(MerchantTypeEnum.CANTEEN)); + if (ObjectUtil.isNotEmpty(selfMchIdList)) { + List ids = dto.getIds(); + if (ObjectUtil.isEmpty(ids)) ids = new ArrayList<>(); + ids.addAll(selfMchIdList); + dto.setIds(ids); } return R.ok(mchManageService.scopePageList(page, dto, dataScope)); } diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/mch/view/MchDeptViewMapper.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/mch/view/MchDeptViewMapper.java index a6a6ad4c..32edd585 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/mch/view/MchDeptViewMapper.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/mch/view/MchDeptViewMapper.java @@ -17,4 +17,6 @@ public interface MchDeptViewMapper { WalletMchDeptViewVo selectByDeptId(@Param("deptId") Long deptId, @Param("mchTypeList") List mchTypeList); List selectMchIdList(@Param("dto") WalletMchDeptViewDto dto); + + List selectDeptIdList(@Param("dto") WalletMchDeptViewDto dto); } diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/MchManageService.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/MchManageService.java index 3abafb02..5827ab37 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/MchManageService.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/MchManageService.java @@ -65,7 +65,12 @@ public interface MchManageService extends IService { /** * 获取用户的食堂商户列表 */ - List getSelfCanteenMchIdList(Long userId,List mchTypeList); + List getSelfCanteenMchIdList(Long userId, List mchTypeList); + + /** + * 获当前食堂商户列表对应的用户部门列表 + */ + List getSelfCanteenUserDeptIdList(Long mchId); /** * 判断用户是否能查看到所有食堂 diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/mch/MchManageServiceImpl.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/mch/MchManageServiceImpl.java index ec4c1438..1d8fe158 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/mch/MchManageServiceImpl.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/mch/MchManageServiceImpl.java @@ -496,6 +496,34 @@ public class MchManageServiceImpl extends ServiceImpl getSelfCanteenUserDeptIdList(Long mchId) { + List mchIds = new ArrayList<>(); + List deptIdList = new ArrayList<>(); + MchManageEntity mchDetail = baseMapper.selectById(mchId); + if (ObjectUtil.isEmpty(mchDetail)) { + return mchIds; + } + deptIdList.add(mchDetail.getDeptId()); + // 查询下级部门 + List descendantList = remoteDeptService.getInnerDescendantList(mchDetail.getDeptId()).getData(); + if (ObjectUtil.isNotEmpty(descendantList)) { + List descendantIds = descendantList.stream().map(SysDept::getDeptId).toList(); + if (ObjectUtil.isNotEmpty(descendantIds)) deptIdList.addAll(descendantIds); + } + WalletMchDeptViewDto dto = new WalletMchDeptViewDto(); + dto.setDeptIdList(deptIdList); + dto.setMchTypeList(List.of(mchDetail.getTypes())); + List selfMchIdList = mchDeptViewMapper.selectDeptIdList(dto); + if (ObjectUtil.isEmpty(selfMchIdList)) { + return mchIds; + } + return selfMchIdList; + } + /** * 判断用户是否能查看到所有食堂 */ diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/resources/mapper/mch/view/MchDeptViewMapper.xml b/pjcloud-wallet/pjcloud-wallet-biz/src/main/resources/mapper/mch/view/MchDeptViewMapper.xml index 39f73f49..60251173 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/resources/mapper/mch/view/MchDeptViewMapper.xml +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/resources/mapper/mch/view/MchDeptViewMapper.xml @@ -91,4 +91,14 @@ group by a.mchId order by a.mchId + +