瀏覽代碼

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

ChenYL 11 月之前
父節點
當前提交
14bb68251b
共有 37 個文件被更改,包括 758 次插入226 次删除
  1. 4 2
      data-easy/src/main/java/com/dataeasy/server/atomic/entity/SubscriptionOrder.java
  2. 5 12
      data-easy/src/main/java/com/dataeasy/server/atomic/entity/SubscriptionPlan.java
  3. 8 0
      data-easy/src/main/java/com/dataeasy/server/atomic/entity/SubscriptionSource.java
  4. 7 0
      data-easy/src/main/java/com/dataeasy/server/atomic/service/ISubscriptionOrderService.java
  5. 7 0
      data-easy/src/main/java/com/dataeasy/server/atomic/service/ISubscriptionPlanService.java
  6. 8 1
      data-easy/src/main/java/com/dataeasy/server/atomic/service/impl/SubscriptionOrderServiceImpl.java
  7. 9 0
      data-easy/src/main/java/com/dataeasy/server/atomic/service/impl/SubscriptionPlanServiceImpl.java
  8. 22 0
      data-easy/src/main/java/com/dataeasy/server/constant/OrderTypeEnum.java
  9. 27 0
      data-easy/src/main/java/com/dataeasy/server/constant/PaymentStatusEnum.java
  10. 9 3
      data-easy/src/main/java/com/dataeasy/server/core/config/BizConfig.java
  11. 28 0
      data-easy/src/main/java/com/dataeasy/server/core/config/CacheConfig.java
  12. 6 5
      data-easy/src/main/java/com/dataeasy/server/core/config/FeignConfig.java
  13. 9 1
      data-easy/src/main/java/com/dataeasy/server/core/config/WebMvcConfig.java
  14. 177 0
      data-easy/src/main/java/com/dataeasy/server/core/config/WxPayConfiguration.java
  15. 6 2
      data-easy/src/main/java/com/dataeasy/server/core/config/WxPayProperties.java
  16. 25 23
      data-easy/src/main/java/com/dataeasy/server/core/interceptor/AuthInterceptor.java
  17. 0 1
      data-easy/src/main/java/com/dataeasy/server/demo/pay/DateTimeUtils.java
  18. 5 11
      data-easy/src/main/java/com/dataeasy/server/demo/pay/PayService.java
  19. 0 40
      data-easy/src/main/java/com/dataeasy/server/demo/pay/WxPayConfiguration.java
  20. 22 0
      data-easy/src/main/java/com/dataeasy/server/pojo/order/SubscriptionOrderRequest.java
  21. 0 6
      data-easy/src/main/java/com/dataeasy/server/pojo/subscription/SubscriptionSourcePlanVO.java
  22. 4 2
      data-easy/src/main/java/com/dataeasy/server/pojo/subscription/SubscriptionSourceVO.java
  23. 38 0
      data-easy/src/main/java/com/dataeasy/server/service/controller/OrderController.java
  24. 9 9
      data-easy/src/main/java/com/dataeasy/server/service/controller/SubscriptionController.java
  25. 13 1
      data-easy/src/main/java/com/dataeasy/server/service/controller/WxController.java
  26. 38 0
      data-easy/src/main/java/com/dataeasy/server/service/manager/ICacheManager.java
  27. 21 0
      data-easy/src/main/java/com/dataeasy/server/service/manager/IOrderManager.java
  28. 12 0
      data-easy/src/main/java/com/dataeasy/server/service/manager/IOrderService.java
  29. 3 3
      data-easy/src/main/java/com/dataeasy/server/service/manager/ISubscriptionManager.java
  30. 8 0
      data-easy/src/main/java/com/dataeasy/server/service/manager/IWxManager.java
  31. 158 0
      data-easy/src/main/java/com/dataeasy/server/service/manager/impl/OrderManagerImpl.java
  32. 3 23
      data-easy/src/main/java/com/dataeasy/server/service/manager/impl/SubscriptionManagerImpl.java
  33. 41 0
      data-easy/src/main/java/com/dataeasy/server/service/manager/impl/WxManagerImpl.java
  34. 5 72
      data-easy/src/main/java/com/dataeasy/server/utiis/DateUtils.java
  35. 9 6
      data-easy/src/main/resources/application-dev.yaml
  36. 9 0
      doc/sql/schema.sql
  37. 3 3
      doc/技术文档.md

+ 4 - 2
data-easy/src/main/java/com/dataeasy/server/atomic/entity/SubscriptionOrder.java

@@ -1,6 +1,7 @@
 package com.dataeasy.server.atomic.entity;
 
 import com.dataeasy.server.common.pojo.BaseEntity;
+import com.dataeasy.server.constant.PaymentStatusEnum;
 import jakarta.persistence.Column;
 import jakarta.persistence.Table;
 import lombok.Data;
