소스 검색

【第一版开发】
1.更新表设计
2.完善代码逻辑

1.功能开发

ChenYL 10 달 전
부모
커밋
66ddd0f843
38개의 변경된 파일743개의 추가작업 그리고 260개의 파일을 삭제
  1. 14 0
      data-easy/src/main/java/com/dataeasy/server/atomic/service/ISubscriptionOrderService.java
  2. 6 0
      data-easy/src/main/java/com/dataeasy/server/atomic/service/ISubscriptionUserConfigService.java
  3. 7 0
      data-easy/src/main/java/com/dataeasy/server/atomic/service/IUserService.java
  4. 19 0
      data-easy/src/main/java/com/dataeasy/server/atomic/service/impl/SubscriptionOrderServiceImpl.java
  5. 1 0
      data-easy/src/main/java/com/dataeasy/server/atomic/service/impl/SubscriptionSourceServiceImpl.java
  6. 11 5
      data-easy/src/main/java/com/dataeasy/server/atomic/service/impl/SubscriptionUserConfigServiceImpl.java
  7. 19 4
      data-easy/src/main/java/com/dataeasy/server/atomic/service/impl/UserServiceImpl.java
  8. 2 0
      data-easy/src/main/java/com/dataeasy/server/common/pojo/JsonResponse.java
  9. 1 1
      data-easy/src/main/java/com/dataeasy/server/constant/ScheduleTaskEnum.java
  10. 1 2
      data-easy/src/main/java/com/dataeasy/server/core/config/WebMvcConfig.java
  11. 0 1
      data-easy/src/main/java/com/dataeasy/server/core/config/WxPayConfiguration.java
  12. 10 5
      data-easy/src/main/java/com/dataeasy/server/core/handler/WxMpSubscribeHandler.java
  13. 3 3
      data-easy/src/main/java/com/dataeasy/server/core/handler/WxMpUnsubscribeHandler.java
  14. 4 1
      data-easy/src/main/java/com/dataeasy/server/core/interceptor/AuthInterceptor.java
  15. 14 5
      data-easy/src/main/java/com/dataeasy/server/core/interceptor/MybatisAuditDataInterceptor.java
  16. 0 53
      data-easy/src/main/java/com/dataeasy/server/demo/TestController.java
  17. 0 23
      data-easy/src/main/java/com/dataeasy/server/demo/pay/DateTimeUtils.java
  18. 0 71
      data-easy/src/main/java/com/dataeasy/server/demo/pay/PayService.java
  19. 1 1
      data-easy/src/main/java/com/dataeasy/server/feign/ProductHuntFeign.java
  20. 5 10
      data-easy/src/main/java/com/dataeasy/server/pojo/subscription/SubscriptionUserConfigVO.java
  21. 77 0
      data-easy/src/main/java/com/dataeasy/server/service/controller/TaskOpController.java
  22. 2 2
      data-easy/src/main/java/com/dataeasy/server/service/controller/WxController.java
  23. 1 1
      data-easy/src/main/java/com/dataeasy/server/service/manager/impl/AiChatManagerImpl.java
  24. 8 0
      data-easy/src/main/java/com/dataeasy/server/service/manager/impl/SubscriptionManagerImpl.java
  25. 37 6
      data-easy/src/main/java/com/dataeasy/server/service/manager/impl/WxManagerImpl.java
  26. 27 9
      data-easy/src/main/java/com/dataeasy/server/task/AbstractTask.java
  27. 3 8
      data-easy/src/main/java/com/dataeasy/server/task/DaLeTouTask.java
  28. 3 4
      data-easy/src/main/java/com/dataeasy/server/task/IpoBondTask.java
  29. 1 1
      data-easy/src/main/java/com/dataeasy/server/task/IpoStockTask.java
  30. 10 1
      data-easy/src/main/java/com/dataeasy/server/task/ProductHuntTask.java
  31. 1 1
      data-easy/src/main/java/com/dataeasy/server/task/ShuangSeQiuTask.java
  32. 33 1
      data-easy/src/main/java/com/dataeasy/server/utiis/DateUtils.java
  33. 2 2
      doc/eclipse-codestyle.xml
  34. 33 4
      doc/sql/schema.sql
  35. 19 19
      doc/技术文档.md
  36. 12 11
      product-hunt/src/main/java/com/producthunt/server/core/interceptor/AuthInterceptor.java
  37. 346 2
      product-hunt/src/main/java/com/producthunt/server/service/controller/ProductHuntController.java
  38. 10 3
      product-hunt/src/main/java/com/producthunt/server/service/manager/impl/ProductHuntManagerImpl.java

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

@@ -1,6 +1,7 @@
 package com.dataeasy.server.atomic.service;
 
 import com.dataeasy.server.atomic.entity.SubscriptionOrder;
+import com.dataeasy.server.atomic.entity.SubscriptionSource;
 
 /**
  * @author tyuio
@@ -15,4 +16,17 @@ public interface ISubscriptionOrderService {
      * @param subscriptionOrder
      */
     void insert(SubscriptionOrder subscriptionOrder);
+
+    /**
+     * 按订单号查询订单
+     * @param orderNo
+     * @return
+     */
+    SubscriptionOrder getByOrderNo(String orderNo);
+
+    /**
+     * 根据主键更新记录
+     * @param subscriptionOrder
+     */
+    void updateById(SubscriptionOrder subscriptionOrder);
 }

+ 6 - 0
data-easy/src/main/java/com/dataeasy/server/atomic/service/ISubscriptionUserConfigService.java

@@ -25,4 +25,10 @@ public interface ISubscriptionUserConfigService {
      * @param subscriptionUserConfig
      */
     void updateById(SubscriptionUserConfig subscriptionUserConfig);
+
+    /**
+     * 新增记录
+     * @param subscriptionUserConfig
+     */
+    void insert(SubscriptionUserConfig subscriptionUserConfig);
 }

+ 7 - 0
data-easy/src/main/java/com/dataeasy/server/atomic/service/IUserService.java

