wangwenju 1 місяць тому
батько
коміт
39e2967484
33 змінених файлів з 1131 додано та 165 видалено
  1. 93 53
      LiangZhiYUMao/pages/vip/index.vue
  2. 24 1
      LiangZhiYUMao/utils/api.js
  3. 1 1
      common/src/main/java/com/zhentao/pojo/Users.java
  4. BIN
      common/target/classes/com/zhentao/pojo/Users.class
  5. 14 0
      service/Essential/pom.xml
  6. 10 0
      service/Essential/src/main/java/com/zhentao/common/Result.java
  7. 22 0
      service/Essential/src/main/java/com/zhentao/config/JacksonConfig.java
  8. 4 1
      service/Essential/src/main/java/com/zhentao/config/WechatPayProperties.java
  9. 31 9
      service/Essential/src/main/java/com/zhentao/controller/VipController.java
  10. 3 0
      service/Essential/src/main/java/com/zhentao/entity/UserVip.java
  11. 31 0
      service/Essential/src/main/java/com/zhentao/entity/Wx.java
  12. 8 0
      service/Essential/src/main/java/com/zhentao/mapper/UserVipMapper.java
  13. 18 0
      service/Essential/src/main/java/com/zhentao/mapper/WxMapper.java
  14. 7 1
      service/Essential/src/main/java/com/zhentao/service/VipService.java
  15. 13 0
      service/Essential/src/main/java/com/zhentao/service/WxService.java
  16. 743 97
      service/Essential/src/main/java/com/zhentao/service/impl/VipServiceImpl.java
  17. 22 0
      service/Essential/src/main/java/com/zhentao/service/impl/WxServiceImpl.java
  18. 25 0
      service/Essential/src/main/resources/apiclient_cert.pem
  19. 28 0
      service/Essential/src/main/resources/apiclient_key.pem
  20. 9 1
      service/Essential/src/main/resources/application.yml
  21. 16 0
      service/Essential/src/main/resources/com/zhentao/mapper/WxMapper.xml
  22. 9 1
      service/Essential/target/classes/application.yml
  23. BIN
      service/Essential/target/classes/com/zhentao/common/Result.class
  24. BIN
      service/Essential/target/classes/com/zhentao/config/WechatPayProperties.class
  25. BIN
      service/Essential/target/classes/com/zhentao/controller/AvatarTestController.class
  26. BIN
      service/Essential/target/classes/com/zhentao/controller/BlacklistController.class
  27. BIN
      service/Essential/target/classes/com/zhentao/controller/VipController.class
  28. BIN
      service/Essential/target/classes/com/zhentao/entity/UserVip.class
  29. BIN
      service/Essential/target/classes/com/zhentao/mapper/UserVipMapper.class
  30. BIN
      service/Essential/target/classes/com/zhentao/service/VipService.class
  31. BIN
      service/Essential/target/classes/com/zhentao/service/impl/VipServiceImpl$1.class
  32. BIN
      service/Essential/target/classes/com/zhentao/service/impl/VipServiceImpl.class
  33. BIN
      service/login/target/classes/com/zhentao/controller/AuthCodeController.class

+ 93 - 53
LiangZhiYUMao/pages/vip/index.vue

@@ -165,8 +165,6 @@
 								benefits: pkg.benefits,
 								recommend: pkg.recommend
 							}))
-							
-							// 不自动选中任何套餐,让用户手动选择
 						}
 					},
 					fail: (err) => {
@@ -217,53 +215,100 @@
 					content: `确认开通${pkg.name}(¥${pkg.price})?`,
 					success: (res) => {
 						if (res.confirm) {
-							// 调用后端购买接口
-							uni.showLoading({
-								title: '处理中...'
+							this.requestPay(userId, pkg)
+						}
+					}
+				})
+			},
+			
+			/**
+			 * 请求支付参数并调起微信支付
+			 */
+			requestPay(userId, pkg) {
+				uni.showLoading({
+					title: '处理中...'
+				})
+				
+				uni.request({
+					url: this.gatewayURL + '/api/vip/purchase',
+					method: 'POST',
+					data: {
+						userId: userId,
+						packageId: pkg.packageId
+					},
+					header: {
+						'content-type': 'application/json',
+						'token': uni.getStorageSync('token') // 携带登录态
+					},
+					success: (res) => {
+						uni.hideLoading()
+						
+						if (res.data && res.data.code === 200) {
+							const payParams = res.data.ext // 后端返回的支付参数
+							this.callWxPay(payParams, pkg) // 调起微信支付
+						} else {
+							uni.showToast({
+								title: res.data?.message || '获取支付参数失败',
+								icon: 'none'
+							})
+						}
+					},
+					fail: (err) => {
+						uni.hideLoading()
+						console.error('请求支付参数失败:', err)
+						uni.showToast({
+							title: '网络请求失败',
+							icon: 'none'
+						})
+					}
+				})
+			},
+			
+			/**
+			 * 调起微信支付组件
+			 */
+			callWxPay(payParams, pkg) {
+				uni.requestPayment({
+					provider: 'wxpay',
+					timeStamp: payParams.timeStamp,
+					nonceStr: payParams.nonceStr,
+					package: payParams.package,
+					signType: payParams.signType,
+					paySign: payParams.paySign,
+					total_fee: payParams.totalFee,
+					success: (payResult) => {
+						if (payResult.errMsg === 'requestPayment:ok') {
+							uni.showToast({
+								title: '支付成功!VIP已开通',
+								icon: 'success',
+								duration: 2000
 							})
 							
-							uni.request({
-								url: this.gatewayURL + '/api/vip/purchase?userId=' + userId + '&packageId=' + pkg.packageId,
-								method: 'POST',
-								success: (res) => {
-									uni.hideLoading()
-									
-									if (res.data && res.data.code === 200) {
-										console.log('✅ VIP购买成功')
-										
-										uni.showToast({
-											title: 'VIP开通成功!',
-											icon: 'success',
-											duration: 2000
-										})
-										
-										// 延迟返回上一页,并触发页面刷新
-										setTimeout(() => {
-											// 通过事件通知个人页面刷新数据
-											uni.$emit('vipPurchased')
-											
-											// 返回上一页
-											uni.navigateBack({
-												success: () => {
-													console.log('✅ 返回个人页面')
-												}
-											})
-										}, 2000)
-									} else {
-										uni.showToast({
-											title: res.data?.message || 'VIP开通失败',
-											icon: 'none'
-										})
+							// 延迟返回上一页,并触发页面刷新
+							setTimeout(() => {
+								// 通过事件通知个人页面刷新数据
+								uni.$emit('vipPurchased')
+								
+								// 返回上一页
+								uni.navigateBack({
+									success: () => {
+										console.log('✅ 返回个人页面')
 									}
-								},
-								fail: (err) => {
-									uni.hideLoading()
-									console.error('VIP购买失败:', err)
-									uni.showToast({
-										title: '网络请求失败',
-										icon: 'none'
-									})
-								}
+								})
+							}, 2000)
+						}
+					},
+					fail: (payError) => {
+						console.error('支付失败:', payError)
+						if (payError.errMsg.includes('cancel')) {
+							uni.showToast({
+								title: '已取消支付',
+								icon: 'none'
+							})
+						} else {
+							uni.showToast({
+								title: `支付失败:${payError.errMsg}`,
+								icon: 'none'
 							})
 						}
 					}
@@ -274,7 +319,7 @@
 </script>
 
 <style lang="scss" scoped>
-	/* 扁平化设计 - 金色VIP主题 */
+	/* 原有样式保持不变 */
 	.vip-page {
 		height: 100vh;
 		background: #F5F5F5;
@@ -284,7 +329,6 @@
 		height: 180rpx;
 	}
 
-	/* 顶部背景 - 金色主题 */
 	.header-bg {
 		background: linear-gradient(135deg, #FFB300 0%, #FFA000 100%);
 		padding: 60rpx 60rpx 120rpx;
@@ -342,7 +386,6 @@
 		}
 	}
 
-	/* 特权列表 - 扁平化金色主题 */
 	.privileges-section {
 		margin: -20rpx 30rpx 30rpx;
 		background-color: #FFFFFF;
@@ -418,7 +461,6 @@
 		}
 	}
 
-	/* 套餐列表 - 金色主题 */
 	.packages-section {
 		margin: 30rpx;
 		
@@ -527,7 +569,6 @@
 		}
 	}
 
-	/* 底部购买按钮 - 金色主题 */
 	.bottom-bar {
 		position: fixed;
 		bottom: 0;
@@ -587,5 +628,4 @@
 			}
 		}
 	}
-</style>
-
+</style>

+ 24 - 1
LiangZhiYUMao/utils/api.js

@@ -527,7 +527,30 @@ export default {
       url: `/dynamic/browse-history?userId=${userId}`,
       method: 'DELETE'
     })
-  }
+  },
+  vip: {
+      // 获取VIP信息(状态、套餐等)
+      getInfo: (userId) => request({ 
+        url: `/vip/info?userId=${userId}` 
+      }),
+      
+      // 获取VIP套餐列表
+      getPackages: () => request({ 
+        url: '/vip/packages' 
+      }),
+      
+      // 购买VIP套餐(获取支付参数)
+      purchase: (userId, packageId) => request({ 
+        url: '/vip/purchase', 
+        method: 'POST', 
+        data: { userId, packageId } 
+      }),
+      
+      // 查询订单状态
+      getOrderStatus: (orderNo) => request({ 
+        url: `/vip/order/status?orderNo=${orderNo}` 
+      })
+    }
 }
 
 // 导出 request 函数供其他模块使用

