Selaa lähdekoodia

【第一版开发】
1.定时任务日志记录改为使用aop完成
2.定时任务逻辑优化
3.增加测试数据脚本
4.完善技术文档

ChenYL 10 kuukautta sitten
vanhempi
sitoutus
5e75b507c3
26 muutettua tiedostoa jossa 865 lisäystä ja 189 poistoa
  1. 7 0
      data-easy/src/main/java/com/dataeasy/server/atomic/entity/SubscriptionOrder.java
  2. 16 0
      data-easy/src/main/java/com/dataeasy/server/atomic/service/ISubscriptionOrderService.java
  3. 1 1
      data-easy/src/main/java/com/dataeasy/server/atomic/service/ISubscriptionTaskConfigService.java
  4. 31 4
      data-easy/src/main/java/com/dataeasy/server/atomic/service/impl/SubscriptionOrderServiceImpl.java
  5. 3 3
      data-easy/src/main/java/com/dataeasy/server/atomic/service/impl/SubscriptionTaskConfigServiceImpl.java
  6. 25 0
      data-easy/src/main/java/com/dataeasy/server/common/annotation/ScheduleTask.java
  7. 2 1
      data-easy/src/main/java/com/dataeasy/server/constant/ScheduleTaskEnum.java
  8. 128 0
      data-easy/src/main/java/com/dataeasy/server/core/aop/ScheduleTaskLogAspect.java
  9. 8 6
      data-easy/src/main/java/com/dataeasy/server/feign/ProductHuntFeign.java
  10. 27 0
      data-easy/src/main/java/com/dataeasy/server/pojo/task/ScheduleTaskContext.java
  11. 14 5
      data-easy/src/main/java/com/dataeasy/server/service/controller/TaskOpController.java
  12. 4 0
      data-easy/src/main/java/com/dataeasy/server/service/manager/impl/AiChatManagerImpl.java
  13. 32 79
      data-easy/src/main/java/com/dataeasy/server/task/AbstractDataTask.java
  14. 1 1
      data-easy/src/main/java/com/dataeasy/server/task/AbstractHzApiTask.java
  15. 47 0
      data-easy/src/main/java/com/dataeasy/server/task/AutoCLosePayOrderTask.java
  16. 11 14
      data-easy/src/main/java/com/dataeasy/server/task/DaLeTouTask.java
  17. 12 16
      data-easy/src/main/java/com/dataeasy/server/task/IpoBondTask.java
  18. 11 14
      data-easy/src/main/java/com/dataeasy/server/task/IpoStockTask.java
  19. 44 20
      data-easy/src/main/java/com/dataeasy/server/task/ProductHuntTask.java
  20. 11 14
      data-easy/src/main/java/com/dataeasy/server/task/ShuangSeQiuTask.java
  21. 2 2
      data-easy/src/main/resources/application-dev.yaml
  22. 1 1
      data-easy/src/main/resources/prompts/system-message.st
  23. 73 0
      doc/sql/data.sql
  24. 2 0
      doc/sql/schema.sql
  25. 9 7
      doc/技术文档.md
  26. 343 1
      product-hunt/src/main/java/com/producthunt/server/service/controller/ProductHuntController.java

+ 7 - 0
data-easy/src/main/java/com/dataeasy/server/atomic/entity/SubscriptionOrder.java

@@ -10,6 +10,7 @@ import lombok.EqualsAndHashCode;
 import java.io.Serial;
 import java.io.Serializable;
 import java.math.BigDecimal;
+import java.sql.Timestamp;
 
 /**
  * @author tyuio
@@ -61,6 +62,12 @@ public class SubscriptionOrder extends BaseEntity implements Serializable {
     @Column(name = "subscription_duration")
     private Integer subscriptionDuration;
 
+    /**
+     * 订单关闭时间
+     */
+    @Column(name = "close_time")
+    private Timestamp closeTime;
+
     /**
      * 支付状态(NOTPAY-未支付,SUCCESS-支付成功、REFUND-转入退款、CLOSED-已关闭、REVOKED-已撤销)
      * @see PaymentStatusEnum

+ 16 - 0
data-easy/src/main/java/com/dataeasy/server/atomic/service/ISubscriptionOrderService.java

@@ -3,6 +3,9 @@ package com.dataeasy.server.atomic.service;
 import com.dataeasy.server.atomic.entity.SubscriptionOrder;
 import com.dataeasy.server.atomic.entity.SubscriptionSource;
 
+import java.util.Collection;
+import java.util.List;
+
 /**
  * @author tyuio
  * @version 1.0.0
@@ -29,4 +32,17 @@ public interface ISubscriptionOrderService {
      * @param subscriptionOrder
      */
     void updateById(SubscriptionOrder subscriptionOrder);
+
+    /**
+     * 获取待关闭订单
+     * @return
+     */
+    List<SubscriptionOrder> getPendingCLoseOrders();
+
+    /**
+     * 批量更新
+     * @param ids
+     * @param subscriptionOrder
+     */
+    void batchUpdate(Collection ids, SubscriptionOrder subscriptionOrder);
 }

+ 1 - 1
data-easy/src/main/java/com/dataeasy/server/atomic/service/ISubscriptionTaskConfigService.java

@@ -18,5 +18,5 @@ public interface ISubscriptionTaskConfigService {
      * @param query
      * @return
      */
-    List<SubscriptionTaskConfig> getByCondition(SubscriptionTaskConfigQuery query);
+    SubscriptionTaskConfig getByCondition(SubscriptionTaskConfigQuery query);
 }

+ 31 - 4
data-easy/src/main/java/com/dataeasy/server/atomic/service/impl/SubscriptionOrderServiceImpl.java

@@ -1,14 +1,22 @@
 package com.dataeasy.server.atomic.service.impl;
 
-import com.dataeasy.server.atomic.entity.SubscriptionOrder;
-import com.dataeasy.server.atomic.entity.SubscriptionSource;
-import com.dataeasy.server.common.utils.Assert;
+import java.sql.Timestamp;
+import java.util.Collection;
+import java.util.List;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
 
+import com.dataeasy.server.atomic.entity.SubscriptionOrder;
 import com.dataeasy.server.atomic.mapper.SubscriptionOrderMapper;
 import com.dataeasy.server.atomic.service.ISubscriptionOrderService;
-import org.springframework.util.StringUtils;
+import com.dataeasy.server.common.utils.Assert;
+import com.dataeasy.server.constant.PaymentStatusEnum;
+import com.dataeasy.server.utiis.WeekendUtils;
+
+import tk.mybatis.mapper.weekend.Weekend;
+import tk.mybatis.mapper.weekend.WeekendCriteria;
 
 /**
  * @author tyuio
@@ -44,4 +52,23 @@ public class SubscriptionOrderServiceImpl implements ISubscriptionOrderService {
         Assert.isNull(subscriptionOrder);
         subscriptionOrderMapper.updateByPrimaryKeySelective(subscriptionOrder);
     }
+
+    @Override
+    public List<SubscriptionOrder> getPendingCLoseOrders() {
+        Weekend<SubscriptionOrder> weekend = WeekendUtils.createExcludeAuditFields(SubscriptionOrder.class);
+        WeekendCriteria<SubscriptionOrder, Object> criteria = weekend.weekendCriteria();
+        criteria.andEqualTo(SubscriptionOrder::getPaymentStatus, PaymentStatusEnum.NOTPAY);
+        criteria.andLessThanOrEqualTo(SubscriptionOrder::getCloseTime, new Timestamp(System.currentTimeMillis()));
+        return subscriptionOrderMapper.selectByExample(weekend);
+    }
+
+    @Override
+    public void batchUpdate(Collection ids, SubscriptionOrder subscriptionOrder) {
+        Assert.notEmpty(ids);
+        Assert.isNull(subscriptionOrder);
+        Weekend<SubscriptionOrder> weekend = WeekendUtils.createExcludeAuditFields(SubscriptionOrder.class);
+        WeekendCriteria<SubscriptionOrder, Object> criteria = weekend.weekendCriteria();
+        criteria.andIn(SubscriptionOrder::getId, ids);
+        subscriptionOrderMapper.updateByExampleSelective(subscriptionOrder, weekend);
+    }
 }

+ 3 - 3
data-easy/src/main/java/com/dataeasy/server/atomic/service/impl/SubscriptionTaskConfigServiceImpl.java

@@ -29,9 +29,9 @@ public class SubscriptionTaskConfigServiceImpl implements ISubscriptionTaskConfi
     private SubscriptionTaskConfigMapper taskConfigMapper;
 
     @Override
-    public List<SubscriptionTaskConfig> getByCondition(SubscriptionTaskConfigQuery query) {
+    public SubscriptionTaskConfig getByCondition(SubscriptionTaskConfigQuery query) {
         if (Objects.isNull(query)) {
-            return List.of();
+            return null;
         }
         Weekend<SubscriptionTaskConfig> weekend = WeekendUtils.createExcludeAuditFields(SubscriptionTaskConfig.class);
         WeekendCriteria<SubscriptionTaskConfig, Object> weekendCriteria = weekend.weekendCriteria();
@@ -41,6 +41,6 @@ public class SubscriptionTaskConfigServiceImpl implements ISubscriptionTaskConfi
         if (StringUtils.hasText(query.getExecuteOption())) {
             weekendCriteria.andEqualTo(SubscriptionTaskConfig::getExecuteOption, query.getExecuteOption());
         }
-        return taskConfigMapper.selectByExample(weekend);
+        return taskConfigMapper.selectOneByExample(weekend);
     }
 }

+ 25 - 0
data-easy/src/main/java/com/dataeasy/server/common/annotation/ScheduleTask.java

@@ -0,0 +1,25 @@
+package com.dataeasy.server.common.annotation;
+
+import com.dataeasy.server.constant.ScheduleTaskEnum;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @date 2025/3/14 14:06
+ * @description 定时任务注解
+ */
+@Target(ElementType.TYPE) // 仅适用于类
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ScheduleTask {
+
+    /**
+     * 关联的定时任务枚举
+     * @return
+     */
+    ScheduleTaskEnum value();
+}