@@ -43,7 +44,7 @@ public class SubscriptionOrder extends BaseEntity implements Serializable {
     private Long subscriptionPlanId;
 
     /**
-     * 订单号(格式:3位业务编码 + YYYYMMDD + 6位序列号)
+     * 订单号(格式:3位业务编码 + yyMMddHHmmss + 3位序列号)
      */
     @Column(name = "order_no")
     private String orderNo;
@@ -62,7 +63,8 @@ public class SubscriptionOrder extends BaseEntity implements Serializable {
 
     /**
      * 支付状态(NOTPAY-未支付,SUCCESS-支付成功、REFUND-转入退款、CLOSED-已关闭、REVOKED-已撤销)
+     * @see PaymentStatusEnum
      */
     @Column(name = "payment_status")
-    private String paymentStatus;
+    private PaymentStatusEnum paymentStatus;
 }

+ 5 - 12
data-easy/src/main/java/com/dataeasy/server/atomic/entity/SubscriptionPlan.java

@@ -1,16 +1,16 @@
 package com.dataeasy.server.atomic.entity;
 
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
 import com.dataeasy.server.common.pojo.BaseEntity;
-import com.dataeasy.server.constant.PaidOptionEnum;
+
 import jakarta.persistence.Column;
 import jakarta.persistence.Table;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
-import java.io.Serial;
-import java.io.Serializable;
-import java.math.BigDecimal;
-
 /**
  * @author tyuio
  * @version 1.0.0
@@ -31,13 +31,6 @@ public class SubscriptionPlan extends BaseEntity implements Serializable {
     @Column(name = "subscription_source_id")
     private Long subscriptionSourceId;
 
-    /**
-     * 付费选项(FREE-免费、PAID-付费)
-     * @see PaidOptionEnum
-     */
-    @Column(name = "paid_option")
-    private PaidOptionEnum paidOption;
-
     /**
      * 订阅价格(单位:元)
      */

+ 8 - 0
data-easy/src/main/java/com/dataeasy/server/atomic/entity/SubscriptionSource.java

@@ -1,6 +1,7 @@
 package com.dataeasy.server.atomic.entity;
 
 import com.dataeasy.server.common.pojo.BaseEntity;
+import com.dataeasy.server.constant.PaidOptionEnum;
 import jakarta.persistence.Column;
 import jakarta.persistence.Table;
 import lombok.Data;
@@ -35,6 +36,13 @@ public class SubscriptionSource extends BaseEntity implements Serializable {
     @Column(name = "sub_title")
     private String subTitle;
 
+    /**
+     * 付费选项(FREE-免费、PAID-付费)
+     * @see PaidOptionEnum
+     */
+    @Column(name = "paid_option")
+    private PaidOptionEnum paidOption;
+
     /**
      * 推送渠道(WXMP-微信服务号)
      */

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

@@ -1,5 +1,7 @@
 package com.dataeasy.server.atomic.service;
 
+import com.dataeasy.server.atomic.entity.SubscriptionOrder;
+
 /**
  * @author tyuio
  * @version 1.0.0
@@ -8,4 +10,9 @@ package com.dataeasy.server.atomic.service;
  */
 public interface ISubscriptionOrderService {
 
+    /**
+     * 新增记录
+     * @param subscriptionOrder
+     */
+    void insert(SubscriptionOrder subscriptionOrder);
 }

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

@@ -19,4 +19,11 @@ public interface ISubscriptionPlanService {
      * @return
      */
     List<SubscriptionPlan> getByCondition(SubscriptionPlanQuery query);
+
+    /**
+     * 根据ID查询
+     * @param id
+     * @return
+     */
+    SubscriptionPlan getById(Long id);
 }

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

@@ -1,5 +1,7 @@
 package com.dataeasy.server.atomic.service.impl;
 
+import com.dataeasy.server.atomic.entity.SubscriptionOrder;
+import com.dataeasy.server.common.utils.Assert;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -16,6 +18,11 @@ import com.dataeasy.server.atomic.service.ISubscriptionOrderService;
 public class SubscriptionOrderServiceImpl implements ISubscriptionOrderService {
 
     @Autowired
-    private SubscriptionOrderMapper mapper;
+    private SubscriptionOrderMapper subscriptionOrderMapper;
 
+    @Override
+    public void insert(SubscriptionOrder subscriptionOrder) {
+        Assert.isNull(subscriptionOrder);
+        subscriptionOrderMapper.insert(subscriptionOrder);
+    }
 }

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

@@ -40,4 +40,13 @@ public class SubscriptionPlanServiceImpl implements ISubscriptionPlanService {
         }
         return subscriptionPlanMapper.selectByExample(weekend);
     }
+
+    @Override
+    public SubscriptionPlan getById(Long id) {
+        if (Objects.isNull(id)) {
+            return null;
+        }
+
+        return subscriptionPlanMapper.selectByPrimaryKey(id);
+    }
 }

+ 22 - 0
data-easy/src/main/java/com/dataeasy/server/constant/OrderTypeEnum.java

@@ -0,0 +1,22 @@
+package com.dataeasy.server.constant;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @date 2025/3/11 12:42
+ * @description 订单类型枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum OrderTypeEnum {
+
+    SOO("订阅源订单");
+
+    /**
+     * 名称
+     */
+    private String name;
+}

+ 27 - 0
data-easy/src/main/java/com/dataeasy/server/constant/PaymentStatusEnum.java

@@ -0,0 +1,27 @@
+package com.dataeasy.server.constant;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @date 2025/3/11 12:19
+ * @description 支付状态(NOTPAY-未支付,SUCCESS-支付成功、REFUND-转入退款、CLOSED-已关闭、REVOKED-已撤销)
+ */
+@Getter
+@AllArgsConstructor
+public enum PaymentStatusEnum {
+
+    NOTPAY("未支付"),
+
+    SUCCESS("支付成功"),
+
+    REFUND("转入退款"),
+
+    CLOSED("已关闭"),
+
+    REVOKED("已撤销");
+
+    private String name;
+}

+ 9 - 3
data-easy/src/main/java/com/dataeasy/server/core/config/BizConfig.java

@@ -1,22 +1,28 @@
 package com.dataeasy.server.core.config;
 
+import lombok.Data;
 import lombok.Getter;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Configuration;
 
-@Getter
+@Data
 @Configuration
+@ConfigurationProperties(prefix = "biz")
 public class BizConfig {
 
     /**
      * 生成token的密钥
      */
-//    @Value("${biz.token.password}")
     private String TokenPassword;
 
     /**
      * token过期时间
      */
-//    @Value("${biz.token.expire:1}")
     private Integer TokenExpire;
+
+    /**
+     * 订单过期时间
+     */
+    private Integer orderExpire;
 }

+ 28 - 0
data-easy/src/main/java/com/dataeasy/server/core/config/CacheConfig.java

@@ -0,0 +1,28 @@
+package com.dataeasy.server.core.config;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @date 2025/3/11 12:27
+ * @description 缓存配置
+ */
+@Configuration
+public class CacheConfig {
+
+    /**
+     * 订单缓存类
+     * @return
+     */
+    @Bean("orderNoCache")
+    public Cache<String, AtomicInteger> orderNoCache() {
+        return Caffeine.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS).build();
+    }
+}

+ 6 - 5
data-easy/src/main/java/com/dataeasy/server/core/config/FeignConfig.java

@@ -1,8 +1,8 @@
 package com.dataeasy.server.core.config;
 
-import feign.Logger;
-import feign.RequestInterceptor;
-import feign.codec.Decoder;
+import java.util.ArrayList;
+import java.util.List;
+
 import org.springframework.beans.factory.ObjectFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
@@ -12,8 +12,8 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.http.MediaType;
 import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
 