+ 1 - 1
common/src/main/java/com/zhentao/pojo/Users.java

@@ -100,6 +100,6 @@ public class Users {
      */
     private Integer hasWechatLogin;
 
-    private String wechatOpenId;
+    private String wechatOpenid;
 
 }

BIN
common/target/classes/com/zhentao/pojo/Users.class


+ 14 - 0
service/Essential/pom.xml

@@ -73,5 +73,19 @@
             <artifactId>weixin-java-pay</artifactId>
             <version>4.5.7.B</version> <!-- 使用最新稳定版本 -->
         </dependency>
+
+        <!-- 用于解析PEM格式私钥 -->
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+            <version>1.70</version>
+        </dependency>
+
+        <!-- Apache工具类(生成随机字符串) -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.12.0</version>
+        </dependency>
     </dependencies>
 </project>

+ 10 - 0
service/Essential/src/main/java/com/zhentao/common/Result.java

@@ -3,6 +3,7 @@ package com.zhentao.common;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.util.Map;
 
 /**
  * 统一响应结果类
@@ -12,6 +13,7 @@ public class Result<T> implements Serializable {
     private Integer code;
     private String message;
     private T data;
+    private Map<String, Object> ext;
 
     public static <T> Result<T> success(T data) {
         Result<T> result = new Result<>();
@@ -29,6 +31,14 @@ public class Result<T> implements Serializable {
         return result;
     }
 
+    public static <T> Result<T> success(String message, Map<String, Object> map) {
+        Result<T> result = new Result<>();
+        result.setCode(200);
+        result.setMessage(message);
+        result.setExt(map);
+        return result;
+    }
+
     public static <T> Result<T> error(String message) {
         Result<T> result = new Result<>();
         result.setCode(500);

+ 22 - 0
service/Essential/src/main/java/com/zhentao/config/JacksonConfig.java

@@ -0,0 +1,22 @@
+package com.zhentao.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.text.SimpleDateFormat;
+
+// Jackson配置类
+@Configuration
+public class JacksonConfig {
+    @Bean
+    public ObjectMapper objectMapper() {
+        ObjectMapper mapper = new ObjectMapper();
+        // 注册Java 8时间模块
+        mapper.registerModule(new JavaTimeModule());
+        // 配置时间格式(包含偏移)
+        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));
+        return mapper;
+    }
+}

+ 4 - 1
service/Essential/src/main/java/com/zhentao/config/WechatPayProperties.java

@@ -9,7 +9,7 @@ import org.springframework.stereotype.Component;
  */
 @Data
 @Component
-@ConfigurationProperties(prefix = "wechat.pay")
+@ConfigurationProperties(prefix = "wx.pay")
 public class WechatPayProperties {
 
     /**
@@ -41,6 +41,9 @@ public class WechatPayProperties {
      * 交易类型,默认为JSAPI
      */
     private String tradeType = "JSAPI";
+
+    private String privateKeyPath;// 商户私钥文件路径(apiclient_key.pem)
+    private String apiV3Key;      // APIv3密钥
 }
 
 

+ 31 - 9
service/Essential/src/main/java/com/zhentao/controller/VipController.java

@@ -1,21 +1,28 @@
 package com.zhentao.controller;
 
+import com.github.binarywang.wxpay.exception.WxPayException;
 import com.zhentao.common.Result;
+import com.zhentao.dto.VipPayOrderRequest;
 import com.zhentao.service.VipService;
 import com.zhentao.vo.UserVipInfoVO;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.Map;
+
 /**
  * VIP控制器
  */