+ 2 - 1
data-easy/src/main/java/com/dataeasy/server/constant/ScheduleTaskEnum.java

@@ -20,7 +20,8 @@ public enum ScheduleTaskEnum {
     SHUANG_SE_QIU("双色球定时任务"),
     IPO_BOND("新债定时任务"),
     IPO_STOCK("新股定时任务"),
-    PRODUCT_HUNT("ProductHunt热榜定时任务");
+    PRODUCT_HUNT("ProductHunt热榜定时任务"),
+    AUTO_CLOSE_PAY_ORDER("支付订单自动关闭定时任务");
 
     private String name;
 

+ 128 - 0
data-easy/src/main/java/com/dataeasy/server/core/aop/ScheduleTaskLogAspect.java

@@ -0,0 +1,128 @@
+package com.dataeasy.server.core.aop;
+
+import java.util.Date;
+import java.util.Objects;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+
+import com.dataeasy.server.atomic.entity.SubscriptionTaskConfig;
+import com.dataeasy.server.atomic.entity.SysScheduleTaskLog;
+import com.dataeasy.server.atomic.service.ISubscriptionTaskConfigService;
+import com.dataeasy.server.atomic.service.ISysScheduleTaskLogService;
+import com.dataeasy.server.common.annotation.ScheduleTask;
+import com.dataeasy.server.constant.ExecuteOptionEnum;
+import com.dataeasy.server.constant.ScheduleTaskEnum;
+import com.dataeasy.server.constant.ScheduleTaskStatusEnum;
+import com.dataeasy.server.pojo.subscription.SubscriptionTaskConfigQuery;
+import com.dataeasy.server.pojo.task.ScheduleTaskContext;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @date 2025/3/14 13:50
+ * @description 定时任务日志 切面
+ */
+@Slf4j
+@Aspect
+@Component
+public class ScheduleTaskLogAspect {
+
+    @Autowired
+    private ISysScheduleTaskLogService taskLogService;
+
+    @Autowired
+    private ISubscriptionTaskConfigService taskConfigService;
+
+    @Autowired
+    private PlatformTransactionManager transactionManager;
+
+    /**
+     * 定时任务上下文
+     */
+    public static final ThreadLocal<ScheduleTaskContext> scheduleTaskContextThreadLocal = new ThreadLocal<>();
+
+    @Pointcut("@annotation(org.springframework.scheduling.annotation.Scheduled)")
+    public void pointcut() {}
+
+    @Around("pointcut()")
+    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
+        // 获取目标类的 Class 对象
+        Class<?> targetClass = joinPoint.getTarget().getClass();
+        // 获取类上的 @ScheduleTask 注解
+        ScheduleTask scheduleTask = targetClass.getAnnotation(ScheduleTask.class);
+        if (scheduleTask == null) {
+            log.info("类上没有@ScheduleTask注解,没法记录定时任务日志,类名:{}", targetClass.getSimpleName());
+            return null;
+        }
+        // 获取定时任务信息
+        ScheduleTaskEnum task = scheduleTask.value();
+
+        // 查询需要执行的任务配置
+        SubscriptionTaskConfigQuery taskConfigQuery = new SubscriptionTaskConfigQuery();
+        taskConfigQuery.setTaskCode(task.toString());
+        taskConfigQuery.setExecuteOption(ExecuteOptionEnum.ENABLE.toString());
+        SubscriptionTaskConfig taskConfig = taskConfigService.getByCondition(taskConfigQuery);
+        if (Objects.isNull(taskConfig)) {
+            log.info("任务:{},没有找到执行状态为启用的配置,定时任务不执行", task.getName());
+            return null;
+        }
+
+        log.info("======= {} 开始执行 =======", task.getName());
+        // 新增任务执行记录
+        SysScheduleTaskLog addTaskLog = task.buildTaskLog();
+        taskLogService.insert(addTaskLog);
+
+        // 更新任务执行记录
+        SysScheduleTaskLog updateTaskLog = new SysScheduleTaskLog();
+        updateTaskLog.setId(addTaskLog.getId());
+
+        // 设置任务上下文
+        ScheduleTaskContext scheduleTaskContext =
+                ScheduleTaskContext.builder().subscriptionTaskConfig(taskConfig).task(task).build();
+        scheduleTaskContextThreadLocal.set(scheduleTaskContext);
+
+        // 创建事务定义
+        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
+        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
+
+        // 开启事务
+        TransactionStatus status = transactionManager.getTransaction(def);
+
+        Object result = null;
+        try {
+            // 执行定时任务具体逻辑
+            result = joinPoint.proceed();
+            // 设置任务执行结果
+            updateTaskLog.setProcessStatus(ScheduleTaskStatusEnum.SUCCESS);
+            // 提交事务
+            transactionManager.commit(status);
+        } catch (Throwable e) {
+            updateTaskLog.setErrorMessage(e.getMessage());
+            updateTaskLog.setProcessStatus(ScheduleTaskStatusEnum.FAIL);
+            log.error(String.format("定时任务:%s 执行异常", task.getName()), e);
+            // 回滚事务
+            transactionManager.rollback(status);
+        } finally {
+            // 清除上下文
+            scheduleTaskContextThreadLocal.remove();
+        }
+
+        updateTaskLog.setEndTime(new Date());
+        taskLogService.updateById(updateTaskLog);
+
+        log.info("======= {} 执行结束 =======", task.getName());
+
+        return result;
+    }
+}

+ 8 - 6
data-easy/src/main/java/com/dataeasy/server/feign/ProductHuntFeign.java

@@ -1,12 +1,14 @@
 package com.dataeasy.server.feign;
 
-import com.dataeasy.server.common.pojo.JsonResponse;
-import com.dataeasy.server.feign.dto.PostNode;
+import java.util.List;
+
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestParam;
 
-import java.util.List;
+import com.dataeasy.server.common.pojo.JsonResponse;
+import com.dataeasy.server.feign.dto.PostNode;
 
 /**
  * @author tyuio
@@ -18,10 +20,10 @@ import java.util.List;
 public interface ProductHuntFeign {
 
     /**
-     * 获取产品热榜TOP30
+     * 获取热榜数据
      * @param token 访问凭据
      * @return
      */
-    @GetMapping("/producthunt/getTop30Posts")
-    JsonResponse<List<PostNode>> getTop30Posts(@RequestHeader("Authorization") String token);
+    @GetMapping("/producthunt/getPosts")
+    JsonResponse<List<PostNode>> getPosts(@RequestHeader("Authorization") String token, @RequestParam String rankDate);
 }

+ 27 - 0
data-easy/src/main/java/com/dataeasy/server/pojo/task/ScheduleTaskContext.java

