TRX 1 vuosi sitten
vanhempi
commit
76c3a14568
17 muutettua tiedostoa jossa 681 lisäystä ja 3 poistoa
  1. 18 0
      FullCardClient/src/main/java/com/zhongshu/card/client/type/ScheduleType.java
  2. 2 1
      FullCardServer/src/main/java/com/zhongshu/card/server/core/dao/projectAbout/ProjectMainPaySettingDao.java
  3. 19 0
      FullCardServer/src/main/java/com/zhongshu/card/server/core/dao/projectAbout/extend/ProjectMainPaySettingDaoExtend.java
  4. 72 0
      FullCardServer/src/main/java/com/zhongshu/card/server/core/dao/projectAbout/impl/ProjectMainPaySettingDaoImpl.java
  5. 21 0
      FullCardServer/src/main/java/com/zhongshu/card/server/core/dao/schedule/ScheduleLogDao.java
  6. 21 0
      FullCardServer/src/main/java/com/zhongshu/card/server/core/dao/schedule/ScheduleTaskConfigDao.java
  7. 14 0
      FullCardServer/src/main/java/com/zhongshu/card/server/core/dao/schedule/extend/ScheduleTaskConfigDaoExtend.java
  8. 62 0
      FullCardServer/src/main/java/com/zhongshu/card/server/core/dao/schedule/impl/ScheduleTaskConfigDaoImpl.java
  9. 6 0
      FullCardServer/src/main/java/com/zhongshu/card/server/core/domain/paySetting/ProjectMainPaySetting.java
  10. 39 0
      FullCardServer/src/main/java/com/zhongshu/card/server/core/domain/schedule/ScheduleLog.java
  11. 45 0
      FullCardServer/src/main/java/com/zhongshu/card/server/core/domain/schedule/ScheduleTaskConfig.java
  12. 37 0
      FullCardServer/src/main/java/com/zhongshu/card/server/core/init/ScheduleTaskiInit.java
  13. 6 0
      FullCardServer/src/main/java/com/zhongshu/card/server/core/service/paySetting/ProjectMainPaySettingService.java
  14. 2 2
      FullCardServer/src/main/java/com/zhongshu/card/server/core/service/paySetting/ProjectPaySettingServiceImpl.java
  15. 134 0
      FullCardServer/src/main/java/com/zhongshu/card/server/core/service/schedule/ScheduleTaskConfigService.java
  16. 163 0
      FullCardServer/src/main/java/com/zhongshu/card/server/core/service/schedule/ScheduledTask.java
  17. 20 0
      FullCardServer/src/main/java/com/zhongshu/card/server/core/service/schedule/TaskContextService.java

+ 18 - 0
FullCardClient/src/main/java/com/zhongshu/card/client/type/ScheduleType.java

@@ -0,0 +1,18 @@
+package com.zhongshu.card.client.type;
+
+import lombok.Getter;
+
+/**
+ * 定时任务类型
+ */
+public enum ScheduleType {
+    SettlementTask("项目结算任务"),
+    ;
+
+    @Getter
+    private String remark;
+
+    ScheduleType(String remark) {
+        this.remark = remark;
+    }
+}

+ 2 - 1
FullCardServer/src/main/java/com/zhongshu/card/server/core/dao/projectAbout/ProjectMainPaySettingDao.java

@@ -1,9 +1,10 @@
 package com.zhongshu.card.server.core.dao.projectAbout;
 
 import com.github.microservice.components.data.mongo.mongo.dao.MongoDao;
+import com.zhongshu.card.server.core.dao.projectAbout.extend.ProjectMainPaySettingDaoExtend;
 import com.zhongshu.card.server.core.domain.paySetting.ProjectMainPaySetting;
 