-import java.util.ArrayList;
-import java.util.List;
+import feign.Logger;
+import feign.codec.Decoder;
 
 /**
  * @className FeignConfig
@@ -65,6 +65,7 @@ public class FeignConfig {
         public Jackson2HttpConverter() {
             List<MediaType> mediaTypes = new ArrayList<>();
             mediaTypes.add(MediaType.TEXT_PLAIN);
+            mediaTypes.add(MediaType.TEXT_HTML);
             setSupportedMediaTypes(mediaTypes);
         }
     }

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

@@ -20,7 +20,15 @@ public class WebMvcConfig implements WebMvcConfigurer {
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
         registry.addInterceptor(authInterceptor)
-                .excludePathPatterns("/health/info", "/wx/mp/entry", "/wx/mp/callback");
+                .excludePathPatterns(
+                        "/health/info",
+                        "/user/login",
+                        "/wx/mp/entry",
+                        "/wx/mp/callback",
+                        "/wx/pay/callback",
+                        "/subscription/querySubscriptionSource",
+                        "/subscription/querySubscriptionSourceDetail"
+                        );
     }
 
     /**

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

@@ -0,0 +1,177 @@
+package com.dataeasy.server.core.config;
+
+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.config.WxPayConfig;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.util.json.GsonParser;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @description 微信支付配置
+ * @date 2025/3/5 8:50
+ */
+@Configuration
+@EnableConfigurationProperties(WxPayProperties.class)
+public class WxPayConfiguration {
+
+    @Autowired
+    private WxPayProperties properties;
+
+    @Bean
+    public WxPayService wxPayService() {
+        final MyWxPayServiceImpl wxPayService = new MyWxPayServiceImpl();
+        WxPayConfig payConfig = new WxPayConfig();
+        payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
+        payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));
+        payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey()));
+        payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
+        payConfig.setNotifyUrl(StringUtils.trimToNull(this.properties.getNotifyUrl()));
+        //以下是apiv3以及支付分相关
+        payConfig.setApiV3Key(StringUtils.trimToNull(this.properties.getApiv3Key()));
+        payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId()));
+        payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath()));
+        wxPayService.setConfig(payConfig);
+        return wxPayService;
+    }
+
+    // TODO 代码有bug需要手动设置Wechatpay-Serial,最新的正式版(4.7.0)还没有修复该问题,
+    //  临时实现一个, 等待修复该bug
+    @Slf4j
+    public static class MyWxPayServiceImpl extends WxPayServiceImpl {
+
+        private static final Gson GSON = new GsonBuilder().create();
+        private static final String ACCEPT = "Accept";
+        private static final String CONTENT_TYPE = "Content-Type";
+        private static final String APPLICATION_JSON = "application/json";
+
+        @Override
+        public WxPayUnifiedOrderV3Result unifiedOrderV3(TradeTypeEnum tradeType, WxPayUnifiedOrderV3Request request) throws WxPayException {
+            if (StringUtils.isBlank(request.getAppid())) {
+                request.setAppid(this.getConfig().getAppId());
+            }
+            if (StringUtils.isBlank(request.getMchid())) {
+                request.setMchid(this.getConfig().getMchId());
+            }
+            if (StringUtils.isBlank(request.getNotifyUrl())) {
+                request.setNotifyUrl(this.getConfig().getNotifyUrl());
+            }
+
+            String url = this.getPayBaseUrl() + tradeType.getPartnerUrl();
+            String response = this.postV3WithWechatpaySerial(url, GSON.toJson(request));
+            return GSON.fromJson(response, WxPayUnifiedOrderV3Result.class);
+        }
+
+        @Override
+        public String postV3WithWechatpaySerial(String url, String requestStr) throws WxPayException {
+            CloseableHttpClient httpClient = this.createApiV3HttpClient();
+            HttpPost httpPost = this.createHttpPost(url, requestStr);
+            httpPost.addHeader(ACCEPT, APPLICATION_JSON);
+            httpPost.addHeader(CONTENT_TYPE, APPLICATION_JSON);
+            // 微信支付 平台证书切换微信支付公钥模式过程中(两种模式并存)Wechatpay-Serial 需要传完整的公钥id给微信标识使用什么签名方式
+            String serialNumber = StringUtils.isEmpty(getConfig().getPublicKeyId()) ?
+                    getConfig().getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase() :
+                    getConfig().getPublicKeyId();
+            httpPost.addHeader("Wechatpay-Serial", serialNumber);
+            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
+                //v3已经改为通过状态码判断200 204 成功
+                int statusCode = response.getStatusLine().getStatusCode();
+                String responseString = "{}";
+                HttpEntity entity = response.getEntity();
+                if (entity != null) {
+                    responseString = EntityUtils.toString(entity, StandardCharsets.UTF_8);
+                }
+
+                if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
+                    this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString);
+                    return responseString;
+                }
+
+                //有错误提示信息返回
+                JsonObject jsonObject = GsonParser.parse(responseString);
+                throw convertException(jsonObject);
+            } catch (Exception e) {
+                this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage());
+                e.printStackTrace();
+                throw (e instanceof WxPayException) ? (WxPayException) e : new WxPayException(e.getMessage(), e);
+            } finally {
+                httpPost.releaseConnection();
+            }
+        }
+
+        @Override
+        public String getV3WithWechatPaySerial(String url) throws WxPayException {
+            HttpGet httpGet = new HttpGet(url);
+            httpGet.addHeader(ACCEPT, APPLICATION_JSON);
+            httpGet.addHeader(CONTENT_TYPE, APPLICATION_JSON);
+            // 微信支付 平台证书切换微信支付公钥模式过程中(两种模式并存)Wechatpay-Serial 需要传完整的公钥id给微信标识使用什么签名方式
+            String serialNumber = StringUtils.isEmpty(getConfig().getPublicKeyId()) ?
+                    getConfig().getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase() :
+                    getConfig().getPublicKeyId();
+            httpGet.addHeader("Wechatpay-Serial", serialNumber);
+            return this.requestV3(url, httpGet);
+        }
+
+        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();
+            WxPayException wxPayException = new WxPayException(message);
+            wxPayException.setErrCode(code);
+            wxPayException.setErrCodeDes(message);
+            return wxPayException;
+        }
+
+        private HttpPost createHttpPost(String url, String requestStr) {
+            HttpPost httpPost = new HttpPost(url);
+            httpPost.setEntity(this.createEntry(requestStr));
+
+            httpPost.setConfig(RequestConfig.custom()
+                    .setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout())
+                    .setConnectTimeout(this.getConfig().getHttpConnectionTimeout())
+                    .setSocketTimeout(this.getConfig().getHttpTimeout())
+                    .build());
+
+            return httpPost;
+        }
+
+        private StringEntity createEntry(String requestStr) {
+            return new StringEntity(requestStr, ContentType.create(APPLICATION_JSON, "utf-8"));
+        }
+
+        private CloseableHttpClient createApiV3HttpClient() throws WxPayException {
+            CloseableHttpClient apiV3HttpClient = this.getConfig().getApiV3HttpClient();
+            if (null == apiV3HttpClient) {
+                return this.getConfig().initApiV3HttpClient();
+            }
+            return apiV3HttpClient;
+        }
+    }
+}

+ 6 - 2
data-easy/src/main/java/com/dataeasy/server/demo/pay/WxPayProperties.java → data-easy/src/main/java/com/dataeasy/server/core/config/WxPayProperties.java