+@Slf4j
 @RestController
 @RequestMapping("/api/vip")
 public class VipController {
-    
+
+
     @Autowired
     private VipService vipService;
-    
+
     /**
      * 获取VIP信息和套餐列表
      * @param userId 用户ID(可选,不传则默认为1)
@@ -28,14 +35,14 @@ public class VipController {
             if (userId == null) {
                 userId = 1L;
             }
-            
+
             UserVipInfoVO vo = vipService.getVipInfo(userId);
             return Result.success(vo);
         } catch (Exception e) {
             return Result.error("获取VIP信息失败: " + e.getMessage());
-        }
+         }
     }
-    
+
     /**
      * 购买VIP
      * @param userId 用户ID
@@ -43,20 +50,35 @@ public class VipController {
      * @return 购买结果
      */
     @PostMapping("/purchase")
-    public Result<String> purchaseVip(@RequestParam Long userId, @RequestParam Long packageId) {
+    public Result<String> purchaseVip(@RequestBody VipPayOrderRequest payOrderRequest) {
         try {
-            Boolean success = vipService.purchaseVip(userId, packageId);
-            if (success) {
-                return Result.success("VIP开通成功!", "success");
+            Map<String, Object> stringObjectMap = vipService.purchaseVip(payOrderRequest.getUserId(), payOrderRequest.getPackageId());
+            if (stringObjectMap != null) {
+                return Result.success("VIP开通成功!", stringObjectMap);
             } else {
                 return Result.error("VIP开通失败");
             }
         } catch (RuntimeException e) {
             return Result.error(e.getMessage());
         } catch (Exception e) {
+            log.error(e.getMessage());
             return Result.error("VIP开通失败");
         }
     }
+
+    /**
+     * 微信支付V2回调接口(需配置为外网可访问)
+     */
+    @PostMapping("/notify")
+    public String handleWxPayNotify(@RequestBody String notifyData) {
+        log.info("收到微信支付回调:{}", notifyData);
+        try {
+            return vipService.handlePayNotify(notifyData);
+        } catch (WxPayException e) {
+            log.error("处理支付回调失败", e);
+            return "处理支付回调失败";
+        }
+    }
 }
 
 

+ 3 - 0
service/Essential/src/main/java/com/zhentao/entity/UserVip.java

@@ -76,6 +76,9 @@ public class UserVip implements Serializable {
      * 创建时间
      */
     private LocalDateTime createTime;
+
+
+    private LocalDateTime paymentTime;
 }
 
 

+ 31 - 0
service/Essential/src/main/java/com/zhentao/entity/Wx.java

@@ -0,0 +1,31 @@
+package com.zhentao.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+/**
+ * 
+ * @TableName wx
+ */
+@TableName(value ="wx")
+@Data
+public class Wx {
+    /**
+     * 
+     */
+    @TableId
+    private String appId;
+
+    /**
+     * 密钥
+     */
+    private String appSecret;
+
+    /**
+     * 商户号
+     */
+    private String mchId;
+}

+ 8 - 0
service/Essential/src/main/java/com/zhentao/mapper/UserVipMapper.java

@@ -18,6 +18,14 @@ public interface UserVipMapper extends BaseMapper<UserVip> {
     @Select("SELECT * FROM user_vip WHERE user_id = #{userId} AND status = 1 " +
             "AND end_time > NOW() ORDER BY end_time DESC LIMIT 1")
     UserVip selectActiveVipByUserId(@Param("userId") Long userId);
+
+    @Select(" SELECT * FROM user_vip \n" +
+            "    WHERE user_id = #{userId} \n" +
+            "    AND status = 1 \n" +
+            "    AND end_time > NOW() \n" +
+            "    ORDER BY end_time DESC \n" +
+            "    LIMIT 1")
+    UserVip selectByOrderNo(String orderNo);
 }
 
 

+ 18 - 0
service/Essential/src/main/java/com/zhentao/mapper/WxMapper.java

@@ -0,0 +1,18 @@
+package com.zhentao.mapper;
+
+import com.zhentao.entity.Wx;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author Lenovo
+* @description 针对表【wx】的数据库操作Mapper
+* @createDate 2025-11-28 09:58:18
+* @Entity com.zhentao.entity.Wx
+*/
+public interface WxMapper extends BaseMapper<Wx> {
+
+}
+
+
+
+

+ 7 - 1
service/Essential/src/main/java/com/zhentao/service/VipService.java

@@ -1,7 +1,10 @@
 package com.zhentao.service;
 
+import com.github.binarywang.wxpay.exception.WxPayException;
 import com.zhentao.vo.UserVipInfoVO;
 
+import java.util.Map;
+
 /**
  * VIP服务接口
  */
@@ -20,7 +23,7 @@ public interface VipService {
      * @param packageId 套餐ID
      * @return 是否成功
      */
-    Boolean purchaseVip(Long userId, Long packageId);
+    Map<String, Object> purchaseVip(Long userId, Long packageId) throws WxPayException;
     
     /**
      * 赠送VIP(签到奖励等)
@@ -30,6 +33,9 @@ public interface VipService {
      * @return 是否成功
      */
     Boolean grantVip(Long userId, Integer days, String source);
+
+
+    String handlePayNotify(String notifyData) throws WxPayException;
 }
 
 

+ 13 - 0
service/Essential/src/main/java/com/zhentao/service/WxService.java

@@ -0,0 +1,13 @@
+package com.zhentao.service;
+
+import com.zhentao.entity.Wx;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author Lenovo
+* @description 针对表【wx】的数据库操作Service
+* @createDate 2025-11-28 09:58:18
+*/
+public interface WxService extends IService<Wx> {
+
+}

+ 743 - 97
service/Essential/src/main/java/com/zhentao/service/impl/VipServiceImpl.java

@@ -1,9 +1,580 @@
+//package com.zhentao.service.impl;
+//
+//import com.fasterxml.jackson.core.type.TypeReference;
+//import com.fasterxml.jackson.databind.ObjectMapper;
+//import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
+//import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderResult;
+//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.github.binarywang.wxpay.util.SignUtils;
+//import com.zhentao.config.WechatPayProperties;
+//import com.zhentao.entity.UserVip;
+//import com.zhentao.entity.VipPackage;
+//import com.zhentao.entity.Wx;
+//import com.zhentao.mapper.UserVipMapper;
+//import com.zhentao.mapper.VipPackageMapper;
+//import com.zhentao.pojo.Users;
+//import com.zhentao.service.UsersService;
+//import com.zhentao.service.VipService;
+//import com.zhentao.service.WxService;
+//import com.zhentao.vo.UserVipInfoVO;
+//import com.zhentao.vo.VipPackageVO;
+//import lombok.extern.slf4j.Slf4j;
+//import org.apache.commons.lang3.RandomStringUtils;
+//import org.apache.commons.lang3.RandomUtils;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.stereotype.Service;
+//import org.springframework.transaction.annotation.Transactional;
+//
+//import java.math.BigDecimal;
+//import java.time.LocalDateTime;
+//import java.time.temporal.ChronoUnit;
+//import java.util.ArrayList;
+//import java.util.HashMap;
+//import java.util.List;
+//import java.util.Map;
+//
+///**
+// * VIP服务实现类
+// */
+//@Service
+//@Slf4j
+//public class VipServiceImpl implements VipService {
+//
+//    @Autowired
+//    private VipPackageMapper vipPackageMapper;
+//
+//    @Autowired
+//    private UserVipMapper userVipMapper;
+//
+//    @Autowired
+//    private WxService wxService;
+//    @Autowired
+//    private UsersService usersService;
+//
+//    @Autowired
+//    private WechatPayProperties wxConfig;
+//
+//    private final ObjectMapper objectMapper = new ObjectMapper();
+//
+//    @Override
+//    public UserVipInfoVO getVipInfo(Long userId) {
+//        UserVipInfoVO vo = new UserVipInfoVO();
+//
+//        // 查询用户当前生效的VIP
+//        UserVip currentVip = userVipMapper.selectActiveVipByUserId(userId);
+//
+//        if (currentVip != null && currentVip.getEndTime().isAfter(LocalDateTime.now())) {
+//            vo.setIsVip(true);
+//            vo.setVipExpireTime(currentVip.getEndTime());
+//
+//            // 计算剩余天数
+//            long days = ChronoUnit.DAYS.between(LocalDateTime.now(), currentVip.getEndTime());
+//            vo.setRemainingDays((int) days);
+//        } else {
+//            vo.setIsVip(false);
+//            vo.setVipExpireTime(null);
+//            vo.setRemainingDays(0);
+//        }
+//
+//        // 查询所有VIP套餐
+//        List<VipPackage> packages = vipPackageMapper.selectAllActive();
+//        List<VipPackageVO> packageVOs = new ArrayList<>();
+//
+//        for (VipPackage pkg : packages) {
+//            VipPackageVO pkgVO = new VipPackageVO();
+//            pkgVO.setPackageId(pkg.getPackageId());
+//            pkgVO.setName(pkg.getPackageName());
+//            pkgVO.setDurationDays(pkg.getDurationDays());
+//            pkgVO.setDuration(getDurationText(pkg.getDurationDays()));
+//            pkgVO.setOriginalPrice(pkg.getOriginalPrice());
+//            pkgVO.setPrice(pkg.getCurrentPrice());
+//            pkgVO.setRecommend(pkg.getIsRecommend() == 1);
+//
+//            // 解析特权列表JSON
+//            try {
+//                if (pkg.getBenefits() != null && !pkg.getBenefits().isEmpty()) {
+//                    List<String> benefits = objectMapper.readValue(
+//                        pkg.getBenefits(),
+//                        new TypeReference<List<String>>() {}
+//                    );
+//                    pkgVO.setBenefits(benefits);
+//                } else {
+//                    pkgVO.setBenefits(new ArrayList<>());
+//                }
+//            } catch (Exception e) {
+//                // JSON解析失败时记录日志并使用空列表
+//                System.err.println("解析套餐 " + pkg.getPackageId() + " 的 benefits 失败: " + e.getMessage());
+//                System.err.println("原始数据: " + pkg.getBenefits());
+//                pkgVO.setBenefits(new ArrayList<>());
+//            }
+//
+//            packageVOs.add(pkgVO);
+//        }
+//
+//        vo.setPackages(packageVOs);
+//
+//        return vo;
+//    }
+//
+////    @Override
+////    @Transactional(rollbackFor = Exception.class)
+////    public Boolean purchaseVip(Long userId, Long packageId) throws WxPayException {
+////        // 查询套餐信息
+////        VipPackage pkg = vipPackageMapper.selectById(packageId);
+////        if (pkg == null || pkg.getStatus() != 1) {
+////            throw new RuntimeException("套餐不存在或已下架");
+////        }
+////
+////        // 生成订单号
+////        String orderNo = generateOrderNo();
+////        LocalDateTime now = LocalDateTime.now();
+////        LocalDateTime startTime = now;
+////        LocalDateTime endTime;
+////
+////
+////        // 查询用户当前VIP状态
+////        UserVip currentVip = userVipMapper.selectActiveVipByUserId(userId);
+////        if (currentVip != null && currentVip.getEndTime().isAfter(now)) {
+////            // 如果已有VIP,从当前VIP结束时间开始累加
+////            startTime = currentVip.getEndTime();
+////        }
+////
+////        // 计算结束时间
+////        endTime = startTime.plusDays(pkg.getDurationDays());
+////
+////
+////        // 创建VIP记录
+////        UserVip userVip = new UserVip();
+////        userVip.setUserId(userId);
+////        userVip.setPackageId(packageId);
+////        userVip.setStartTime(startTime);
+////        userVip.setEndTime(endTime);
+////        userVip.setDurationDays(pkg.getDurationDays());
+////        userVip.setPaymentAmount(pkg.getCurrentPrice());
+////        userVip.setPaymentMethod("在线支付"); // TODO: 实际支付方式
+////        userVip.setOrderNo(orderNo);
+////        userVip.setStatus(1);
+////        userVip.setSource("购买");
+////        userVip.setCreateTime(now);
+////
+////        int result = userVipMapper.insert(userVip);
+////        String timeExpire = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis());
+////        Wx wx = wxService.list().get(0);
+////        WxPayUnifiedOrderV3Request wxPayUnifiedOrderV3Request = new WxPayUnifiedOrderV3Request();
+////        wxPayUnifiedOrderV3Request.setOutTradeNo(orderNo);
+////        wxPayUnifiedOrderV3Request.setDescription(pkg.getPackageName());
+////        wxPayUnifiedOrderV3Request.setTimeExpire(timeExpire);
+////        wxPayUnifiedOrderV3Request.setNotifyUrl("https://mini.workervip.com/pay/notify");
+////        //订单金额
+////        WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
+////        amount.setTotal(pkg.getCurrentPrice().intValue());
+////        amount.setCurrency("CNY");
+////        wxPayUnifiedOrderV3Request.setAmount(amount);
+////
+////        Users byId = usersService.getById(userId);
+////        //付款人员
+////        WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
+////        payer.setOpenid(byId.getWechatOpenid());
+////        TradeTypeEnum tradeTypeEnum =TradeTypeEnum.JSAPI;
+////        wxPayUnifiedOrderV3Request.setPayer(payer);
+////        WxPayConfig wxPayConfig=new WxPayConfig();
+////
+////        wxPayConfig.setAppId(wx.getAppId());
+////        wxPayConfig.setMchId(wx.getMchId());
+////        wxPayConfig.setMchKey(wx.getAppSecret());
+////        wxPayConfig.setApiV3Key(wx.getAppSecret());
+////        wxPayConfig.setPayBaseUrl("https://api.mch.weixin.qq.com");
+////        wxPayConfig.setPrivateKeyPath(null);
+////        wxPayConfig.setNotifyUrl("https://mini.workervip.com/pay/notify");
+////        wxPayConfig.setPrivateCertPath("classpath:/apiclient_cert.pem");
+////        wxPayConfig.setPrivateKeyPath("classpath:/apiclient_key.pem");
+////        WxPayService wxPayService = new WxPayServiceImpl();
+////        wxPayService.setConfig(wxPayConfig);
+////        Object obj=wxPayService.createOrderV3(tradeTypeEnum, wxPayUnifiedOrderV3Request);
+////        System.out.println(obj);
+////
+////        return result > 0;
+////    }
+//
+//    @Override
+//    @Transactional(rollbackFor = Exception.class)
+//    public Boolean grantVip(Long userId, Integer days, String source) {
+//        LocalDateTime now = LocalDateTime.now();
+//        LocalDateTime startTime = now;
+//        LocalDateTime endTime;
+//
+//        // 查询用户当前VIP状态
+//        UserVip currentVip = userVipMapper.selectActiveVipByUserId(userId);
+//        if (currentVip != null && currentVip.getEndTime().isAfter(now)) {
+//            // 如果已有VIP,从当前VIP结束时间开始累加
+//            startTime = currentVip.getEndTime();
+//        }
+//
+//        // 计算结束时间
+//        endTime = startTime.plusDays(days);
+//
+//        // 创建VIP记录
+//        UserVip userVip = new UserVip();
+//        userVip.setUserId(userId);
+//        userVip.setPackageId(0L); // 赠送的VIP没有套餐ID
+//        userVip.setStartTime(startTime);
+//        userVip.setEndTime(endTime);
+//        userVip.setDurationDays(days);
+//        userVip.setPaymentAmount(BigDecimal.ZERO);
+//        userVip.setPaymentMethod("赠送");
+//        userVip.setOrderNo(generateOrderNo());
+//        userVip.setStatus(1);
+//        userVip.setSource(source);
+//        userVip.setCreateTime(now);
+//
+//        int result = userVipMapper.insert(userVip);
+//
+//        return result > 0;
+//    }
+//
+////    /**
+////     * 生成订单号
+////     */
+////    private String generateOrderNo() {
+////        return "VIP" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 8);
+////    }
+//
+//    /**
+//     * 获取时长单位文字
+//     */
+//    private String getDurationText(Integer days) {
+//        if (days == 30) return "月";
+//        if (days == 90) return "季";
+//        if (days == 365) return "年";
+//        return days + "天";
+//    }
+//
+//
+////    @Override
+////    @Transactional(rollbackFor = Exception.class)
+////    public Map<String, Object> purchaseVip(Long userId, Long packageId) throws WxPayException {
+////        // 1. 校验套餐
+////        VipPackage pkg = vipPackageMapper.selectById(packageId);
+////        if (pkg == null || pkg.getStatus() != 1) {
+////            throw new RuntimeException("套餐不存在或已下架");
+////        }
+////
+////        // 2. 生成订单号和时间计算
+////        String orderNo = generateOrderNo();
+////        LocalDateTime now = LocalDateTime.now();
+////        LocalDateTime startTime = now;
+////
+////        // 处理用户现有VIP续期
+////        UserVip currentVip = userVipMapper.selectActiveVipByUserId(userId);
+////        if (currentVip != null && currentVip.getEndTime().isAfter(now)) {
+////            startTime = currentVip.getEndTime();
+////        }
+////        LocalDateTime endTime = startTime.plusDays(pkg.getDurationDays());
+////
+////        // 3. 创建VIP记录(状态设为待支付)
+////        UserVip userVip = new UserVip();
+////        userVip.setUserId(userId);
+////        userVip.setPackageId(packageId);
+////        userVip.setStartTime(startTime);
+////        userVip.setEndTime(endTime);
+////        userVip.setDurationDays(pkg.getDurationDays());
+////        userVip.setPaymentAmount(pkg.getCurrentPrice());
+////        userVip.setPaymentMethod("微信支付");
+////        userVip.setOrderNo(orderNo);
+////        userVip.setStatus(0); // 0-待支付,1-已生效
+////        userVip.setSource("购买");
+////        userVip.setCreateTime(now);
+////        userVipMapper.insert(userVip);
+////
+////        // 4. 构建微信支付V3请求
+////        WxPayUnifiedOrderV3Request payRequest = buildWxPayRequest(orderNo, pkg, userId);
+////
+////        // 5. 调用微信支付下单(非事务内执行,避免阻塞)
+////        WxPayUnifiedOrderV3Result.JsapiResult wxPayOrder = createWxPayOrder(payRequest);
+////        String[] split = wxPayOrder.getPackageValue().split("=");
+////        String prepayId = split[1];
+////        Map<String, Object> stringObjectMap = generatePayParams(prepayId);
+////
+////        // 6. 返回支付参数给前端
+////        BigDecimal packagePrice = pkg.getCurrentPrice(); // 套餐价格(单位:元,如19.9)
+////        int totalFee = packagePrice.multiply(new BigDecimal("100")).intValue(); // 转为分:19.9元 → 1990
+////        stringObjectMap.put("totalFee",totalFee);
+////        stringObjectMap.put("orderNo", orderNo);
+////        return stringObjectMap;
+////    }
+////
+////    /**
+////     * 构建微信支付V3请求参数
+////     */
+////    private WxPayUnifiedOrderV3Request buildWxPayRequest(String orderNo, VipPackage pkg, Long userId) {
+////        // 正确的过期时间格式(ISO8601)
+////        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX");
+////        String timeExpire = LocalDateTime.now().plusMinutes(30)
+////                .atZone(ZoneId.of("Asia/Shanghai")) // 明确指定东八区
+////                .format(formatter);
+////
+////        // 查询用户OpenID
+////        Users user = usersService.getById(userId);
+////        if (user == null || user.getWechatOpenid() == null) {
+////            throw new RuntimeException("用户微信信息未绑定");
+////        }
+////
+////        // 构建请求对象
+////        WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
+////        request.setOutTradeNo(orderNo);
+////        request.setDescription(pkg.getPackageName());
+////        request.setTimeExpire(timeExpire);
+////        request.setNotifyUrl("https://mini.workervip.com/pay/notify");
+////
+////        // 金额(转换为分)
+////        WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
+////        amount.setTotal(pkg.getCurrentPrice().multiply(new BigDecimal("100")).intValue());
+////        amount.setCurrency("CNY");
+////        request.setAmount(amount);
+////
+////        // 支付者信息
+////        WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
+////        payer.setOpenid(user.getWechatOpenid());
+////        request.setPayer(payer);
+////
+////        return request;
+////    }
+////
+////    /**
+////     * 初始化微信支付配置并下单
+////     */
+////    private WxPayUnifiedOrderV3Result.JsapiResult createWxPayOrder(WxPayUnifiedOrderV3Request request) throws WxPayException {
+////        // 从配置文件/数据库读取正确配置
+////        Wx wxConfig = wxService.list().get(0);
+////        WxPayConfig payConfig = new WxPayConfig();
+////
+////        // 正确配置(APIv3密钥需从商户平台获取)
+////        payConfig.setAppId(wxConfig.getAppId());
+////        payConfig.setMchId(wxConfig.getMchId());
+////        payConfig.setApiV3Key("7f133cbabf119b4d213bc6ekffe3b149"); // 替换为真实32位APIv3密钥
+////        payConfig.setNotifyUrl("https://mini.workervip.com/pay/notify");
+////        payConfig.setPrivateCertPath("classpath:apiclient_cert.pem"); // 确保证书存在
+////        payConfig.setPrivateKeyPath("classpath:apiclient_key.pem");
+////
+////        // 初始化支付服务
+////        WxPayService wxPayService = new WxPayServiceImpl();
+////        wxPayService.setConfig(payConfig);
+////
+////        // 调用V3下单接口
+////        WxPayUnifiedOrderV3Result.JsapiResult result = wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request);
+////
+////        // 解析支付参数(前端需用此调起支付)
+////        return result;
+////    }
+////
+//    /**
+//     * 生成唯一订单号
+//     */
+//    private String generateOrderNo() {
+//        return "VIP" + System.currentTimeMillis() + RandomUtils.nextInt(1000, 9999);
+//    }
+////    private Map<String, Object> generatePayParams(String prepayId) {
+////        Map<String, Object> payParams = new HashMap<>();
+////        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
+////        String nonceStr = RandomStringUtils.randomAlphanumeric(32);
+////
+////        // 使用微信支付配置的AppID(而非自定义配置)
+////        Wx wxConfig = wxService.list().get(0);
+////        String signatureStr = wxConfig.getAppId() + "\n" + timeStamp + "\n" + nonceStr + "\n" + prepayId + "\n";
+////
+////        // 从Classpath读取私钥并签名
+////        PrivateKey privateKey = getPrivateKeyFromClasspath("/apiclient_key.pem");
+////        String paySign = signWithPrivateKey(signatureStr, privateKey);
+////
+////        payParams.put("timeStamp", timeStamp);
+////        payParams.put("nonceStr", nonceStr);
+////        payParams.put("package", prepayId);
+////        payParams.put("signType", "RSA");
+////        payParams.put("paySign", paySign);
+////        payParams.put("orderNo", generateOrderNo()); // 传递正确订单号
+////
+////        return payParams;
+////    }
+////
+////    /**
+////     * 从Classpath读取私钥(修复FileReader无法读取classpath路径问题)
+////     */
+////    private PrivateKey getPrivateKeyFromClasspath(String classpath) {
+////        try (InputStream inputStream = getClass().getResourceAsStream(classpath);
+////             PemReader pemReader = new PemReader(new InputStreamReader(inputStream))) {
+////
+////            PemObject pemObject = pemReader.readPemObject();
+////            byte[] privateKeyBytes = pemObject.getContent();
+////
+////            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
+////            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+////            return keyFactory.generatePrivate(keySpec);
+////
+////        } catch (Exception e) {
+////            throw new RuntimeException("读取私钥失败", e);
+////        }
+////    }
+////
+////    /**
+////     * 使用商户私钥进行SHA256withRSA签名
+////     */
+////    private String signWithPrivateKey(String data, PrivateKey privateKey) {
+////        try {
+////            Signature signature = Signature.getInstance("SHA256withRSA");
+////            signature.initSign(privateKey);
+////            signature.update(data.getBytes(StandardCharsets.UTF_8));
+////            return Base64.getEncoder().encodeToString(signature.sign());
+////        } catch (Exception e) {
+////            throw new RuntimeException("签名失败", e);
+////        }
+////    }
+//@Override
+//@Transactional(rollbackFor = Exception.class)
+//public Map<String, Object> purchaseVip(Long userId, Long packageId) throws WxPayException {
+//    // 1. 校验套餐
+//    VipPackage pkg = vipPackageMapper.selectById(packageId);
+//    if (pkg == null || pkg.getStatus() != 1) {
+//        throw new RuntimeException("套餐不存在或已下架");
+//    }
+//
+//    // 2. 生成订单号和时间计算
+//    String orderNo = generateOrderNo();
+//    LocalDateTime now = LocalDateTime.now();
+//    LocalDateTime startTime = now;
+//
+//    // 处理用户现有VIP续期
+//    UserVip currentVip = userVipMapper.selectActiveVipByUserId(userId);
+//    if (currentVip != null && currentVip.getEndTime().isAfter(now)) {
+//        startTime = currentVip.getEndTime();
+//    }
+//    LocalDateTime endTime = startTime.plusDays(pkg.getDurationDays());
+//
+//    // 3. 创建VIP记录(状态设为待支付)
+//    UserVip userVip = new UserVip();
+//    userVip.setUserId(userId);
+//    userVip.setPackageId(packageId);
+//    userVip.setStartTime(startTime);
+//    userVip.setEndTime(endTime);
+//    userVip.setDurationDays(pkg.getDurationDays());
+//    userVip.setPaymentAmount(pkg.getCurrentPrice());
+//    userVip.setPaymentMethod("微信支付");
+//    userVip.setOrderNo(orderNo);
+//    userVip.setStatus(0); // 0-待支付,1-已生效
+//    userVip.setSource("购买");
+//    userVip.setCreateTime(now);
+//    userVipMapper.insert(userVip);
+//
+//    // 4. 构建微信支付V2请求
+//    WxPayUnifiedOrderRequest payRequest = buildWxPayV2Request(orderNo, pkg, userId);
+//
+//    // 5. 调用微信支付V2下单接口
+//    WxPayUnifiedOrderResult wxPayResult = createWxPayV2Order(payRequest);
+//
+//    // 6. 生成前端调起支付的参数
+//    Map<String, Object> payParams = generateV2PayParams(wxPayResult, pkg);
+//
+//    // 7. 返回支付参数给前端
+//    payParams.put("orderNo", orderNo);
+//    return payParams;
+//}
+//
+//    /**
+//     * 构建微信支付V2请求参数
+//     */
+//    private WxPayUnifiedOrderRequest buildWxPayV2Request(String orderNo, VipPackage pkg, Long userId) {
+//        // 查询用户OpenID
+//        Users user = usersService.getById(userId);
+//        if (user == null || user.getWechatOpenid() == null) {
+//            throw new RuntimeException("用户微信信息未绑定");
+//        }
+//
+//        WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
+//        request.setOutTradeNo(orderNo); // 商户订单号
+//        request.setBody(pkg.getPackageName()); // 商品描述
+//        request.setTotalFee(pkg.getCurrentPrice().multiply(new BigDecimal("100")).intValue()); // 总金额(分)
+//        request.setSpbillCreateIp("127.0.0.1"); // 终端IP(实际部署需改为用户真实IP)
+//        request.setNotifyUrl("https://mini.workervip.com/pay/notify"); // 回调地址
+//        request.setTradeType("JSAPI"); // 支付类型(JSAPI/小程序)
+//        request.setOpenid(user.getWechatOpenid()); // 用户OpenID(JSAPI必填)
+//
+//        return request;
+//    }
+//
+//    /**
+//     * 初始化微信支付V2配置并下单
+//     */
+//    private WxPayUnifiedOrderResult createWxPayV2Order(WxPayUnifiedOrderRequest request) throws WxPayException {
+//        Wx wxConfig = wxService.list().get(0);
+//        WxPayConfig payConfig = new WxPayConfig();
+//
+//        // V2配置核心参数
+//        payConfig.setAppId(wxConfig.getAppId()); // 公众号/小程序AppID
+//        payConfig.setMchId(wxConfig.getMchId()); // 商户号
+//        payConfig.setMchKey("7f633cbabd894b4d213bc6edffe3b119"); // V2密钥(商户平台设置的32位密钥)
+//        payConfig.setNotifyUrl("https://mini.workervip.com/pay/notify");
+//
+//        WxPayService wxPayService = new WxPayServiceImpl();
+//        wxPayService.setConfig(payConfig);
+//
+//        // 调用V2统一下单接口
+//        return wxPayService.unifiedOrder(request);
+//    }
+//
+//    /**
+//     * 生成V2版本前端调起支付的参数
+//     */
+//    private Map<String, Object> generateV2PayParams(WxPayUnifiedOrderResult wxPayResult, VipPackage pkg) {
+//        Wx wxConfig = wxService.list().get(0);
+//        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
+//        String nonceStr = RandomStringUtils.randomAlphanumeric(32);
+//        String prepayId = wxPayResult.getPrepayId();
+//
+//        // V2签名参数(注意参数顺序和格式)
+//        Map<String, String> signParams = new HashMap<>();
+//        signParams.put("appId", wxConfig.getAppId());
+//        signParams.put("timeStamp", timeStamp);
+//        signParams.put("nonceStr", nonceStr);
+//        signParams.put("package", "prepay_id=" + prepayId);
+//        signParams.put("signType", "MD5"); // V2默认MD5签名
+//
+//        // 生成签名(使用商户密钥)
+//        String paySign = SignUtils.createSign(signParams, "7f633cbabd894b4d213bc6edffe3b119");
+//
+//        // 组装前端需要的参数
+//        Map<String, Object> payParams = new HashMap<>();
+//        payParams.put("appId", wxConfig.getAppId());
+//        payParams.put("timeStamp", timeStamp);
+//        payParams.put("nonceStr", nonceStr);
+//        payParams.put("package", "prepay_id=" + prepayId); // 注意格式
+//        payParams.put("signType", "MD5");
+//        payParams.put("paySign", paySign);
+//        payParams.put("totalFee", pkg.getCurrentPrice().multiply(new BigDecimal("100")).intValue()); // 总金额(分)
+//
+//        return payParams;
+//    }
+//
+//}
+//
+//
+
+
 package com.zhentao.service.impl;
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
-import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
+import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
+import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
+import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderResult;
+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.github.binarywang.wxpay.util.SignUtils;
+import com.zhentao.config.WechatPayProperties;
 import com.zhentao.entity.UserVip;
 import com.zhentao.entity.VipPackage;
 import com.zhentao.entity.Wx;
@@ -15,27 +586,28 @@ import com.zhentao.service.VipService;
 import com.zhentao.service.WxService;
 import com.zhentao.vo.UserVipInfoVO;
 import com.zhentao.vo.VipPackageVO;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.RandomUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
-import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
+import java.util.*;
 
 /**
- * VIP服务实现类
+ * VIP服务实现类(支付成功后激活VIP)
  */
 @Service
+@Slf4j
 public class VipServiceImpl implements VipService {
-    
+
     @Autowired
     private VipPackageMapper vipPackageMapper;
-    
+
     @Autowired
     private UserVipMapper userVipMapper;
 
@@ -43,21 +615,20 @@ public class VipServiceImpl implements VipService {
     private WxService wxService;
     @Autowired
     private UsersService usersService;
-    
+
+    @Autowired
+    private WechatPayProperties wxConfig;
+
     private final ObjectMapper objectMapper = new ObjectMapper();
-    
+
     @Override
     public UserVipInfoVO getVipInfo(Long userId) {
         UserVipInfoVO vo = new UserVipInfoVO();
-        
-        // 查询用户当前生效的VIP
+
         UserVip currentVip = userVipMapper.selectActiveVipByUserId(userId);
-        
         if (currentVip != null && currentVip.getEndTime().isAfter(LocalDateTime.now())) {
             vo.setIsVip(true);
             vo.setVipExpireTime(currentVip.getEndTime());
-            
-            // 计算剩余天数
             long days = ChronoUnit.DAYS.between(LocalDateTime.now(), currentVip.getEndTime());
             vo.setRemainingDays((int) days);
         } else {
@@ -65,11 +636,9 @@ public class VipServiceImpl implements VipService {
             vo.setVipExpireTime(null);
             vo.setRemainingDays(0);
         }
-        
-        // 查询所有VIP套餐
+
         List<VipPackage> packages = vipPackageMapper.selectAllActive();
         List<VipPackageVO> packageVOs = new ArrayList<>();
-        
         for (VipPackage pkg : packages) {
             VipPackageVO pkgVO = new VipPackageVO();
             pkgVO.setPackageId(pkg.getPackageId());
@@ -79,121 +648,144 @@ public class VipServiceImpl implements VipService {
             pkgVO.setOriginalPrice(pkg.getOriginalPrice());
             pkgVO.setPrice(pkg.getCurrentPrice());
             pkgVO.setRecommend(pkg.getIsRecommend() == 1);
-            
-            // 解析特权列表JSON
+
             try {
                 if (pkg.getBenefits() != null && !pkg.getBenefits().isEmpty()) {
                     List<String> benefits = objectMapper.readValue(
-                        pkg.getBenefits(), 
-                        new TypeReference<List<String>>() {}
+                            pkg.getBenefits(),
+                            new TypeReference<List<String>>() {}
                     );
                     pkgVO.setBenefits(benefits);
                 } else {
                     pkgVO.setBenefits(new ArrayList<>());
                 }
             } catch (Exception e) {
-                // JSON解析失败时记录日志并使用空列表
-                System.err.println("解析套餐 " + pkg.getPackageId() + " 的 benefits 失败: " + e.getMessage());
-                System.err.println("原始数据: " + pkg.getBenefits());
                 pkgVO.setBenefits(new ArrayList<>());
             }
-            
             packageVOs.add(pkgVO);
         }
-        
         vo.setPackages(packageVOs);
-        
         return vo;
     }
-    
+
+    /**
+     * 下单:仅创建待支付订单,不激活VIP
+     */
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public Boolean purchaseVip(Long userId, Long packageId) {
-        // 查询套餐信息
+    public Map<String, Object> purchaseVip(Long userId, Long packageId) throws WxPayException {
+        // 1. 校验套餐
         VipPackage pkg = vipPackageMapper.selectById(packageId);
         if (pkg == null || pkg.getStatus() != 1) {
             throw new RuntimeException("套餐不存在或已下架");
         }
-        
-        // 生成订单号
-        String orderNo = generateOrderNo();
-        String timeExpire = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis());
-        Wx wx = wxService.list().get(0);
-        WxPayUnifiedOrderV3Request wxPayUnifiedOrderV3Request = new WxPayUnifiedOrderV3Request();
-        wxPayUnifiedOrderV3Request.setOutTradeNo(orderNo);
-        wxPayUnifiedOrderV3Request.setDescription(pkg.getPackageName());
-        wxPayUnifiedOrderV3Request.setTimeExpire(timeExpire);
-        wxPayUnifiedOrderV3Request.setNotifyUrl("https://mini.workervip.com/pay/notify");
-        //订单金额
-        WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
-        amount.setTotal(pkg.getCurrentPrice().intValue());
-        amount.setCurrency("CNY");
-        wxPayUnifiedOrderV3Request.setAmount(amount);
-
-        Users byId = usersService.getById(userId);
-        //付款人员
-        WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
-        payer.setOpenid(byId.getWechatOpenId());
-        TradeTypeEnum tradeTypeEnum =TradeTypeEnum.JSAPI;
-
-
 
+        // 2. 生成订单号
+        String orderNo = generateOrderNo();
         LocalDateTime now = LocalDateTime.now();
         LocalDateTime startTime = now;
-        LocalDateTime endTime;
-
-        
-        // 查询用户当前VIP状态
+        // 处理续期:如果用户已有生效VIP,从结束时间开始累加
         UserVip currentVip = userVipMapper.selectActiveVipByUserId(userId);
         if (currentVip != null && currentVip.getEndTime().isAfter(now)) {
-            // 如果已有VIP,从当前VIP结束时间开始累加
             startTime = currentVip.getEndTime();
         }
-        
-        // 计算结束时间
-        endTime = startTime.plusDays(pkg.getDurationDays());
+        LocalDateTime endTime = startTime.plusDays(pkg.getDurationDays());
 
-
-        // 创建VIP记录
+        // 3. 创建待支付的VIP订单记录(状态为0-待支付,无生效时间)
         UserVip userVip = new UserVip();
         userVip.setUserId(userId);
         userVip.setPackageId(packageId);
-        userVip.setStartTime(startTime);
-        userVip.setEndTime(endTime);
+        userVip.setStartTime(startTime); // 支付成功前不设置生效时间
+        userVip.setEndTime(endTime);   // 支付成功前不设置结束时间
         userVip.setDurationDays(pkg.getDurationDays());
         userVip.setPaymentAmount(pkg.getCurrentPrice());
-        userVip.setPaymentMethod("在线支付"); // TODO: 实际支付方式
+        userVip.setPaymentMethod("微信支付");
         userVip.setOrderNo(orderNo);
-        userVip.setStatus(1);
+        userVip.setStatus(1); // 0-待支付
         userVip.setSource("购买");
-        userVip.setCreateTime(now);
-        
-        int result = userVipMapper.insert(userVip);
-        
-        return result > 0;
+        userVip.setCreateTime(LocalDateTime.now());
+        userVipMapper.insert(userVip);
+
+        // 4. 构建微信支付V2请求并下单
+        WxPayUnifiedOrderRequest payRequest = buildWxPayV2Request(orderNo, pkg, userId);
+        WxPayUnifiedOrderResult wxPayResult = createWxPayV2Order(payRequest);
+
+        // 5. 生成前端调起支付的参数
+        Map<String, Object> payParams = generateV2PayParams(wxPayResult, pkg);
+        payParams.put("orderNo", orderNo);
+        return payParams;
     }
-    
+
+    /**
+     * 微信支付回调处理:支付成功后激活VIP
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public String handlePayNotify(String notifyData) throws WxPayException {
+        // 1. 解析回调数据并验证签名
+        WxPayConfig payConfig = getWxPayConfig();
+        WxPayService wxPayService = new WxPayServiceImpl();
+        wxPayService.setConfig(payConfig);
+
+        WxPayOrderNotifyResult notifyResult = wxPayService.parseOrderNotifyResult(notifyData);
+        String orderNo = notifyResult.getOutTradeNo(); // 商户订单号
+        String transactionId = notifyResult.getTransactionId(); // 微信支付订单号
+
+        // 2. 查询待支付的VIP订单
+        UserVip pendingVip = userVipMapper.selectByOrderNo(orderNo);
+        if (pendingVip == null || pendingVip.getStatus() != 0) {
+            log.warn("订单不存在或已处理:{}", orderNo);
+            return WxPayNotifyResponse.fail("订单不存在或已处理");
+        }
+
+        // 3. 验证支付金额(可选)
+        VipPackage pkg = vipPackageMapper.selectById(pendingVip.getPackageId());
+        int totalFee = pkg.getCurrentPrice().multiply(new BigDecimal("100")).intValue();
+        if (Integer.parseInt(String.valueOf(notifyResult.getTotalFee())) != totalFee) {
+            log.warn("订单金额不一致:{},实际支付:{}", orderNo, notifyResult.getTotalFee());
+            return WxPayNotifyResponse.fail("金额不一致");
+        }
+
+        // 4. 支付成功,激活VIP
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime startTime = now;
+
+        // 处理续期:如果用户已有生效VIP,从结束时间开始累加
+        UserVip currentVip = userVipMapper.selectActiveVipByUserId(pendingVip.getUserId());
+        if (currentVip != null && currentVip.getEndTime().isAfter(now)) {
+            startTime = currentVip.getEndTime();
+        }
+        LocalDateTime endTime = startTime.plusDays(pkg.getDurationDays());
+
+        // 更新VIP订单为已支付并激活
+        pendingVip.setStatus(1); // 1-已生效
+        pendingVip.setStartTime(startTime);
+        pendingVip.setEndTime(endTime);
+        pendingVip.setPaymentTime(now);
+        pendingVip.setOrderNo(transactionId);
+        userVipMapper.updateById(pendingVip);
+
+        log.info("VIP激活成功:用户{},订单{},到期时间{}", pendingVip.getUserId(), orderNo, endTime);
+        return WxPayNotifyResponse.success("处理成功");
+    }
+
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Boolean grantVip(Long userId, Integer days, String source) {
+        // 原有逻辑不变...
         LocalDateTime now = LocalDateTime.now();
         LocalDateTime startTime = now;
         LocalDateTime endTime;
-        
-        // 查询用户当前VIP状态
+
         UserVip currentVip = userVipMapper.selectActiveVipByUserId(userId);
         if (currentVip != null && currentVip.getEndTime().isAfter(now)) {
-            // 如果已有VIP,从当前VIP结束时间开始累加
             startTime = currentVip.getEndTime();
         }
-        
-        // 计算结束时间
         endTime = startTime.plusDays(days);
-        
-        // 创建VIP记录
+
         UserVip userVip = new UserVip();
         userVip.setUserId(userId);
-        userVip.setPackageId(0L); // 赠送的VIP没有套餐ID
+        userVip.setPackageId(0L);
         userVip.setStartTime(startTime);
         userVip.setEndTime(endTime);
         userVip.setDurationDays(days);
@@ -203,28 +795,82 @@ public class VipServiceImpl implements VipService {
         userVip.setStatus(1);
         userVip.setSource(source);
         userVip.setCreateTime(now);
-        
+
         int result = userVipMapper.insert(userVip);
-        
         return result > 0;
     }
-    
-    /**
-     * 生成订单号
-     */
-    private String generateOrderNo() {
-        return "VIP" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 8);
-    }
-    
-    /**
-     * 获取时长单位文字
-     */
     private String getDurationText(Integer days) {
         if (days == 30) return "月";
         if (days == 90) return "季";
         if (days == 365) return "年";
         return days + "天";
     }
-}
 
+    private String generateOrderNo() {
+        return "VIP" + System.currentTimeMillis() + RandomUtils.nextInt(1000, 9999);
+    }
+
+    private WxPayUnifiedOrderRequest buildWxPayV2Request(String orderNo, VipPackage pkg, Long userId) {
+        Users user = usersService.getById(userId);
+        if (user == null || user.getWechatOpenid() == null) {
+            throw new RuntimeException("用户微信信息未绑定");
+        }
+
+        WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
+        request.setOutTradeNo(orderNo);
+        request.setBody(pkg.getPackageName());
+        request.setTotalFee(pkg.getCurrentPrice().multiply(new BigDecimal("100")).intValue());
+        request.setSpbillCreateIp("127.0.0.1");
+        request.setNotifyUrl("https://mini.workervip.com/pay/notify"); // 回调地址
+        request.setTradeType("JSAPI");
+        request.setOpenid(user.getWechatOpenid());
+        return request;
+    }
+
+    private WxPayUnifiedOrderResult createWxPayV2Order(WxPayUnifiedOrderRequest request) throws WxPayException {
+        WxPayService wxPayService = new WxPayServiceImpl();
+        wxPayService.setConfig(getWxPayConfig());
+        return wxPayService.unifiedOrder(request);
+    }
+
+    private Map<String, Object> generateV2PayParams(WxPayUnifiedOrderResult wxPayResult, VipPackage pkg) {
+        Wx wxConfig = wxService.list().get(0);
+        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
+        String nonceStr = RandomStringUtils.randomAlphanumeric(32);
+        String prepayId = wxPayResult.getPrepayId();
+
+        Map<String, String> signParams = new HashMap<>();
+        signParams.put("appId", wxConfig.getAppId());
+        signParams.put("timeStamp", timeStamp);
+        signParams.put("nonceStr", nonceStr);
+        signParams.put("package", "prepay_id=" + prepayId);
+        signParams.put("signType", "MD5");
 
+        String paySign = SignUtils.createSign(signParams,"7f633cbabd894b4d213bc6edffe3b119");
+
+        Map<String, Object> payParams = new HashMap<>();
+        payParams.put("appId", wxConfig.getAppId());
+        payParams.put("timeStamp", timeStamp);
+        payParams.put("nonceStr", nonceStr);
+        payParams.put("package", "prepay_id=" + prepayId);
+        payParams.put("signType", "MD5");
+        payParams.put("paySign", paySign);
+        payParams.put("totalFee", pkg.getCurrentPrice().multiply(new BigDecimal("100")).intValue());
+        return payParams;
+    }
+
+    /**
+     * 获取微信支付配置
+     */
+    private WxPayConfig getWxPayConfig() {
+        Wx wxConfig = wxService.list().get(0);
+        WxPayConfig payConfig = new WxPayConfig();
+        payConfig.setAppId(wxConfig.getAppId());
+        payConfig.setMchId(wxConfig.getMchId());
+        payConfig.setMchKey("7f633cbabd894b4d213bc6edffe3b119");
+        payConfig.setNotifyUrl("https://mini.workervip.com/pay/notify");
+        return payConfig;
+    }
+
+
+}

+ 22 - 0
service/Essential/src/main/java/com/zhentao/service/impl/WxServiceImpl.java

@@ -0,0 +1,22 @@
+package com.zhentao.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.entity.Wx;
+import com.zhentao.service.WxService;
+import com.zhentao.mapper.WxMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author Lenovo
+* @description 针对表【wx】的数据库操作Service实现
+* @createDate 2025-11-28 09:58:18
+*/
+@Service
+public class WxServiceImpl extends ServiceImpl<WxMapper, Wx>
+    implements WxService{
+
+}
+
+
+
+

+ 25 - 0
service/Essential/src/main/resources/apiclient_cert.pem

@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEMTCCAxmgAwIBAgIUFnAl/3Kjn0XiAdG6+477emUYq24wDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
+FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
+Q0EwHhcNMjUxMTI2MTE0MTUxWhcNMzAxMTI1MTE0MTUxWjCBijETMBEGA1UEAwwK
+MTcyNzY2MzgzOTEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTYwNAYDVQQL
+DC3ljJfkuqzkuK3ova/np5HkurrmiY3nrqHnkIbmnInpmZDotKPku7vlhazlj7gx
+CzAJBgNVBAYTAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAKCm20JOlNqFkQyT16l7KPlQX3IdUHwBbnwDCZkDba2K
+AvPnIL+m/LGq020H5a4ger8XihmsnUpAwKG2vg5SE4oAz3M/yMEti7V0mzh65a0e
+CrbU76nLf7vp4Y49ZetLRuDfncmB0NasTQVExXDJ86Qep3spWnaRyfZS4EvI1vQM
+Sca4V018Bg+z3IvTh2v/Eq6AsnI4vDeolHjU0WVcfnoI1dpJLcq+515iBmQK2W4t
+AyJxFWukSKD6doHynmhGWMn40rOSFWAczhU9zL1pFuaMvqrdh39FmVujFne57E1P
+hzoDjrmeuXmfdWuVoj9enRmw2A4BmhREoS/IIaq9RNMCAwEAAaOBuTCBtjAJBgNV
+HRMEAjAAMAsGA1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0
+cDovL2V2Y2EuaXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIw
+RTUwREJDMDRCMDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0
+MjJFMTJCMjdBOUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUA
+A4IBAQBG34mh4g1K8KOdCZH4hEs/YgPMnL0u2TtWgqKwLP0/uTxAZnmE9dJ6OAQc
+v6L8cJLuo5S8GZUHVOcWDdrm2+PlyG0nc+NI1hlNtK2e5gHkpubN27nfCuPM0hXS
+IXGkcehJwOf/ebB9Pv+dEL8TVNJu2cKRYcRwYzHpOtK1X4nt+M0YQdYycOWY0jLB
+5U3W8Ryq01q47gaczUFuHad7dUAcWb5R/vD2rLnnxoJMEdsADaoYo0M0TjpPC+OZ
+w2q2fU4o4sCnVvW+LgZI2YrgTXwbrBoNWIDbmuh1ZUajGZKFErN96ye9RizW1+SB
+vqYSyTQ+WkuHB3qowDRscvG5npjR
+-----END CERTIFICATE-----

+ 28 - 0
service/Essential/src/main/resources/apiclient_key.pem

@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCgpttCTpTahZEM
+k9epeyj5UF9yHVB8AW58AwmZA22tigLz5yC/pvyxqtNtB+WuIHq/F4oZrJ1KQMCh
+tr4OUhOKAM9zP8jBLYu1dJs4euWtHgq21O+py3+76eGOPWXrS0bg353JgdDWrE0F
+RMVwyfOkHqd7KVp2kcn2UuBLyNb0DEnGuFdNfAYPs9yL04dr/xKugLJyOLw3qJR4
+1NFlXH56CNXaSS3KvudeYgZkCtluLQMicRVrpEig+naB8p5oRljJ+NKzkhVgHM4V
+Pcy9aRbmjL6q3Yd/RZlboxZ3uexNT4c6A465nrl5n3VrlaI/Xp0ZsNgOAZoURKEv
+yCGqvUTTAgMBAAECggEAEQvjdBO2g8FsdLMzTQ5+eIXouskVgzL7sDj6SbwtZR6h
+1ZOOzAdCBPAlYwpVeraDj8dFphfE0N4yEna7j/VxkVEosgfwm17j1is7VTDlMgtg
+A6pwi8nGEgBtKJSnPeJM8QuJSJp3uG+r9IUpliA3tBB0oQLwUGiDz5lXp3L2LAr+
+s3+M15pLKrY9Wm5yCCQGdiB9nI4zpwJLgyW8wZfET5BiQlvpPqfGX5Wg2IMCYuYV
+jxR/AqKUvuo4vuDO6DceCwww6YqgZwu1ojH4ClyRZaiO6IP2jZY5OwqCgH+eFUFR
+uEbmjmEsO0yti326wsSgXn6d4PKFQLq+QSKzmK0yYQKBgQDQTnSIBPkQWOdZzNGr
+DSefvDnQk9Xzy+sS4PAW9Nxk2VXDNcaWHUYAKImnEwrO7mESQYEf3ExsU4pzr6sE
+gp/OpH+vTPDrbKIPrviLJKO78nKOsDL15yQPWcoZrkr+ui/E9KYqOY+eMjPBmxH3
+8d+zrsLh7J7V1YJUBZWCzcAliwKBgQDFbzMVew0g+DODFuzJWmwBaI9XMO2imxWL
+Z/14a8Te+M4ZrvV292338noKa2/+gF5LP9cR8C1ml4tD8McOjsUoqJf9RalpgMPU
+fbLN1+uTkGnUpH2w1BuxRv4ctCaa7vVjO9gY/FnQrYHoFU7wc1zH4FPrsQRL0cqo
+nRgDrlqW2QKBgF8sKVPoKb+NTw6ZZQZo+TyjLCsfEHbWKW4ztZ5Xe8bcAbIyBKyb
+bVsvKk1ahuqqwVEs+okLByPLO4vFrDZgVBF+2RPH7J8zwAu4Xrh8Ig8CI9MLsAzn
+8XLODGeVWZBuktrDCJRsPjECMOF5L0mAiju3UjUU/jEm3ufysS/VP8AnAoGAUfjL
+Ut3WI5wpdwHrqeX69dK9wMu1vk95cHnqiTbldFYE7DDyBUTRyaJI/U6CHz6Y59FH
+sFDx6Kh+z+SGIQsx3KGzCRsFLaKiq/TMah591rSc3Aa3kNVf8gixtKUvaiBQjbTp
+EeakO7nuN7exILAlE2+WKENEr14ZMEyZDd40CnECgYBUkKbI8T43PtdTwO13iEH7
+UAJU7UoLnhqcGUlgnkFb3XGmFWmnI72FkreRc4QChBihytU7sbvirtws0y7dupJ+
+a9kyqywvbs2d4Lh9XkPC0HU26gzPe7W6a9Qr82MfEH39QktcClADvFHw4aIM8XuG
+fmoHyJHu3xsxlSMv2K9XLw==
+-----END PRIVATE KEY-----

+ 9 - 1
service/Essential/src/main/resources/application.yml

@@ -78,4 +78,12 @@ avatar:
   upload:
     path: D:/marriage-avatars/  # 本地存储路径
   url:
-    prefix: http://localhost:8083/avatars/  # 访问URL前缀(通过网关)
+    prefix: http://localhost:8083/avatars/  # 访问URL前缀(通过网关)
+
+wx:
+  pay:
+    appId: wx3e90d662a801266e       # 替换为你的小程序AppID
+    mchId: 1727663839               # 替换为你的商户号
+    privateKeyPath: /path/to/apiclient_key.pem  # 私钥文件路径
+    apiV3Key: d82ce405f04a47de14382bef4180239d      # 替换为你的APIv3密钥
+    notifyUrl: https://your-domain.com/api/vip/pay/notify # 支付回调地址

+ 16 - 0
service/Essential/src/main/resources/com/zhentao/mapper/WxMapper.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zhentao.mapper.WxMapper">
+
+    <resultMap id="BaseResultMap" type="com.zhentao.entity.Wx">
+            <id property="appId" column="app_id" />
+            <result property="appSecret" column="app_secret" />
+            <result property="mchId" column="mch_id" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        app_id,app_secret,mch_id
+    </sql>
+</mapper>

+ 9 - 1
service/Essential/target/classes/application.yml

@@ -78,4 +78,12 @@ avatar:
   upload:
     path: D:/marriage-avatars/  # 本地存储路径
   url:
-    prefix: http://localhost:8083/avatars/  # 访问URL前缀(通过网关)
+    prefix: http://localhost:8083/avatars/  # 访问URL前缀(通过网关)
+
+wx:
+  pay:
+    appId: wx3e90d662a801266e       # 替换为你的小程序AppID
+    mchId: 1727663839               # 替换为你的商户号
+    privateKeyPath: /path/to/apiclient_key.pem  # 私钥文件路径
+    apiV3Key: d82ce405f04a47de14382bef4180239d      # 替换为你的APIv3密钥
+    notifyUrl: https://your-domain.com/api/vip/pay/notify # 支付回调地址

BIN
service/Essential/target/classes/com/zhentao/common/Result.class


BIN
service/Essential/target/classes/com/zhentao/config/WechatPayProperties.class


BIN
service/Essential/target/classes/com/zhentao/controller/AvatarTestController.class


BIN
service/Essential/target/classes/com/zhentao/controller/BlacklistController.class


BIN
service/Essential/target/classes/com/zhentao/controller/VipController.class


BIN
service/Essential/target/classes/com/zhentao/entity/UserVip.class


BIN
service/Essential/target/classes/com/zhentao/mapper/UserVipMapper.class


BIN
service/Essential/target/classes/com/zhentao/service/VipService.class


BIN
service/Essential/target/classes/com/zhentao/service/impl/VipServiceImpl$1.class


BIN
service/Essential/target/classes/com/zhentao/service/impl/VipServiceImpl.class


BIN
service/login/target/classes/com/zhentao/controller/AuthCodeController.class