-public interface ProjectMainPaySettingDao extends MongoDao<ProjectMainPaySetting> {
+public interface ProjectMainPaySettingDao extends MongoDao<ProjectMainPaySetting>, ProjectMainPaySettingDaoExtend {
 
     ProjectMainPaySetting findTopById(String id);
 

+ 19 - 0
FullCardServer/src/main/java/com/zhongshu/card/server/core/dao/projectAbout/extend/ProjectMainPaySettingDaoExtend.java

@@ -0,0 +1,19 @@
+package com.zhongshu.card.server.core.dao.projectAbout.extend;
+
+import com.zhongshu.card.client.model.projectAbout.PayChannelConfigSearch;
+import com.zhongshu.card.server.core.domain.paySetting.PayChannelConfig;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+/**
+ * @Author TRX
+ * @CreateDate: 2023/7/7
+ * @Version: 1.0
+ */
+public interface ProjectMainPaySettingDaoExtend {
+
+    String acquire(String key, Long expiration);
+
+    boolean release(String key);
+
+}

+ 72 - 0
FullCardServer/src/main/java/com/zhongshu/card/server/core/dao/projectAbout/impl/ProjectMainPaySettingDaoImpl.java

@@ -0,0 +1,72 @@
+package com.zhongshu.card.server.core.dao.projectAbout.impl;
+
+import com.github.microservice.components.data.mongo.mongo.helper.DBHelper;
+import com.github.microservice.core.util.token.TokenUtil;
+import com.zhongshu.card.client.model.projectAbout.PayChannelConfigSearch;
+import com.zhongshu.card.server.core.dao.BaseImpl;
+import com.zhongshu.card.server.core.dao.projectAbout.extend.PayChannelConfigDaoExtend;
+import com.zhongshu.card.server.core.dao.projectAbout.extend.ProjectMainPaySettingDaoExtend;
+import com.zhongshu.card.server.core.domain.paySetting.PayChannelConfig;
+import com.zhongshu.card.server.core.domain.paySetting.ProjectMainPaySetting;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.mongodb.core.FindAndModifyOptions;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.Update;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * @Author TRX
+ * @CreateDate: 2023/4/12
+ * @Version: 1.0
+ */
+public class ProjectMainPaySettingDaoImpl extends BaseImpl implements ProjectMainPaySettingDaoExtend {
+
+    @Autowired
+    private DBHelper dbHelper;
+
+    @Autowired
+    private MongoTemplate mongoTemplate;
+
+    public String acquire(String key, Long expiration) {
+        Query query = Query.query(Criteria.where("_id").is(key).and("token").isNull());
+        String token = TokenUtil.create();
+        Update update = new Update().set("expireAt", System.currentTimeMillis() + expiration).set("token", token);
+
+        FindAndModifyOptions options = new FindAndModifyOptions().upsert(false).returnNew(true);
+
+        ProjectMainPaySetting doc = mongoTemplate.findAndModify(query, update, options, ProjectMainPaySetting.class);
+        if (doc == null) {
+            ProjectMainPaySetting lockObj = mongoTemplate.findOne(Query.query(Criteria.where("_id").is(key)), ProjectMainPaySetting.class);
+            doc = lockObj;
+        }
+        boolean locked = doc != null && doc.getToken() != null && doc.getToken().equals(token);
+
+        // 如果已过期
+        if (!locked && doc != null && doc.getExpireAt() < System.currentTimeMillis()) {
+            release(key);
+            // 成功释放锁, 再次尝试获取锁
+            return this.acquire(key, expiration);
+        }
+        return locked ? token : null;
+    }
+
+    public boolean release(String key) {
+        Query releaseQuery = Query.query(Criteria.where("_id").is(key));
+        Update releaseUpdate = new Update().set("expireAt", null).set("token", null);
+        FindAndModifyOptions releaseOptions = new FindAndModifyOptions().upsert(true).returnNew(true);
+        ProjectMainPaySetting flowDisposition = mongoTemplate.findAndModify(releaseQuery, releaseUpdate, releaseOptions, ProjectMainPaySetting.class);
+        return StringUtils.isEmpty(flowDisposition.getToken());
+    }
+
+}

+ 21 - 0
FullCardServer/src/main/java/com/zhongshu/card/server/core/dao/schedule/ScheduleLogDao.java

@@ -0,0 +1,21 @@
+package com.zhongshu.card.server.core.dao.schedule;
+
+import com.github.microservice.components.data.mongo.mongo.dao.MongoDao;
+import com.zhongshu.card.server.core.domain.schedule.ScheduleLog;
+import com.zhongshu.card.server.core.domain.schedule.ScheduleTaskConfig;
+
+import java.util.List;
+
+/**
+ * 定时任务执行日志
+ *
+ * @author TRX
+ * @date 2024/3/21
+ */
+public interface ScheduleLogDao extends MongoDao<ScheduleLog> {
+
+    ScheduleLog findTopById(String id);
+
+    List<ScheduleLog> findByScheduleTaskConfigAndCreateTimeBetweenAndStatus(ScheduleTaskConfig scheduleTaskConfig, Long begin, Long end, Integer status);
+
+}

+ 21 - 0
FullCardServer/src/main/java/com/zhongshu/card/server/core/dao/schedule/ScheduleTaskConfigDao.java

@@ -0,0 +1,21 @@
+package com.zhongshu.card.server.core.dao.schedule;
+
+import com.github.microservice.components.data.mongo.mongo.dao.MongoDao;
+import com.zhongshu.card.server.core.dao.devices.extend.DeviceBindDaoExtend;
+import com.zhongshu.card.server.core.dao.schedule.extend.ScheduleTaskConfigDaoExtend;
+import com.zhongshu.card.server.core.domain.devices.DeviceBind;
+import com.zhongshu.card.server.core.domain.schedule.ScheduleTaskConfig;
+
+/**
+ * 定时任务
+ *
+ * @author TRX
+ * @date 2024/3/21
+ */
+public interface ScheduleTaskConfigDao extends MongoDao<ScheduleTaskConfig>, ScheduleTaskConfigDaoExtend {
+
+    ScheduleTaskConfig findTopById(String id);
+
+    ScheduleTaskConfig findTopByAboutDataId(String aboutDataId);
+
+}

+ 14 - 0
FullCardServer/src/main/java/com/zhongshu/card/server/core/dao/schedule/extend/ScheduleTaskConfigDaoExtend.java

@@ -0,0 +1,14 @@
+package com.zhongshu.card.server.core.dao.schedule.extend;
+
+/**
+ * @Author TRX
+ * @CreateDate: 2023/7/7
+ * @Version: 1.0
+ */
+public interface ScheduleTaskConfigDaoExtend {
+
+    String acquire(String key, Long expiration);
+
+    boolean release(String key);
+
+}

+ 62 - 0
FullCardServer/src/main/java/com/zhongshu/card/server/core/dao/schedule/impl/ScheduleTaskConfigDaoImpl.java

@@ -0,0 +1,62 @@
+package com.zhongshu.card.server.core.dao.schedule.impl;
+
+import com.github.microservice.components.data.mongo.mongo.helper.DBHelper;
+import com.github.microservice.core.util.token.TokenUtil;
+import com.zhongshu.card.server.core.dao.BaseImpl;
+import com.zhongshu.card.server.core.dao.projectAbout.extend.ProjectMainPaySettingDaoExtend;
+import com.zhongshu.card.server.core.dao.schedule.extend.ScheduleTaskConfigDaoExtend;
+import com.zhongshu.card.server.core.domain.paySetting.ProjectMainPaySetting;
+import com.zhongshu.card.server.core.domain.schedule.ScheduleTaskConfig;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.FindAndModifyOptions;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.Update;
+
+/**
+ * @Author TRX
+ * @CreateDate: 2023/4/12
+ * @Version: 1.0
+ */
+public class ScheduleTaskConfigDaoImpl extends BaseImpl implements ScheduleTaskConfigDaoExtend {
+
+    @Autowired
+    private DBHelper dbHelper;
+
+    @Autowired
+    private MongoTemplate mongoTemplate;
+
+    public String acquire(String key, Long expiration) {
+        Query query = Query.query(Criteria.where("_id").is(key).and("token").isNull());
+        String token = TokenUtil.create();
+        Update update = new Update().set("expireAt", System.currentTimeMillis() + expiration).set("token", token);
+
+        FindAndModifyOptions options = new FindAndModifyOptions().upsert(false).returnNew(true);
+
+        ScheduleTaskConfig doc = mongoTemplate.findAndModify(query, update, options, ScheduleTaskConfig.class);
+        if (doc == null) {
+            ScheduleTaskConfig lockObj = mongoTemplate.findOne(Query.query(Criteria.where("_id").is(key)), ScheduleTaskConfig.class);
+            doc = lockObj;
+        }
+        boolean locked = doc != null && doc.getToken() != null && doc.getToken().equals(token);
+
+        // 如果已过期
+        if (!locked && doc != null && doc.getExpireAt() < System.currentTimeMillis()) {
+            release(key);
+            // 成功释放锁, 再次尝试获取锁
+            return this.acquire(key, expiration);
+        }
+        return locked ? token : null;
+    }
+
+    public boolean release(String key) {
+        Query releaseQuery = Query.query(Criteria.where("_id").is(key));
+        Update releaseUpdate = new Update().set("expireAt", null).set("token", null);
+        FindAndModifyOptions releaseOptions = new FindAndModifyOptions().upsert(true).returnNew(true);
+        ScheduleTaskConfig flowDisposition = mongoTemplate.findAndModify(releaseQuery, releaseUpdate, releaseOptions, ScheduleTaskConfig.class);
+        return StringUtils.isEmpty(flowDisposition.getToken());
+    }
+
+}

+ 6 - 0
FullCardServer/src/main/java/com/zhongshu/card/server/core/domain/paySetting/ProjectMainPaySetting.java

@@ -77,4 +77,10 @@ public class ProjectMainPaySetting extends SuperMain {
     @Schema(description = "全局机构分账比例,0-100")
     private BigDecimal orgScale = BigDecimal.ZERO;
 
+    @Schema(description = "锁过期时间")
+    private long expireAt;
+
+    @Schema(description = "锁token")
+    private String token;
+
 }

+ 39 - 0
FullCardServer/src/main/java/com/zhongshu/card/server/core/domain/schedule/ScheduleLog.java

@@ -0,0 +1,39 @@
+package com.zhongshu.card.server.core.domain.schedule;
+
+import com.zhongshu.card.server.core.domain.base.SuperMain;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+/**
+ * @author TRX
+ * @date 2024/11/6
+ */
+@Data
+@Document
+@AllArgsConstructor
+@NoArgsConstructor
+public class ScheduleLog extends SuperMain {
+
+    public static final Integer STATUS_SUCESS = 1;
+    public static final Integer STATUS_FAILED = 0;
+    public static final Integer STATUS_PASS = 2;
+
+    @Schema(description = "关联的定时任务信息")
+    private ScheduleTaskConfig scheduleTaskConfig;
+
+    private Long executeTime;
+
+    private String executeTimeStr;
+
+    private Long costTime;
+
+    private String result;
+
+    private String exception;
+
+    private Integer status;
+
+}

+ 45 - 0
FullCardServer/src/main/java/com/zhongshu/card/server/core/domain/schedule/ScheduleTaskConfig.java

@@ -0,0 +1,45 @@
+package com.zhongshu.card.server.core.domain.schedule;
+
+import com.zhongshu.card.client.type.DataState;
+import com.zhongshu.card.client.type.ScheduleType;
+import com.zhongshu.card.server.core.domain.base.SuperMain;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+/**
+ * 定时任务记录
+ *
+ * @author TRX
+ * @date 2024/11/4
+ */
+@Data
+@Document
+@AllArgsConstructor
+@NoArgsConstructor
+public class ScheduleTaskConfig extends SuperMain {
+
+    @Schema(description = "关联的数据ID")
+    private String aboutDataId;
+
+    @Schema(description = "定时任务类型")
+    private ScheduleType scheduleType;
+
+    @Schema(description = "数据状态")
+    private DataState dataState = DataState.Enable;
+
+    @Schema(description = "定时任务的表达式")
+    private String expression;
+
+    @Schema(description = "执行状态 1.正在执行 0.等待执行")
+    private Integer executeStatus = 0;
+
+    @Schema(description = "中心token")
+    private String token;
+
+    @Schema(description = "过期时间")
+    private Long expireAt;
+
+}

+ 37 - 0
FullCardServer/src/main/java/com/zhongshu/card/server/core/init/ScheduleTaskiInit.java

@@ -0,0 +1,37 @@
+package com.zhongshu.card.server.core.init;
+
+import com.zhongshu.card.server.core.service.schedule.ScheduleTaskConfigService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 定时任务初始化
+ *
+ * @author TRX
+ * @date 2024/6/3
+ */
+@Component
+@Slf4j
+public class ScheduleTaskiInit implements CommandLineRunner {
+
+    @Autowired
+    private ScheduleTaskConfigService scheduleTaskConfigService;
+
+    @Override
+    public void run(String... args) throws Exception {
+        CompletableFuture.runAsync(() -> {
+            try {
+                log.info("加载定时任务");
+                TimeUnit.SECONDS.sleep(10);
+                scheduleTaskConfigService.initAllTaskLockSchedule();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        });
+    }
+}

+ 6 - 0
FullCardServer/src/main/java/com/zhongshu/card/server/core/service/paySetting/ProjectMainPaySettingService.java

@@ -11,6 +11,7 @@ import com.zhongshu.card.server.core.domain.paySetting.PayShareList;
 import com.zhongshu.card.server.core.domain.paySetting.ProjectMainPaySetting;
 import com.zhongshu.card.server.core.domain.paySetting.ProjectOrgPaySettingInfo;
 import com.zhongshu.card.server.core.service.base.SuperService;
+import com.zhongshu.card.server.core.service.schedule.ScheduleTaskConfigService;
 import com.zhongshu.card.server.core.util.BeanUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.ObjectUtils;
@@ -44,6 +45,9 @@ public class ProjectMainPaySettingService extends SuperService {
     @Autowired
     private OrganizationDao organizationDao;
 
+    @Autowired
+    private ScheduleTaskConfigService scheduleTaskConfigService;
+
     /**
      * 保存项目配置
      *
@@ -72,6 +76,8 @@ public class ProjectMainPaySettingService extends SuperService {
         mainPaySetting.setPaymentChannelType(orgPaySettingInfo.getPaymentChannelType());
 
         projectMainPaySettingDao.save(mainPaySetting);
+        // 加入定时任务
+        scheduleTaskConfigService.initProjectSettlementRulesTask(mainPaySetting);
         return ResultContent.buildSuccess();
     }
 

+ 2 - 2
FullCardServer/src/main/java/com/zhongshu/card/server/core/service/paySetting/ProjectPaySettingServiceImpl.java

@@ -360,9 +360,9 @@ public class ProjectPaySettingServiceImpl extends SuperService {
      */
     public List<PayConfigField> getWxConfig() {
         List<PayConfigField> fields = new ArrayList<>(4);
-        fields.add(PayConfigField.builder().name("appid").key("appId").type(PayFieldType.Str).isMust(Boolean.TRUE).maxLength(100).build());
+        fields.add(PayConfigField.builder().name("appID").key("appId").type(PayFieldType.Str).isMust(Boolean.TRUE).maxLength(100).build());
         fields.add(PayConfigField.builder().name("商户证书序列号").key("mchSerialNo").type(PayFieldType.Str).isMust(Boolean.TRUE).maxLength(100).build());
-        fields.add(PayConfigField.builder().name("商户id").key("mchId").type(PayFieldType.Str).isMust(Boolean.TRUE).maxLength(100).build());
+        fields.add(PayConfigField.builder().name("商户ID").key("mchId").type(PayFieldType.Str).isMust(Boolean.TRUE).maxLength(100).build());
         fields.add(PayConfigField.builder().name("V3密钥").key("apiV3Key").type(PayFieldType.Str).isMust(Boolean.TRUE).maxLength(100).build());
         fields.add(PayConfigField.builder().name("证书文件").key("privateKeyFile").type(PayFieldType.File).isMust(Boolean.FALSE).build());
         fields.add(PayConfigField.builder().name("支付成功回调地址").key("notifyUrl").type(PayFieldType.Str).isMust(Boolean.TRUE).maxLength(500).build());

+ 134 - 0
FullCardServer/src/main/java/com/zhongshu/card/server/core/service/schedule/ScheduleTaskConfigService.java

@@ -0,0 +1,134 @@
+package com.zhongshu.card.server.core.service.schedule;
+
+import com.github.microservice.net.ResultContent;
+import com.zhongshu.card.client.type.DataState;
+import com.zhongshu.card.client.type.ScheduleType;
+import com.zhongshu.card.client.type.paySetting.RegularType;
+import com.zhongshu.card.client.type.paySetting.SettlementRulesType;
+import com.zhongshu.card.server.core.dao.projectAbout.ProjectMainPaySettingDao;
+import com.zhongshu.card.server.core.dao.schedule.ScheduleTaskConfigDao;
+import com.zhongshu.card.server.core.domain.paySetting.ProjectMainPaySetting;
+import com.zhongshu.card.server.core.domain.schedule.ScheduleTaskConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 定时任务服务
+ *
+ * @author TRX
+ * @date 2024/11/6
+ */
+@Slf4j
+@Service
+public class ScheduleTaskConfigService {
+
+    @Autowired
+    private ScheduleTaskConfigDao scheduleTaskConfigDao;
+
+    @Autowired
+    private ProjectMainPaySettingDao projectMainPaySettingDao;
+
+    @Autowired
+    private ScheduledTask scheduledTask;
+
+    // 每天好多点、好多分
+    private static final String CRON_TEMPLATE = "0 %s %s * * ?";
+
+    /**
+     * 加载系统的定时任务
+     */
+    public void initAllTaskLockSchedule() {
+        this.loadAllSchedule();
+    }
+
+    //----------------------------定时任务数据组装 start-----------------------
+
+    /**
+     * 添加项目的结算定时任务
+     *
+     * @param entity
+     * @return
+     */
+    public ResultContent initProjectSettlementRulesTask(ProjectMainPaySetting entity) {
+        if (ObjectUtils.isNotEmpty(entity)) {
+            try {
+                String lockKey = projectMainPaySettingDao.acquire(entity.getId(), 5000L);
+                if (StringUtils.isNotEmpty(lockKey)) {
+                    String aboutDataId = entity.getProjectOid();
+                    ScheduleTaskConfig taskConfig = scheduleTaskConfigDao.findTopByAboutDataId(aboutDataId);
+                    if (ObjectUtils.isNotEmpty(taskConfig)) {
+                        taskConfig = new ScheduleTaskConfig();
+                    }
+                    taskConfig.setAboutDataId(aboutDataId);
+                    taskConfig.setDataState(DataState.Enable);
+                    taskConfig.setScheduleType(ScheduleType.SettlementTask);
+                    String expression = "";
+                    String timeStr = entity.getTimeStr();
+                    String[] timeParts = timeStr.split(":");
+                    String hour = timeParts[0];
+                    String minute = timeParts[1];
+
+                    SettlementRulesType rulesType = entity.getSettlementRulesType();
+                    if (rulesType == SettlementRulesType.Dn) {
+                        // D+N  每天执行
+                        expression = String.format(CRON_TEMPLATE, minute, hour);
+                    } else if (rulesType == SettlementRulesType.Regular) {
+                        // 定期
+                        RegularType regularType = entity.getRegularType();
+                        if (regularType == RegularType.Day) {
+                            // 每天
+                            expression = String.format(CRON_TEMPLATE, minute, hour);
+                        } else if (regularType == RegularType.Week) {
+                            // 每周 星期一
+                            expression = String.format("0 %s %s ? * MON", minute, hour);
+                        } else if (regularType == RegularType.Month) {
+                            // 每月 1号
+                            expression = String.format("0 %s %s 1 * ?", minute, hour);
+                        }
+                    }
+                    taskConfig.setExpression(expression);
+                    taskConfig.setRemark(String.format("项目结算定时任务:%s", entity.getProjectOid()));
+                    scheduleTaskConfigDao.save(taskConfig);
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            } finally {
+                projectMainPaySettingDao.release(entity.getId());
+            }
+        }
+        this.loadAllSchedule();
+        return ResultContent.buildSuccess();
+    }
+
+    private void loadAllSchedule() {
+        try {
+            log.info("================系统启动加载定时任务====开始===========");
+            List<ScheduleTaskConfig> list = scheduleTaskConfigDao.findAll();
+            log.info("================系统启动加载定时任务size: {}", list.size());
+            scheduledTask.refreshTask(list);
+            log.info("================系统启动加载定时任务====结束===========");
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.info("================系统启动加载定时任务====异常=========== {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 根据关联的业务数据ID 删除定时任务
+     *
+     * @param aboutDataId
+     * @return
+     */
+    public ResultContent deleteTask(String aboutDataId) {
+        if (StringUtils.isNotEmpty(aboutDataId)) {
+            ScheduleTaskConfig entity = scheduleTaskConfigDao.findTopByAboutDataId(aboutDataId);
+            scheduleTaskConfigDao.delete(entity);
+        }
+        return ResultContent.buildSuccess();
+    }
+}

+ 163 - 0
FullCardServer/src/main/java/com/zhongshu/card/server/core/service/schedule/ScheduledTask.java

@@ -0,0 +1,163 @@
+package com.zhongshu.card.server.core.service.schedule;
+
+import com.zhongshu.card.client.utils.DateUtils;
+import com.zhongshu.card.server.core.dao.schedule.ScheduleLogDao;
+import com.zhongshu.card.server.core.dao.schedule.ScheduleTaskConfigDao;
+import com.zhongshu.card.server.core.domain.schedule.ScheduleLog;
+import com.zhongshu.card.server.core.domain.schedule.ScheduleTaskConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.SchedulingConfigurer;
+import org.springframework.scheduling.config.CronTask;
+import org.springframework.scheduling.config.ScheduledTaskRegistrar;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StopWatch;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.PreDestroy;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledFuture;
+
+/**
+ * @author TRX
+ * @date 2024/11/6
+ */
+@Slf4j
+@Component
+public class ScheduledTask implements SchedulingConfigurer {
+
+    // 动态定时任务
+    private volatile ScheduledTaskRegistrar registrar;
+
+    private final ConcurrentHashMap<String, ScheduledFuture<?>> scheduledFutures = new ConcurrentHashMap<>();
+
+    private final ConcurrentHashMap<String, CronTask> cronTasks = new ConcurrentHashMap<>();
+
+    @Autowired
+    private ScheduleTaskConfigDao scheduleTaskConfigDao;
+
+    @Autowired
+    private ScheduleLogDao scheduleLogDao;
+
+    @Autowired
+    private TaskContextService taskContextService;
+
+    @Override
+    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
+        registrar.setScheduler(Executors.newScheduledThreadPool(16));
+        this.registrar = registrar;
+    }
+
+    @PreDestroy
+    public void destroy() {
+        this.registrar.destroy();
+    }
+
+    public void refreshTask(List<ScheduleTaskConfig> tasks) {
+
+        // 删除已经取消任务
+        scheduledFutures.keySet().forEach(key -> {
+            //如果为空,取消所有
+            if (Objects.isNull(tasks) || tasks.size() == 0) {
+                scheduledFutures.get(key).cancel(false);
+                scheduledFutures.remove(key);
+                cronTasks.remove(key);
+                return;
+            }
+            tasks.forEach(task -> {
+                if (!Objects.equals(key, task.getAboutDataId())) {
+                    if (scheduledFutures.containsKey(key)) {
+                        scheduledFutures.get(key).cancel(false);
+                        scheduledFutures.remove(key);
+                        cronTasks.remove(key);
+                    }
+                }
+            });
+        });
+
+        // 添加新任务、更改执行规则任务
+        tasks.forEach(item -> {
+            String expression = item.getExpression();
+            // 任务表达式为空则跳过
+            if (StringUtils.isEmpty(expression)) {
+                return;
+            }
+
+            // 任务已存在并且表达式未发生变化则跳过
+            if (scheduledFutures.containsKey(item.getAboutDataId()) && cronTasks.get(item.getAboutDataId()).getExpression().equals(expression)) {
+                return;
+            }
+
+            // 任务执行时间发生了变化,则删除该任务
+            if (scheduledFutures.containsKey(item.getAboutDataId())) {
+                scheduledFutures.get(item.getAboutDataId()).cancel(false);
+                scheduledFutures.remove(item.getAboutDataId());
+                cronTasks.remove(item.getAboutDataId());
+            }
+
+            CronTask task = new CronTask(new Runnable() {
+                @Override
+                public void run() {
+                    StopWatch stopWatch = new StopWatch("flow-unlock-task-watch-" + Thread.currentThread().getName());
+                    ScheduleLog scheduleLog = new ScheduleLog();
+                    scheduleLog.setExecuteTime(System.currentTimeMillis());
+                    scheduleLog.setExecuteTimeStr(DateUtils.paresTime(System.currentTimeMillis(), DateUtils.FORMAT_LONG));
+                    // 执行业务逻辑
+                    try {
+                        log.info("====执行单个任务,配置ID【{}】,任务ID【{}】执行规则【{}】=======",
+                                item.getId(), item.getAboutDataId(), item.getExpression());
+                        scheduleLog.setScheduleTaskConfig(item);
+
+                        String token = scheduleTaskConfigDao.acquire(item.getId(), 10000L);
+                        boolean newLock = org.apache.commons.lang3.StringUtils.isNotEmpty(token);
+                        if (newLock) {
+                            log.info("获取到执行锁,开始执行");
+
+                            Long begin = DateUtils.getCurrentDayStartTime().getTime();
+                            Long end = DateUtils.getCurrentDayEndTime().getTime();
+                            List<ScheduleLog> existsLogs = scheduleLogDao.findByScheduleTaskConfigAndCreateTimeBetweenAndStatus(item, begin, end, ScheduleLog.STATUS_SUCESS);
+
+                            if (CollectionUtils.isEmpty(existsLogs)) {
+                                stopWatch.start("item:" + item.getAboutDataId());
+                                taskContextService.execute(item);
+                                scheduleLog.setStatus(ScheduleLog.STATUS_SUCESS);
+                                scheduleLog.setResult("执行成功");
+                            } else {
+                                log.warn("已查询到执行结果,跳过执行,已执行成功记录数:{}", existsLogs.size());
+                                scheduleLog.setStatus(ScheduleLog.STATUS_PASS);
+                                scheduleLog.setResult("已查询到执行结果,跳过执行");
+                            }
+                        } else {
+                            log.warn("未获取到执行锁,跳过执行");
+                            scheduleLog.setStatus(ScheduleLog.STATUS_PASS);
+                            scheduleLog.setResult("未获取到执行锁,跳过执行");
+                        }
+                    } catch (Exception e) {
+                        log.error("执行任务异常,异常信息:", e);
+                        scheduleLog.setStatus(ScheduleLog.STATUS_FAILED);
+                        scheduleLog.setException("执行出错:" + e.getStackTrace().toString());
+                    } finally {
+                        if (stopWatch.isRunning()) {
+                            stopWatch.stop();
+                            scheduleLog.setCostTime(stopWatch.getLastTaskTimeMillis());
+                            log.info("任务执行结束,耗时:{}ms", stopWatch.getLastTaskTimeMillis());
+                        }
+
+                        scheduleLogDao.save(scheduleLog);
+                        scheduleTaskConfigDao.release(item.getId());
+                    }
+                }
+            }, expression);
+
+            ScheduledFuture<?> future = registrar.getScheduler().schedule(task.getRunnable(), task.getTrigger());
+            cronTasks.put(item.getAboutDataId(), task);
+            scheduledFutures.put(item.getAboutDataId(), future);
+        });
+
+    }
+
+}

+ 20 - 0
FullCardServer/src/main/java/com/zhongshu/card/server/core/service/schedule/TaskContextService.java

@@ -0,0 +1,20 @@
+package com.zhongshu.card.server.core.service.schedule;
+
+import com.zhongshu.card.server.core.domain.schedule.ScheduleTaskConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author TRX
+ * @date 2024/11/6
+ */
+@Slf4j
+@Service
+public class TaskContextService {
+
+    public void execute(ScheduleTaskConfig taskConfig) {
+        log.info("定时任务执行: {}", taskConfig.getScheduleType().getRemark());
+
+    }
+
+}