@@ -1,8 +1,7 @@
-package com.dataeasy.server.demo.pay;
+package com.dataeasy.server.core.config;
 
 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
 
 /**
  * @author tyuio
@@ -49,4 +48,9 @@ public class WxPayProperties {
      */
     private String publicKeyPath;
 
+    /**
+     * 支付回调地址
+     */
+    private String notifyUrl;
+
 }

+ 25 - 23
data-easy/src/main/java/com/dataeasy/server/core/interceptor/AuthInterceptor.java

@@ -1,6 +1,8 @@
 package com.dataeasy.server.core.interceptor;
 
 import com.auth0.jwt.interfaces.Claim;
+import com.dataeasy.server.atomic.entity.User;
+import com.dataeasy.server.atomic.service.IUserService;
 import com.dataeasy.server.common.exception.LoginException;
 import com.dataeasy.server.common.utils.Assert;
 import com.dataeasy.server.utiis.TokenUtils;
@@ -26,35 +28,35 @@ import java.util.Map;
 @Component
 public class AuthInterceptor implements HandlerInterceptor {
 
-//    @Autowired
-//    private IUserService userService;
+    @Autowired
+    private IUserService userService;
 
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
             throws Exception {
 
         // 判断请求头中是否有token
-//        String token = request.getHeader("Authorization");
-//        if (!StringUtils.hasText(token)) {
-//            throw LoginException.fail("登录校验异常,原因:没有token凭据");
-//        }
-//
-//        // token解析获取用户ID
-//        Long currentUserId = null;
-//        try {
-//            Map<String, Claim> verify = TokenUtils.verify(token);
-//            currentUserId = verify.get("userId").asLong();
-//        } catch (Exception e) {
-//            log.error("登录校验异常,token:{}", token, e);
-//            throw LoginException.fail("登录校验异常");
-//        }
-//
-//        // 校验系统中是否存在该用户
-//        User currentUser = userService.getById(currentUserId);
-//        Assert.isNullInBusiness(currentUser, "不存在的用户");
-//
-//        // 把用户信息设置如入上下文
-//        UserUtils.setCurrentId(currentUser.getId());
+        String token = request.getHeader("Authorization");
+        if (!StringUtils.hasText(token)) {
+            throw LoginException.fail("登录校验异常,原因:没有token凭据");
+        }
+
+        // token解析获取用户ID
+        Long currentUserId = null;
+        try {
+            Map<String, Claim> verify = TokenUtils.verify(token);
+            currentUserId = verify.get("userId").asLong();
+        } catch (Exception e) {
+            log.error("登录校验异常,token:{}", token, e);
+            throw LoginException.fail("登录校验异常");
+        }
+
+        // 校验系统中是否存在该用户
+        User currentUser = userService.getById(currentUserId);
+        Assert.isNullInBusiness(currentUser, "不存在的用户");
+
+        // 把用户信息设置如入上下文
+        UserUtils.setCurrentId(currentUser.getId());
 
         return true;
     }

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

@@ -1,6 +1,5 @@
 package com.dataeasy.server.demo.pay;
 
-import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 

+ 5 - 11
data-easy/src/main/java/com/dataeasy/server/demo/pay/PayService.java

@@ -1,26 +1,20 @@
 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.WxPayNotifyResponse;
 import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Response;
 import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
-import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
-import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
-import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
 import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
-import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
 import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
 import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
-import com.github.binarywang.wxpay.constant.WxPayConstants;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.service.WxPayService;
+
 import jakarta.servlet.http.HttpServletRequest;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.io.IOUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.math.BigDecimal;
 
 /**
  * @author tyuio

+ 0 - 40
data-easy/src/main/java/com/dataeasy/server/demo/pay/WxPayConfiguration.java

@@ -1,40 +0,0 @@
-package com.dataeasy.server.demo.pay;
-
-import com.github.binarywang.wxpay.config.WxPayConfig;
-import com.github.binarywang.wxpay.service.WxPayService;
-import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * @author tyuio
- * @version 1.0.0
- * @description TODO
- * @date 2025/3/5 8:50
- */
-@Configuration
-@EnableConfigurationProperties(WxPayProperties.class)
-public class WxPayConfiguration {
-
-    @Autowired
-    private WxPayProperties properties;
-
-    @Bean
-    public WxPayService wxPayService() {
-        final WxPayServiceImpl wxPayService = new WxPayServiceImpl();
-        WxPayConfig payConfig = new WxPayConfig();
-        payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
-        payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));
-        payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey()));
-        payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
-        //以下是apiv3以及支付分相关
-        payConfig.setApiV3Key(StringUtils.trimToNull(this.properties.getApiv3Key()));
-        payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId()));
-        payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath()));
-        wxPayService.setConfig(payConfig);
-        return wxPayService;
-    }
-}

+ 22 - 0
data-easy/src/main/java/com/dataeasy/server/pojo/order/SubscriptionOrderRequest.java

@@ -0,0 +1,22 @@
+package com.dataeasy.server.pojo.order;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @date 2025/3/11 11:19
+ * @description 订阅源订阅订单请求
+ */
+@Data
+public class SubscriptionOrderRequest {
+
+    /**
+     * 订阅源价格计划ID
+     */
+    @NotNull(message = "订阅源价格计划ID不能为空")
+    private Long subscriptionPlanId;
+
+
+}

+ 0 - 6
data-easy/src/main/java/com/dataeasy/server/pojo/subscription/SubscriptionSourcePlanVO.java

@@ -19,12 +19,6 @@ public class SubscriptionSourcePlanVO {
      */
     private Long id;
 
-    /**
-     * 付费选项(FREE-免费、PAID-付费)
-     * @see PaidOptionEnum
-     */
-    private PaidOptionEnum paidOption;
-
     /**
      * 订阅价格(单位:元)
      */

+ 4 - 2
data-easy/src/main/java/com/dataeasy/server/pojo/subscription/SubscriptionSourceVO.java

@@ -2,6 +2,7 @@ package com.dataeasy.server.pojo.subscription;
 
 import java.math.BigDecimal;
 
+import com.dataeasy.server.constant.PaidOptionEnum;
 import lombok.Data;
 
 /**
@@ -29,7 +30,8 @@ public class SubscriptionSourceVO {
     private String subTitle;
 
     /**
-     * 订阅价格
+     * 付费选项(FREE-免费、PAID-付费)
+     * @see PaidOptionEnum
      */
-    private BigDecimal subscriptionPrice;
+    private PaidOptionEnum paidOption;
 }

+ 38 - 0
data-easy/src/main/java/com/dataeasy/server/service/controller/OrderController.java