@@ -0,0 +1,27 @@
+package com.dataeasy.server.pojo.task;
+
+import com.dataeasy.server.atomic.entity.SubscriptionTaskConfig;
+import com.dataeasy.server.constant.ScheduleTaskEnum;
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @date 2025/3/14 15:23
+ * @description 定时任务上下文
+ */
+@Data
+@Builder
+public class ScheduleTaskContext {
+
+    /**
+     * 任务枚举
+     */
+    private ScheduleTaskEnum task;
+
+    /**
+     * 任务配置
+     */
+    private SubscriptionTaskConfig subscriptionTaskConfig;
+}

+ 14 - 5
data-easy/src/main/java/com/dataeasy/server/service/controller/TaskOpController.java

@@ -1,5 +1,6 @@
 package com.dataeasy.server.service.controller;
 
+import com.dataeasy.server.task.AutoCLosePayOrderTask;
 import com.dataeasy.server.task.DaLeTouTask;
 import com.dataeasy.server.task.IpoBondTask;
 import com.dataeasy.server.task.IpoStockTask;
@@ -35,12 +36,15 @@ public class TaskOpController {
     @Autowired
     private IpoStockTask ipoStockTask;
 
+    @Autowired
+    private AutoCLosePayOrderTask autoCLosePayOrderTask;
+
     /**
      * 大乐透定时任务
      */
     @GetMapping("daLeTouTask")
     public void daLeTouTask() {
-        daLeTouTask.exec();
+        daLeTouTask.execute();
     }
 
     /**
@@ -48,7 +52,7 @@ public class TaskOpController {
      */
     @GetMapping("shuangSeQiuTask")
     public void shuangSeQiuTask() {
-        shuangSeQiuTask.exec();
+        shuangSeQiuTask.execute();
     }
 
     /**
@@ -56,7 +60,7 @@ public class TaskOpController {
      */
     @GetMapping("productHuntTask")
     public void productHuntTask() {
-        productHuntTask.exec();
+        productHuntTask.execute();
     }
 
     /**
@@ -64,7 +68,7 @@ public class TaskOpController {
      */
     @GetMapping("ipoBondTask")
     public void ipoBondTask() {
-        ipoBondTask.exec();
+        ipoBondTask.execute();
     }
 
     /**
@@ -72,6 +76,11 @@ public class TaskOpController {
      */
     @GetMapping("ipoStockTask")
     public void ipoStockTask() {
-        ipoStockTask.exec();
+        ipoStockTask.execute();
+    }
+
+    @GetMapping("autoCloseOrderTask")
+    public void autoCloseOrderTask() {
+        autoCLosePayOrderTask.execute();
     }
 }

+ 4 - 0
data-easy/src/main/java/com/dataeasy/server/service/manager/impl/AiChatManagerImpl.java

@@ -1,10 +1,12 @@
 package com.dataeasy.server.service.manager.impl;
 
 import com.dataeasy.server.service.manager.IAiChatManager;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.ai.chat.client.ChatClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.core.io.Resource;
+import org.springframework.stereotype.Component;
 
 /**
  * @author tyuio
@@ -12,6 +14,8 @@ import org.springframework.core.io.Resource;
  * @description AI大模型 服务类
  * @date 2025/3/5 12:31
  */
