feat:极光推送

This commit is contained in:
dzc committed 2026-07-07 17:05:50 +08:00
1 parent 122160ba8d
commit df1eeef0d0
45 files changed
+2157 -90

No files matched your search

+117 -19
View File
@@ -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 ='推送通知用户明细记录';
@@ -96,4 +96,9 @@ public interface ServiceNameConstants {
* 综合服务
*/
String INTEGRATED_SERVICE = "pjcloud-integrated-biz";
/**
* Quartz 定时任务服务
*/
String DAEMON_QUARTZ_SERVICE = "pjcloud-daemon-quartz";
}
@@ -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;
}
@@ -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;
}
@@ -13,4 +13,6 @@ public class JgPusDto {
private List<Long> userIds;
private List<String> tagList;
}
@@ -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<ThirdPushConfEntity> {
@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)
* <p>
* 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;
}
}
@@ -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<ThirdPushRecordEntity> {
@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;
}
@@ -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<ThirdPushRecordUserEntity> {
@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;
}
@@ -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
+5
View File
@@ -196,6 +196,11 @@
<version>5.7.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.bsd</groupId>
<artifactId>pjcloud-daemon-quartz-api</artifactId>
<version>5.7.0</version>
</dependency>
</dependencies>
<profiles>
<profile>
@@ -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<Boolean> create(@RequestBody ThirdPushConfEntity dto) {
if (ObjectUtil.isEmpty(dto)) {
return R.ok(false);
}
return R.ok(thirdPushConfService.create(dto));
}
/**
* 修改推送配置
*/
@Operation(summary = "修改推送配置")
@PostMapping("/edit")
public R<Boolean> 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<Boolean> 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<ThirdPushConfEntity> detail(@PathVariable Long id) {
return R.ok(thirdPushConfService.getById(id));
}
/**
* 查询推送配置列表
*/
@Operation(summary = "查询推送配置列表")
@PostMapping("/list")
public R<List<ThirdPushConfEntity>> list(@RequestBody(required = false) ThirdPushConfEntity dto) {
ThirdPushConfEntity query = ObjectUtil.defaultIfNull(dto, new ThirdPushConfEntity());
LambdaQueryWrapper<ThirdPushConfEntity> wrapper = Wrappers.<ThirdPushConfEntity>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<String> execute(@PathVariable("id") Long id) {
if (ObjectUtil.isEmpty(id)) {
return R.failed("推送配置ID不能为空");
}
return R.ok(thirdPushExecuteService.executeByConfId(String.valueOf(id)));
}
}
@@ -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<Long> 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) {
@@ -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<ThirdPushConfEntity> {
}
@@ -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<ThirdPushRecordEntity> {
}
@@ -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<ThirdPushRecordUserEntity> {
}
@@ -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);
@@ -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<ThirdPushConfMapper, ThirdPushConfEntity> 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<ThirdPushConfEntity>()
.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<Boolean> 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<Boolean> 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<Boolean> 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<Boolean> 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<SysJobDTO> 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<MchManageVo> 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<Boolean> 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());
}
}
}
@@ -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;
/**
* 推送任务统一执行入口
* <p>
* 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<Long> targetDeptIds = listTargetDeptIds(conf);
if (CollUtil.isEmpty(targetDeptIds)) {
log.info("推送目标为空,跳过执行 confId={}", pushConfId);
return JOB_LOG_STATUS_SUCCESS;
}
List<Long> 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<Long> listTargetDeptIds(ThirdPushConfEntity conf) {
if (ObjectUtil.equals(conf.getBizType(), ThirdPushBizTypeEnum.MCH_PUSH.getType())) {
Long mchId = Long.valueOf(conf.getBizId());
R<List<Long>> 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<Long> listTargetUserIds(List<Long> deptIds) {
if (CollUtil.isEmpty(deptIds)) {
return List.of();
}
R<List<SysUser>> 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<Long> targetUserIds) {
List<List<Long>> 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<Long> 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<Long> 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<List<Long>> splitList(List<Long> sourceList, int batchSize) {
if (CollUtil.isEmpty(sourceList) || batchSize <= 0) {
return List.of();
}
java.util.ArrayList<List<Long>> 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;
}
}
}
@@ -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<ThirdPushRecordMapper, ThirdPushRecordEntity> 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);
}
}
@@ -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<ThirdPushRecordUserMapper, ThirdPushRecordUserEntity> implements ThirdPushRecordUserService {
private final RemoteUserService remoteUserService;
@Override
public Boolean saveUsers(Long recordId, Long confId, String batchNo, List<Long> deptIds, String bizType, String bizId, String status) {
if (recordId == null || confId == null || ObjectUtil.isEmpty(batchNo) || CollUtil.isEmpty(deptIds)) {
return false;
}
List<Long> 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<Long> userIds, String bizType, String bizId, String status) {
if (recordId == null || confId == null || ObjectUtil.isEmpty(batchNo) || CollUtil.isEmpty(userIds)) {
return false;
}
List<ThirdPushRecordUserEntity> 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<ThirdPushRecordUserEntity>()
.eq(ThirdPushRecordUserEntity::getRecordId, recordId)
.set(ThirdPushRecordUserEntity::getStatus, status)
.set(ObjectUtil.isNotEmpty(errorMsg), ThirdPushRecordUserEntity::getErrorMsg, errorMsg)
.set(ObjectUtil.isEmpty(errorMsg), ThirdPushRecordUserEntity::getErrorMsg, null));
}
/**
* 根据部门列表解析目标用户
*/
private List<Long> listUserIdsByDeptIds(List<Long> deptIds) {
List<Long> validDeptIds = deptIds.stream()
.filter(ObjectUtil::isNotEmpty)
.distinct()
.toList();
if (CollUtil.isEmpty(validDeptIds)) {
return List.of();
}
R<List<SysUser>> 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<ThirdPushRecordUserEntity> buildRecordUsers(Long recordId, Long confId, String batchNo, List<Long> 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();
}
}
@@ -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<ThirdPushConfEntity> {
/**
* 创建推送配置
*/
Boolean create(ThirdPushConfEntity entity);
/**
* 修改推送配置
*/
Boolean edit(ThirdPushConfEntity entity);
}
@@ -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);
}
@@ -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<ThirdPushRecordEntity> {
/**
* 创建推送批次记录
*
* @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);
}
@@ -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<ThirdPushRecordUserEntity> {
/**
* 根据部门批量写入用户明细
*
* @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<Long> 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<Long> userIds, String bizType, String bizId, String status);
/**
* 按批次记录更新用户明细状态
*/
Boolean updateStatusByRecordId(Long recordId, String status, String errorMsg);
}
@@ -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<JgPusDto> 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<JgPusDto> 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<JgPusDto> 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<JgPusDto> 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<JgPusDto> 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<JgPusDto> 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<JgPusDto> 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<SysUser> buildUsers(int count) {
List<SysUser> users = new ArrayList<>();
for (int i = 1; i <= count; i++) {
SysUser user = new SysUser();
user.setUserId((long) i);
users.add(user);
}
return users;
}
}
@@ -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<ThirdPushRecordUserEntity> savedEntities = new ArrayList<>();
private TestableThirdPushRecordUserServiceImpl() {
super(null);
}
@Override
public boolean saveBatch(Collection<ThirdPushRecordUserEntity> entityList) {
this.savedEntities.clear();
this.savedEntities.addAll(entityList);
return true;
}
}
}
@@ -0,0 +1,30 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.bsd</groupId>
<artifactId>pjcloud-visual</artifactId>
<version>5.7.0</version>
</parent>
<artifactId>pjcloud-daemon-quartz-api</artifactId>
<dependencies>
<dependency>
<groupId>com.bsd</groupId>
<artifactId>pjcloud-common-core</artifactId>
</dependency>
<dependency>
<groupId>com.bsd</groupId>
<artifactId>pjcloud-common-feign</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -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;
}
@@ -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<SysJobDTO> getByJobName(@RequestParam("jobName") String jobName, @RequestParam("jobGroup") String jobGroup);
/**
* 校验 cron 表达式
*/
@NoToken
@GetMapping("/sys-job/inner/validate-cron")
R<Boolean> validateCron(@RequestParam("cron") String cron);
/**
* 新增定时任务
*/
@NoToken
@PostMapping("/sys-job/inner")
R<Boolean> saveInner(@RequestBody SysJobDTO sysJob);
/**
* 修改定时任务
*/
@NoToken
@PutMapping("/sys-job/inner")
R<Boolean> updateInner(@RequestBody SysJobDTO sysJob);
/**
* 删除定时任务
*/
@NoToken
@DeleteMapping("/sys-job/inner/{id}")
R<Boolean> removeInnerById(@PathVariable("id") Long id);
}
@@ -0,0 +1 @@
com.bsd.pjcloud.daemon.quartz.api.feign.RemoteSysJobService
@@ -15,6 +15,11 @@
<description>基于quartz后台跑批定时任务模块</description>
<dependencies>
<dependency>
<groupId>com.bsd</groupId>
<artifactId>pjcloud-daemon-quartz-api</artifactId>
<version>5.7.0</version>
</dependency>
<!--注册中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
@@ -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);
}
});
}
@@ -76,7 +76,7 @@ public class SysJobController {
LambdaQueryWrapper<SysJob> wrapper = Wrappers.<SysJob>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();
}
@@ -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<SysJobDTO> getByJobName(@RequestParam("jobName") String jobName, @RequestParam("jobGroup") String jobGroup) {
SysJob sysJob = sysJobService.getOne(Wrappers.<SysJob>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<Boolean> validateCron(@RequestParam("cron") String cron) {
return taskUtil.isValidCron(cron) ? R.ok(Boolean.TRUE) : R.failed(Boolean.FALSE, "cron表达式不合法");
}
/**
* 新增定时任务
*/
@PostMapping
public R<Boolean> 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<Boolean> 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<Boolean> 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;
}
}
@@ -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());
}
@@ -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());
}
@@ -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();
@@ -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);
}
}
+1
View File
@@ -35,6 +35,7 @@
<module>pjcloud-report-platform</module>
<module>pjcloud-codegen</module>
<module>pjcloud-daemon-elastic-job</module>
<module>pjcloud-daemon-quartz-api</module>
<module>pjcloud-daemon-quartz</module>
<module>pjcloud-jimu-platform</module>
<module>pjcloud-pay-platform</module>
@@ -63,4 +63,8 @@ public interface RemoteMchService {
@NoToken
@GetMapping("/mch-manage/get-views-mch-by-dept-id")
R<MchManageVo> getViewsMchByDeptId(@RequestParam Long deptId, @RequestParam List<MerchantTypeEnum> mchTypeList);
@NoToken
@GetMapping("/mch-manage/get-self-canteen-user-dept-id-list")
R<List<Long>> getSelfCanteenUserDeptIdList(@RequestParam Long mchId);
}
@@ -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<List<Long>> getSelfCanteenUserDeptIdList(@RequestParam Long mchId) {
List<Long> 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<MchManageVo> 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<SysDept> deptIdList = remoteDeptService.getDescendantList(dto.getParentDeptId()).getData();
if (ObjectUtil.isNotEmpty(deptIdList)) {
List<Long> 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<Long> selfMchIdList = mchManageService.getSelfCanteenMchIdList(user.getId(), List.of(MerchantTypeEnum.CANTEEN));
if (ObjectUtil.isNotEmpty(selfMchIdList)) {
List<Long> ids = dto.getIds();
if (ObjectUtil.isEmpty(ids)) ids = new ArrayList<>();
ids.addAll(selfMchIdList);
dto.setIds(ids);
}
return R.ok(mchManageService.scopePageList(page, dto, dataScope));
}
@@ -17,4 +17,6 @@ public interface MchDeptViewMapper {
WalletMchDeptViewVo selectByDeptId(@Param("deptId") Long deptId, @Param("mchTypeList") List<String> mchTypeList);
List<Long> selectMchIdList(@Param("dto") WalletMchDeptViewDto dto);
List<Long> selectDeptIdList(@Param("dto") WalletMchDeptViewDto dto);
}
@@ -65,7 +65,12 @@ public interface MchManageService extends IService<MchManageEntity> {
/**
* 获取用户的食堂商户列表
*/
List<Long> getSelfCanteenMchIdList(Long userId,List<MerchantTypeEnum> mchTypeList);
List<Long> getSelfCanteenMchIdList(Long userId, List<MerchantTypeEnum> mchTypeList);
/**
* 获当前食堂商户列表对应的用户部门列表
*/
List<Long> getSelfCanteenUserDeptIdList(Long mchId);
/**
* 判断用户是否能查看到所有食堂
@@ -496,6 +496,34 @@ public class MchManageServiceImpl extends ServiceImpl<MchManageMapper, MchManage
return selfMchIdList;
}
/**
* 获取用户食堂商户列表对应的用户部门列表
*/
@Override
public List<Long> getSelfCanteenUserDeptIdList(Long mchId) {
List<Long> mchIds = new ArrayList<>();
List<Long> deptIdList = new ArrayList<>();
MchManageEntity mchDetail = baseMapper.selectById(mchId);
if (ObjectUtil.isEmpty(mchDetail)) {
return mchIds;
}
deptIdList.add(mchDetail.getDeptId());
// 查询下级部门
List<SysDept> descendantList = remoteDeptService.getInnerDescendantList(mchDetail.getDeptId()).getData();
if (ObjectUtil.isNotEmpty(descendantList)) {
List<Long> 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<Long> selfMchIdList = mchDeptViewMapper.selectDeptIdList(dto);
if (ObjectUtil.isEmpty(selfMchIdList)) {
return mchIds;
}
return selfMchIdList;
}
/**
* 判断用户是否能查看到所有食堂
*/
@@ -91,4 +91,14 @@
group by a.mchId
order by a.mchId
</select>
<select id="selectDeptIdList" resultType="java.lang.Long">
select a.deptId
from v_all_mch_dept_tree a
<where>
<include refid="whereA"/>
</where>
group by a.deptId
order by a.deptId
</select>
</mapper>