@@ -34,6 +34,13 @@ public interface IUserService {
      */
     User getByMaOpenId(String maOpenId);
 
+    /**
+     * 根据公众号/服务号openId获取用户
+     * @param mpOpenId 公众号/服务号 openid
+     * @return
+     */
+    User getByMpOpenId(String mpOpenId);
+
     /**
      * 根据微信unionId获取用户
      * @param unionId

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

@@ -1,12 +1,14 @@
 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 org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import com.dataeasy.server.atomic.mapper.SubscriptionOrderMapper;
 import com.dataeasy.server.atomic.service.ISubscriptionOrderService;
+import org.springframework.util.StringUtils;
 
 /**
  * @author tyuio
@@ -25,4 +27,21 @@ public class SubscriptionOrderServiceImpl implements ISubscriptionOrderService {
         Assert.isNull(subscriptionOrder);
         subscriptionOrderMapper.insert(subscriptionOrder);
     }
+
+    @Override
+    public SubscriptionOrder getByOrderNo(String orderNo) {
+        if (!StringUtils.hasText(orderNo)) {
+            return null;
+        }
+
+        SubscriptionOrder subscriptionOrder = new SubscriptionOrder();
+        subscriptionOrder.setOrderNo(orderNo);
+        return subscriptionOrderMapper.selectOne(subscriptionOrder);
+    }
+
+    @Override
+    public void updateById(SubscriptionOrder subscriptionOrder) {
+        Assert.isNull(subscriptionOrder);
+        subscriptionOrderMapper.updateByPrimaryKeySelective(subscriptionOrder);
+    }
 }

+ 1 - 0
data-easy/src/main/java/com/dataeasy/server/atomic/service/impl/SubscriptionSourceServiceImpl.java

@@ -9,6 +9,7 @@ import org.springframework.stereotype.Service;
 import com.dataeasy.server.atomic.mapper.SubscriptionSourceMapper;
 import com.dataeasy.server.atomic.service.ISubscriptionSourceService;
 import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
 import tk.mybatis.mapper.weekend.Weekend;
 import tk.mybatis.mapper.weekend.WeekendCriteria;
 

+ 11 - 5
data-easy/src/main/java/com/dataeasy/server/atomic/service/impl/SubscriptionUserConfigServiceImpl.java

@@ -26,7 +26,7 @@ import java.util.Objects;
 public class SubscriptionUserConfigServiceImpl implements ISubscriptionUserConfigService {
 
     @Autowired
-    private SubscriptionUserConfigMapper userConfigMapper;
+    private SubscriptionUserConfigMapper subscriptionUserConfigMapper;
 
     @Override
     public List<SubscriptionUserConfig> getByCondition(SubscriptionUserConfigQuery query) {
@@ -42,18 +42,24 @@ public class SubscriptionUserConfigServiceImpl implements ISubscriptionUserConfi
             weekendCriteria.andEqualTo(SubscriptionUserConfig::getPushOption, query.getPushOption());
         }
         if (Objects.nonNull(query.getCurrentTime())) {
-            weekendCriteria.andGreaterThanOrEqualTo(SubscriptionUserConfig::getStartTime, query.getCurrentTime());
-            weekendCriteria.andLessThanOrEqualTo(SubscriptionUserConfig::getEndTime, query.getCurrentTime());
+            weekendCriteria.andLessThanOrEqualTo(SubscriptionUserConfig::getStartTime, query.getCurrentTime());
+            weekendCriteria.andGreaterThanOrEqualTo(SubscriptionUserConfig::getEndTime, query.getCurrentTime());
         }
         if (Objects.nonNull(query.getUserId())) {
             weekendCriteria.andEqualTo(SubscriptionUserConfig::getUserId, query.getUserId());
         }
-        return userConfigMapper.selectByExample(weekend);
+        return subscriptionUserConfigMapper.selectByExample(weekend);
     }
 
     @Override
     public void updateById(SubscriptionUserConfig subscriptionUserConfig) {
         Assert.isNull(subscriptionUserConfig);
-        userConfigMapper.updateByPrimaryKeySelective(subscriptionUserConfig);
+        subscriptionUserConfigMapper.updateByPrimaryKeySelective(subscriptionUserConfig);
+    }
+
+    @Override
+    public void insert(SubscriptionUserConfig subscriptionUserConfig) {
+        Assert.isNull(subscriptionUserConfig);
+        subscriptionUserConfigMapper.insertSelective(subscriptionUserConfig);
     }
 }

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

@@ -12,6 +12,7 @@ import org.springframework.util.CollectionUtils;
 import tk.mybatis.mapper.weekend.Weekend;
 import tk.mybatis.mapper.weekend.WeekendCriteria;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -49,13 +50,24 @@ public class UserServiceImpl implements IUserService {
     }
 
     @Override
-    public User getByMaOpenId(String unionId) {
-        if (StringUtils.isBlank(unionId)) {
+    public User getByMaOpenId(String maOpenId) {
+        if (StringUtils.isBlank(maOpenId)) {
+            return null;
+        }
+
+        User queryUser = new User();
+        queryUser.setMaOpenId(maOpenId);
+        return userMapper.selectOne(queryUser);
+    }
+
+    @Override
+    public User getByMpOpenId(String mpOpenId) {
+        if (StringUtils.isBlank(mpOpenId)) {
             return null;
         }
 
         User queryUser = new User();
-        queryUser.setMaOpenId(unionId);
+        queryUser.setMpOpenId(mpOpenId);
         return userMapper.selectOne(queryUser);
     }
 
@@ -88,6 +100,9 @@ public class UserServiceImpl implements IUserService {
 
         User updateUser = new User();
         updateUser.setId(userId);
-        userMapper.updateByPrimaryKeySelectiveForce(updateUser, Arrays.asList("mp_open_id"));
+        List<String> forceUpdateProperties = new ArrayList<>();
+        forceUpdateProperties.add("maOpenId");
+        forceUpdateProperties.add("mp_open_id");
+        userMapper.updateByPrimaryKeySelectiveForce(updateUser, forceUpdateProperties);
     }
 }

+ 2 - 0
data-easy/src/main/java/com/dataeasy/server/common/pojo/JsonResponse.java

@@ -4,6 +4,7 @@ import com.dataeasy.server.common.constant.ResponseCodeEnum;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
 
 import java.io.Serial;
 import java.io.Serializable;
@@ -15,6 +16,7 @@ import java.util.Date;
  * @param <T>
  */
 @Data
+@NoArgsConstructor
 @EqualsAndHashCode
 public class JsonResponse<T> implements Serializable {
 

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

@@ -30,7 +30,7 @@ public enum ScheduleTaskEnum {
      */
     public SysScheduleTaskLog buildTaskLog() {
         return SysScheduleTaskLog.builder()
-                .taskCode(ScheduleTaskEnum.DA_LE_TOU)
+                .taskCode(this)
                 .taskName(this.name)
                 .startTime(new Date())
                 .processStatus(ScheduleTaskStatusEnum.RUNNING)

+ 1 - 2
data-easy/src/main/java/com/dataeasy/server/core/config/WebMvcConfig.java

@@ -23,8 +23,7 @@ public class WebMvcConfig implements WebMvcConfigurer {
                 .excludePathPatterns(
                         "/health/info",
                         "/user/login",
-                        "/wx/mp/entry",
-                        "/wx/mp/callback",
+                        "/wx/mp/endpoint",
                         "/wx/pay/callback",
                         "/subscription/querySubscriptionSource",
                         "/subscription/querySubscriptionSourceDetail"

+ 0 - 1
data-easy/src/main/java/com/dataeasy/server/core/config/WxPayConfiguration.java

@@ -139,7 +139,6 @@ public class WxPayConfiguration {
         }
 
         private WxPayException convertException(JsonObject jsonObject) {
-            //todo 这里考虑使用新的适用于V3的异常
             JsonElement codeElement = jsonObject.get("code");
             String code = codeElement == null ? null : codeElement.getAsString();
             String message = jsonObject.get("message").getAsString();

+ 10 - 5
data-easy/src/main/java/com/dataeasy/server/core/handler/WxMpSubscribeHandler.java

@@ -3,6 +3,8 @@ package com.dataeasy.server.core.handler;
 import java.util.Map;
 import java.util.Objects;
 
+import me.chanjar.weixin.mp.api.WxMpUserService;
+import me.chanjar.weixin.mp.bean.result.WxMpUser;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -36,25 +38,28 @@ public class WxMpSubscribeHandler implements WxMpMessageHandler {
                                     Map<String, Object> context, WxMpService weixinService,
                                     WxSessionManager sessionManager) throws WxErrorException {
 
+        // 获取用户信息
+        WxMpUser wxMpUser = weixinService.getUserService().userInfo(wxMessage.getFromUser());
+
         // 查询用户是否存在,不存在则新增用户
-        User user = userService.getByUnionId(wxMessage.getUnionId());
+        User user = userService.getByUnionId(wxMpUser.getUnionId());
         if (Objects.isNull(user)) {
             // 新增
             User addUser = new User();
-            addUser.setUnionId(wxMessage.getUnionId());
-            addUser.setMpOpenId(wxMessage.getOpenId());
+            addUser.setUnionId(wxMpUser.getUnionId());
+            addUser.setMpOpenId(wxMessage.getFromUser());
             userService.insert(addUser);
         } else {
             // 更新
             User updateUser = new User();
             updateUser.setId(user.getId());
-            updateUser.setMpOpenId(wxMessage.getOpenId());
+            updateUser.setMpOpenId(wxMessage.getFromUser());
             userService.updateById(updateUser);
         }
 
         TextBuilder textBuilder = new TextBuilder();
         textBuilder.content("欢迎关注小石知数");
-        textBuilder.toUser(wxMessage.getOpenId());
+        textBuilder.toUser(wxMessage.getFromUser());
         return textBuilder.build();
     }
 }

+ 3 - 3
data-easy/src/main/java/com/dataeasy/server/core/handler/WxMpUnsubscribeHandler.java

@@ -37,14 +37,14 @@ public class WxMpUnsubscribeHandler implements WxMpMessageHandler {
                                     WxSessionManager sessionManager) throws WxErrorException {
 
         // 用户取消关注,置空openid
-        User user = userService.getByUnionId(wxMessage.getUnionId());
+        User user = userService.getByMpOpenId(wxMessage.getFromUser());
         if (Objects.nonNull(user)) {
             userService.unsetMpOpenIdByID(user.getId());
         }
 
         TextBuilder textBuilder = new TextBuilder();
-        textBuilder.content("已关注小石知数,再会!");
-        textBuilder.toUser(wxMessage.getOpenId());
+        textBuilder.content("已取消关注小石知数,再会!");
+        textBuilder.toUser(wxMessage.getFromUser());
         return textBuilder.build();
     }
 }

+ 4 - 1
data-easy/src/main/java/com/dataeasy/server/core/interceptor/AuthInterceptor.java

@@ -16,6 +16,7 @@ import org.springframework.util.StringUtils;
 import org.springframework.web.servlet.HandlerInterceptor;
 
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * @author ChenYL
@@ -53,7 +54,9 @@ public class AuthInterceptor implements HandlerInterceptor {
 
         // 校验系统中是否存在该用户
         User currentUser = userService.getById(currentUserId);
-        Assert.isNullInBusiness(currentUser, "不存在的用户");
+        if (Objects.isNull(currentUser)) {
+            throw LoginException.fail("不存在的用户,请重新登录");
+        }
 
         // 把用户信息设置如入上下文
         UserUtils.setCurrentId(currentUser.getId());

+ 14 - 5
data-easy/src/main/java/com/dataeasy/server/core/interceptor/MybatisAuditDataInterceptor.java

@@ -74,11 +74,20 @@ public class MybatisAuditDataInterceptor implements Interceptor {
             // 批量更新
             if (obj instanceof Map) {
                 Map<?, ?> map = (Map<?, ?>) obj;
-                List<?> list = (List<?>) map.get("list");
-                if (list != null) {
-                    for (Object item : list) {
-                        BaseEntity entity = ((BaseEntity) item);
-                        assignUpdateDefaultValue(entity, currentUserId, currentTime);
+                if (map.containsKey("list")) {
+                    List<?> list = (List<?>) map.get("list");
+                    if (list != null) {
+                        for (Object item : list) {
+                            BaseEntity entity = ((BaseEntity) item);
+                            assignUpdateDefaultValue(entity, currentUserId, currentTime);
+                        }
+                    }
+                } else {
+                    for (Object o : map.values()) {
+                        if (o instanceof BaseEntity) {
+                            BaseEntity entity = ((BaseEntity) o);
+                            assignUpdateDefaultValue(entity, currentUserId, currentTime);
+                        }
                     }
                 }
             }

+ 0 - 53
data-easy/src/main/java/com/dataeasy/server/demo/TestController.java

@@ -1,53 +0,0 @@
-package com.dataeasy.server.demo;
-
-import com.dataeasy.server.common.annotation.IgnoreResponseWrapper;
-import com.dataeasy.server.demo.pay.PayService;
-import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-import com.github.binarywang.wxpay.exception.WxPayException;
-
-import cn.binarywang.wx.miniapp.api.WxMaService;
-import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
-import jakarta.servlet.http.HttpServletRequest;
-import lombok.extern.slf4j.Slf4j;
-import me.chanjar.weixin.common.error.WxErrorException;
-
-/**
- * @author tyuio
- * @version 1.0.0
- * @description TODO
- * @date 2025/3/3 14:35
- */
-@Slf4j
-@RestController
-public class TestController {
-
-    @Autowired
-    private PayService payService;
-
-    @GetMapping("/test/t2")
-    public WxPayUnifiedOrderV3Result.JsapiResult t2() throws WxPayException {
-        WxPayUnifiedOrderV3Result.JsapiResult result = payService.createDdzqOrder();
-        return result;
-    }
-
-    @IgnoreResponseWrapper
-    @PostMapping("/pay/ddzqOrder")
-    public String ddzqOrder(HttpServletRequest request) {
-        return payService.ddzqOrder(request);
-    }
-
-    @Autowired
-    private WxMaService wxMaService;
-
-    @GetMapping("/test/login")
-    public String login(@RequestParam String code) throws WxErrorException {
-        WxMaJscode2SessionResult wxMaJscode2SessionResult = wxMaService.jsCode2SessionInfo(code);
-        return wxMaJscode2SessionResult.getOpenid();
-    }
-}

+ 0 - 23
data-easy/src/main/java/com/dataeasy/server/demo/pay/DateTimeUtils.java

@@ -1,23 +0,0 @@
-package com.dataeasy.server.demo.pay;
-
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-
-/**
- * @author tyuio
- * @version 1.0.0
- * @description TODO
- * @date 2025/3/4 14:52
- */
-public class DateTimeUtils {
-
-    public static final String DATE_FORMAT_yyyyMMddHHmmss = "yyyyMMddHHmmss";
-    public static final DateTimeFormatter sdf = DateTimeFormatter.ofPattern(DATE_FORMAT_yyyyMMddHHmmss);
-
-    public static String yyyyMMddHHmmssDateTime(int minute) {
-        LocalDateTime now = LocalDateTime.now();
-        LocalDateTime localDateTime = now.plusMinutes(minute);
-        return sdf.format(localDateTime);
-
-    }
-}

+ 0 - 71
data-easy/src/main/java/com/dataeasy/server/demo/pay/PayService.java

@@ -1,71 +0,0 @@
-package com.dataeasy.server.demo.pay;
-
-import org.apache.commons.io.IOUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
-import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Response;
-import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
-import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
-import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
-import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
-import com.github.binarywang.wxpay.exception.WxPayException;
-import com.github.binarywang.wxpay.service.WxPayService;
-
-import jakarta.servlet.http.HttpServletRequest;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * @author tyuio
- * @version 1.0.0
- * @description TODO
- * @date 2025/3/4 14:46
- */
-@Slf4j
-@Service
-public class PayService {
-
-    @Autowired
-    private WxPayService wxPayService;
-
-    public WxPayUnifiedOrderV3Result.JsapiResult createDdzqOrder()throws WxPayException {
-        WxPayUnifiedOrderV3Request wxOrderV3 = new WxPayUnifiedOrderV3Request();
-        wxOrderV3.setDescription("测试商品得描述书");
-        wxOrderV3.setOutTradeNo("20250305000007");
-        wxOrderV3.setTimeExpire("2025-03-05T16:34:56+08:00");
-        wxOrderV3.setNotifyUrl("http://hcedwe.natappfree.cc/pay/ddzqOrder");
-        WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
-        WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
-        payer.setOpenid("oSoRX7Ll01EB7XfsYt5aVrF_KfLU");
-        wxOrderV3.setPayer(payer);
-        amount.setTotal(1);
-        wxOrderV3.setAmount(amount);
-        return wxPayService.createOrderV3(TradeTypeEnum.JSAPI, wxOrderV3);
-    }
-
-    public String ddzqOrder(HttpServletRequest request) {
-
-        try {
-            String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
-            SignatureHeader signatureHeader = new SignatureHeader();
-            signatureHeader.setNonce(request.getHeader("Wechatpay-Nonce"));
-            signatureHeader.setSignature(request.getHeader("Wechatpay-Signature"));
-            signatureHeader.setSerial(request.getHeader("Wechatpay-Serial"));
-            signatureHeader.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));
-            WxPayNotifyV3Result payResult1 = wxPayService.parseOrderNotifyV3Result(xmlResult, signatureHeader);
-            WxPayNotifyV3Result.DecryptNotifyResult payResult = payResult1.getResult();
-            String masterOrderId = payResult.getOutTradeNo();//内部订单记录ID
-            String wxTransactionId = payResult.getTransactionId();//商户号订单ID
-            log.info("内部订单ID:{}", masterOrderId);
-            log.info("商户号订单ID:{}", wxTransactionId);
-            log.info("微信真实支付金额:{}", payResult.getAmount());
-            log.info("微信支付,回调处理成功");
-            return WxPayNotifyV3Response.success("处理成功!");
-        } catch (Exception e) {
-            e.printStackTrace();
-            return WxPayNotifyV3Response.fail(e.getMessage());
-        }
-
-    }
-}

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

@@ -20,6 +20,6 @@ public interface ProductHuntFeign {
      * 获取产品热榜TOP30
      * @return
      */
-    @GetMapping("/getTop30Posts")
+    @GetMapping("/producthunt/getTop30Posts")
     JsonResponse<List<PostNode>> getTop30Posts();
 }

+ 5 - 10
data-easy/src/main/java/com/dataeasy/server/pojo/subscription/SubscriptionUserConfigVO.java

@@ -35,19 +35,14 @@ public class SubscriptionUserConfigVO {
      */
     private Long subscriptionSourceId;
 
-    /**
-     * 订阅开始时间
-     */
-    private Timestamp startTime;
-
-    /**
-     * 订阅结束时间
-     */
-    private Timestamp endTime;
-
     /**
      * 消息推送选项(ENABLE-开启、DISABLE-关闭)
      * @see PushOptionEnum
      */
     private PushOptionEnum pushOption;
+
+    /**
+     * 剩余有效期
+     */
+    private Long remainingDays;
 }

+ 77 - 0
data-easy/src/main/java/com/dataeasy/server/service/controller/TaskOpController.java

@@ -0,0 +1,77 @@
+package com.dataeasy.server.service.controller;
+
+import com.dataeasy.server.task.DaLeTouTask;
+import com.dataeasy.server.task.IpoBondTask;
+import com.dataeasy.server.task.IpoStockTask;
+import com.dataeasy.server.task.ProductHuntTask;
+import com.dataeasy.server.task.ShuangSeQiuTask;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @date 2025/3/13 8:53
+ * @description 定时任务运维操作接口
+ */
+@RestController
+@RequestMapping("/taskOp")
+public class TaskOpController {
+
+    @Autowired
+    private DaLeTouTask daLeTouTask;
+
+    @Autowired
+    private ShuangSeQiuTask shuangSeQiuTask;
+
+    @Autowired
+    private ProductHuntTask productHuntTask;
+
+    @Autowired
+    private IpoBondTask ipoBondTask;
+
+    @Autowired
+    private IpoStockTask ipoStockTask;
+
+    /**
+     * 大乐透定时任务
+     */
+    @GetMapping("daLeTouTask")
+    public void daLeTouTask() {
+        daLeTouTask.exec();
+    }
+
+    /**
+     * 双色球定时任务
+     */
+    @GetMapping("shuangSeQiuTask")
+    public void shuangSeQiuTask() {
+        shuangSeQiuTask.exec();
+    }
+
+    /**
+     * ProductHunt热榜定时任务
+     */
+    @GetMapping("productHuntTask")
+    public void productHuntTask() {
+        productHuntTask.exec();
+    }
+
+    /**
+     * 新债定时任务
+     */
+    @GetMapping("ipoBondTask")
+    public void ipoBondTask() {
+        ipoBondTask.exec();
+    }
+
+    /**
+     * 新股定时任务
+     */
+    @GetMapping("ipoStockTask")
+    public void ipoStockTask() {
+        ipoStockTask.exec();
+    }
+}

+ 2 - 2
data-easy/src/main/java/com/dataeasy/server/service/controller/WxController.java

@@ -35,7 +35,7 @@ public class WxController {
      * @return 接入成功返回 echoStr 的值,否则随便返回
      */
     @IgnoreResponseWrapper
-    @GetMapping("/mp/entry")
+    @GetMapping("/mp/endpoint")
     public String mpEntry(@RequestParam("signature") String signature, @RequestParam("timestamp") String timestamp,
         @RequestParam("nonce") String nonce, @RequestParam("echostr") String echoStr) {
         return wxManager.mpEntry(signature, timestamp, nonce, echoStr);
@@ -54,7 +54,7 @@ public class WxController {
      * @return xml
      */
     @IgnoreResponseWrapper
-    @PostMapping("/mp/callback")
+    @PostMapping("/mp/endpoint")
     public String mpCallback(@RequestBody String requestBody, @RequestParam("signature") String signature,
         @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce,
         @RequestParam("openid") String openid, @RequestParam(name = "encrypt_type", required = false) String encType,

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

@@ -9,7 +9,7 @@ import org.springframework.core.io.Resource;
 /**
  * @author tyuio
  * @version 1.0.0
- * @description TODO
+ * @description AI大模型 服务类
  * @date 2025/3/5 12:31
  */
 public class AiChatManagerImpl implements IAiChatManager {

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

@@ -25,6 +25,8 @@ import org.springframework.stereotype.Component;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
 
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -131,6 +133,12 @@ public class SubscriptionManagerImpl implements ISubscriptionManager {
                 subscriptionUserConfigVO.setTitle(subscriptionSource.getTitle());
                 subscriptionUserConfigVO.setSubTitle(subscriptionSource.getSubTitle());
             }
+
+            LocalDateTime startTime = userConfig.getStartTime().toLocalDateTime();
+            LocalDateTime endTime = userConfig.getEndTime().toLocalDateTime();
+            long remainingDays = ChronoUnit.DAYS.between(startTime, endTime);
+            subscriptionUserConfigVO.setRemainingDays(remainingDays);
+
             return subscriptionUserConfigVO;
         }).toList();
     }

+ 37 - 6
data-easy/src/main/java/com/dataeasy/server/service/manager/impl/WxManagerImpl.java

@@ -1,5 +1,14 @@
 package com.dataeasy.server.service.manager.impl;
 
+import com.dataeasy.server.atomic.entity.SubscriptionOrder;
+import com.dataeasy.server.atomic.entity.SubscriptionUserConfig;
+import com.dataeasy.server.atomic.service.ISubscriptionOrderService;
+import com.dataeasy.server.atomic.service.ISubscriptionSourceService;
+import com.dataeasy.server.atomic.service.ISubscriptionUserConfigService;
+import com.dataeasy.server.common.exception.BusinessException;
+import com.dataeasy.server.constant.PaymentStatusEnum;
+import com.dataeasy.server.constant.PushOptionEnum;
+import com.dataeasy.server.utiis.DateUtils;
 import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
 import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Response;
 import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
@@ -19,6 +28,9 @@ import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
 import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Optional;
 
 /**
  * @author tyuio
@@ -39,6 +51,12 @@ public class WxManagerImpl implements IWxManager {
     @Autowired
     private WxPayService wxPayService;
 
+    @Autowired
+    private ISubscriptionOrderService subscriptionOrderService;
+
+    @Autowired
+    private ISubscriptionUserConfigService subscriptionUserConfigService;
+
     @Override
     public String mpEntry(String signature, String timestamp, String nonce, String echoStr) {
         log.info("微信公众号/服务号接入传递的参数 signature:[{}],timestamp:[{}],nonce:[{}],echostr:[{}]", signature, timestamp, nonce,
@@ -91,6 +109,7 @@ public class WxManagerImpl implements IWxManager {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public String payCallback(HttpServletRequest request) {
         try {
             // 获取请求体
@@ -108,13 +127,25 @@ public class WxManagerImpl implements IWxManager {
             log.info("回调结果:{}", payResult1);
 
             WxPayNotifyV3Result.DecryptNotifyResult payResult = payResult1.getResult();
-            String masterOrderId = payResult.getOutTradeNo();//内部订单记录ID
-            String wxTransactionId = payResult.getTransactionId();//商户号订单ID
+
+            // 查找订单
+            SubscriptionOrder subscriptionOrder = Optional.ofNullable(subscriptionOrderService.getByOrderNo(payResult.getOutTradeNo())).orElseThrow(() -> BusinessException.fail(String.format("根据订单号:{} 查找不到订单", payResult.getOutTradeNo())));
+            // 更新订单状态
+            SubscriptionOrder updateSubscriptionOrder = new SubscriptionOrder();
+            updateSubscriptionOrder.setId(subscriptionOrder.getId());
+            updateSubscriptionOrder.setPaymentStatus(PaymentStatusEnum.SUCCESS);
+            subscriptionOrderService.updateById(updateSubscriptionOrder);
+
+            // 支付成功创建用户配置
+            SubscriptionUserConfig addSubscriptionUserConfig = new SubscriptionUserConfig();
+            addSubscriptionUserConfig.setPushOption(PushOptionEnum.ENABLED);
+            addSubscriptionUserConfig.setUserId(subscriptionOrder.getUserId());
+            addSubscriptionUserConfig.setSubscriptionSourceId(subscriptionOrder.getSubscriptionSourceId());
+            addSubscriptionUserConfig.setStartTime(DateUtils.getCurrentTimestamp());
+            addSubscriptionUserConfig.setEndTime(DateUtils.getTimestampSpecial(subscriptionOrder.getSubscriptionDuration()));
+            subscriptionUserConfigService.insert(addSubscriptionUserConfig);
+
             // TODO 这里要回写订单信息
-            log.info("内部订单ID:{}", masterOrderId);
-            log.info("商户号订单ID:{}", wxTransactionId);
-            log.info("微信真实支付金额:{}", payResult.getAmount());
-            log.info("微信支付,回调处理成功");
             return WxPayNotifyV3Response.success("处理成功!");
         } catch (Exception e) {
             log.error("微信支付回调处理失败", e);

+ 27 - 9
data-easy/src/main/java/com/dataeasy/server/task/AbstractTask.java

@@ -1,6 +1,7 @@
 package com.dataeasy.server.task;
 
 import java.sql.Timestamp;
+import java.util.Date;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -61,7 +62,7 @@ public abstract class AbstractTask {
     protected WxMessageProperties wxMessageProperties;
 
     /**
-     * 定时任务执行主逻辑
+     * 定时任务执行入口
      */
     public void executeMain() {
         ScheduleTaskEnum task = getTaskEnum();
@@ -70,6 +71,31 @@ public abstract class AbstractTask {
         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());
@@ -134,14 +160,6 @@ public abstract class AbstractTask {
                     .build();
             wxMpManager.sendTemplateMessage(mpTemplateMessage);
         }
-
-        // 更新任务执行记录
-        SysScheduleTaskLog updateTaskLog = new SysScheduleTaskLog();
-        updateTaskLog.setId(addTaskLog.getId());
-        updateTaskLog.setProcessStatus(ScheduleTaskStatusEnum.SUCCESS);
-        taskLogService.updateById(updateTaskLog);
-
-        log.info("======= {} 执行结束 =======", task.getName());
     }
 
     /**

+ 3 - 8
data-easy/src/main/java/com/dataeasy/server/task/DaLeTouTask.java

@@ -33,7 +33,7 @@ public class DaLeTouTask extends AbstractHzApiTask {
     @Autowired
     private IDataDaLeTouService daLeTouService;
 
-    @Scheduled(cron = "30 21 * * 1,3,6 *")
+//    @Scheduled(cron = "30 21 * * 1,3,6 *")
     @Transactional(rollbackFor = Exception.class)
     public void exec() {
         executeMain();
@@ -68,13 +68,8 @@ public class DaLeTouTask extends AbstractHzApiTask {
     public TemplateMessage getTemplateMessage() {
         // 这是模板所需数据
         List<WxMpTemplateData> templateDataList = new ArrayList();
-        templateDataList.add(new WxMpTemplateData("first", "预约成功"));
-        templateDataList.add(new WxMpTemplateData("keyword1", "测试111"));
-        templateDataList.add(new WxMpTemplateData("keyword2","测试2222"));
-        templateDataList.add(new WxMpTemplateData("keyword3","测试333"));
-        templateDataList.add(new WxMpTemplateData("keyword4","测试4444"));
-        templateDataList.add(new WxMpTemplateData("remark","测试备注"));
-
+        templateDataList.add(new WxMpTemplateData("thing76", "大乐透开奖结果-25031期"));
+        templateDataList.add(new WxMpTemplateData("time4", "2025-03-13"));
         return TemplateMessage.builder()
                 .url("www.baidu.com")
                 .templateId(wxMessageProperties.getTemplateId())

+ 3 - 4
data-easy/src/main/java/com/dataeasy/server/task/IpoBondTask.java

@@ -6,6 +6,7 @@ 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;
@@ -39,7 +40,7 @@ public class IpoBondTask extends AbstractTask {
     @Autowired
     private IDataIpoBondService ipoBondService;
 
-    @Scheduled(cron = "45 8 * * * *")
+//    @Scheduled(cron = "45 8 * * * *")
     @Transactional(rollbackFor = Exception.class)
     public void exec() {
         executeMain();
@@ -53,9 +54,7 @@ public class IpoBondTask extends AbstractTask {
     @Override
     public void fetchData() {
         // 拉取数据
-        LocalDate today = LocalDate.now();
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
-        String todayStr = sdf.format(today);
+        String todayStr = DateUtils.YYYYMMDD_FORMATTER.format(LocalDate.now());
         FinanceRequest financeRequest = new FinanceRequest();
         financeRequest.setStartDate(todayStr);
         financeRequest.setEndDate(todayStr);

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

@@ -37,7 +37,7 @@ public class IpoStockTask extends AbstractTask {
     @Autowired
     private IDataIpoStockService ipoStockService;
 
-    @Scheduled(cron = "45 8 * * * *")
+//    @Scheduled(cron = "45 8 * * * *")
     @Transactional(rollbackFor = Exception.class)
     public void exec() {
         executeMain();

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

@@ -1,5 +1,6 @@
 package com.dataeasy.server.task;
 
+import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
@@ -41,7 +42,7 @@ public class ProductHuntTask extends AbstractTask {
     @Autowired
     private IDataProductHuntPostService productHuntPostService;
 
-    @Scheduled(cron = "0 16 * * * *")
+//    @Scheduled(cron = "0 16 * * * *")
     @Transactional(rollbackFor = Exception.class)
     public void exec() {
         executeMain();
@@ -80,6 +81,14 @@ public class ProductHuntTask extends AbstractTask {
             BeanUtils.copyProperties(v, post);
             post.setRankDate(rankDate);
             post.setRankNum(rankNum.getAndIncrement());
+            post.setId(null);
+            post.setPostId(v.getId());
+            if (Objects.nonNull(v.getCreatedAt())) {
+                post.setCreatedAt(Date.from(v.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant()));
+            }
+            if (Objects.nonNull(v.getFeaturedAt())) {
+                post.setFeaturedAt(Date.from(v.getFeaturedAt().atZone(ZoneId.systemDefault()).toInstant()));
+            }
             return post;
         }).collect(Collectors.toList());
 

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

@@ -33,7 +33,7 @@ public class ShuangSeQiuTask extends AbstractHzApiTask {
     @Autowired
     private IDataShuangSeQiuService shuangSeQiuService;
 
-    @Scheduled(cron = "40 21 * * 2,4,7 *")
+//    @Scheduled(cron = "40 21 * * 2,4,7 *")
     @Transactional(rollbackFor = Exception.class)
     public void exec() {
         executeMain();

+ 33 - 1
data-easy/src/main/java/com/dataeasy/server/utiis/DateUtils.java

@@ -1,5 +1,7 @@
 package com.dataeasy.server.utiis;
 
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 
 /**
@@ -10,6 +12,11 @@ import java.time.format.DateTimeFormatter;
  */
 public class DateUtils {
 
+    /**
+     * 日期格式化器,格式:yyyyMMdd
+     */
+    public static final DateTimeFormatter YYYYMMDD_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
+
     /**
      * 日期格式化器,格式:yyMMddHHmmss
      */
@@ -18,5 +25,30 @@ public class DateUtils {
     /**
      * 日期格式化器,格式:yyyy-MM-ddTHH:mm:ss+08:00
      */
-    public static final DateTimeFormatter YYYYMMDDTHHMMSS_OFFSET_ZONE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss+08:00");
+    public static final DateTimeFormatter YYYYMMDDTHHMMSS_OFFSET_ZONE_FORMATTER =
+        DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss+08:00");
+
+    /**
+     * 获取当前时间
+     * 
+     * @return
+     */
+    public static Timestamp getCurrentTimestamp() {
+        return new Timestamp(System.currentTimeMillis());
+    }
+
+    /**
+     * 获取在当前时间时间的基础上加上指定天数的时间,并且时分秒为23:59:59
+     * @param plusDays 加的天数
+     * @return
+     */
+    public static Timestamp getTimestampSpecial(Integer plusDays) {
+        LocalDateTime adjustedDateTime = LocalDateTime.now()
+                .plusDays(plusDays)
+                .withHour(23)
+                .withMinute(59)
+                .withSecond(59)
+                .withNano(999999999);
+        return Timestamp.valueOf(adjustedDateTime);
+    }
 }

+ 2 - 2
doc/eclipse-codestyle.xml

@@ -363,7 +363,7 @@
         <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="do not insert"/>
         <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
         <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="insert"/>
-        <!--Idea可以通过Wrap Always实现 TODO-->
+        <!--Idea可以通过Wrap Always实现 -->
         <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
         <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
         <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
@@ -593,7 +593,7 @@
         <!--Java:KEEP_SIMPLE_BLOCKS_IN_ONE_LINE-->
         <setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
 
-        <!--Java:CLASS_BRACE_STYLE,统一使用end_of_line TODO-->
+        <!--Java:CLASS_BRACE_STYLE,统一使用end_of_line -->
         <setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
         <setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
 

+ 33 - 4
doc/sql/schema.sql

@@ -360,7 +360,36 @@ ALTER TABLE data_easy.subscription_source CHANGE paid_option paid_option varchar
 ALTER TABLE data_easy.subscription_source MODIFY COLUMN paid_option varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT 'PAID' NOT NULL COMMENT '付费选项(FREE-免费、PAID-付费)';
 ALTER TABLE data_easy.subscription_order MODIFY COLUMN order_no varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '订单号(格式:3位业务编码 + YYYYMMDD + 3位序列号)';
 ALTER TABLE data_easy.subscription_order MODIFY COLUMN order_no varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '订单号(格式:3位业务编码 + YYYYMMDD + 3位序列号)';
-
-
-
-
+ALTER TABLE data_easy.sys_schedule_task_log MODIFY COLUMN error_message varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '错误信息';
+ALTER TABLE data_easy.data_ipo_bond MODIFY COLUMN planned_issue_amount decimal(22,4) DEFAULT 0.0000 NULL COMMENT '计划发行总量';
+ALTER TABLE data_easy.data_ipo_bond MODIFY COLUMN actual_issue_amount decimal(22,4) DEFAULT 0.0000 NULL COMMENT '实际发行总量';
+ALTER TABLE data_easy.data_ipo_bond MODIFY COLUMN issue_par_value decimal(20,2) DEFAULT 0.00 NULL COMMENT '发行面值';
+ALTER TABLE data_easy.data_ipo_bond MODIFY COLUMN issue_price decimal(20,2) DEFAULT 0.00 NULL COMMENT '发行价格';
+ALTER TABLE data_easy.data_ipo_bond MODIFY COLUMN initial_conversion_price decimal(20,2) DEFAULT 0.00 NULL COMMENT '初始转股价格';
+ALTER TABLE data_easy.data_ipo_bond MODIFY COLUMN online_subscription_max decimal(20,2) DEFAULT 0.00 NULL COMMENT '网上申购数量上限';
+ALTER TABLE data_easy.data_ipo_bond MODIFY COLUMN online_subscription_min decimal(20,2) DEFAULT 0.00 NULL COMMENT '网上申购数量下限';
+ALTER TABLE data_easy.data_ipo_bond MODIFY COLUMN online_subscription_unit decimal(20,2) DEFAULT 0.00 NULL COMMENT '网上申购单位';
+ALTER TABLE data_easy.data_ipo_bond MODIFY COLUMN allotment_price decimal(20,2) DEFAULT 0.00 NULL COMMENT '配售价格';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN total_issued decimal(22,4) DEFAULT 0.0000 NULL COMMENT '发行总数';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN online_issued decimal(22,4) DEFAULT 0.0000 NULL COMMENT '网上发行';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN max_market_value_for_subscription decimal(22,4) DEFAULT 0.0000 NULL COMMENT '顶格申购需配市值';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN subscription_limit int DEFAULT 0 NULL COMMENT '申购上限';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN issue_price decimal(20,2) DEFAULT 0.00 NULL COMMENT '发行价格';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN latest_price decimal(20,2) DEFAULT 0.00 NULL COMMENT '最新价';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN first_day_closing_price decimal(20,2) DEFAULT 0.00 NULL COMMENT '首日收盘价';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN subscription_date date NULL COMMENT '申购日期';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN winning_number_announcement_date date NULL COMMENT '中签号公布日';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN payment_date_for_winning date NULL COMMENT '中签缴款日期';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN listing_date date NULL COMMENT '上市日期';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN issue_p_e_ratio decimal(5,2) DEFAULT 0.00 NULL COMMENT '发行市盈率';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN industry_p_e_ratio decimal(5,2) DEFAULT 0.00 NULL COMMENT '行业市盈率';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN winning_rate decimal(11,10) DEFAULT 0.0000000000 NULL COMMENT '中签率';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN cumulative_bid_multiple decimal(20,2) DEFAULT 0.00 NULL COMMENT '询价累计报价倍数';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN bidding_firms_count int DEFAULT 0 NULL COMMENT '配售对象报价家数';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN increase_rate decimal(20,2) DEFAULT 0.00 NULL COMMENT '涨幅';
+ALTER TABLE data_easy.data_ipo_stock MODIFY COLUMN profit_rer_winning_lot decimal(20,2) DEFAULT 0.00 NULL COMMENT '每中一签获利';
+ALTER TABLE data_easy.data_product_hunt_post CHANGE createdAt created_at timestamp NULL COMMENT '帖子的创建日期和时间';
+ALTER TABLE data_easy.data_product_hunt_post CHANGE featuredAt featured_at timestamp NULL COMMENT '帖子被特色展示的日期和时间';
+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 '用户昵称';

+ 19 - 19
doc/技术文档.md

@@ -132,9 +132,9 @@
 | name             | varchar(200)  | 帖子名称                           |
 | tagline          | varchar(300)  | 帖子的标语                         |
 | description      | varchar(1000) | 帖子信息                           |
-| votesCount       | int           | 投票数                             |
-| createdAt        | timestamp     | 帖子的创建日期和时间               |
-| featuredAt       | timestamp     | 帖子被特色展示的日期和时间         |
+| votes_count      | int           | 投票数                             |
+| created_at       | timestamp     | 帖子的创建日期和时间               |
+| featured_at      | timestamp     | 帖子被特色展示的日期和时间         |
 | website          | varchar(500)  | 指向该帖子网站的重定向 URL         |
 | url              | varchar(500)  | 帖子URL                            |
 | created_by       | bigint        | 创建人                             |
@@ -400,22 +400,22 @@
 
 表名:sys_schedule_task_log
 
-| 字段             | 类型         | 描述                                                |
-| ---------------- | ------------ | --------------------------------------------------- |
-| id               | bigint       | 主键                                                |
-| task_code        | varchar(100) | 定时任务编码                                        |
-| task_name        | varchar(100) | 定时任务名称                                        |
-| launch_method    | varchar(10)  | 启动方式(AUTO-自动,MANUAL-手动)                  |
-| start_time       | timestamp    | 任务开始时间                                        |
-| end_time         | timestamp    | 任务结束时间                                        |
-| process_status   | varchar(10)  | 执行状态(RUNNING-执行中,SUCCESS-成功,FAIL-失败) |
-| error_message    | varchar(300) | 错误信息                                            |
-| created_by       | bigint       | 创建人                                              |
-| creation_time    | timestamp    | 创建时间                                            |
-| last_updated_by  | bigint       | 最后更新人                                          |
-| last_update_time | timestamp    | 最后更新时间                                        |
-| version          | bigint       | 版本号                                              |
-| delete_flag      | tinyint      | 逻辑删除标志(0-未删除,1-已删除)                  |
+| 字段             | 类型          | 描述                                                |
+| ---------------- | ------------- | --------------------------------------------------- |
+| id               | bigint        | 主键                                                |
+| task_code        | varchar(100)  | 定时任务编码                                        |
+| task_name        | varchar(100)  | 定时任务名称                                        |
+| launch_method    | varchar(10)   | 启动方式(AUTO-自动,MANUAL-手动)                  |
+| start_time       | timestamp     | 任务开始时间                                        |
+| end_time         | timestamp     | 任务结束时间                                        |
+| process_status   | varchar(10)   | 执行状态(RUNNING-执行中,SUCCESS-成功,FAIL-失败) |
+| error_message    | varchar(5000) | 错误信息                                            |
+| created_by       | bigint        | 创建人                                              |
+| creation_time    | timestamp     | 创建时间                                            |
+| last_updated_by  | bigint        | 最后更新人                                          |
+| last_update_time | timestamp     | 最后更新时间                                        |
+| version          | bigint        | 版本号                                              |
+| delete_flag      | tinyint       | 逻辑删除标志(0-未删除,1-已删除)                  |
 
 
 

+ 12 - 11
product-hunt/src/main/java/com/producthunt/server/core/interceptor/AuthInterceptor.java

@@ -33,18 +33,19 @@ public class AuthInterceptor implements HandlerInterceptor {
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
             throws Exception {
 
+        // TODO 这里要修改
         // 判断请求头中是否有token
-        String apiKeyStr = request.getHeader("Authorization");
-        if (!StringUtils.hasText(apiKeyStr)) {
-            throw LoginException.fail("登录校验异常,原因:没有token凭据");
-        }
-
-        // TODO 这个加解密部分要加点变化的内容
-        String apiKey = encryptor.decrypt(apiKeyStr);
-        if (!bizConfig.getApiKey().equals(apiKey)) {
-            log.warn("登录校验失败,apiKey不符,apiKey:{}", apiKey);
-            throw LoginException.fail("登录校验失败,apiKey不符");
-        }
+//        String apiKeyStr = request.getHeader("Authorization");
+//        if (!StringUtils.hasText(apiKeyStr)) {
+//            throw LoginException.fail("登录校验异常,原因:没有token凭据");
+//        }
+//
+//        // TODO 这个加解密部分要加点变化的内容
+//        String apiKey = encryptor.decrypt(apiKeyStr);
+//        if (!bizConfig.getApiKey().equals(apiKey)) {
+//            log.warn("登录校验失败,apiKey不符,apiKey:{}", apiKey);
+//            throw LoginException.fail("登录校验失败,apiKey不符");
+//        }
 
         return true;
     }

+ 346 - 2
product-hunt/src/main/java/com/producthunt/server/service/controller/ProductHuntController.java

@@ -1,5 +1,11 @@
 package com.producthunt.server.service.controller;
 
+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.databind.util.JSONPObject;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import com.producthunt.server.dto.PostNode;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -26,8 +32,346 @@ 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" +
+            "    ]";
+
     @GetMapping("/getTop30Posts")
-    public List<PostNode> getTop30Posts() {
-        return productHuntManager.getTop30Posts();
+    public List<PostNode> getTop30Posts() throws JsonProcessingException {
+//        return productHuntManager.getTop30Posts();
+        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;
     }
 }

+ 10 - 3
product-hunt/src/main/java/com/producthunt/server/service/manager/impl/ProductHuntManagerImpl.java

@@ -16,6 +16,7 @@ import org.springframework.util.CollectionUtils;
 
 import java.text.SimpleDateFormat;
 import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -29,6 +30,11 @@ import java.util.List;
 @Component
 public class ProductHuntManagerImpl implements IProductHuntManager {
 
+    /**
+     * 日期格式化器,格式:yyyyMMdd
+     */
+    public static final DateTimeFormatter YYYY_MM_DD_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
     @Autowired
     private ProductHuntFeign productHuntFeign;
 
@@ -71,9 +77,7 @@ public class ProductHuntManagerImpl implements IProductHuntManager {
     @Override
     public List<PostNode> getTop30Posts() {
         // 获取查询的日期范围
-        LocalDate today = LocalDate.now();
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
-        String dateStr = sdf.format(today);
+        String dateStr = YYYY_MM_DD_FORMATTER.format(LocalDate.now().minusDays(1));
 
         // 构造GraphQl客户端
         String accessToken = getAccessToken();
@@ -104,6 +108,9 @@ public class ProductHuntManagerImpl implements IProductHuntManager {
             }
             hasNextPage = postResponse.getPageInfo().getHasNextPage();
         }
+        if (CollectionUtils.isEmpty(posts)) {
+            return List.of();
+        }
         return posts.subList(0, 30);
     }
 }