@@ -0,0 +1,38 @@
+package com.dataeasy.server.service.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.dataeasy.server.pojo.order.SubscriptionOrderRequest;
+import com.dataeasy.server.service.manager.IOrderManager;
+import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
+import com.github.binarywang.wxpay.exception.WxPayException;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @date 2025/3/11 11:25
+ * @description 订单 控制器
+ */
+@Validated
+@RestController
+@RequestMapping("/order")
+public class OrderController {
+
+    @Autowired
+    private IOrderManager orderManager;
+
+    /**
+     * 创建订单
+     * @param request
+     * @return
+     */
+    @PostMapping("/createOrder")
+    public WxPayUnifiedOrderV3Result.JsapiResult createOrder(@RequestBody @Validated SubscriptionOrderRequest request) throws WxPayException {
+        return orderManager.createOrder(request);
+    }
+}

+ 9 - 9
data-easy/src/main/java/com/dataeasy/server/service/controller/SubscriptionController.java

@@ -36,9 +36,9 @@ public class SubscriptionController {
     /**
      * 查询订阅源
      */
-    @GetMapping("/querySource")
-    public List<SubscriptionSourceVO> querySource() {
-        return subscriptionManager.querySource();
+    @GetMapping("/querySubscriptionSource")
+    public List<SubscriptionSourceVO> querySubscriptionSource() {
+        return subscriptionManager.querySubscriptionSource();
     }
 
     /**
@@ -46,17 +46,17 @@ public class SubscriptionController {
      * @param id 订阅源ID
      * @return
      */
-    @GetMapping("/querySourceDetail")
-    public SubscriptionSourceDetailVO querySourceDetail(@RequestParam(required = false) @NotNull(message = "订阅源id不能为空") Long id) {
-        return subscriptionManager.querySourceDetail(id);
+    @GetMapping("/querySubscriptionSourceDetail")
+    public SubscriptionSourceDetailVO querySubscriptionSourceDetail(@RequestParam(required = false) @NotNull(message = "订阅源id不能为空") Long id) {
+        return subscriptionManager.querySubscriptionSourceDetail(id);
     }
 
     /**
      * 查询用户已订阅的订阅源
      */
-    @GetMapping("/querySourceUserConfig")
-    public List<SubscriptionUserConfigVO> querySourceUserConfig() {
-        return subscriptionManager.querySourceUserConfig();
+    @GetMapping("/querySubscriptionUserConfig")
+    public List<SubscriptionUserConfigVO> querySubscriptionUserConfig() {
+        return subscriptionManager.querySubscriptionUserConfig();
     }
 
     /**

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

@@ -1,5 +1,6 @@
 package com.dataeasy.server.service.controller;
 
+import jakarta.servlet.http.HttpServletRequest;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -54,10 +55,21 @@ public class WxController {
      */
     @IgnoreResponseWrapper
     @PostMapping("/mp/callback")
-    public String entryCallback(@RequestBody String requestBody, @RequestParam("signature") String signature,
+    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,
         @RequestParam(name = "msg_signature", required = false) String msgSignature) {
         return wxManager.mpCallback(requestBody, signature, timestamp, nonce, openid, encType, msgSignature);
     }
+
+    /**
+     * 微信支付回调
+     * @param request
+     * @return
+     */
+    @IgnoreResponseWrapper
+    @PostMapping("/pay/callback")
+    public String payCallback(HttpServletRequest request) {
+        return wxManager.payCallback(request);
+    }
 }

+ 38 - 0
data-easy/src/main/java/com/dataeasy/server/service/manager/ICacheManager.java

@@ -0,0 +1,38 @@
+package com.dataeasy.server.service.manager;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @date 2025/3/11 11:14
+ * @description 缓存服务类
+ */
+public interface ICacheManager {
+
+    /**
+     * 添加缓存
+     *
+     * @param cacheName 缓存名称
+     * @param key 缓存key
+     * @param value 缓存值
+     */
+    void put(String cacheName, String key, Object value) ;
+
+    /**
+     * 获取缓存(泛型)
+     *
+     * @param cacheName 缓存名称
+     * @param key 缓存key
+     * @param clazz 缓存类
+     * @param <T> 返回值泛型
+     * @return
+     */
+    <T> T get(String cacheName, String key, Class<T> clazz);
+
+    /**
+     * 失效缓存
+     *
+     * @param cacheName 缓存名称
+     * @param key 缓存key
+     */
+     void evict(String cacheName, String key);
+}

+ 21 - 0
data-easy/src/main/java/com/dataeasy/server/service/manager/IOrderManager.java

@@ -0,0 +1,21 @@
+package com.dataeasy.server.service.manager;
+
+import com.dataeasy.server.pojo.order.SubscriptionOrderRequest;
+import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
+import com.github.binarywang.wxpay.exception.WxPayException;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @date 2025/3/11 11:27
+ * @description 订单服务类
+ */
+public interface IOrderManager {
+
+    /**
+     * 创建订单
+     * @param request
+     * @return
+     */
+    WxPayUnifiedOrderV3Result.JsapiResult createOrder(SubscriptionOrderRequest request) throws WxPayException;
+}

+ 12 - 0
data-easy/src/main/java/com/dataeasy/server/service/manager/IOrderService.java

@@ -0,0 +1,12 @@
+package com.dataeasy.server.service.manager;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @date 2025/3/11 11:17
+ * @description 订单服务类
+ */
+public interface IOrderService {
+
+
+}

+ 3 - 3
data-easy/src/main/java/com/dataeasy/server/service/manager/ISubscriptionManager.java

@@ -18,19 +18,19 @@ public interface ISubscriptionManager {
     /**
      * 查询订阅源
      */
-    List<SubscriptionSourceVO> querySource();
+    List<SubscriptionSourceVO> querySubscriptionSource();
 
     /**
      * 查询指定订阅源详情
      * @param id 订阅源ID
      * @return
      */
-    SubscriptionSourceDetailVO querySourceDetail(Long id);
+    SubscriptionSourceDetailVO querySubscriptionSourceDetail(Long id);
 
     /**
      * 查询用户已订阅的订阅源
      */
-    List<SubscriptionUserConfigVO> querySourceUserConfig();
+    List<SubscriptionUserConfigVO> querySubscriptionUserConfig();
 
     /**
      * 修改消息推送选项

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

@@ -1,5 +1,6 @@
 package com.dataeasy.server.service.manager;
 
+import jakarta.servlet.http.HttpServletRequest;
 import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
 
 /**
@@ -36,6 +37,13 @@ public interface IWxManager {
     String mpCallback(String requestBody, String signature, String timestamp, String nonce, String openid,
         String encType, String msgSignature);
 
+    /**
+     * 微信支付回调
+     * @param request
+     * @return
+     */
+    String payCallback(HttpServletRequest request);
+
     /**
      * 发送模板信息
      * 

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

@@ -0,0 +1,158 @@
+package com.dataeasy.server.service.manager.impl;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.dataeasy.server.atomic.entity.SubscriptionOrder;
+import com.dataeasy.server.atomic.entity.SubscriptionPlan;
+import com.dataeasy.server.atomic.entity.SubscriptionSource;
+import com.dataeasy.server.atomic.entity.User;
+import com.dataeasy.server.atomic.service.ISubscriptionOrderService;
+import com.dataeasy.server.atomic.service.ISubscriptionPlanService;
+import com.dataeasy.server.atomic.service.ISubscriptionSourceService;
+import com.dataeasy.server.atomic.service.ISubscriptionUserConfigService;
+import com.dataeasy.server.atomic.service.IUserService;
+import com.dataeasy.server.common.exception.BusinessException;
+import com.dataeasy.server.constant.OrderTypeEnum;
+import com.dataeasy.server.constant.PaymentStatusEnum;
+import com.dataeasy.server.core.config.BizConfig;
+import com.dataeasy.server.pojo.order.SubscriptionOrderRequest;
+import com.dataeasy.server.service.manager.IOrderManager;
+import com.dataeasy.server.utiis.DateUtils;
+import com.dataeasy.server.utiis.UserUtils;
+import com.github.benmanes.caffeine.cache.Cache;
+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 lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @date 2025/3/11 11:29
+ * @description 订单服务类
+ */
+@Slf4j
+@Service
+public class OrderManagerImpl implements IOrderManager {
+
+    /**
+     * 数值:100
+     */
+    private static final BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100);
+
+
+    @Autowired
+    private ISubscriptionOrderService subscriptionOrderService;
+
+    @Autowired
+    private ISubscriptionSourceService subscriptionSourceService;
+
+    @Autowired
+    private ISubscriptionPlanService subscriptionPlanService;
+
+    @Autowired
+    private ISubscriptionUserConfigService subscriptionUserConfigService;
+
+    @Autowired
+    private IUserService userService;
+
+    @Autowired
+    private BizConfig bizConfig;
+
+    @Autowired
+    @Qualifier("orderNoCache")
+    private Cache<String, AtomicInteger> orderNoCache;
+
+    @Autowired
+    private WxPayService wxPayService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public WxPayUnifiedOrderV3Result.JsapiResult createOrder(SubscriptionOrderRequest request) throws WxPayException {
+        SubscriptionPlan subscriptionPlan =
+            Optional.ofNullable(subscriptionPlanService.getById(request.getSubscriptionPlanId())).orElseThrow(() -> {
+                log.warn("订阅源价格计划不存在,id:{}", request.getSubscriptionPlanId());
+                throw BusinessException.fail("订阅源价格计划不存在");
+            });
+        if (Objects.isNull(subscriptionPlan.getSubscriptionPrice())) {
+            log.warn("订阅价格不能为空,id:{}", subscriptionPlan.getId());
+            throw BusinessException.fail("订阅价格不能为空");
+        }
+        if (subscriptionPlan.getSubscriptionPrice().compareTo(BigDecimal.ZERO) == -1) {
+            log.warn("订阅价格不能为负数,id:{}", subscriptionPlan.getId());
+            throw BusinessException.fail("订阅价格不能为负数");
+        }
+
+        SubscriptionSource subscriptionSource =
+            Optional.ofNullable(subscriptionSourceService.getById(subscriptionPlan.getSubscriptionSourceId()))
+                .orElseThrow(() -> {
+                    log.warn("订阅源不存在,id:{}", subscriptionPlan.getSubscriptionSourceId());
+                    throw BusinessException.fail("订阅源不存在");
+                });
+
+        User user = Optional.ofNullable(userService.getById(UserUtils.getCurrentUserId())).orElseThrow(() -> BusinessException.fail("找不到当前用户的信息"));
+
+        // 订单记录
+        SubscriptionOrder subscriptionOrder = new SubscriptionOrder();
+        subscriptionOrder.setUserId(UserUtils.getCurrentUserId());
+        subscriptionOrder.setSubscriptionSourceId(subscriptionSource.getId());
+        subscriptionOrder.setSubscriptionPlanId(subscriptionPlan.getId());
+        subscriptionOrder.setSubscriptionPrice(subscriptionPlan.getSubscriptionPrice());
+        subscriptionOrder.setSubscriptionDuration(subscriptionPlan.getSubscriptionDuration());
+        subscriptionOrder.setPaymentStatus(PaymentStatusEnum.NOTPAY);
+        subscriptionOrder.setOrderNo(generateOrderNo(OrderTypeEnum.SOO));
+        subscriptionOrderService.insert(subscriptionOrder);
+
+        // 设置金额,微信的单位为分,因此先转换成分
+        WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
+        BigDecimal total = subscriptionPlan.getSubscriptionPrice().multiply(ONE_HUNDRED);
+        amount.setTotal(total.intValue());
+
+        // 设置待支付用户的openid
+        WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
+        payer.setOpenid(user.getMaOpenId());
+
+        // 设置订单的过期时间
+        LocalDateTime expireDateTime = LocalDateTime.now().plusMinutes(bizConfig.getOrderExpire());
+        String timeExpire = expireDateTime.format(DateUtils.YYYYMMDDTHHMMSS_OFFSET_ZONE_FORMATTER);
+
+        // 发送到微信的报文
+        WxPayUnifiedOrderV3Request wxOrderV3 = new WxPayUnifiedOrderV3Request();
+        wxOrderV3.setDescription(String.format("%s-%s天", subscriptionSource.getTitle(), subscriptionPlan.getSubscriptionDuration()));
+        wxOrderV3.setOutTradeNo(subscriptionOrder.getOrderNo());
+        wxOrderV3.setTimeExpire(timeExpire);
+        wxOrderV3.setPayer(payer);
+        wxOrderV3.setAmount(amount);
+        return wxPayService.createOrderV3(TradeTypeEnum.JSAPI, wxOrderV3);
+    }
+
+    /**
+     * 根据订单类型生成订单号
+     * @param orderType 订单类型
+     * @return 订单号,格式:3位业务编码 + yyMMddHHmmss + 3位序列号
+     */
+    public synchronized String generateOrderNo(OrderTypeEnum orderType) {
+        // 订单号前缀,格式:3位业务编码 + yyMMddHHmmss
+        String todayStr = LocalDateTime.now().format(DateUtils.YYMMDDHHMMSS_FORMATTER);
+        String orderPrefix = String.format("%s%s", orderType, todayStr);
+
+        AtomicInteger orderNoSequence = orderNoCache.getIfPresent(orderPrefix);
+        if (Objects.isNull(orderNoSequence)) {
+            orderNoSequence = new AtomicInteger(1);
+            orderNoCache.put(orderPrefix, orderNoSequence);
+        }
+        return String.format("%s%03d", orderPrefix, orderNoSequence.getAndIncrement());
+    }
+}

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

@@ -53,42 +53,22 @@ public class SubscriptionManagerImpl implements ISubscriptionManager {
     private ISubscriptionUserConfigService subscriptionUserConfigService;
 
     @Override
-    public List<SubscriptionSourceVO> querySource() {
+    public List<SubscriptionSourceVO> querySubscriptionSource() {
         SubscriptionSourceQuery subscriptionSourceQuery = new SubscriptionSourceQuery();
         List<SubscriptionSource> sourceList = subscriptionSourceService.getByCondition(subscriptionSourceQuery);
         if (CollectionUtils.isEmpty(sourceList)) {
             return List.of();
         }
 
-        // 获取订阅源与价格的对应关系
-        List<Long> sourceIds = sourceList.stream().map(SubscriptionSource::getId).collect(Collectors.toList());
-        SubscriptionPlanQuery subscriptionPlanQuery = new SubscriptionPlanQuery();
-        subscriptionPlanQuery.setSubscriptionSourceIds(sourceIds);
-        List<SubscriptionPlan> planList = subscriptionPlanService.getByCondition(subscriptionPlanQuery);
-        Map<Long, List<SubscriptionPlan>> sourcePlanMap = planList.stream().collect(Collectors.groupingBy(SubscriptionPlan::getSubscriptionSourceId));
-        // 对价格进行排序
-        sourcePlanMap.forEach((sourceId, pList) -> {
-            pList.sort((o1, o2) -> {
-                if (Objects.isNull(o1.getSubscriptionDuration()) || Objects.isNull(o2.getSubscriptionDuration())) {
-                    return 0;
-                }
-                return o1.getSubscriptionDuration().compareTo(o2.getSubscriptionDuration());
-            });
-        });
-
         return sourceList.stream().map(source -> {
             SubscriptionSourceVO subscriptionSourceVO = new SubscriptionSourceVO();
             BeanUtils.copyProperties(source, subscriptionSourceVO);
-            List<SubscriptionPlan> subscriptionPlans = sourcePlanMap.get(source.getId());
-            if (!CollectionUtils.isEmpty(subscriptionPlans)) {
-                subscriptionSourceVO.setSubscriptionPrice(subscriptionPlans.get(0).getSubscriptionPrice());
-            }
             return subscriptionSourceVO;
         }).toList();
     }
 
     @Override
-    public SubscriptionSourceDetailVO querySourceDetail(Long id) {
+    public SubscriptionSourceDetailVO querySubscriptionSourceDetail(Long id) {
         SubscriptionSource subscriptionSource = subscriptionSourceService.getById(id);
         Assert.isNullInBusiness(subscriptionSource, "找不到对应的订阅源");
 
@@ -124,7 +104,7 @@ public class SubscriptionManagerImpl implements ISubscriptionManager {
     }
 
     @Override
-    public List<SubscriptionUserConfigVO> querySourceUserConfig() {
+    public List<SubscriptionUserConfigVO> querySubscriptionUserConfig() {
         // 查询当前用户拥有的订阅源
         SubscriptionUserConfigQuery subscriptionUserConfigQuery = new SubscriptionUserConfigQuery();
         subscriptionUserConfigQuery.setUserId(UserUtils.getCurrentUserId());

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

@@ -1,5 +1,11 @@
 package com.dataeasy.server.service.manager.impl;
 
+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.service.WxPayService;
+import jakarta.servlet.http.HttpServletRequest;
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -30,6 +36,9 @@ public class WxManagerImpl implements IWxManager {
     @Autowired
     private WxMpMessageRouter wxMpMessageRouter;
 
+    @Autowired
+    private WxPayService wxPayService;
+
     @Override
     public String mpEntry(String signature, String timestamp, String nonce, String echoStr) {
         log.info("微信公众号/服务号接入传递的参数 signature:[{}],timestamp:[{}],nonce:[{}],echostr:[{}]", signature, timestamp, nonce,
@@ -81,6 +90,38 @@ public class WxManagerImpl implements IWxManager {
         return out;
     }
 
+    @Override
+    public String payCallback(HttpServletRequest request) {
+        try {
+            // 获取请求体
+            String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
+            log.info("xmlResult: {}", xmlResult);
+            // 获取请求头签名数据
+            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"));
+            log.info("signatureHeader: {}", signatureHeader);
+            // 获取回调结果
+            WxPayNotifyV3Result payResult1 = wxPayService.parseOrderNotifyV3Result(xmlResult, signatureHeader);
+            log.info("回调结果:{}", payResult1);
+
+            WxPayNotifyV3Result.DecryptNotifyResult payResult = payResult1.getResult();
+            String masterOrderId = payResult.getOutTradeNo();//内部订单记录ID
+            String wxTransactionId = payResult.getTransactionId();//商户号订单ID
+            // TODO 这里要回写订单信息
+            log.info("内部订单ID:{}", masterOrderId);
+            log.info("商户号订单ID:{}", wxTransactionId);
+            log.info("微信真实支付金额:{}", payResult.getAmount());
+            log.info("微信支付,回调处理成功");
+            return WxPayNotifyV3Response.success("处理成功!");
+        } catch (Exception e) {
+            log.error("微信支付回调处理失败", e);
+            return WxPayNotifyV3Response.fail(e.getMessage());
+        }
+    }
+
     @Override
     public void sendTemplateMessage(WxMpTemplateMessage templateMessage) {
         // TODO 这里要考虑把数据存入数据库,方便后续查询

+ 5 - 72
data-easy/src/main/java/com/dataeasy/server/utiis/DateUtils.java

@@ -1,11 +1,6 @@
 package com.dataeasy.server.utiis;
 
-import java.text.SimpleDateFormat;
-import java.time.DayOfWeek;
-import java.time.LocalDate;
-import java.time.temporal.TemporalAdjusters;
-import java.util.ArrayList;
-import java.util.List;
+import java.time.format.DateTimeFormatter;
 
 /**
  * @author tyuio
@@ -16,74 +11,12 @@ import java.util.List;
 public class DateUtils {
 
     /**
-     * 完整的日期时间格式(yyyy-MM-dd HH:mm:ss)
+     * 日期格式化器,格式:yyMMddHHmmss
      */
-    public static final String ALL_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+    public static final DateTimeFormatter YYMMDDHHMMSS_FORMATTER = DateTimeFormatter.ofPattern("yyMMddHHmmss");
 
     /**
-     * 日期格式(yyyy-MM-dd)
+     * 日期格式化器,格式:yyyy-MM-ddTHH:mm:ss+08:00
      */
-    public static final String DATE_FORMAT = "yyyy-MM-dd";
-
-    /**
-     * 创建完整的日期时间格式化器(yyyy-MM-dd HH:mm:ss)
-     * @return
-     */
-    public static SimpleDateFormat buildDateTimeFormat() {
-        return new SimpleDateFormat(ALL_DATE_TIME_FORMAT);
-    }
-
-    /**
-     * 创建日期格式化器(yyyy-MM-dd)
-     * @return
-     */
-    public static SimpleDateFormat buildDateFormat() {
-        return new SimpleDateFormat(DATE_FORMAT);
-    }
-
-    /**
-     * 获取一周的时间范围
-     * @return 返回日期字符串列表
-     */
-    public static List<LocalDate> getWeeklyDateRange() {
-        LocalDate today = LocalDate.now();
-
-        // 获取本周的第一天(假设周一为一周的第一天)
-        LocalDate firstDayOfWeek = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
-
-        // 打印本周的所有日期
-        List<LocalDate> list = new ArrayList<>(7);
-        for (int i = 0; i < 7; i++) {
-            LocalDate date = firstDayOfWeek.plusDays(i);
-            list.add(date);
-        }
-        return list;
-    }
-
-    /**
-     * 获取昨天的日期
-     * @return
-     */
-    public static LocalDate getYesterdayDate() {
-        LocalDate today = LocalDate.now();
-        return today.minusDays(1);
-    }
-
-    /**
-     * 获取上周的周一日期
-     * @return
-     */
-    public static LocalDate getLastWeekMonday(LocalDate date) {
-        // 获取上周的周一日期
-        return date.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
-    }
-
-    /**
-     * 获取上周的周日日期
-     * @return
-     */
-    public static LocalDate getLastWeekSunday(LocalDate date) {
-        return getLastWeekMonday(date).plusDays(6);
-    }
-
+    public static final DateTimeFormatter YYYYMMDDTHHMMSS_OFFSET_ZONE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss+08:00");
 }

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

@@ -1,7 +1,8 @@
 spring:
   datasource:
     username: root
-    url: jdbc:mysql://localhost:3306/data_easy?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
+    password: 12345678
+    url: jdbc:mysql://192.168.123.242:3306/data_easy?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
   ai:
     openai:
       base-url: ENC(H4FobFSxLct+ZapdAa5GWKbBC3u32wRgWrVaksCJyHvyTPmpqz2P4gq7QfFPIARI)
@@ -9,7 +10,8 @@ spring:
 
 logging:
   level:
-    com.dataeasy.server.atomic.mapper: debug
+    com.dataeasy.server: debug
+    cn.binarywang.wx: debug
 
 # 微信配置
 wx:
@@ -28,7 +30,8 @@ wx:
     # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
     keyPath: C:\Software\WXCertUtil\cert\apiclient_cert.p12
     # pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
-    publichKeyPath: E:\Download\pub_key.pem
+    publicKeyPath: E:\Download\pub_key.pem
+    notifyUrl: http://krdvzy.natappfree.cc/wx/pay/callback
   # 小程序
   miniapp:
     appid: ENC(825U/6iYUhZJK7ojeBUya+WATlVilGB8py4AQPA0jwfY8oPwjZ53wA==)
@@ -45,6 +48,6 @@ hz-api:
 
 # 系统配置
 biz:
-  token:
-    password: ENC()
-    expire: 7
+  tokenPassword: 1234567890
+  tokenExpire: 7
+  orderExpire: 15

+ 9 - 0
doc/sql/schema.sql

@@ -354,4 +354,13 @@ CREATE TABLE `subscription_order` (
 ALTER TABLE data_easy.sys_schedule_task_log ADD error_message varchar(300) NULL COMMENT '错误信息';
 ALTER TABLE data_easy.sys_schedule_task_log CHANGE error_message error_message varchar(300) NULL COMMENT '错误信息' AFTER process_status;
 ALTER TABLE data_easy.subscription_user_config MODIFY COLUMN push_option varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '消息推送选项(ENABLED-开启、DISABLED-关闭)';
+ALTER TABLE data_easy.subscription_plan DROP COLUMN paid_option;
+ALTER TABLE data_easy.subscription_source ADD COLUMN paid_option VARCHAR(10) NOT NULL COMMENT '付费选项(FREE-免费、PAID-付费)';
+ALTER TABLE data_easy.subscription_source CHANGE paid_option paid_option varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '付费选项(FREE-免费、PAID-付费)' AFTER sub_title;
+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位序列号)';
+
+
+
 

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

@@ -5,7 +5,7 @@
 ## 系统功能
 
 * 用户
-  * (已开发,待测试)登录
+  * (已开发,差unionid部分)登录
   * (小程序支持即可)注销
   * (已开发,已测试)修改昵称
   * (已开发,已测试)查询用户基本信息
@@ -326,6 +326,7 @@
 | push_frequency   | varchar(100)  | 推送频率                           |
 | description      | varchar(1000) | 详情描述                           |
 | pics             | varchar(2000) | 详情图片                           |
+| paid_option      | varchar(10)   | 付费选项(FREE-免费、PAID-付费)   |
 | created_by       | bigint        | 创建人                             |
 | creation_time    | timestamp     | 创建时间                           |
 | last_updated_by  | bigint        | 最后更新人                         |
@@ -343,7 +344,6 @@
 | ---------------------- | ------------- | ---------------------------------- |
 | id                     | bigint        | 主键                               |
 | subscription_source_id | bigint        | 订阅源ID                           |
-| paid_option            | varchar(10)   | 付费选项(FREE-免费、PAID-付费)   |
 | subscription_price     | decimal(12,2) | 订阅价格(单位:元)               |
 | subscription_duration  | int           | 订阅时长(单位:天)               |
 | created_by             | bigint        | 创建人                             |
@@ -455,7 +455,7 @@
 | user_id                | bigint        | 用户ID                                                       |
 | subscription_source_id | bigint        | 订阅源ID                                                     |
 | subscription_plan_id   | bigint        | 订阅价格计划ID                                               |
-| order_no               | varchar(15)   | 订单号(格式:3位业务编码 + YYYYMMDD + 6位序列号)           |
+| order_no               | varchar(30)   | 订单号(格式:3位业务编码 + yyMMddHHmmss + 3位序列号)       |
 | subscription_price     | decimal(12,2) | 订阅价格(单位:元)                                         |
 | subscription_duration  | int           | 订阅时长(单位:天)                                         |
 | payment_status         | varchar(10)   | 支付状态(NOTPAY-未支付,SUCCESS-支付成功、REFUND-转入退款、CLOSED-已关闭、REVOKED-已撤销) |