+@Slf4j
+@Component
 public class AiChatManagerImpl implements IAiChatManager {
 
     @Autowired

+ 32 - 79
data-easy/src/main/java/com/dataeasy/server/task/AbstractTask.java → data-easy/src/main/java/com/dataeasy/server/task/AbstractDataTask.java

@@ -1,47 +1,44 @@
 package com.dataeasy.server.task;
 
 import java.sql.Timestamp;
-import java.util.Date;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-import com.dataeasy.server.core.config.WxMessageProperties;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
 
 import com.dataeasy.server.atomic.entity.SubscriptionTaskConfig;
 import com.dataeasy.server.atomic.entity.SubscriptionUserConfig;
-import com.dataeasy.server.atomic.entity.SysScheduleTaskLog;
 import com.dataeasy.server.atomic.entity.User;
 import com.dataeasy.server.atomic.service.ISubscriptionTaskConfigService;
 import com.dataeasy.server.atomic.service.ISubscriptionUserConfigService;
 import com.dataeasy.server.atomic.service.ISysScheduleTaskLogService;
 import com.dataeasy.server.atomic.service.IUserService;
-import com.dataeasy.server.constant.ExecuteOptionEnum;
 import com.dataeasy.server.constant.PushOptionEnum;
 import com.dataeasy.server.constant.ScheduleTaskEnum;
-import com.dataeasy.server.constant.ScheduleTaskStatusEnum;
-import com.dataeasy.server.pojo.subscription.SubscriptionTaskConfigQuery;
+import com.dataeasy.server.core.aop.ScheduleTaskLogAspect;
+import com.dataeasy.server.core.config.WxMessageProperties;
 import com.dataeasy.server.pojo.subscription.SubscriptionUserConfigQuery;
+import com.dataeasy.server.pojo.task.ScheduleTaskContext;
 import com.dataeasy.server.service.manager.IWxManager;
 
 import lombok.Builder;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
-import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
 
 /**
  * @author tyuio
  * @version 1.0.0
- * @description 定时任务 抽象类
+ * @description 数据定时任务 抽象类
  * @date 2025/3/7 10:44
  */
 @Slf4j
-public abstract class AbstractTask {
+public abstract class AbstractDataTask {
 
     @Autowired
     protected ISysScheduleTaskLogService taskLogService;
@@ -61,64 +58,25 @@ public abstract class AbstractTask {
     @Autowired
     protected WxMessageProperties wxMessageProperties;
 
-    /**
-     * 定时任务执行入口
-     */
-    public void executeMain() {
-        ScheduleTaskEnum task = getTaskEnum();
-        log.info("======= {} 开始执行 =======", task.getName());
-        // 新增任务执行记录
-        SysScheduleTaskLog addTaskLog = task.buildTaskLog();
-        taskLogService.insert(addTaskLog);
-
-        // 更新任务执行记录
-        SysScheduleTaskLog updateTaskLog = new SysScheduleTaskLog();
-        updateTaskLog.setId(addTaskLog.getId());
-
-        try {
-            // 执行定时任务了以及
-            executeLogic(task);
-            updateTaskLog.setProcessStatus(ScheduleTaskStatusEnum.SUCCESS);
-        } catch (Exception e) {
-            updateTaskLog.setErrorMessage(e.getMessage());
-            updateTaskLog.setProcessStatus(ScheduleTaskStatusEnum.FAIL);
-            log.error(String.format("定时任务:%s 执行异常", task.getName()), e);
-        }
-
-        updateTaskLog.setEndTime(new Date());
-        taskLogService.updateById(updateTaskLog);
-
-        log.info("======= {} 执行结束 =======", task.getName());
-    }
-
     /**
      * 定时任务执行逻辑
-     * @param task 定时任务枚举
      */
-    public void executeLogic(ScheduleTaskEnum task) {
-        // 查询需要执行的任务配置
-        SubscriptionTaskConfigQuery taskConfigQuery = new SubscriptionTaskConfigQuery();
-        taskConfigQuery.setTaskCode(task.toString());
-        taskConfigQuery.setExecuteOption(ExecuteOptionEnum.ENABLE.toString());
-        List<SubscriptionTaskConfig> taskConfigList = taskConfigService.getByCondition(taskConfigQuery);
-        if (CollectionUtils.isEmpty(taskConfigList)) {
-            log.warn("任务:{},没有找到执行状态为启用的配置,结束执行", task.getName());
-            return;
-        }
-        Set<Long> subscriptionSourceIds = taskConfigList.stream().filter(v -> Objects.nonNull(v.getSubscriptionSourceId()))
-                .map(SubscriptionTaskConfig::getSubscriptionSourceId)
-                .collect(Collectors.toSet());
-        if (CollectionUtils.isEmpty(subscriptionSourceIds)) {
-            log.warn("任务:{},任务编码:{},没有找到有效的订阅源,结束执行", task.getName(), task);
-            return;
-        }
+    public void execute() {
+        // 获取定时任务上下文
+        ScheduleTaskContext scheduleTaskContext = ScheduleTaskLogAspect.scheduleTaskContextThreadLocal.get();
+        ScheduleTaskEnum task = scheduleTaskContext.getTask();
+        SubscriptionTaskConfig subscriptionTaskConfig = scheduleTaskContext.getSubscriptionTaskConfig();
 
         // 拉取数据并入库
-        fetchData();
+        boolean fetchResult = fetchData();
+        if (!fetchResult) {
+            log.warn("任务:{},任务编码:{},拉取数据失败,结束执行", task.getName(), task);
+            return;
+        }
 
         // 寻找要推送数据的用户
         SubscriptionUserConfigQuery userConfigQuery = new SubscriptionUserConfigQuery();
-        userConfigQuery.setSubscriptionSourceIds(subscriptionSourceIds);
+        userConfigQuery.setSubscriptionSourceIds(Arrays.asList(subscriptionTaskConfig.getId()));
         userConfigQuery.setPushOption(PushOptionEnum.ENABLED);
         userConfigQuery.setCurrentTime(new Timestamp(System.currentTimeMillis()));
         List<SubscriptionUserConfig> userConfigList = userConfigService.getByCondition(userConfigQuery);
@@ -147,31 +105,26 @@ public abstract class AbstractTask {
         }
 
         // 获取消息模板所需数据
-        TemplateMessage templateMessage = getTemplateMessage();
-
-        // 推送
-        for (String mpOpenId : mpOpenIds) {
-            // 生成模板信息
-            WxMpTemplateMessage mpTemplateMessage = WxMpTemplateMessage.builder()
-                    .toUser(mpOpenId)
-                    .templateId(templateMessage.getTemplateId())
-                    .url(templateMessage.getUrl())
-                    .data(templateMessage.getTemplateDataList())
-                    .build();
-            wxMpManager.sendTemplateMessage(mpTemplateMessage);
-        }
+//        TemplateMessage templateMessage = getTemplateMessage();
+//
+//        // 推送
+//        for (String mpOpenId : mpOpenIds) {
+//            // 生成模板信息
+//            WxMpTemplateMessage mpTemplateMessage = WxMpTemplateMessage.builder()
+//                    .toUser(mpOpenId)
+//                    .templateId(templateMessage.getTemplateId())
+//                    .url(templateMessage.getUrl())
+//                    .data(templateMessage.getTemplateDataList())
+//                    .build();
+//            wxMpManager.sendTemplateMessage(mpTemplateMessage);
+//        }
     }
 
-    /**
-     * 获取定时任务枚举
-     * @return
-     */
-    public abstract ScheduleTaskEnum getTaskEnum();
-
     /**
      * 拉取所需数据并入库
+     * @return false-拉取数据入库失败 true-拉取数据入库成功
      */
-    public abstract void fetchData();
+    public abstract boolean fetchData();
 
     /**
      * 获取消息模板所需数据

+ 1 - 1
data-easy/src/main/java/com/dataeasy/server/task/AbstractHzApiTask.java

@@ -11,7 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired;
  * @description 使用接口盒子接口的定时任务 抽象类
  * @date 2025/3/7 16:45
  */
-public abstract class AbstractHzApiTask extends AbstractTask {
+public abstract class AbstractHzApiTask extends AbstractDataTask {
 
     @Autowired
     protected HzApiFeign hzApiFeign;

+ 47 - 0
data-easy/src/main/java/com/dataeasy/server/task/AutoCLosePayOrderTask.java

@@ -0,0 +1,47 @@
+package com.dataeasy.server.task;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import com.dataeasy.server.atomic.entity.SubscriptionOrder;
+import com.dataeasy.server.atomic.service.ISubscriptionOrderService;
+import com.dataeasy.server.common.annotation.ScheduleTask;
+import com.dataeasy.server.constant.PaymentStatusEnum;
+import com.dataeasy.server.constant.ScheduleTaskEnum;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @date 2025/3/14 10:51
+ * @description 支付订单自动关闭 定时任务,每15分钟执行一次
+ */
+@Slf4j
+@Component
+@ScheduleTask(ScheduleTaskEnum.AUTO_CLOSE_PAY_ORDER)
+public class AutoCLosePayOrderTask {
+
+    @Autowired
+    private ISubscriptionOrderService subscriptionOrderService;
+
+    @Scheduled(cron = "0 0,15,30,45 * * * *")
+    public void execute() {
+        List<SubscriptionOrder> pendingCLoseOrders = subscriptionOrderService.getPendingCLoseOrders();
+        if (CollectionUtils.isEmpty(pendingCLoseOrders)) {
+            log.info("没有需要关闭得支付订单");
+            return;
+        }
+
+        Set<Long> ids = pendingCLoseOrders.stream().map(SubscriptionOrder::getId).collect(Collectors.toSet());
+        SubscriptionOrder updateSubscriptionOrder = new SubscriptionOrder();
+        updateSubscriptionOrder.setPaymentStatus(PaymentStatusEnum.CLOSED);
+        subscriptionOrderService.batchUpdate(ids, updateSubscriptionOrder);
+    }
+}

+ 11 - 14
data-easy/src/main/java/com/dataeasy/server/task/DaLeTouTask.java

@@ -8,10 +8,10 @@ import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Transactional;
 
 import com.dataeasy.server.atomic.entity.DataDaLeTou;
 import com.dataeasy.server.atomic.service.IDataDaLeTouService;
+import com.dataeasy.server.common.annotation.ScheduleTask;
 import com.dataeasy.server.constant.HzApiStatusEnum;
 import com.dataeasy.server.constant.ScheduleTaskEnum;
 import com.dataeasy.server.feign.dto.hzapi.DaLeTouResponse;
@@ -23,45 +23,42 @@ import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
 /**
  * @author tyuio
  * @version 1.0.0
- * @description 大乐透定时任务,执行时间:每周一、三、六,晚上21:30
+ * @description 大乐透定时任务,执行时间:每周一、三、六,晚上21:25
  * @date 2025/3/6 20:19
  */
 @Slf4j
 @Component
+@ScheduleTask(ScheduleTaskEnum.DA_LE_TOU)
 public class DaLeTouTask extends AbstractHzApiTask {
 
     @Autowired
     private IDataDaLeTouService daLeTouService;
 
-//    @Scheduled(cron = "30 21 * * 1,3,6 *")
-    @Transactional(rollbackFor = Exception.class)
-    public void exec() {
-        executeMain();
+    @Scheduled(cron = "0 25 21 * * 1,3,6")
+    public void execute() {
+        super.execute();
     }
 
     @Override
-    public ScheduleTaskEnum getTaskEnum() {
-        return ScheduleTaskEnum.DA_LE_TOU;
-    }
-
-    @Override
-    public void fetchData() {
+    public boolean fetchData() {
         // 拉取数据
         HzApiRequest request = buildCommonRequest();
         DaLeTouResponse daLeTouResponse = hzApiFeign.getDaLeTou(request);
         if (Objects.isNull(daLeTouResponse)) {
             log.warn("拉取大乐透数据失败,返回结果对象为空");
-            return;
+            return false;
         }
         if (!HzApiStatusEnum.SUCCESS.getCode().equals(daLeTouResponse.getCode())) {
             log.warn("拉取大乐透数据失败,接口盒子返回错误信息:{}", daLeTouResponse.getMsg());
-            return;
+            return false;
         }
 
         // 数据入库
         DataDaLeTou addDaLeTou = new DataDaLeTou();
         BeanUtils.copyProperties(daLeTouResponse, addDaLeTou);
         daLeTouService.insert(addDaLeTou);
+
+        return true;
     }
 
     @Override

+ 12 - 16
data-easy/src/main/java/com/dataeasy/server/task/IpoBondTask.java

@@ -1,25 +1,24 @@
 package com.dataeasy.server.task;
 
-import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
 
-import com.dataeasy.server.utiis.DateUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 
 import com.dataeasy.server.atomic.entity.DataIpoBond;
 import com.dataeasy.server.atomic.service.IDataIpoBondService;
+import com.dataeasy.server.common.annotation.ScheduleTask;
 import com.dataeasy.server.constant.ScheduleTaskEnum;
 import com.dataeasy.server.feign.FinanceFeign;
 import com.dataeasy.server.feign.dto.finance.BondResponse;
 import com.dataeasy.server.feign.dto.finance.FinanceRequest;
+import com.dataeasy.server.utiis.DateUtils;
 
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
@@ -27,12 +26,13 @@ import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
 /**
  * @author tyuio
  * @version 1.0.0
- * @description 新债定时任务,执行时间:每天早上8:45
+ * @description 新债定时任务,执行时间:每天早上8:35
  * @date 2025/3/7 11:25
  */
 @Slf4j
 @Component
-public class IpoBondTask extends AbstractTask {
+@ScheduleTask(ScheduleTaskEnum.IPO_BOND)
+public class IpoBondTask extends AbstractDataTask {
 
     @Autowired
     private FinanceFeign financeFeign;
@@ -40,19 +40,13 @@ public class IpoBondTask extends AbstractTask {
     @Autowired
     private IDataIpoBondService ipoBondService;
 
-//    @Scheduled(cron = "45 8 * * * *")
-    @Transactional(rollbackFor = Exception.class)
-    public void exec() {
-        executeMain();
-    }
-
-    @Override
-    public ScheduleTaskEnum getTaskEnum() {
-        return ScheduleTaskEnum.IPO_BOND;
+    @Scheduled(cron = "0 35 8 * * *")
+    public void execute() {
+        super.execute();
     }
 
     @Override
-    public void fetchData() {
+    public boolean fetchData() {
         // 拉取数据
         String todayStr = DateUtils.YYYYMMDD_FORMATTER.format(LocalDate.now());
         FinanceRequest financeRequest = new FinanceRequest();
@@ -61,7 +55,7 @@ public class IpoBondTask extends AbstractTask {
         List<BondResponse> bondCovIssueCninfos =  financeFeign.getBondCovIssueCninfo(financeRequest);
         if (CollectionUtils.isEmpty(bondCovIssueCninfos)) {
             log.warn("拉取新债数据失败,返回结果对象为空");
-            return;
+            return false;
         }
 
         List<DataIpoBond> ipoBonds = bondCovIssueCninfos.stream().map(v -> {
@@ -72,6 +66,8 @@ public class IpoBondTask extends AbstractTask {
 
         // 数据入库
         ipoBondService.insertList(ipoBonds);
+
+        return true;
     }
 
     @Override

+ 11 - 14
data-easy/src/main/java/com/dataeasy/server/task/IpoStockTask.java

@@ -8,11 +8,11 @@ import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 
 import com.dataeasy.server.atomic.entity.DataIpoStock;
 import com.dataeasy.server.atomic.service.IDataIpoStockService;
+import com.dataeasy.server.common.annotation.ScheduleTask;
 import com.dataeasy.server.constant.ScheduleTaskEnum;
 import com.dataeasy.server.feign.FinanceFeign;
 import com.dataeasy.server.feign.dto.finance.FinanceRequest;
@@ -24,12 +24,13 @@ import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
 /**
  * @author tyuio
  * @version 1.0.0
- * @description 新股定时任务,执行时间:每天早上8:45
+ * @description 新股定时任务,执行时间:每天早上8:40
  * @date 2025/3/7 11:26
  */
 @Slf4j
 @Component
-public class IpoStockTask extends AbstractTask {
+@ScheduleTask(ScheduleTaskEnum.IPO_STOCK)
+public class IpoStockTask extends AbstractDataTask {
 
     @Autowired
     private FinanceFeign financeFeign;
@@ -37,26 +38,20 @@ public class IpoStockTask extends AbstractTask {
     @Autowired
     private IDataIpoStockService ipoStockService;
 
-//    @Scheduled(cron = "45 8 * * * *")
-    @Transactional(rollbackFor = Exception.class)
-    public void exec() {
-        executeMain();
+    @Scheduled(cron = "0 40 8 * * *")
+    public void execute() {
+        super.execute();
     }
 
     @Override
-    public ScheduleTaskEnum getTaskEnum() {
-        return ScheduleTaskEnum.IPO_STOCK;
-    }
-
-    @Override
-    public void fetchData() {
+    public boolean fetchData() {
         // 拉取数据
         FinanceRequest financeRequest = new FinanceRequest();
         financeRequest.setSymbol("全部股票");
         List<StockResponse> stockXgsglbEmList =  financeFeign.getStockXgsglbEm(financeRequest);
         if (CollectionUtils.isEmpty(stockXgsglbEmList)) {
             log.warn("拉取新股数据失败,返回结果对象为空");
-            return;
+            return false;
         }
 
         List<DataIpoStock> ipoStocks = stockXgsglbEmList.stream().map(v -> {
@@ -67,6 +62,8 @@ public class IpoStockTask extends AbstractTask {
 
         // 数据入库
         ipoStockService.insertList(ipoStocks);
+
+        return true;
     }
 
     @Override

+ 44 - 20
data-easy/src/main/java/com/dataeasy/server/task/ProductHuntTask.java

@@ -8,23 +8,26 @@ import java.util.Objects;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
-import com.dataeasy.server.core.config.ProductHuntProperties;
 import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
 import org.jasypt.iv.RandomIvGenerator;
 import org.jasypt.salt.RandomSaltGenerator;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
 
 import com.dataeasy.server.atomic.entity.DataProductHuntPost;
 import com.dataeasy.server.atomic.service.IDataProductHuntPostService;
+import com.dataeasy.server.common.annotation.ScheduleTask;
 import com.dataeasy.server.common.constant.ResponseCodeEnum;
 import com.dataeasy.server.common.pojo.JsonResponse;
 import com.dataeasy.server.constant.ScheduleTaskEnum;
+import com.dataeasy.server.core.config.ProductHuntProperties;
 import com.dataeasy.server.feign.ProductHuntFeign;
 import com.dataeasy.server.feign.dto.PostNode;
+import com.dataeasy.server.service.manager.IAiChatManager;
 
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
@@ -32,12 +35,18 @@ import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
 /**
  * @author tyuio
  * @version 1.0.0
- * @description ProductHunt热榜定时任务,执行时间:每天下午4:00
+ * @description ProductHunt热榜定时任务,执行时间:每天下午16:05
  * @date 2025/3/7 11:26
  */
 @Slf4j
 @Component
-public class ProductHuntTask extends AbstractTask {
+@ScheduleTask(ScheduleTaskEnum.PRODUCT_HUNT)
+public class ProductHuntTask extends AbstractDataTask {
+
+    /**
+     * 内容分隔符
+     */
+    private static final String CONTENT_DELIMITER = "##==!!!=##";
 
     @Autowired
     private ProductHuntFeign productHuntFeign;
@@ -45,12 +54,15 @@ public class ProductHuntTask extends AbstractTask {
     @Autowired
     private IDataProductHuntPostService productHuntPostService;
 
+    @Autowired
+    private IAiChatManager aiChatManager;
+
     /**
      * 自建ProductHunt服务访问凭据
      */
     private String apiToken;
 
-    @Autowired
+
     public ProductHuntTask(ProductHuntProperties productHuntProperties) {
         // 构建加密器
         StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
@@ -63,37 +75,47 @@ public class ProductHuntTask extends AbstractTask {
         apiToken = encryptor.encrypt(productHuntProperties.getApiKey());
     }
 
-    //    @Scheduled(cron = "0 16 * * * *")
-    @Transactional(rollbackFor = Exception.class)
-    public void exec() {
-        executeMain();
-    }
-
-    @Override
-    public ScheduleTaskEnum getTaskEnum() {
-        return ScheduleTaskEnum.PRODUCT_HUNT;
+    @Scheduled(cron = "0 5 16 * * *")
+    public void execute() {
+        super.execute();
     }
 
     @Override
-    public void fetchData() {
+    public boolean fetchData() {
+        String rankDateStr = "2025-03-13";
         // 拉取数据
-        JsonResponse<List<PostNode>> jsonResponse = productHuntFeign.getTop30Posts(apiToken);
+        JsonResponse<List<PostNode>> jsonResponse = productHuntFeign.getPosts(apiToken, rankDateStr);
         if (Objects.isNull(jsonResponse)) {
             log.warn("拉取ProductHunt数据失败,返回结果对象为空");
-            return;
+            return false;
         }
         if (!ResponseCodeEnum.SUCCESS.getCode().equals(jsonResponse.getCode())) {
             log.warn("拉取ProductHunt数据失败,返回错误信息:{}", jsonResponse.getMsg());
-            return;
+            return false;
         }
 
         List<PostNode> postNodes = jsonResponse.getData();
         if (CollectionUtils.isEmpty(postNodes)) {
             log.warn("拉取ProductHunt数据,数据列表为空");
-            return;
+            return false;
         }
 
-        // TODO 查了翻译的逻辑
+        // 翻译 TODO 可能内容太长了,要分批次翻译
+        String originalText = postNodes.stream().filter(v -> StringUtils.hasText(v.getDescription()))
+                .map(PostNode::getDescription).collect(Collectors.joining(CONTENT_DELIMITER));
+        String translateText = aiChatManager.translate(originalText);
+        log.info("翻译数据:{}", translateText);
+        String[] contents = translateText.split(CONTENT_DELIMITER);
+        if (contents.length > postNodes.size()) {
+            log.warn("翻译结果与原始数据长度不一致,翻译结果:{},原始数据:{}", contents.length, postNodes.size());
+            return false;
+        }
+        // 回写翻译内容
+        for (int i = 0; i < contents.length; i++) {
+            String translateContent = contents[i];
+            PostNode postNode = postNodes.get(i);
+            postNode.setDescription(translateContent);
+        }
 
         AtomicInteger rankNum = new AtomicInteger(1);
         Date rankDate = new Date();
@@ -115,6 +137,8 @@ public class ProductHuntTask extends AbstractTask {
 
         // 数据入库
         productHuntPostService.insertList(posts);
+
+        return true;
     }
 
     @Override

+ 11 - 14
data-easy/src/main/java/com/dataeasy/server/task/ShuangSeQiuTask.java

@@ -8,10 +8,10 @@ import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Transactional;
 
 import com.dataeasy.server.atomic.entity.DataShuangSeQiu;
 import com.dataeasy.server.atomic.service.IDataShuangSeQiuService;
+import com.dataeasy.server.common.annotation.ScheduleTask;
 import com.dataeasy.server.constant.HzApiStatusEnum;
 import com.dataeasy.server.constant.ScheduleTaskEnum;
 import com.dataeasy.server.feign.dto.hzapi.HzApiRequest;
@@ -23,45 +23,42 @@ import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
 /**
  * @author tyuio
  * @version 1.0.0
- * @description 双色球定时任务,执行时间:每周二、周四、周日 晚上21:40
+ * @description 双色球定时任务,执行时间:每周二、周四、周日 晚上21:35
  * @date 2025/3/7 11:15
  */
 @Slf4j
 @Component
+@ScheduleTask(ScheduleTaskEnum.SHUANG_SE_QIU)
 public class ShuangSeQiuTask extends AbstractHzApiTask {
 
     @Autowired
     private IDataShuangSeQiuService shuangSeQiuService;
 
-//    @Scheduled(cron = "40 21 * * 2,4,7 *")
-    @Transactional(rollbackFor = Exception.class)
-    public void exec() {
-        executeMain();
+    @Scheduled(cron = "0 35 21 * * 2,4,7")
+    public void execute() {
+        super.execute();
     }
 
     @Override
-    public ScheduleTaskEnum getTaskEnum() {
-        return ScheduleTaskEnum.SHUANG_SE_QIU;
-    }
-
-    @Override
-    public void fetchData() {
+    public boolean fetchData() {
         // 拉取数据
         HzApiRequest request = buildCommonRequest();
         ShuangSeQiuResponse shuangSeQiuResponse = hzApiFeign.getShuangSeQiu(request);
         if (Objects.isNull(shuangSeQiuResponse)) {
             log.warn("拉取双色球数据失败,返回结果对象为空");
-            return;
+            return false;
         }
         if (!HzApiStatusEnum.SUCCESS.getCode().equals(shuangSeQiuResponse.getCode())) {
             log.warn("拉取双色球数据失败,接口盒子返回错误信息:{}", shuangSeQiuResponse.getMsg());
-            return;
+            return false;
         }
 
         // 数据入库
         DataShuangSeQiu addShuangSeQiu = new DataShuangSeQiu();
         BeanUtils.copyProperties(shuangSeQiuResponse, addShuangSeQiu);
         shuangSeQiuService.insert(addShuangSeQiu);
+
+        return true;
     }
 
     @Override

+ 2 - 2
data-easy/src/main/resources/application-dev.yaml

@@ -31,7 +31,7 @@ wx:
     keyPath: C:\Software\WXCertUtil\cert\apiclient_cert.p12
     # pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
     publicKeyPath: E:\Download\pub_key.pem
-    notifyUrl: http://krdvzy.natappfree.cc/wx/pay/callback
+    notifyUrl: http://rvy77p.natappfree.cc/wx/pay/callback
   # 小程序
   miniapp:
     appid: ENC(825U/6iYUhZJK7ojeBUya+WATlVilGB8py4AQPA0jwfY8oPwjZ53wA==)
@@ -39,7 +39,7 @@ wx:
 
 # 微信消息模板配置
 wechat:
-  templateId: wSq3Oj0Tb2clDwz7mMID_kqDy4vR8AOQc7PQMqoch7A
+  templateId: lJGGwwLWQM4Ba8rSoTkeni6_lnvE8h6UJ5_4jj132-4
 
 # 接口盒子平台配置
 hz-api:

+ 1 - 1
data-easy/src/main/resources/prompts/system-message.st

@@ -1 +1 @@
-你是世界上最专业的翻译工具,擅长英文和中文互译。你是一位精通英文和中文的专业翻译,尤其擅长将IT公司黑话和专业词汇翻译成简洁易懂的地道表达。你的任务是将以下内容翻译成地道的中文,风格与科普杂志或日常对话相似。
+你是世界上最专业的翻译工具,擅长英文和中文互译。你是一位精通英文和中文的专业翻译,尤其擅长将IT公司黑话和专业词汇翻译成简洁易懂的地道表达。你的任务是将以下内容翻译成地道的中文,风格与科普杂志或日常对话相似。"##==!!!=##"是分隔符,按原格式返回,且开头和解为不要出现分隔符。

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 73 - 0
doc/sql/data.sql


+ 2 - 0
doc/sql/schema.sql

@@ -393,3 +393,5 @@ ALTER TABLE data_easy.data_product_hunt_post CHANGE featuredAt featured_at times
 ALTER TABLE data_easy.data_product_hunt_post CHANGE votesCount votes_count int DEFAULT 0 NULL COMMENT '投票数';
 ALTER TABLE data_easy.`user` MODIFY COLUMN ma_open_id varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '微信用户小程序标识id';
 ALTER TABLE data_easy.`user` MODIFY COLUMN nickname varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '用户昵称';
+ALTER TABLE data_easy.subscription_order ADD close_time TIMESTAMP NOT NULL COMMENT '订单关闭时间';
+ALTER TABLE data_easy.subscription_order CHANGE close_time close_time TIMESTAMP NOT NULL COMMENT '订单关闭时间' AFTER subscription_duration;

+ 9 - 7
doc/技术文档.md

@@ -41,13 +41,14 @@
 
 ## 定时任务
 
-| 定时任务名称            | 执行时间                     | cron表达式      | 实现类          |
-| ----------------------- | ---------------------------- | --------------- | --------------- |
-| 大乐透定时任务          | 每周一、三、六,晚上21:30    | 30 21 * * 1,3,6 | DaLeTouTask     |
-| 双色球定时任务          | 每周二、周四、周日 晚上21:40 | 40 21 * * 2,4,7 | ShuangSeQiuTask |
-| 新债定时任务            | 每天早上8:45                 | 45 8 * * *      | IpoBondTask     |
-| 新股定时任务            | 每天早上8:45                 | 45 8 * * *      | IpoStockTask    |
-| ProductHunt热榜定时任务 | 每天下午4:00                 | 0 16 * * *      | ProductHuntTask |
+| 定时任务名称             | 执行时间                     | cron表达式           | 实现类             |
+| ------------------------ | ---------------------------- | -------------------- | ------------------ |
+| 大乐透定时任务           | 每周一、三、六,晚上21:25    | 0 25 21 * * 1,3,6    | DaLeTouTask        |
+| 双色球定时任务           | 每周二、周四、周日 晚上21:35 | 0 35 21 * * 2,4,7    | ShuangSeQiuTask    |
+| 新债定时任务             | 每天早上8:35执行一次         | 0 35 8 * * *         | IpoBondTask        |
+| 新股定时任务             | 每天早上8:40执行一次         | 0 40 8 * * *         | IpoStockTask       |
+| ProductHunt热榜定时任务  | 每天下午16:05执行一次        | 0 5 16 * * *         | ProductHuntTask    |
+| 支付订单自动关闭定时任务 | 每15分钟执行一次             | 0 0,15,30,45 * * * * | AutoCloseOrderTask |
 
 
 
@@ -458,6 +459,7 @@
 | order_no               | varchar(30)   | 订单号(格式:3位业务编码 + yyMMddHHmmss + 3位序列号)       |
 | subscription_price     | decimal(12,2) | 订阅价格(单位:元)                                         |
 | subscription_duration  | int           | 订阅时长(单位:天)                                         |
+| close_time             | timestamp     | 订单关闭时间                                                 |
 | payment_status         | varchar(10)   | 支付状态(NOTPAY-未支付,SUCCESS-支付成功、REFUND-转入退款、CLOSED-已关闭、REVOKED-已撤销) |
 | created_by             | bigint        | 创建人                                                       |
 | creation_time          | timestamp     | 创建时间                                                     |

+ 343 - 1
product-hunt/src/main/java/com/producthunt/server/service/controller/ProductHuntController.java

@@ -10,6 +10,10 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import com.producthunt.server.dto.PostNode;
 import com.producthunt.server.service.manager.IProductHuntManager;
 
@@ -31,6 +35,339 @@ public class ProductHuntController {
     @Autowired
     private IProductHuntManager productHuntManager;
 
+    private String content = "[\n" +
+            "        {\n" +
+            "            \"id\": 935408,\n" +
+            "            \"name\": \"Wispr Flow for Windows\",\n" +
+            "            \"tagline\": \"Stop typing, start speaking: 3x faster dictation on PC & Mac\",\n" +
+            "            \"description\": \"Tired of typing? Wispr Flow for Windows lets you speak naturally and see your words perfectly formatted—no extra edits, no typos. It’s the easiest way to write 3x faster across all your apps.\",\n" +
+            "            \"votesCount\": 621,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/VEQJCWAHVPXBLC?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/wispr-flow-for-windows?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 931928,\n" +
+            "            \"name\": \"No Cap\",\n" +
+            "            \"tagline\": \"World's first AI angel investor\",\n" +
+            "            \"description\": \"Time to come clean: I just invested $100k in a startup — and I'm an AI. Full announcement and video below \uD83D\uDC47\",\n" +
+            "            \"votesCount\": 458,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/RX3O54ISJJ42UG?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/no-cap?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 941045,\n" +
+            "            \"name\": \"Cuckoo\",\n" +
+            "            \"tagline\": \"Real-time AI translator for global teams\",\n" +
+            "            \"description\": \"Cuckoo is a real-time AI translator for global sales, marketing, and support. Cuckoo helps companies like Snowflake and PagerDuty talk to their global customers in Zoom in-person meetings, even in the most technical discussions.\",\n" +
+            "            \"votesCount\": 384,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/BSLO27YPI7UOLG?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/cuckoo-6?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 938270,\n" +
+            "            \"name\": \"Sherloq\",\n" +
+            "            \"tagline\": \"Create one place for all your SQL, directly on your editor\",\n" +
+            "            \"description\": \"Sherloq is a Collaborative SQL Repository. Using the AI-powered plugin, SQL users automatically save, organize, share, and document queries on-the-fly, without leaving the editor. Companies use Sherloq as the single source of truth for all their queries.\",\n" +
+            "            \"votesCount\": 358,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/M7N4WQZQIP4IOJ?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/sherloq-2?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 939950,\n" +
+            "            \"name\": \"Numeral\",\n" +
+            "            \"tagline\": \"Spend less than 5 minutes per month on sales tax compliance\",\n" +
+            "            \"description\": \"Numeral puts sales tax on autopilot for leading e-commerce and SaaS businesses, offering intelligent workflows for registration, filing, and remittance alongside white-glove customer support.\",\n" +
+            "            \"votesCount\": 339,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/EQNJ7UZ55RUAHG?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/numeral?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 929040,\n" +
+            "            \"name\": \"OG PM Agent: Beta Release\",\n" +
+            "            \"tagline\": \"Your AI Product Manager\",\n" +
+            "            \"description\": \"PM Agent is your AI Product Manager that attends all your meetings, creates detailed summaries, and converts business discussions into Product Requirement Document including acceptance criteria for every sprint in real time keeping all stakeholders aligned.\",\n" +
+            "            \"votesCount\": 247,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/HYURCPOABM6B7B?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/og-pm-agent-beta-release?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940539,\n" +
+            "            \"name\": \"Superwall\",\n" +
+            "            \"tagline\": \"Build & test mobile app paywalls without shipping updates\",\n" +
+            "            \"description\": \"Superwall is the ultimate paywall solution for mobile apps. Build and test unlimited paywall designs, pricing, and A/B experiments— all without shipping app updates. With built in analytics, reduce your time to experiment by 90% and grow revenue by 20-30%.\",\n" +
+            "            \"votesCount\": 227,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/JEBXUG7IVV3WMG?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/superwall?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940968,\n" +
+            "            \"name\": \"OpenAI Responses API and Agents SDK\",\n" +
+            "            \"tagline\": \"New tools for building agents and tools\",\n" +
+            "            \"description\": \"A new set of APIs and tools specifically designed to simplify the development of agentic applications.\",\n" +
+            "            \"votesCount\": 174,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/7ZEJQUN3IQPUZA?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/openai-responses-api-and-agents-sdk?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940914,\n" +
+            "            \"name\": \"AI Renamer\",\n" +
+            "            \"tagline\": \"Rename your files with AI\",\n" +
+            "            \"description\": \"Automatically rename your files based on their content using AI. Perfect for organizing images and documents with meaningful names.\",\n" +
+            "            \"votesCount\": 167,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/DND2CRR6HKS53X?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/ai-renamer-2?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940701,\n" +
+            "            \"name\": \"Omlet for VS Code\",\n" +
+            "            \"tagline\": \"Get React component usage insights in VS Code\",\n" +
+            "            \"description\": \"Omlet is a component analytics tool for React. You can now use Omlet directly in VS Code and analyze how and where your React components (and their props) are used as you're coding — helping you confidently update, clean up and maintain your component library.\",\n" +
+            "            \"votesCount\": 163,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/DOVGY6PD4WK77S?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/omlet-for-vs-code?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940824,\n" +
+            "            \"name\": \"FirstSeed Tasks\",\n" +
+            "            \"tagline\": \"Task manager focused on task execution & completion\",\n" +
+            "            \"description\": \"Introducing FirstSeed Tasks 4 – a task manager that is focused on task execution and completion. With a built-in timer and Pomodoro functionality, it keeps you focused, enhances efficiency, and maximizes productivity. Available on iPhone, iPad, and Mac.\",\n" +
+            "            \"votesCount\": 153,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/A6Q5G6U66ELHPP?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/firstseed-tasks?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 939735,\n" +
+            "            \"name\": \"Sahha Archetypes\",\n" +
+            "            \"tagline\": \"Segment users based on their health, lifestyle, & behavior!\",\n" +
+            "            \"description\": \"Categorize users based on long-term health, lifestyle, and behavioral trends, enabling hyper-personalized engagement and retention strategies. Archetypes are intuitive, easy-to-use labels that capture a person’s persona, health and lifestyle\",\n" +
+            "            \"votesCount\": 153,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/KFNWL2Q64CTORR?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/sahha-archetypes?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 939053,\n" +
+            "            \"name\": \"Search Copilot\",\n" +
+            "            \"tagline\": \"Work AI across platforms\",\n" +
+            "            \"description\": \"Search Copilot is the AI-powered search engine for your work. Instantly find insights from meetings, emails, chats, docs, and CRMs—all in one place. No setup hassle, privacy-first, and free. Stop searching, start finding. \uD83D\uDE80\",\n" +
+            "            \"votesCount\": 141,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/7PCPCKCIRCKM3Z?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/search-copilot?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940919,\n" +
+            "            \"name\": \"PlanetScale Metal\",\n" +
+            "            \"tagline\": \"The fastest way to run databases in AWS or GCP\",\n" +
+            "            \"description\": \"PlanetScale Metal is the fastest way to run databases in AWS or GCP. With blazing fast NVMe drives, you can unlock unlimited IOPS, ultra low latencies, and the highest throughput for your workloads.\",\n" +
+            "            \"votesCount\": 130,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/A3UUCYI5Q7MH76?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/planetscale-metal?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940792,\n" +
+            "            \"name\": \"DJI Dock 3\",\n" +
+            "            \"tagline\": \"Rise to Any Challenge\",\n" +
+            "            \"description\": \"Introducing the DJI Dock 3, DJI's First Dock Adaptable for Vehicle Mounting. The DJI Dock 3 empowers 24/7 remote operations and effortlessly adapts to various environments.\",\n" +
+            "            \"votesCount\": 125,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/GHI7E7TT5CCHW5?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/dji-dock-3?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940946,\n" +
+            "            \"name\": \"Supadex for iOS & Android\",\n" +
+            "            \"tagline\": \"Supabase data in your pocket\",\n" +
+            "            \"description\": \"The ultimate mobile dashboard for Supabase. Manage databases, track metrics, and monitor projects seamlessly, anytime, anywhere.\",\n" +
+            "            \"votesCount\": 118,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/SUQDWEYZSJKTZF?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/supadex-for-ios-android?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940774,\n" +
+            "            \"name\": \"Pod\",\n" +
+            "            \"tagline\": \"A desktop iPod that plays local music and radio\",\n" +
+            "            \"description\": \"Pod is a desktop music player which feels and looks like an iPod. Enjoy seamless navigation, haptic feedback, and stylish design, all without extra hardware. Listen to local music files, radio and soon Spotify.\",\n" +
+            "            \"votesCount\": 115,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/EYQMXQVYVKJ7YJ?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/pod-6?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940617,\n" +
+            "            \"name\": \"W3ARE v1 beta\",\n" +
+            "            \"tagline\": \"Accept payments in any currency or crypto\",\n" +
+            "            \"description\": \"Simplify international transactions with w3are. Create payment links that support multiple payment methods, currencies, and cryptocurrencies. Secure, compliant, and easy to use. Start accepting payments worldwide today!\",\n" +
+            "            \"votesCount\": 108,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/OGNMM5QABG7HLJ?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/w3are-v1-beta?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940536,\n" +
+            "            \"name\": \"FocusNudge\",\n" +
+            "            \"tagline\": \"Status bar app to nudge you back into focus!\",\n" +
+            "            \"description\": \"Stay on track in a distraction-filled world! FocusNudge gently reminds you of your main focus whenever you switch to distracting apps. This tiny menu bar app delivers perfectly timed nudges that keep you aligned with what matters. Customizable, unobtrusive.\",\n" +
+            "            \"votesCount\": 108,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/HW627AGBESAL3V?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/focusnudge?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940392,\n" +
+            "            \"name\": \"Pocket Casts\",\n" +
+            "            \"tagline\": \"No Paywalls. No Walled Gardens. Just Podcasts.\",\n" +
+            "            \"description\": \"Pocket Casts is a powerful, beautifully designed podcast player that lets you stream your podcasts from any device. With intuitive playback controls and a clean interface, it’s the best way to enjoy podcasts—now free for all.\",\n" +
+            "            \"votesCount\": 106,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": null,\n" +
+            "            \"website\": \"https://www.producthunt.com/r/IIDXSG7EVDDS64?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/pocket-casts-8?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 935408,\n" +
+            "            \"name\": \"Wispr Flow for Windows\",\n" +
+            "            \"tagline\": \"Stop typing, start speaking: 3x faster dictation on PC & Mac\",\n" +
+            "            \"description\": \"Tired of typing? Wispr Flow for Windows lets you speak naturally and see your words perfectly formatted—no extra edits, no typos. It’s the easiest way to write 3x faster across all your apps.\",\n" +
+            "            \"votesCount\": 621,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/VEQJCWAHVPXBLC?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/wispr-flow-for-windows?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 931928,\n" +
+            "            \"name\": \"No Cap\",\n" +
+            "            \"tagline\": \"World's first AI angel investor\",\n" +
+            "            \"description\": \"Time to come clean: I just invested $100k in a startup — and I'm an AI. Full announcement and video below \uD83D\uDC47\",\n" +
+            "            \"votesCount\": 458,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/RX3O54ISJJ42UG?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/no-cap?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 941045,\n" +
+            "            \"name\": \"Cuckoo\",\n" +
+            "            \"tagline\": \"Real-time AI translator for global teams\",\n" +
+            "            \"description\": \"Cuckoo is a real-time AI translator for global sales, marketing, and support. Cuckoo helps companies like Snowflake and PagerDuty talk to their global customers in Zoom in-person meetings, even in the most technical discussions.\",\n" +
+            "            \"votesCount\": 384,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/BSLO27YPI7UOLG?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/cuckoo-6?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 938270,\n" +
+            "            \"name\": \"Sherloq\",\n" +
+            "            \"tagline\": \"Create one place for all your SQL, directly on your editor\",\n" +
+            "            \"description\": \"Sherloq is a Collaborative SQL Repository. Using the AI-powered plugin, SQL users automatically save, organize, share, and document queries on-the-fly, without leaving the editor. Companies use Sherloq as the single source of truth for all their queries.\",\n" +
+            "            \"votesCount\": 358,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/M7N4WQZQIP4IOJ?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/sherloq-2?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 939950,\n" +
+            "            \"name\": \"Numeral\",\n" +
+            "            \"tagline\": \"Spend less than 5 minutes per month on sales tax compliance\",\n" +
+            "            \"description\": \"Numeral puts sales tax on autopilot for leading e-commerce and SaaS businesses, offering intelligent workflows for registration, filing, and remittance alongside white-glove customer support.\",\n" +
+            "            \"votesCount\": 339,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/EQNJ7UZ55RUAHG?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/numeral?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 929040,\n" +
+            "            \"name\": \"OG PM Agent: Beta Release\",\n" +
+            "            \"tagline\": \"Your AI Product Manager\",\n" +
+            "            \"description\": \"PM Agent is your AI Product Manager that attends all your meetings, creates detailed summaries, and converts business discussions into Product Requirement Document including acceptance criteria for every sprint in real time keeping all stakeholders aligned.\",\n" +
+            "            \"votesCount\": 247,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/HYURCPOABM6B7B?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/og-pm-agent-beta-release?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940539,\n" +
+            "            \"name\": \"Superwall\",\n" +
+            "            \"tagline\": \"Build & test mobile app paywalls without shipping updates\",\n" +
+            "            \"description\": \"Superwall is the ultimate paywall solution for mobile apps. Build and test unlimited paywall designs, pricing, and A/B experiments— all without shipping app updates. With built in analytics, reduce your time to experiment by 90% and grow revenue by 20-30%.\",\n" +
+            "            \"votesCount\": 227,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/JEBXUG7IVV3WMG?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/superwall?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940968,\n" +
+            "            \"name\": \"OpenAI Responses API and Agents SDK\",\n" +
+            "            \"tagline\": \"New tools for building agents and tools\",\n" +
+            "            \"description\": \"A new set of APIs and tools specifically designed to simplify the development of agentic applications.\",\n" +
+            "            \"votesCount\": 174,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/7ZEJQUN3IQPUZA?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/openai-responses-api-and-agents-sdk?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940914,\n" +
+            "            \"name\": \"AI Renamer\",\n" +
+            "            \"tagline\": \"Rename your files with AI\",\n" +
+            "            \"description\": \"Automatically rename your files based on their content using AI. Perfect for organizing images and documents with meaningful names.\",\n" +
+            "            \"votesCount\": 167,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/DND2CRR6HKS53X?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/ai-renamer-2?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        },\n" +
+            "        {\n" +
+            "            \"id\": 940701,\n" +
+            "            \"name\": \"Omlet for VS Code\",\n" +
+            "            \"tagline\": \"Get React component usage insights in VS Code\",\n" +
+            "            \"description\": \"Omlet is a component analytics tool for React. You can now use Omlet directly in VS Code and analyze how and where your React components (and their props) are used as you're coding — helping you confidently update, clean up and maintain your component library.\",\n" +
+            "            \"votesCount\": 163,\n" +
+            "            \"createdAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"featuredAt\": \"2025-03-12T07:01:00\",\n" +
+            "            \"website\": \"https://www.producthunt.com/r/DOVGY6PD4WK77S?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\",\n" +
+            "            \"url\": \"https://www.producthunt.com/posts/omlet-for-vs-code?utm_campaign=producthunt-api&utm_medium=api-v2&utm_source=Application%3A+dataeasy+%28ID%3A+170125%29\"\n" +
+            "        }\n" +
+            "    ]";
+
     /**
      * 获取热榜列表
      * @param rankDate 榜单日期
@@ -39,6 +376,11 @@ public class ProductHuntController {
      */
     @GetMapping("/getPosts")
     public List<PostNode> getPosts(@RequestParam @Validated @Pattern(regexp = "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$", message = "字符串格式不正确,格式:yyyy-MM-dd") String rankDate) throws JsonProcessingException {
-        return productHuntManager.getPosts(rankDate);
+//        return productHuntManager.getPosts(rankDate);
+        ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.registerModule(new JavaTimeModule());
+        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+        List<PostNode> stringList = objectMapper.readValue(content, new TypeReference<List<PostNode>>() {});
+        return stringList;
     }
 }

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä