瀏覽代碼

新增vip会员管理功能接口

codingliang 7 月之前
父節點
當前提交
b6e2f11829
共有 48 個文件被更改,包括 1516 次插入284 次删除
  1. 4 0
      db/update_251111.sql
  2. 7 0
      src/main/java/com/sqx/modules/app/service/UserService.java
  3. 1 1
      src/main/java/com/sqx/modules/app/service/impl/UserMoneyServiceImpl.java
  4. 9 0
      src/main/java/com/sqx/modules/app/service/impl/UserServiceImpl.java
  5. 9 0
      src/main/java/com/sqx/modules/coupon/service/TbCouponUserService.java
  6. 18 0
      src/main/java/com/sqx/modules/coupon/service/impl/TbTbCouponUserServiceImpl.java
  7. 5 11
      src/main/java/com/sqx/modules/datacentre/controller/DataCentreController.java
  8. 31 62
      src/main/java/com/sqx/modules/datacentre/service/impl/DataCentreServiceImpl.java
  9. 38 15
      src/main/java/com/sqx/modules/member/controller/SpecialFavorStudentController.java
  10. 47 17
      src/main/java/com/sqx/modules/member/controller/VipPromoCodeController.java
  11. 0 57
      src/main/java/com/sqx/modules/member/controller/VipPromoRecordController.java
  12. 33 0
      src/main/java/com/sqx/modules/member/controller/app/AppSpecialFavorStudentController.java
  13. 42 0
      src/main/java/com/sqx/modules/member/controller/app/AppVipController.java
  14. 5 0
      src/main/java/com/sqx/modules/member/dao/SpecialFavorStudentDao.java
  15. 2 0
      src/main/java/com/sqx/modules/member/dao/VipPromoCodeDao.java
  16. 5 0
      src/main/java/com/sqx/modules/member/dao/VipPromoRecordDao.java
  17. 30 0
      src/main/java/com/sqx/modules/member/dto/SpecialFavorStudentDTO.java
  18. 25 0
      src/main/java/com/sqx/modules/member/dto/SpecialFavorStudentVerifyDTO.java
  19. 34 0
      src/main/java/com/sqx/modules/member/dto/VipPromoCodeDTO.java
  20. 41 0
      src/main/java/com/sqx/modules/member/dto/VipPromoRecordDTO.java
  21. 2 0
      src/main/java/com/sqx/modules/member/entity/SpecialFavorStudent.java
  22. 2 0
      src/main/java/com/sqx/modules/member/entity/VipPromoCode.java
  23. 2 1
      src/main/java/com/sqx/modules/member/entity/VipPromoRecord.java
  24. 36 0
      src/main/java/com/sqx/modules/member/query/SpecialFavorStudentQuery.java
  25. 25 0
      src/main/java/com/sqx/modules/member/query/VipPromoCodeQuery.java
  26. 32 0
      src/main/java/com/sqx/modules/member/query/VipPromoRecordQuery.java
  27. 22 0
      src/main/java/com/sqx/modules/member/service/SpecialFavorStudentService.java
  28. 24 1
      src/main/java/com/sqx/modules/member/service/VipPromoCodeService.java
  29. 13 1
      src/main/java/com/sqx/modules/member/service/VipPromoRecordService.java
  30. 28 0
      src/main/java/com/sqx/modules/member/service/VipService.java
  31. 151 2
      src/main/java/com/sqx/modules/member/service/impl/SpecialFavorStudentServiceImpl.java
  32. 77 2
      src/main/java/com/sqx/modules/member/service/impl/VipPromoCodeServiceImpl.java
  33. 57 2
      src/main/java/com/sqx/modules/member/service/impl/VipPromoRecordServiceImpl.java
  34. 152 0
      src/main/java/com/sqx/modules/member/service/impl/VipServiceImpl.java
  35. 57 0
      src/main/java/com/sqx/modules/member/vo/SpecialFavorStudentVO.java
  36. 43 0
      src/main/java/com/sqx/modules/member/vo/VipPromoCodeVO.java
  37. 48 0
      src/main/java/com/sqx/modules/member/vo/VipPromoRecordVO.java
  38. 0 2
      src/main/java/com/sqx/modules/pay/controller/app/ApiWeiXinPayController.java
  39. 4 0
      src/main/java/com/sqx/modules/pay/dto/GetPayParamDTO.java
  40. 5 0
      src/main/java/com/sqx/modules/pay/entity/PayDetails.java
  41. 3 48
      src/main/java/com/sqx/modules/pay/service/impl/NewPayServiceImpl.java
  42. 1 0
      src/main/java/com/sqx/modules/pay/service/impl/PayDetailsServiceImpl.java
  43. 8 62
      src/main/java/com/sqx/modules/pay/service/impl/WxServiceImpl.java
  44. 142 0
      src/main/java/com/sqx/modules/utils/PromotionCodeUtil.java
  45. 108 0
      src/main/java/com/sqx/modules/utils/VipExpirationUtil.java
  46. 40 0
      src/main/resources/mapper/member/SpecialFavorStudentMapper.xml
  47. 8 0
      src/main/resources/mapper/member/VipPromoCodeMapper.xml
  48. 40 0
      src/main/resources/mapper/member/VipPromoRecordMapper.xml

+ 4 - 0
db/update_251111.sql

@@ -40,4 +40,8 @@ CREATE TABLE vip_promo_record(
     PRIMARY KEY (id)
 )  COMMENT = '会员推广记录';
 
+-- 支付详情添加额外参数字段
+alter table pay_details add extra varchar(100) comment '额外备用参数' after remark;
+
+
 

+ 7 - 0
src/main/java/com/sqx/modules/app/service/UserService.java

@@ -237,4 +237,11 @@ public interface UserService extends IService<UserEntity> {
     Result sendMsgDXB(String phone, String state, int code);
 
     UserEntity getByAdminId(Long adminUserId);
+
+    /**
+     * 更新用户为会员
+     * @param userId             用户id
+     * @param futureTimeWithDays 会员过期时间
+     */
+    void updateUserToVip(Long userId, String futureTimeWithDays);
 }

+ 1 - 1
src/main/java/com/sqx/modules/app/service/impl/UserMoneyServiceImpl.java

@@ -273,7 +273,7 @@ public class UserMoneyServiceImpl extends ServiceImpl<UserMoneyDao, UserMoney> i
         Integer changeType = updateMoneyBO.getType();
         if (changeType == 2) {
             if (userMoney.getMoney().compareTo(changeAmount) <= 0) {
-                throw new SqxException("当前店铺钱包余额不足,扣减失败!");
+                throw new SqxException("用户钱包余额不足,扣减失败!");
             }
         }
 

+ 9 - 0
src/main/java/com/sqx/modules/app/service/impl/UserServiceImpl.java

@@ -812,6 +812,15 @@ public class UserServiceImpl extends ServiceImpl<UserDao, UserEntity> implements
         return this.getOne(queryWrapper);
     }
 
+    @Override
+    public void updateUserToVip(Long userId, String futureTimeWithDays) {
+        UserEntity user = new UserEntity();
+        user.setUserId(userId);
+        user.setIsVip(1);
+        user.setVipExpirationTime(futureTimeWithDays);
+        this.updateById(user);
+    }
+
     private Result sendMsgAlibaba(String phone, String state, int code) {
         //阿里云短信accessKeyId
         CommonInfo three = commonInfoService.findOne(93);

+ 9 - 0
src/main/java/com/sqx/modules/coupon/service/TbCouponUserService.java

@@ -18,4 +18,13 @@ public interface TbCouponUserService extends IService<TbCouponUser> {
 
     Result selectCouponByUserId(Integer page, Integer limit, Long userId, Integer status, String phone,String shopName,Long shopId,Integer shopFlag);
 
+    /**
+     * 会员购买成功后添加优惠券
+     *
+     * @param userId            用户id
+     * @param money             优惠券金额
+     * @param expirationDays 有效天数
+     * @param expirationTime    优惠券过期时间
+     */
+    void addAfterVipBuySuccess(Long userId, BigDecimal money, String expirationDays, String expirationTime);
 }

+ 18 - 0
src/main/java/com/sqx/modules/coupon/service/impl/TbTbCouponUserServiceImpl.java

@@ -15,6 +15,7 @@ import com.sqx.modules.integral.dao.UserIntegralDao;
 import com.sqx.modules.integral.dao.UserIntegralDetailsDao;
 import com.sqx.modules.integral.entity.UserIntegral;
 import com.sqx.modules.integral.entity.UserIntegralDetails;
+import com.sqx.modules.utils.VipExpirationUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -128,4 +129,21 @@ public class TbTbCouponUserServiceImpl extends ServiceImpl<TbCouponUserDao, TbCo
         PageUtils pageUtils = new PageUtils(tbCouponUserDao.selectCouponByUserId(pages, userId, status, phone,shopName,shopId,shopFalg));
         return Result.success().put("data", pageUtils);
     }
+
+    @Override
+    public void addAfterVipBuySuccess(Long userId, BigDecimal money, String expirationDays, String expirationTime) {
+        TbCouponUser tbCouponUser = new TbCouponUser();
+        tbCouponUser.setUserId(userId);
+        tbCouponUser.setCouponName("会员专属优惠券");
+        tbCouponUser.setCouponPicture("图片");
+        tbCouponUser.setCreateTime(VipExpirationUtil.formatCurrentTime());
+        tbCouponUser.setExpirationTime(expirationTime);
+        tbCouponUser.setMinMoney(BigDecimal.valueOf(0));
+        tbCouponUser.setGoodsType("0");
+        tbCouponUser.setMoney(money);
+        tbCouponUser.setStatus(0);
+        tbCouponUser.setEndTime(expirationDays);
+        tbCouponUser.setShopId(0L);
+        tbCouponUserDao.insert(tbCouponUser);
+    }
 }

+ 5 - 11
src/main/java/com/sqx/modules/datacentre/controller/DataCentreController.java

@@ -15,7 +15,11 @@ import com.sqx.modules.order.service.AppOrderService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.GetMapping;
+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 javax.validation.Valid;
 import java.math.BigDecimal;
@@ -251,13 +255,6 @@ public class DataCentreController {
         return dataCentreService.selectCashDeposit(phone, type, page, limit, userId);
     }
 
-   /* @ApiOperation("用户中心 用户列表")
-    @GetMapping(value = "findAllUser")
-    public Result findAllUser(Integer page, Integer limit, String phone, Integer userType, String platform, String nickName, String invitationCode){
-
-        return dataCentreService.findAllUser(page, limit, phone, userType, platform, nickName, invitationCode);
-    }*/
-
     @ApiOperation("查看反馈列表")
     @GetMapping(value = "selectFeedbackList")
     public Result selectFeedbackList(Integer page, Integer limit, Integer feedbackType) {
@@ -265,12 +262,9 @@ public class DataCentreController {
         return dataCentreService.selectFeedbackList(page, limit, feedbackType);
     }
 
-    /*==========================================================*/
-
     @ApiOperation("管理端赠送用户会员")
     @PostMapping(value = "presenterVip")
     public Result presenterVip(Long userId) {
-
         return dataCentreService.presenterVip(userId);
     }
 

+ 31 - 62
src/main/java/com/sqx/modules/datacentre/service/impl/DataCentreServiceImpl.java

@@ -14,8 +14,7 @@ import com.sqx.modules.app.service.UserMoneyDetailsService;
 import com.sqx.modules.app.service.UserService;
 import com.sqx.modules.common.entity.CommonInfo;
 import com.sqx.modules.common.service.CommonInfoService;
-import com.sqx.modules.coupon.dao.TbCouponUserDao;
-import com.sqx.modules.coupon.entity.TbCouponUser;
+import com.sqx.modules.coupon.service.TbCouponUserService;
 import com.sqx.modules.datacentre.dao.DataCentreDao;
 import com.sqx.modules.datacentre.query.DataCenterQuery;
 import com.sqx.modules.datacentre.query.RankListQuery;
@@ -26,47 +25,40 @@ import com.sqx.modules.errand.entity.TbIndent;
 import com.sqx.modules.order.dao.AppOrderDao;
 import com.sqx.modules.order.entity.TbOrder;
 import com.sqx.modules.pay.controller.app.AliPayController;
-import com.sqx.modules.pay.controller.query.WalletDetailQuery;
 import com.sqx.modules.pay.dao.PayDetailsDao;
 import com.sqx.modules.pay.entity.CashOut;
 import com.sqx.modules.pay.entity.PayDetails;
 import com.sqx.modules.pay.service.WxErrRiderService;
-import com.sqx.modules.pay.vo.WalletDetailVO;
+import com.sqx.modules.utils.VipExpirationUtil;
 import com.sqx.modules.utils.excel.ExcelData;
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
-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.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
 
 @Service
 @Slf4j
+@RequiredArgsConstructor
 public class DataCentreServiceImpl implements DataCentreService {
 
-    @Autowired
-    private DataCentreDao dataCentreDao;
-    @Autowired
-    private UserDao userDao;
-    @Autowired
-    private AppOrderDao appOrderDao;
-    @Autowired
-    private WxErrRiderService wxErrRiderService;
-    @Autowired
-    private AliPayController aliPayController;
-    @Autowired
-    private UserService userService;
-    @Autowired
-    private PayDetailsDao payDetailsDao;
-    @Autowired
-    private UserMoneyDetailsService userMoneyDetailsService;
-    @Autowired
-    private CommonInfoService commonInfoService;
-    @Autowired
-    private TbCouponUserDao tbCouponUserDao;
+    private final DataCentreDao dataCentreDao;
+    private final UserDao userDao;
+    private final AppOrderDao appOrderDao;
+    private final WxErrRiderService wxErrRiderService;
+    private final UserService userService;
+    private final PayDetailsDao payDetailsDao;
+    private final UserMoneyDetailsService userMoneyDetailsService;
+    private final CommonInfoService commonInfoService;
+    private final TbCouponUserService tbCouponUserService;
 
     @Override
     public Result dataCentre() {
@@ -383,46 +375,25 @@ public class DataCentreServiceImpl implements DataCentreService {
     @Transactional
     @Override
     public Result presenterVip(Long userId) {
-        UserEntity userEntity1 = userDao.selectById(userId);
-        if(userEntity1.getIsVip()==1){
+        UserEntity user = userDao.selectById(userId);
+        if(user.getIsVip() ==1 && VipExpirationUtil.isVipValid(user.getVipExpirationTime())){
             return Result.error("该用户已经是会员,不能重复添加!");
         }
-        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-        /*Calendar calendar1 = java.util.Calendar.getInstance();
-        Calendar calendar2 = java.util.Calendar.getInstance();
-        try {
-            calendar1.setTime(dateFormat.parse(userEntity1.getVipExpirationTime()));
-            calendar2.setTime(dateFormat.parse(dateFormat.format(new Date())));
-        } catch (java.text.ParseException e) {
-            System.out.println("日期格式不正确");
-        }
-        if(calendar1.compareTo(calendar2)>0){
-            return Result.error("用户已经是会员,请勿重复操作!");
-        }*/
+
+        // 读取配置
         CommonInfo one = commonInfoService.findOne(314);
         CommonInfo one1 = commonInfoService.findOne(315);
         CommonInfo one2 = commonInfoService.findOne(316);
-        UserEntity userEntity = new UserEntity();
-        userEntity.setUserId(userId);
-        userEntity.setIsVip(1);
-        Calendar calendar = Calendar.getInstance();
-        calendar.add(Calendar.DATE, +Integer.valueOf(one.getValue()));    //得到前一个月
-        userEntity.setVipExpirationTime(dateFormat.format(calendar.getTime()));
-        userDao.updateById(userEntity);
+
+        // 会员失效时间
+        String expirationTime = VipExpirationUtil.getFutureTimeWithDays(Integer.valueOf(one.getValue()));
+
+        // 更新用户为会员
+        userService.updateUserToVip(userId, expirationTime);
+
+        // 新增会员专属优惠券
         for(int i = 0;i<Integer.valueOf(one1.getValue());i++){
-            TbCouponUser tbCouponUser = new TbCouponUser();
-            tbCouponUser.setUserId(userId);
-            tbCouponUser.setCouponName("会员专属优惠券");
-            tbCouponUser.setCouponPicture("图片");
-            tbCouponUser.setCreateTime(dateFormat.format(new Date()));
-            tbCouponUser.setExpirationTime(dateFormat.format(calendar.getTime()));
-            tbCouponUser.setMinMoney(BigDecimal.valueOf(0));
-            tbCouponUser.setGoodsType("0");
-            tbCouponUser.setMoney(new BigDecimal(one2.getValue()));
-            tbCouponUser.setStatus(0);
-            tbCouponUser.setEndTime(one.getValue());
-            tbCouponUser.setShopId(0L);
-            tbCouponUserDao.insert(tbCouponUser);
+            tbCouponUserService.addAfterVipBuySuccess(userId, new BigDecimal(one2.getValue()), one.getValue(), expirationTime);
         }
         return Result.success();
     }
@@ -461,6 +432,4 @@ public class DataCentreServiceImpl implements DataCentreService {
         userDao.updateById(userEntity);
         return Result.success();
     }
-
-
 }

+ 38 - 15
src/main/java/com/sqx/modules/member/controller/SpecialFavorStudentController.java

@@ -1,23 +1,27 @@
 package com.sqx.modules.member.controller;
 
-import com.sqx.modules.member.entity.SpecialFavorStudent;
+import cn.hutool.core.util.ObjectUtil;
+import com.sqx.common.exception.SqxException;
+import com.sqx.common.utils.Result;
+import com.sqx.modules.member.dto.SpecialFavorStudentDTO;
+import com.sqx.modules.member.query.SpecialFavorStudentQuery;
 import com.sqx.modules.member.service.SpecialFavorStudentService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import lombok.RequiredArgsConstructor;
-import javax.validation.Valid;
-import java.util.Arrays;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.PutMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
 
-import com.sqx.common.utils.Result;
+import javax.validation.Valid;
+import java.util.Arrays;
 
 @Api(tags = "特殊照顾学生")
 @RestController
@@ -26,25 +30,27 @@ import com.sqx.common.utils.Result;
 public class SpecialFavorStudentController{
     
     private final SpecialFavorStudentService specialFavorStudentService;
-    
-    @GetMapping("{id}")
-    @ApiOperation("根据id获取特殊照顾学生信息")
-    public Result getById(@PathVariable Long id) {
-        SpecialFavorStudent specialFavorStudent = specialFavorStudentService.getById(id);
-        return Result.success().put("data", specialFavorStudent);
+
+    @GetMapping("pages")
+    @ApiOperation("分页查询特殊照顾学生列表")
+    public Result pages(@Valid SpecialFavorStudentQuery query) {
+        return Result.success().put("data", specialFavorStudentService.pages(query));
     }
 
     @PostMapping
     @ApiOperation("添加特殊照顾学生")
-    public Result add(@RequestBody @Valid SpecialFavorStudent specialFavorStudent) {
-        specialFavorStudentService.save(specialFavorStudent);
+    public Result add(@RequestBody @Valid SpecialFavorStudentDTO dto) {
+        specialFavorStudentService.save(dto);
         return Result.success();
     }
 
     @PutMapping
     @ApiOperation("修改特殊照顾学生")
-    public Result update(@RequestBody @Valid SpecialFavorStudent specialFavorStudent) {
-        specialFavorStudentService.updateById(specialFavorStudent);
+    public Result update(@RequestBody @Valid SpecialFavorStudentDTO dto) {
+        if (ObjectUtil.isNull(dto.getId())) {
+            throw new SqxException("id不能为空");
+        }
+        specialFavorStudentService.update(dto);
         return Result.success();
     }
 
@@ -54,4 +60,21 @@ public class SpecialFavorStudentController{
         specialFavorStudentService.removeByIds(Arrays.asList(ids));
         return Result.success();
     }
+
+    @PostMapping("import")
+    @ApiOperation("导入特殊照顾学生")
+    public Result importData(@RequestParam("file") MultipartFile file) {
+        if (file.isEmpty()) {
+            throw new SqxException("文件不能为空");
+        }
+
+        specialFavorStudentService.importData(file);
+        return Result.success();
+    }
+
+    @GetMapping("export")
+    @ApiOperation("导出特殊照顾学生")
+    public void export(@Valid SpecialFavorStudentQuery query) {
+        specialFavorStudentService.export(query);
+    }
 }

+ 47 - 17
src/main/java/com/sqx/modules/member/controller/VipPromoCodeController.java

@@ -1,50 +1,67 @@
 package com.sqx.modules.member.controller;
 
-import com.sqx.modules.member.entity.VipPromoCode;
+import cn.hutool.core.util.ObjectUtil;
+import com.sqx.common.exception.SqxException;
+import com.sqx.common.utils.PageUtils;
+import com.sqx.common.utils.Result;
+import com.sqx.modules.member.dto.VipPromoCodeDTO;
+import com.sqx.modules.member.query.VipPromoCodeQuery;
+import com.sqx.modules.member.query.VipPromoRecordQuery;
 import com.sqx.modules.member.service.VipPromoCodeService;
+import com.sqx.modules.member.service.VipPromoRecordService;
+import com.sqx.modules.utils.PromotionCodeUtil;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import lombok.RequiredArgsConstructor;
-import javax.validation.Valid;
-import java.util.Arrays;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.PutMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
-import com.sqx.common.utils.Result;
+import javax.validation.Valid;
+import java.util.Arrays;
 
-@Api(tags = "会员推广")
+@Api(tags = "会员推广管理")
 @RestController
-@RequestMapping("vipPromoCode")
+@RequestMapping("vip-promo")
 @RequiredArgsConstructor
 public class VipPromoCodeController{
     
     private final VipPromoCodeService vipPromoCodeService;
-    
-    @GetMapping("{id}")
-    @ApiOperation("根据id获取会员推广码信息")
-    public Result getById(@PathVariable Long id) {
-        VipPromoCode vipPromoCode = vipPromoCodeService.getById(id);
-        return Result.success().put("data", vipPromoCode);
+    private final VipPromoRecordService vipPromoRecordService;
+
+    @GetMapping("pages")
+    @ApiOperation("分页查询会员推广码")
+    public Result pages(@Valid VipPromoCodeQuery query) {
+        PageUtils page = vipPromoCodeService.pages(query);
+        return Result.success().put("data", page);
+    }
+
+    @GetMapping("generate-promo-code")
+    @ApiOperation("生成会员推广码")
+    public Result generatePromoCode() {
+        String promoCode = PromotionCodeUtil.generatePromotionCode();
+        return Result.success().put("data", promoCode);
     }
 
     @PostMapping
     @ApiOperation("添加会员推广码")
-    public Result add(@RequestBody @Valid VipPromoCode vipPromoCode) {
-        vipPromoCodeService.save(vipPromoCode);
+    public Result add(@RequestBody @Valid VipPromoCodeDTO dto) {
+        vipPromoCodeService.save(dto);
         return Result.success();
     }
 
     @PutMapping
     @ApiOperation("修改会员推广码")
-    public Result update(@RequestBody @Valid VipPromoCode vipPromoCode) {
-        vipPromoCodeService.updateById(vipPromoCode);
+    public Result update(@RequestBody @Valid VipPromoCodeDTO dto) {
+        if (ObjectUtil.isNull(dto.getId())) {
+            throw new SqxException("id不能为空");
+        }
+        vipPromoCodeService.update(dto);
         return Result.success();
     }
 
@@ -54,4 +71,17 @@ public class VipPromoCodeController{
         vipPromoCodeService.removeByIds(Arrays.asList(ids));
         return Result.success();
     }
+
+    @GetMapping("record/pages")
+    @ApiOperation("分页查询会员推广记录")
+    public Result recordPages(@Valid VipPromoRecordQuery query) {
+        PageUtils page = vipPromoRecordService.pages(query);
+        return Result.success().put("data", page);
+    }
+
+    @GetMapping("record/export")
+    @ApiOperation("导出会员推广记录")
+    public void recordExport(@Valid VipPromoRecordQuery query) {
+        vipPromoRecordService.recordExport(query);
+    }
 }

+ 0 - 57
src/main/java/com/sqx/modules/member/controller/VipPromoRecordController.java

@@ -1,57 +0,0 @@
-package com.sqx.modules.member.controller;
-
-import com.sqx.modules.member.entity.VipPromoRecord;
-import com.sqx.modules.member.service.VipPromoRecordService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import io.swagger.annotations.ApiParam;
-import lombok.RequiredArgsConstructor;
-import javax.validation.Valid;
-import java.util.Arrays;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import com.sqx.common.utils.Result;
-
-@Api(tags = "会员推广记录")
-@RestController
-@RequestMapping("vipPromoRecord")
-@RequiredArgsConstructor
-public class VipPromoRecordController{
-    
-    private final VipPromoRecordService vipPromoRecordService;
-    
-    @GetMapping("{id}")
-    @ApiOperation("根据id获取会员推广记录信息")
-    public Result getById(@PathVariable Long id) {
-        VipPromoRecord vipPromoRecord = vipPromoRecordService.getById(id);
-        return Result.success().put("data", vipPromoRecord);
-    }
-
-    @PostMapping
-    @ApiOperation("添加会员推广记录")
-    public Result add(@RequestBody @Valid VipPromoRecord vipPromoRecord) {
-        vipPromoRecordService.save(vipPromoRecord);
-        return Result.success();
-    }
-
-    @PutMapping
-    @ApiOperation("修改会员推广记录")
-    public Result update(@RequestBody @Valid VipPromoRecord vipPromoRecord) {
-        vipPromoRecordService.updateById(vipPromoRecord);
-        return Result.success();
-    }
-
-    @DeleteMapping
-    @ApiOperation("删除会员推广记录")
-    public Result delete(@RequestBody @ApiParam("id的集合") Long[] ids) {
-        vipPromoRecordService.removeByIds(Arrays.asList(ids));
-        return Result.success();
-    }
-}

+ 33 - 0
src/main/java/com/sqx/modules/member/controller/app/AppSpecialFavorStudentController.java

@@ -0,0 +1,33 @@
+package com.sqx.modules.member.controller.app;
+
+import com.sqx.common.utils.Result;
+import com.sqx.modules.app.annotation.Login;
+import com.sqx.modules.member.dto.SpecialFavorStudentVerifyDTO;
+import com.sqx.modules.member.service.SpecialFavorStudentService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+
+@Api(tags = "特殊照顾学生")
+@RestController
+@RequestMapping("app/special-favor-student")
+@RequiredArgsConstructor
+public class AppSpecialFavorStudentController {
+    
+    private final SpecialFavorStudentService specialFavorStudentService;
+
+    @Login
+    @ApiOperation("特殊照顾学生认证审核")
+    @PostMapping("verify")
+    public Result verify(@RequestBody @Valid SpecialFavorStudentVerifyDTO dto, @RequestAttribute("userId") Long userId) {
+        specialFavorStudentService.verify(userId, dto);
+        return Result.success();
+    }
+}

+ 42 - 0
src/main/java/com/sqx/modules/member/controller/app/AppVipController.java

@@ -0,0 +1,42 @@
+package com.sqx.modules.member.controller.app;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.sqx.common.utils.Result;
+import com.sqx.modules.app.annotation.Login;
+import com.sqx.modules.member.service.VipService;
+import com.sqx.modules.pay.vo.PayTransactionsVO;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author codingliang
+ * @date 2025-11-12
+ */
+@RestController
+@RequestMapping(value = "app/vip")
+@RequiredArgsConstructor
+public class AppVipController {
+
+    private final VipService vipService;
+
+    @Login
+    @ApiOperation(value = "获取购买vip支付参数", notes = "promoCode 为优惠券码,可选")
+    @GetMapping(value = "get-pay-param")
+    public Result getPayParam(@RequestAttribute("userId") Long userId, String promoCode){
+        PayTransactionsVO payParamVO = vipService.getPayParam(userId, promoCode);
+
+        return Result.success(BeanUtil.beanToMap(payParamVO, false, true));
+    }
+
+    @Login
+    @ApiOperation(value = "余额购买vip", notes = "promoCode 为优惠券码,可选")
+    @GetMapping(value = "balance-pay")
+    public Result balancePay(@RequestAttribute("userId") Long userId, String promoCode){
+        vipService.balancePay(userId, promoCode);
+        return Result.success();
+    }
+}

+ 5 - 0
src/main/java/com/sqx/modules/member/dao/SpecialFavorStudentDao.java

@@ -1,10 +1,15 @@
 package com.sqx.modules.member.dao;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.sqx.modules.member.entity.SpecialFavorStudent;
+import com.sqx.modules.member.query.SpecialFavorStudentQuery;
+import com.sqx.modules.member.vo.SpecialFavorStudentVO;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
 
 @Mapper
 public interface SpecialFavorStudentDao extends BaseMapper<SpecialFavorStudent> {
 
+    IPage<SpecialFavorStudentVO> pages(@Param("page") IPage<SpecialFavorStudentVO> page, @Param("query") SpecialFavorStudentQuery query);
 }

+ 2 - 0
src/main/java/com/sqx/modules/member/dao/VipPromoCodeDao.java

@@ -3,8 +3,10 @@ package com.sqx.modules.member.dao;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.sqx.modules.member.entity.VipPromoCode;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
 
 @Mapper
 public interface VipPromoCodeDao extends BaseMapper<VipPromoCode> {
 
+    void incrementPromoCountById(@Param("promoCodeId") Long promoCodeId);
 }

+ 5 - 0
src/main/java/com/sqx/modules/member/dao/VipPromoRecordDao.java

@@ -1,10 +1,15 @@
 package com.sqx.modules.member.dao;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.sqx.modules.member.entity.VipPromoRecord;
+import com.sqx.modules.member.query.VipPromoRecordQuery;
+import com.sqx.modules.member.vo.VipPromoRecordVO;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
 
 @Mapper
 public interface VipPromoRecordDao extends BaseMapper<VipPromoRecord> {
 
+    IPage<VipPromoRecordVO> pages(@Param("page") IPage<VipPromoRecordVO> page, @Param("query") VipPromoRecordQuery query);
 }

+ 30 - 0
src/main/java/com/sqx/modules/member/dto/SpecialFavorStudentDTO.java

@@ -0,0 +1,30 @@
+package com.sqx.modules.member.dto;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+
+
+@Data
+@ApiModel(value = "特殊照顾学生")
+public class SpecialFavorStudentDTO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+
+    @ApiModelProperty(name = "id")
+    private Long id;
+
+    @ApiModelProperty(name = "真实姓名")
+    @NotBlank(message = "真实姓名不能为空")
+    @ExcelProperty(value = "姓名")
+    private String name;
+    
+    @ApiModelProperty(name = "学号")
+    @NotBlank(message = "学号不能为空")
+    @ExcelProperty(value = "学号")
+    private String studentNumber;
+}

+ 25 - 0
src/main/java/com/sqx/modules/member/dto/SpecialFavorStudentVerifyDTO.java

@@ -0,0 +1,25 @@
+package com.sqx.modules.member.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 特殊照顾学生审核DTO
+ * @author codingliang
+ * @date 2025-11-12
+ */
+@Data
+@ApiModel(description = "特殊照顾学生审核")
+public class SpecialFavorStudentVerifyDTO {
+
+    @ApiModelProperty(value = "学生姓名")
+    @NotBlank(message = "学生姓名不能为空")
+    private String name;
+
+    @ApiModelProperty(value = "学生学号")
+    @NotBlank(message = "学生学号不能为空")
+    private Long studentNumber;
+}

+ 34 - 0
src/main/java/com/sqx/modules/member/dto/VipPromoCodeDTO.java

@@ -0,0 +1,34 @@
+package com.sqx.modules.member.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+* 会员推广码
+* @author : codingliang
+* @date : 2025-11-11
+*/
+@Data
+@ApiModel(value = "会员推广码")
+public class VipPromoCodeDTO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "id")
+    private Long id;
+
+    @ApiModelProperty(value = "推广人名称")
+    private String userName;
+
+    @ApiModelProperty(value = "推广码")
+    private String promoCode;
+
+    @ApiModelProperty(value = "推广人身份证号码")
+    private String idCard;
+
+    @ApiModelProperty(value = "推广人手机号码")
+    private String phone;
+}

+ 41 - 0
src/main/java/com/sqx/modules/member/dto/VipPromoRecordDTO.java

@@ -0,0 +1,41 @@
+package com.sqx.modules.member.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+* 会员推广记录
+* @author : codingliang
+* @date : 2025-11-11
+*/
+@Data
+@Builder
+public class VipPromoRecordDTO {
+
+    @ApiModelProperty("推荐码")
+    private String promoCode;
+
+    @ApiModelProperty("用户id")
+    private Long userId;
+    
+    @ApiModelProperty("用户名称")
+    private String userName;
+    
+    @ApiModelProperty("用户手机号")
+    private String userPhone;
+    
+    @ApiModelProperty("创建时间")
+    private Date createTime;
+
+    @ApiModelProperty("支付类型;1微信支付、2余额支付、3暖心照顾")
+    private String payType;
+    
+    @ApiModelProperty("支付金额")
+    private Double payAmount;
+    
+    @ApiModelProperty("支付关联id")
+    private String payRelId;
+}

+ 2 - 0
src/main/java/com/sqx/modules/member/entity/SpecialFavorStudent.java

@@ -2,6 +2,7 @@ package com.sqx.modules.member.entity;
 
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 
@@ -27,6 +28,7 @@ public class SpecialFavorStudent implements Serializable {
     /**
      * 删除标识;0未删除、1已删除
      */
+    @TableLogic(value = "0", delval = "1")
     private String delFlag;
     
     /**

+ 2 - 0
src/main/java/com/sqx/modules/member/entity/VipPromoCode.java

@@ -2,6 +2,7 @@ package com.sqx.modules.member.entity;
 
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 
@@ -27,6 +28,7 @@ public class VipPromoCode implements Serializable {
     /**
      * 删除标识;0未删除、1已删除
      */
+    @TableLogic(value = "0", delval = "1")
     private String delFlag;
 
     /**

+ 2 - 1
src/main/java/com/sqx/modules/member/entity/VipPromoRecord.java

@@ -2,8 +2,8 @@ package com.sqx.modules.member.entity;
 
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
 import com.baomidou.mybatisplus.annotation.TableName;
-import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -28,6 +28,7 @@ public class VipPromoRecord implements Serializable {
     /**
      * 删除标识;0未删除、1已删除
      */
+    @TableLogic(value = "0", delval = "1")
     private String delFlag;
 
     /**

+ 36 - 0
src/main/java/com/sqx/modules/member/query/SpecialFavorStudentQuery.java

@@ -0,0 +1,36 @@
+package com.sqx.modules.member.query;
+
+import com.sqx.common.query.PageQuery;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+/**
+ * 特殊照顾学生查询参数
+ * @author codingliang
+ * @date 2025-11-12
+ */
+@Data
+@ApiModel("特殊照顾学生查询参数")
+public class SpecialFavorStudentQuery extends PageQuery {
+
+    @ApiModelProperty("真实姓名")
+    private String name;
+
+    @ApiModelProperty("学号")
+    private String studentNumber;
+
+    @ApiModelProperty("验证状态;0未认证、1已认证")
+    private String verifyStatus;
+
+    @ApiModelProperty("查询认证开始时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date startTime;
+
+    @ApiModelProperty("查询认证结束时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date endTime;
+}

+ 25 - 0
src/main/java/com/sqx/modules/member/query/VipPromoCodeQuery.java

@@ -0,0 +1,25 @@
+package com.sqx.modules.member.query;
+
+import com.sqx.common.query.PageQuery;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 会员推广码查询参数
+ * @author codingliang
+ * @date 2025-11-12
+ */
+@Data
+@ApiModel(description = "会员推广码查询参数")
+public class VipPromoCodeQuery extends PageQuery {
+
+    @ApiModelProperty(value = "会员用户名")
+    private String userName;
+
+    @ApiModelProperty(value = "推广码")
+    private String promoCode;
+
+    @ApiModelProperty(value = "推广人手机号码")
+    private String phone;
+}

+ 32 - 0
src/main/java/com/sqx/modules/member/query/VipPromoRecordQuery.java

@@ -0,0 +1,32 @@
+package com.sqx.modules.member.query;
+
+import com.sqx.common.query.PageQuery;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+@Data
+@ApiModel(description = "会员推广记录查询参数")
+public class VipPromoRecordQuery extends PageQuery {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "推广码id")
+    private Long promoCodeId;
+
+    @ApiModelProperty(value = "用户手机号")
+    private String userPhone;
+
+    @ApiModelProperty(value = "支付类型 1微信支付、2余额支付、3暖心照顾")
+    private String payType;
+    
+    @ApiModelProperty(value = "查询开始时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date startTime;
+
+    @ApiModelProperty(value = "查询结束时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date endTime;
+}

+ 22 - 0
src/main/java/com/sqx/modules/member/service/SpecialFavorStudentService.java

@@ -1,7 +1,29 @@
 package com.sqx.modules.member.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.sqx.common.utils.PageUtils;
+import com.sqx.modules.member.dto.SpecialFavorStudentDTO;
+import com.sqx.modules.member.dto.SpecialFavorStudentVerifyDTO;
 import com.sqx.modules.member.entity.SpecialFavorStudent;
+import com.sqx.modules.member.query.SpecialFavorStudentQuery;
+import org.springframework.web.multipart.MultipartFile;
 
 public interface SpecialFavorStudentService extends IService<SpecialFavorStudent> {
+
+    PageUtils pages(SpecialFavorStudentQuery query);
+
+    void save(SpecialFavorStudentDTO dto);
+
+    void update(SpecialFavorStudentDTO dto);
+
+    void importData(MultipartFile file);
+
+    void export(SpecialFavorStudentQuery query);
+
+    /**
+     * 特殊照顾学生认证审核
+     * @param userId 登录用户id
+     * @param dto    特殊照顾学生认证审核参数
+     */
+    void verify(Long userId, SpecialFavorStudentVerifyDTO dto);
 }

+ 24 - 1
src/main/java/com/sqx/modules/member/service/VipPromoCodeService.java

@@ -1,13 +1,36 @@
 package com.sqx.modules.member.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.sqx.common.utils.PageUtils;
+import com.sqx.modules.member.dto.VipPromoCodeDTO;
 import com.sqx.modules.member.entity.VipPromoCode;
+import com.sqx.modules.member.query.VipPromoCodeQuery;
 
 /**
  * 会员推广码
+ *
  * @author : codingliang
  * @date : 2025-11-11
  */
 public interface VipPromoCodeService extends IService<VipPromoCode> {
-    
+
+    PageUtils pages(VipPromoCodeQuery query);
+
+    void save(VipPromoCodeDTO dto);
+
+    void update(VipPromoCodeDTO dto);
+
+    /**
+     * 根据推广码获取会员推广码
+     *
+     * @param promoCode 推广码
+     * @return 会员推广码
+     */
+    VipPromoCode getByCode(String promoCode);
+
+    /**
+     * 增加推广次数
+     * @param promoCodeId 会员推广码id
+     */
+    void increasePromoCount(Long promoCodeId);
 }

+ 13 - 1
src/main/java/com/sqx/modules/member/service/VipPromoRecordService.java

@@ -1,8 +1,20 @@
 package com.sqx.modules.member.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.sqx.common.utils.PageUtils;
+import com.sqx.modules.member.dto.VipPromoRecordDTO;
 import com.sqx.modules.member.entity.VipPromoRecord;
+import com.sqx.modules.member.query.VipPromoRecordQuery;
 
 public interface VipPromoRecordService extends IService<VipPromoRecord> {
-    
+
+    PageUtils pages(VipPromoRecordQuery query);
+
+    void recordExport(VipPromoRecordQuery query);
+
+    /**
+     * 新增会员推广记录
+     * @param recordDTO 会员推广记录dto
+     */
+    void add(VipPromoRecordDTO recordDTO);
 }

+ 28 - 0
src/main/java/com/sqx/modules/member/service/VipService.java

@@ -0,0 +1,28 @@
+package com.sqx.modules.member.service;
+
+import com.sqx.modules.pay.entity.PayDetails;
+import com.sqx.modules.pay.vo.PayTransactionsVO;
+
+public interface VipService {
+
+    /**
+     * 获取购买vip支付参数
+     * @param userId 用户id
+     * @param promoCode 优惠券码,可选
+     * @return 支付参数
+     */
+    PayTransactionsVO getPayParam(Long userId, String promoCode);
+
+     /**
+      * 处理会员支付成功通知
+      * @param payDetails 支付详情
+      */
+    void paySuccess(PayDetails payDetails);
+
+     /**
+      * 余额购买vip
+      * @param userId 用户id
+      * @param promoCode 优惠券码,可选
+      */
+    void balancePay(Long userId, String promoCode);
+}

+ 151 - 2
src/main/java/com/sqx/modules/member/service/impl/SpecialFavorStudentServiceImpl.java

@@ -1,14 +1,163 @@
 package com.sqx.modules.member.service.impl;
 
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sqx.common.exception.SqxException;
+import com.sqx.common.utils.PageUtils;
+import com.sqx.common.validator.ValidatorUtils;
+import com.sqx.modules.app.entity.UserEntity;
+import com.sqx.modules.app.service.UserService;
 import com.sqx.modules.member.dao.SpecialFavorStudentDao;
+import com.sqx.modules.member.dto.SpecialFavorStudentDTO;
+import com.sqx.modules.member.dto.SpecialFavorStudentVerifyDTO;
+import com.sqx.modules.member.dto.VipPromoRecordDTO;
 import com.sqx.modules.member.entity.SpecialFavorStudent;
+import com.sqx.modules.member.query.SpecialFavorStudentQuery;
 import com.sqx.modules.member.service.SpecialFavorStudentService;
-import org.springframework.stereotype.Service;
+import com.sqx.modules.member.service.VipPromoRecordService;
+import com.sqx.modules.member.vo.SpecialFavorStudentVO;
+import com.sqx.modules.utils.VipExpirationUtil;
+import com.sqx.modules.utils.excel.EasyExcelUtil;
+import com.sqx.modules.utils.excel.ExcelFinishCallBack;
 import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
 
 @Service
 @RequiredArgsConstructor
 public class SpecialFavorStudentServiceImpl extends ServiceImpl<SpecialFavorStudentDao, SpecialFavorStudent> implements SpecialFavorStudentService {
-   
+
+    private final UserService userService;
+    private final VipPromoRecordService vipPromoRecordService;
+
+    @Override
+    public PageUtils pages(SpecialFavorStudentQuery query) {
+        IPage<SpecialFavorStudentVO> page = baseMapper.pages(new Page<>(query.getPage(), query.getLimit()), query);
+        return new PageUtils(page);
+    }
+
+    @Override
+    public void save(SpecialFavorStudentDTO dto) {
+        // 检验参数
+        checkParam(dto);
+
+        SpecialFavorStudent specialFavorStudent = new SpecialFavorStudent();
+        BeanUtil.copyProperties(dto, specialFavorStudent);
+        specialFavorStudent.setCreateTime(new Date());
+        baseMapper.insert(specialFavorStudent);
+    }
+
+    @Override
+    public void update(SpecialFavorStudentDTO dto) {
+        // 校验参数
+        checkParam(dto);
+
+        SpecialFavorStudent specialFavorStudent = new SpecialFavorStudent();
+        BeanUtil.copyProperties(dto, specialFavorStudent);
+        baseMapper.updateById(specialFavorStudent);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void importData(MultipartFile file) {
+        EasyExcelUtil.readAnalysis(file, SpecialFavorStudentDTO.class, new ExcelFinishCallBack<SpecialFavorStudentDTO>() {
+            @Override
+            public void doAfterAllAnalysed(List<SpecialFavorStudentDTO> result) {
+                if (!result.isEmpty()) {
+                    saveBatchData(result);
+                }
+            }
+
+            @Override
+            public void doSaveBatch(List<SpecialFavorStudentDTO> result) {
+                saveBatchData(result);
+            }
+
+            private void saveBatchData(List<SpecialFavorStudentDTO> dtoList) {
+                List<SpecialFavorStudent> entities = new ArrayList<>();
+                Date now = new Date();
+                for (SpecialFavorStudentDTO dto : dtoList) {
+                    // 校验参数
+                    ValidatorUtils.validateEntity(dto);
+                    checkParam(dto);
+
+                    SpecialFavorStudent entity = new SpecialFavorStudent();
+                    BeanUtil.copyProperties(dto, entity);
+                    entity.setCreateTime(now);
+                    entities.add(entity);
+                }
+
+                // 批量保存
+                saveBatch(entities);
+            }
+        });
+    }
+
+    @Override
+    public void export(SpecialFavorStudentQuery query) {
+        IPage<SpecialFavorStudentVO> page = baseMapper.pages(new Page<>(query.getPage(), query.getLimit()), query);
+        List<SpecialFavorStudentVO> records = page.getRecords();
+        records.forEach(e -> {
+            e.setVerifyStatus(e.getVerifyStatus() == "1" ? "已验证" : "未验证");
+        });
+
+        EasyExcelUtil.exportExcel(records, SpecialFavorStudentVO.class, "特殊照顾学生列表", "特殊照顾学生列表");
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void verify(Long userId, SpecialFavorStudentVerifyDTO dto) {
+        LambdaQueryWrapper<SpecialFavorStudent> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(SpecialFavorStudent::getStudentNumber, dto.getStudentNumber());
+        queryWrapper.eq(SpecialFavorStudent::getName, dto.getName());
+
+        SpecialFavorStudent specialFavorStudent = baseMapper.selectOne(queryWrapper);
+        if (ObjectUtil.isNull(specialFavorStudent)) {
+            throw new SqxException("认证失败,身份不符合");
+        }
+
+        UserEntity user = userService.getById(userId);
+        if(ObjectUtil.isNotNull(user.getIsVip()) && user.getIsVip() == 1 && VipExpirationUtil.isVipValid(user.getVipExpirationTime())) {
+            new SqxException("已经是会员,不能重复购买!");
+        }
+
+        // 更新认证状态
+        specialFavorStudent.setVerifyStatus("1");
+        specialFavorStudent.setVerifyUserId(userId);
+        specialFavorStudent.setLastVerifyTime(new Date());
+
+        updateById(specialFavorStudent);
+
+        // 添加会员推广记录
+        VipPromoRecordDTO recordDTO = VipPromoRecordDTO.builder()
+                .userId(userId)
+                .userName(user.getNickName())
+                .userPhone(user.getPhone())
+                .payType("3")
+                .payAmount(0.0)
+                .build();
+        vipPromoRecordService.add(recordDTO);
+
+        // 更新用户为会员
+        userService.updateUserToVip(userId, VipExpirationUtil.getFutureTimeWithDays(30));
+    }
+
+    private void checkParam(SpecialFavorStudentDTO dto) {
+        // 校验学号是否存在
+        LambdaQueryWrapper<SpecialFavorStudent> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(SpecialFavorStudent::getStudentNumber, dto.getStudentNumber());
+        queryWrapper.ne(ObjectUtil.isNotNull(dto.getId()), SpecialFavorStudent::getId, dto.getId());
+        if (count(queryWrapper) > 0) {
+            throw new SqxException("学号[" + dto.getStudentNumber() + "]已存在");
+        }
+    }
 }

+ 77 - 2
src/main/java/com/sqx/modules/member/service/impl/VipPromoCodeServiceImpl.java

@@ -1,14 +1,89 @@
 package com.sqx.modules.member.service.impl;
 
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sqx.common.exception.SqxException;
+import com.sqx.common.utils.PageUtils;
 import com.sqx.modules.member.dao.VipPromoCodeDao;
+import com.sqx.modules.member.dto.VipPromoCodeDTO;
 import com.sqx.modules.member.entity.VipPromoCode;
+import com.sqx.modules.member.query.VipPromoCodeQuery;
 import com.sqx.modules.member.service.VipPromoCodeService;
-import org.springframework.stereotype.Service;
 import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
 
 @Service
 @RequiredArgsConstructor
 public class VipPromoCodeServiceImpl extends ServiceImpl<VipPromoCodeDao, VipPromoCode> implements VipPromoCodeService {
-   
+
+    @Override
+    public PageUtils pages(VipPromoCodeQuery query) {
+        IPage<VipPromoCode> page = baseMapper.selectPage(
+                new Page<>(query.getPage(), query.getLimit()),
+                new LambdaQueryWrapper<VipPromoCode>()
+                        .like(StrUtil.isNotBlank(query.getUserName()), VipPromoCode::getUserName, query.getUserName())
+                        .like(StrUtil.isNotBlank(query.getPromoCode()), VipPromoCode::getPromoCode, query.getPromoCode())
+                        .like(StrUtil.isNotBlank(query.getPhone()), VipPromoCode::getPhone, query.getPhone())
+                        .orderByDesc(VipPromoCode::getCreateTime)
+        );
+
+        List<VipPromoCode> vos = page.getRecords().stream().map(e -> {
+            VipPromoCode vo = new VipPromoCode();
+            BeanUtil.copyProperties(e, vo);
+            return vo;
+        }).collect(Collectors.toList());
+
+        return new PageUtils(vos, (int) page.getTotal(), query.getLimit(), query.getPage());
+    }
+
+    @Override
+    public void save(VipPromoCodeDTO dto) {
+        // 检验参数
+        checkParam(dto);
+
+        VipPromoCode vipPromoCode = new VipPromoCode();
+        BeanUtil.copyProperties(dto, vipPromoCode);
+        vipPromoCode.setCreateTime(new Date());
+        baseMapper.insert(vipPromoCode);
+    }
+
+    @Override
+    public void update(VipPromoCodeDTO dto) {
+        // 校验参数
+        checkParam(dto);
+
+        VipPromoCode vipPromoCode = new VipPromoCode();
+        BeanUtil.copyProperties(dto, vipPromoCode);
+        baseMapper.updateById(vipPromoCode);
+    }
+
+    @Override
+    public VipPromoCode getByCode(String promoCode) {
+        LambdaQueryWrapper<VipPromoCode> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(VipPromoCode::getPromoCode, promoCode);
+        return baseMapper.selectOne(wrapper);
+    }
+
+    @Override
+    public void increasePromoCount(Long promoCodeId) {
+        baseMapper.incrementPromoCountById(promoCodeId);
+    }
+
+    private void checkParam(VipPromoCodeDTO dto) {
+        LambdaQueryWrapper<VipPromoCode> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(VipPromoCode::getPromoCode, dto.getPromoCode());
+        wrapper.ne(ObjectUtil.isNotNull(dto.getId()), VipPromoCode::getId, dto.getId());
+        if (baseMapper.selectCount(wrapper) > 0) {
+            throw new SqxException("推广码已存在");
+        }
+    }
 }

+ 57 - 2
src/main/java/com/sqx/modules/member/service/impl/VipPromoRecordServiceImpl.java

@@ -1,14 +1,69 @@
 package com.sqx.modules.member.service.impl;
 
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sqx.common.utils.PageUtils;
 import com.sqx.modules.member.dao.VipPromoRecordDao;
+import com.sqx.modules.member.dto.VipPromoRecordDTO;
+import com.sqx.modules.member.entity.VipPromoCode;
 import com.sqx.modules.member.entity.VipPromoRecord;
+import com.sqx.modules.member.query.VipPromoRecordQuery;
+import com.sqx.modules.member.service.VipPromoCodeService;
 import com.sqx.modules.member.service.VipPromoRecordService;
-import org.springframework.stereotype.Service;
+import com.sqx.modules.member.vo.VipPromoRecordVO;
+import com.sqx.modules.utils.excel.EasyExcelUtil;
 import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+import java.util.List;
 
 @Service
 @RequiredArgsConstructor
 public class VipPromoRecordServiceImpl extends ServiceImpl<VipPromoRecordDao, VipPromoRecord> implements VipPromoRecordService {
-   
+
+    private final VipPromoCodeService vipPromoCodeService;
+
+    @Override
+    public PageUtils pages(VipPromoRecordQuery query) {
+        IPage<VipPromoRecordVO> pages = baseMapper.pages(new Page<>(query.getPage(), query.getLimit()), query);
+
+        return new PageUtils(pages);
+    }
+
+    @Override
+    public void recordExport(VipPromoRecordQuery query) {
+        IPage<VipPromoRecordVO> page = baseMapper.pages(new Page<>(query.getPage(), query.getLimit()), query);
+
+        List<VipPromoRecordVO> records = page.getRecords();
+        records.forEach(e -> e.setPayType(e.getPayType() == "1" ? "微信支付" : (e.getPayType() == "2" ? "余额支付" : "暖心照顾")));
+
+        EasyExcelUtil.exportExcel(records, VipPromoRecordVO.class, "会员推广记录列表", "sheet1");
+    }
+
+    @Override
+    @Transactional
+    public void add(VipPromoRecordDTO recordDTO) {
+        VipPromoRecord record = new VipPromoRecord();
+        BeanUtil.copyProperties(recordDTO, record);
+
+        record.setCreateTime(new Date());
+        if (StrUtil.isNotBlank(recordDTO.getPromoCode())) {
+            VipPromoCode promoCode = vipPromoCodeService.getByCode(recordDTO.getPromoCode());
+            if (ObjectUtil.isNull(promoCode)) {
+                return;
+            }
+
+            record.setPromoCodeId(promoCode.getId());
+
+            vipPromoCodeService.increasePromoCount(promoCode.getId());
+        }
+
+        save(record);
+    }
 }

+ 152 - 0
src/main/java/com/sqx/modules/member/service/impl/VipServiceImpl.java

@@ -0,0 +1,152 @@
+package com.sqx.modules.member.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.sqx.common.exception.SqxException;
+import com.sqx.modules.app.bo.UpdateMoneyBO;
+import com.sqx.modules.app.entity.UserEntity;
+import com.sqx.modules.app.service.UserMoneyService;
+import com.sqx.modules.app.service.UserService;
+import com.sqx.modules.common.entity.CommonInfo;
+import com.sqx.modules.common.service.CommonInfoService;
+import com.sqx.modules.coupon.service.TbCouponUserService;
+import com.sqx.modules.member.dto.VipPromoRecordDTO;
+import com.sqx.modules.member.service.VipPromoRecordService;
+import com.sqx.modules.member.service.VipService;
+import com.sqx.modules.pay.dto.GetPayParamDTO;
+import com.sqx.modules.pay.entity.PayDetails;
+import com.sqx.modules.pay.enums.PayChannelEnum;
+import com.sqx.modules.pay.enums.PaySceneEnum;
+import com.sqx.modules.pay.service.NewPayService;
+import com.sqx.modules.pay.vo.PayTransactionsVO;
+import com.sqx.modules.utils.VipExpirationUtil;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+/**
+ * vip服务
+ * @author codingliang
+ * @date 2025-11-12
+ */
+@Service
+@RequiredArgsConstructor
+public class VipServiceImpl implements VipService {
+
+    private final NewPayService newPayService;
+    private final UserService userService;
+    private final CommonInfoService commonInfoService;
+    private final TbCouponUserService tbCouponUserService;
+    private final VipPromoRecordService vipPromoRecordService;
+    private final UserMoneyService userMoneyService;
+
+    @Override
+    public PayTransactionsVO getPayParam(Long userId, String promoCode) {
+        UserEntity user = userService.getById(userId);
+        if(ObjectUtil.isNotNull(user.getIsVip()) && user.getIsVip() == 1 && VipExpirationUtil.isVipValid(user.getVipExpirationTime())) {
+            new SqxException("已经是会员,不能重复购买!");
+        }
+
+        // 查询会员价格
+        CommonInfo one = commonInfoService.findOne(313);
+        BigDecimal vipPrice = BigDecimal.valueOf(Double.valueOf(one.getValue()));
+
+        // 构建获取支付参数dto
+        GetPayParamDTO dto = GetPayParamDTO.builder()
+                .orderNo(IdWorker.getTimeId())
+                .orderDesc("墨轩商场会员")
+                .amount(vipPrice.setScale(2, RoundingMode.HALF_UP))
+                .userId(userId)
+                .userThirdId(user.getOpenId())
+                .payChannel(PayChannelEnum.WECHAT_JS_API)
+                .paySceneEnum(PaySceneEnum.ORDER_PAY)
+                .extra(promoCode)
+                .build();
+
+        return newPayService.getWechatTransactionsParamVO(dto);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void paySuccess(PayDetails payDetails) {
+        Long userId = payDetails.getUserId();
+        UserEntity user = userService.getById(userId);
+
+        // 新增会员充值记录
+        VipPromoRecordDTO recordDTO = VipPromoRecordDTO.builder()
+                .userId(userId)
+                .userName(user.getNickName())
+                .userPhone(user.getPhone())
+                .payType("1")
+                .promoCode(payDetails.getExtra())
+                .payAmount(payDetails.getMoney())
+                .payRelId(payDetails.getOrderId())
+                .build();
+        vipPromoRecordService.add(recordDTO);
+
+        // 更新用户为会员
+        afterPaySuccess(payDetails.getUserId());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void balancePay(Long userId, String promoCode) {
+        UserEntity user = userService.getById(userId);
+        if(ObjectUtil.isNotNull(user.getIsVip()) && user.getIsVip() == 1 && VipExpirationUtil.isVipValid(user.getVipExpirationTime())) {
+            new SqxException("已经是会员,不能重复购买!");
+        }
+
+        // 会员价格
+        CommonInfo vipPrice = commonInfoService.findOne(313);
+
+        // 扣减用户余额
+        UpdateMoneyBO updateMoneyBO = UpdateMoneyBO.builder()
+                .type(2)
+                .classify(3)
+                .userId(userId)
+                .amount(new BigDecimal(vipPrice.getValue()))
+                .title("购买会员")
+                .build();
+        userMoneyService.updateUserMoney(updateMoneyBO);
+
+        // 新增会员充值记录
+        VipPromoRecordDTO recordDTO = VipPromoRecordDTO.builder()
+                .userId(userId)
+                .userName(user.getNickName())
+                .userPhone(user.getPhone())
+                .payType("2")
+                .promoCode(promoCode)
+                .payAmount(Double.valueOf(vipPrice.getValue()))
+                .payRelId("")
+                .build();
+        vipPromoRecordService.add(recordDTO);
+
+        // 更新用户为会员
+        afterPaySuccess(userId);
+    }
+
+    private void afterPaySuccess(Long userId) {
+        // 会员期限(天)
+        CommonInfo vipExpirationDays = commonInfoService.findOne(314);
+
+        // 会员赠送红包数量
+        CommonInfo vipRedPacketNum = commonInfoService.findOne(315);
+
+        // 会员赠送红包金额
+        CommonInfo vipRedPacketMoney = commonInfoService.findOne(316);
+
+        // 会员失效时间
+        String expirationTime = VipExpirationUtil.getFutureTimeWithDays(Integer.valueOf(vipExpirationDays.getValue()));
+
+        // 更新用户为会员
+        userService.updateUserToVip(userId, VipExpirationUtil.getFutureTimeWithDays(30));
+
+        // 新增会员专属优惠券
+        for(int i = 0; i<Integer.valueOf(vipRedPacketNum.getValue()); i++){
+            tbCouponUserService.addAfterVipBuySuccess(userId, new BigDecimal(vipRedPacketMoney.getValue()), vipExpirationDays.getValue(), expirationTime);
+        }
+    }
+}

+ 57 - 0
src/main/java/com/sqx/modules/member/vo/SpecialFavorStudentVO.java

@@ -0,0 +1,57 @@
+package com.sqx.modules.member.vo;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+* 特殊照顾学生
+* @author : codingliang
+* @date : 2025-11-11
+*/
+@Data
+public class SpecialFavorStudentVO implements Serializable {
+   private static final long serialVersionUID = 1L;
+
+
+   @ApiModelProperty(value = "id")
+   @ExcelProperty("id")
+   private Long id;
+
+   @ApiModelProperty(value = "真实姓名")
+   @ExcelProperty("真实姓名")
+   private String name;
+
+   @ApiModelProperty(value = "学号")
+   @ExcelProperty("学号")
+   private String studentNumber;
+
+   @ApiModelProperty(value = "验证状态;0未认证、1已认证")
+   @ExcelProperty("验证状态")
+   private String verifyStatus;
+
+   @ApiModelProperty(value = "创建时间")
+   @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+   @ExcelProperty("创建时间")
+   private Date createTime;
+
+   @ApiModelProperty(value = "验证用户id")
+   private Long verifyUserId;
+
+   @ApiModelProperty(value = "验证用户名")
+   @ExcelProperty("验证用户名")
+   private String verifyUserName;
+
+   @ApiModelProperty(value = "验证用户手机号")
+   @ExcelProperty("验证用户手机号")
+   private String verifyUserPhone;
+
+   @ApiModelProperty(value = "最后验证时间")
+   @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+   @ExcelProperty("最后验证时间")
+   private Date lastVerifyTime;
+}

+ 43 - 0
src/main/java/com/sqx/modules/member/vo/VipPromoCodeVO.java

@@ -0,0 +1,43 @@
+package com.sqx.modules.member.vo;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+* 会员推广码
+* @author : codingliang
+* @date : 2025-11-11
+*/
+@Data
+@ApiModel(description = "会员推广码")
+public class VipPromoCodeVO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "id")
+    @ExcelProperty("id")
+    private Long id;
+
+    @ApiModelProperty(value = "推广人名称")
+    @ExcelProperty("推广人名称")
+    private String userName;
+
+    @ApiModelProperty(value = "推广码")
+    @ExcelProperty("推广码")
+    private String promoCode;
+
+    @ApiModelProperty(value = "推广人身份证号码")
+    @ExcelProperty("推广人身份证号码")
+    private String idCard;
+
+    @ApiModelProperty(value = "推广人手机号码")
+    @ExcelProperty("推广人手机号码")
+    private String phone;
+
+    @ApiModelProperty(value = "推广人次")
+    @ExcelProperty("推广人次")
+    private Integer promoCount;
+}

+ 48 - 0
src/main/java/com/sqx/modules/member/vo/VipPromoRecordVO.java

@@ -0,0 +1,48 @@
+package com.sqx.modules.member.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+* 会员推广记录
+* @author : codingliang
+* @date : 2025-11-11
+*/
+@Data
+@ApiModel(description = "会员推广记录VO")
+public class VipPromoRecordVO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "id")
+    private Long id;
+
+    @ApiModelProperty(value = "推广码id")
+    private Long promoCodeId;
+
+    @ApiModelProperty(value = "用户id")
+    private Long userId;
+    
+    @ApiModelProperty(value = "用户名称")
+    private String userName;
+
+    @ApiModelProperty(value = "用户手机号")
+    private String userPhone;
+    
+    @ApiModelProperty(value = "创建时间")
+    private Date createTime;
+
+    @ApiModelProperty(value = "支付类型;1微信支付、2余额支付、3暖心照顾")
+    private String payType;
+    
+    @ApiModelProperty(value = "支付金额")
+    private Double payAmount;
+
+    @ApiModelProperty(value = "会员过期时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date expireTime;
+}

+ 0 - 2
src/main/java/com/sqx/modules/pay/controller/app/ApiWeiXinPayController.java

@@ -67,7 +67,6 @@ public class ApiWeiXinPayController {
     @ApiOperation("余额购买会员")
     @PostMapping(value = "balanceBuyVip")
     public Result balanceBuyVip(@RequestAttribute Long userId){
-
         return wxService.balanceBuyVip(userId);
     }
 
@@ -75,7 +74,6 @@ public class ApiWeiXinPayController {
     @ApiOperation("微信支付宝购买会员")
     @PostMapping(value = "wxPayJsApiBuyVip")
     public Result wxPayJsApiBuyVip(@RequestAttribute Long userId, Integer type) throws Exception {
-
         return wxService.wxPayJsApiBuyVip(userId, type);
     }
 

+ 4 - 0
src/main/java/com/sqx/modules/pay/dto/GetPayParamDTO.java

@@ -48,4 +48,8 @@ public class GetPayParamDTO {
      * 支付场景
      */
     private PaySceneEnum paySceneEnum;
+    /**
+     * 额外参数
+     */
+    private String extra;
 }

+ 5 - 0
src/main/java/com/sqx/modules/pay/entity/PayDetails.java

@@ -74,6 +74,11 @@ public class PayDetails implements Serializable {
      */
     private String remark;
 
+     /**
+     * 额外备用参数
+     */
+    private String extra;
+
     /**
      * 支付系统交易订单号
      */

+ 3 - 48
src/main/java/com/sqx/modules/pay/service/impl/NewPayServiceImpl.java

@@ -4,8 +4,6 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.ekyong.www.pay.common.Constant;
 import com.ekyong.www.pay.pay.qrcode.api.RhtQrcodePayApi;
 import com.ekyong.www.pay.pay.qrcode.bean.QrcodeOpenPayRequestBean;
@@ -18,43 +16,15 @@ import com.ekyong.www.pay.pay.split.bean.SplitPayResponseBean;
 import com.ekyong.www.pay.util.SignUtil;
 import com.sqx.common.exception.SqxException;
 import com.sqx.common.utils.SpringContextUtils;
-import com.sqx.modules.activity.service.ActivityPartRecordService;
-import com.sqx.modules.activity.service.ActivityService;
-import com.sqx.modules.address.entity.Address;
-import com.sqx.modules.address.service.AddressService;
 import com.sqx.modules.app.bo.UpdateMoneyBO;
-import com.sqx.modules.app.dao.UserBrowseDao;
-import com.sqx.modules.app.dao.UserDao;
-import com.sqx.modules.app.dao.UserMoneyDao;
-import com.sqx.modules.app.dao.UserMoneyDetailsDao;
 import com.sqx.modules.app.service.UserMoneyService;
-import com.sqx.modules.app.service.UserService;
 import com.sqx.modules.common.service.CommonInfoService;
-import com.sqx.modules.coupon.dao.TbCouponUserDao;
 import com.sqx.modules.datacentre.service.DataCentreService;
-import com.sqx.modules.errand.dao.TbIndentDao;
-import com.sqx.modules.errand.service.TbIndentService;
-import com.sqx.modules.errand.service.TbIndentSmsSendLogService;
-import com.sqx.modules.errand.service.TbIndentSmsTemplateService;
-import com.sqx.modules.goods.dao.GoodsDao;
-import com.sqx.modules.goods.dao.GoodsShopDao;
-import com.sqx.modules.goods.dao.GoodsShopRelevancyDao;
-import com.sqx.modules.goods.dao.GoodsSkuDao;
-import com.sqx.modules.goods.entity.GoodsShop;
 import com.sqx.modules.goods.service.GoodsShopService;
-import com.sqx.modules.integral.dao.UserIntegralDao;
-import com.sqx.modules.integral.dao.UserIntegralDetailsDao;
-import com.sqx.modules.message.dao.MessageInfoDao;
-import com.sqx.modules.order.dao.AppOrderDao;
-import com.sqx.modules.order.dao.EvaluateDao;
-import com.sqx.modules.order.dao.OrderGoodsDao;
-import com.sqx.modules.order.entity.OrderGoods;
+import com.sqx.modules.member.service.VipService;
 import com.sqx.modules.order.entity.TbOrder;
 import com.sqx.modules.order.service.AppOrderService;
-import com.sqx.modules.order.service.EvaluateRiderService;
 import com.sqx.modules.pay.config.WechatPayConfig;
-import com.sqx.modules.pay.controller.app.AliPayController;
-import com.sqx.modules.pay.dao.PayDetailsDao;
 import com.sqx.modules.pay.dto.GetPayParamDTO;
 import com.sqx.modules.pay.entity.PayDetails;
 import com.sqx.modules.pay.enums.PayChannelEnum;
@@ -62,32 +32,18 @@ import com.sqx.modules.pay.enums.PaySceneEnum;
 import com.sqx.modules.pay.enums.PayStateEnums;
 import com.sqx.modules.pay.service.NewPayService;
 import com.sqx.modules.pay.service.PayDetailsService;
-import com.sqx.modules.pay.service.WxErrService;
 import com.sqx.modules.pay.vo.WechatTransactionsParamVO;
-import com.sqx.modules.shop.service.ShopMessageService;
-import com.sqx.modules.shop.service.ShopTypeService;
-import com.sqx.modules.sys.service.SysUserService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
-import org.apache.http.util.EntityUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.PlatformTransactionManager;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
 import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.Arrays;
-import java.util.List;
 import java.util.Map;
 
 @Slf4j
@@ -303,9 +259,8 @@ public class NewPayServiceImpl implements NewPayService {
      * @param payDetails
      */
     private void handleVipPaySuccess(PayDetails payDetails) {
-        dataCentreService.presenterVip(payDetails.getUserId());
-
-        // 改动钱包明细??
+        VipService vipService = SpringContextUtils.getBean(VipService.class);
+        vipService.paySuccess(payDetails);
     }
 
     /**

+ 1 - 0
src/main/java/com/sqx/modules/pay/service/impl/PayDetailsServiceImpl.java

@@ -104,6 +104,7 @@ public class PayDetailsServiceImpl extends ServiceImpl<PayDetailsDao, PayDetails
         payDetails.setClassify(dto.getPaySceneEnum().getSceneCode());
         payDetails.setType(dto.getPayChannel().getCode());
         payDetails.setRemark(payParam);
+        payDetails.setExtra(dto.getExtra());
         payDetailsDao.insert(payDetails);
     }
 

+ 8 - 62
src/main/java/com/sqx/modules/pay/service/impl/WxServiceImpl.java

@@ -179,8 +179,14 @@ public class WxServiceImpl implements WxService {
         if(userMoney.getMoney().doubleValue()<Double.valueOf(one.getValue())){
             return Result.error("余额不足!");
         }
-        userMoneyService.updateMoney(2, userId, Double.valueOf(one.getValue()));
+
+        // 开通会员操作
         dataCentreService.presenterVip(userId);
+
+        // 扣除用户余额
+        userMoneyService.updateMoney(2, userId, Double.valueOf(one.getValue()));
+
+        // 添加用户余额记录
         UserMoneyDetails userMoneyDetails = new UserMoneyDetails();
         userMoneyDetails.setUserId(userId);
         userMoneyDetails.setTitle("用户开通会员");
@@ -805,64 +811,6 @@ public class WxServiceImpl implements WxService {
         }
     }
 
-
-    /*@Override
-    public boolean refund(Orders orders) {
-        WXConfigUtil config = null;
-        try {
-            config = new WXConfigUtil();
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-        int commInfoId = 0;
-        Integer payWay = null;
-        //orders.getPayWay(); //支付方式(1app微信 2微信公众号 3微信小程序 4app支付宝 5H5支付宝 6零钱)
-        switch (payWay) {
-            case 1:
-                commInfoId = 5;
-                break; //appId
-            case 2:
-                commInfoId = 74;
-                break; //公众号id
-            case 3:
-                commInfoId = 45;
-                break; //小程序id
-        }
-        config.setAppId(commonInfoService.findOne(commInfoId).getValue());
-        config.setKey(commonInfoService.findOne(75).getValue());
-        config.setMchId(commonInfoService.findOne(76).getValue());
-        WXPay wxpay = new WXPay(config);
-        Map<String, String> data = new HashMap<>();
-        data.put("appid", config.getAppID());
-        data.put("mch_id", config.getMchID());
-        data.put("nonce_str", WXPayUtil.generateNonceStr());
-        try {
-            data.put("sign", WXPayUtil.generateSignature(data, config.getKey(), WXPayConstants.SignType.MD5));
-        } catch (Exception e) {
-            e.printStackTrace();
-            return false;
-        }
-        data.put("out_trade_no", orders.getOrdersNo()); //订单号,支付单号一致
-        data.put("out_refund_no", orders.getOrdersNo()); //退款单号,同一笔用不同的退款单号
-        double total_fee = 0.00;
-        data.put("total_fee", new Double(orders.getPayMoney().doubleValue() * 100).intValue() + ""); //1块等于微信支付传入100);
-        data.put("refund_fee", new Double(orders.getPayMoney().doubleValue() * 100).intValue() + ""); //1块等于微信支付传入100);
-        //使用官方API退款
-        try {
-            Map<String, String> response = wxpay.refund(data);
-            if ("SUCCESS".equals(response.get("return_code"))) {//主要返回以下5个参数
-                System.err.println("退款成功");
-                return true;
-            } else {
-                return false;
-            }
-        } catch (Exception e) {
-            log.info("返回");
-            e.printStackTrace();
-            return false;
-        }
-    }*/
-
     @Override
     public boolean wxRefund(PayDetails payDetails){
         WXConfigUtil config = null;
@@ -913,6 +861,4 @@ public class WxServiceImpl implements WxService {
             return false;
         }
     }
-
-
-}
+}

+ 142 - 0
src/main/java/com/sqx/modules/utils/PromotionCodeUtil.java

@@ -0,0 +1,142 @@
+package com.sqx.modules.utils;
+
+/**
+ * 推荐码生成工具类
+ */
+public class PromotionCodeUtil {
+
+    /** 自定义进制字符集(避免使用易混淆的字符) */
+    private static final char[] r = new char[]{ 'M', 'J', 'U', 'D', 'Z', 'X', '9', 'C', '7', 'P',
+            'E', '8', '6', 'B', 'G', 'H', 'S', '2', '5', 'F',
+            'R', '4', 'Q', 'W', 'K', '3', 'V', 'Y', 'T', 'N'};
+
+    /** 进制长度 */
+    private static final int binLen = r.length;
+
+    /** 推荐码长度 */
+    private static final int codeLength = 6; // 8位长度
+
+    /** 用于生成唯一ID的锁对象 */
+    private static final Object lock = new Object();
+
+    /** 最后生成的时间戳,用于防止短时间内生成重复码 */
+    private static long lastTimestamp = -1L;
+
+    /** 序列号,用于同一毫秒内生成多个码时保证唯一性 */
+    private static long sequence = 0L;
+
+    /** 序列号最大值 */
+    private static final long MAX_SEQUENCE = 99999L;
+
+    /** 节点ID(在集群环境中非常重要) */
+    private static final int NODE_ID;
+
+    // 静态初始化块,初始化节点ID
+    static {
+        int nodeId1;
+        // 优先从环境变量获取节点ID
+        String nodeIdStr = System.getenv("NODE_ID");
+        if (nodeIdStr != null && !nodeIdStr.isEmpty()) {
+            try {
+                nodeId1 = Integer.parseInt(nodeIdStr) & 0x3F; // 限制在0-63之间
+            } catch (NumberFormatException e) {
+                nodeId1 = generateRandomNodeId();
+            }
+        } else {
+            // 如果没有配置,生成随机节点ID
+            nodeId1 = generateRandomNodeId();
+        }
+        NODE_ID = nodeId1;
+        System.out.println("当前服务器节点ID: " + NODE_ID);
+    }
+
+    /**
+     * 生成随机节点ID(当环境变量未配置时使用)
+     */
+    private static int generateRandomNodeId() {
+        // 使用服务器信息和当前时间生成相对唯一的节点ID
+        try {
+            String hostName = java.net.InetAddress.getLocalHost().getHostName();
+            int hashCode = hostName.hashCode();
+            // 结合当前时间戳的低8位,增加随机性
+            long timestamp = System.currentTimeMillis() & 0xFF;
+            return (Math.abs(hashCode) ^ (int)timestamp) & 0x3F; // 限制在0-63之间
+        } catch (Exception e) {
+            // 如果获取主机名失败,使用纯随机数
+            return (int)(Math.random() * 64);
+        }
+    }
+
+    /**
+     * 生成唯一推荐码(支持集群环境)
+     * @return 唯一的推荐码字符串
+     */
+    public static String generatePromotionCode() {
+        // 生成基础唯一数值
+        long uniqueValue = generateUniqueValue();
+
+        // 使用进制转换算法
+        StringBuilder sb = new StringBuilder();
+        while (uniqueValue > 0) {
+            int ind = (int) (uniqueValue % binLen);
+            sb.append(r[ind]);
+            uniqueValue = uniqueValue / binLen;
+        }
+
+        // 确保推荐码长度
+        String code = sb.toString();
+        if (code.length() < codeLength) {
+            // 使用随机字符填充前面
+            StringBuilder padding = new StringBuilder();
+            int paddingLength = codeLength - code.length();
+            for (int i = 0; i < paddingLength; i++) {
+                long randomSeed = System.nanoTime() + sequence;
+                int randomIndex = (int) (Math.abs(randomSeed) % binLen);
+                padding.append(r[randomIndex]);
+            }
+            code = padding.append(sb).toString();
+        } else if (code.length() > codeLength) {
+            // 如果超过长度,截取前codeLength位
+            code = code.substring(0, codeLength);
+        }
+
+        return code;
+    }
+
+    /**
+     * 生成唯一数值,结合时间戳、节点ID和序列号
+     * @return 唯一数值
+     */
+    private static long generateUniqueValue() {
+        synchronized (lock) {
+            long timestamp = System.currentTimeMillis();
+
+            // 如果是同一毫秒,序列号加1
+            if (timestamp == lastTimestamp) {
+                sequence++;
+                // 防止序列号溢出
+                if (sequence > MAX_SEQUENCE) {
+                    // 等待下一毫秒
+                    while (System.currentTimeMillis() <= timestamp) {
+                        Thread.yield();
+                    }
+                    timestamp = System.currentTimeMillis();
+                    sequence = 0L;
+                }
+            } else {
+                // 不同毫秒,序列号重置
+                sequence = 0L;
+            }
+
+            lastTimestamp = timestamp;
+
+            // 将节点ID纳入唯一性计算
+            // 格式:时间戳(41位) + 节点ID(6位) + 序列号(17位)
+            long result = (timestamp << 23);
+            result |= ((long)NODE_ID << 17);
+            result |= (sequence & 0x1FFFF); // 17位序列号
+
+            return Math.abs(result);
+        }
+    }
+}

+ 108 - 0
src/main/java/com/sqx/modules/utils/VipExpirationUtil.java

@@ -0,0 +1,108 @@
+package com.sqx.modules.utils;
+
+import com.sqx.common.exception.SqxException;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * VIP有效期校验工具类
+ * 提供VIP过期状态检查、剩余天数计算等功能
+ */
+public class VipExpirationUtil {
+    
+    /**
+     * VIP过期时间格式(yyyy-MM-dd HH:mm:ss)
+     */
+    public static final String VIP_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
+    
+    /**
+     * 日期时间格式化器
+     */
+    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(VIP_DATE_FORMAT);
+    
+    /**
+     * 检查VIP是否已过期
+     * @param vipExpirationTime VIP过期时间字符串(yyyy-MM-dd HH:mm:ss格式)
+     * @return 如果已过期返回true,否则返回false
+     * @throws SqxException 当时间格式错误时抛出
+     */
+    public static boolean isVipExpired(String vipExpirationTime) {
+        if (vipExpirationTime == null || vipExpirationTime.trim().isEmpty()) {
+            return true; // 空值视为已过期
+        }
+        try {
+            // 过期时间
+            LocalDateTime expirationDateTime = LocalDateTime.parse(vipExpirationTime, FORMATTER);
+            
+            return LocalDateTime.now().isAfter(expirationDateTime);
+        } catch (DateTimeParseException e) {
+            throw new SqxException("VIP过期时间格式错误,应为:" + VIP_DATE_FORMAT, e);
+        }
+    }
+    
+    /**
+     * 检查VIP是否在有效期内
+     * @param vipExpirationTime VIP过期时间字符串(yyyy-MM-dd HH:mm:ss格式)
+     * @return 如果在有效期内返回true,否则返回false
+     */
+    public static boolean isVipValid(String vipExpirationTime) {
+        return !isVipExpired(vipExpirationTime);
+    }
+    
+    /**
+     * 计算VIP剩余天数
+     * @param vipExpirationTime VIP过期时间字符串(yyyy-MM-dd HH:mm:ss格式)
+     * @return VIP剩余天数,如果已过期则返回负数
+     * @throws IllegalArgumentException 当时间格式错误时抛出
+     */
+    public static long getRemainingDays(String vipExpirationTime) {
+        if (vipExpirationTime == null || vipExpirationTime.trim().isEmpty()) {
+            return -1; // 空值视为已过期
+        }
+        
+        try {
+            // 过期时间
+            LocalDateTime expirationDateTime = LocalDateTime.parse(vipExpirationTime, FORMATTER);
+            
+            // 计算剩余天数
+            return ChronoUnit.DAYS.between(LocalDateTime.now(), expirationDateTime);
+            
+        } catch (DateTimeParseException e) {
+            throw new SqxException("VIP过期时间格式错误,应为:" + VIP_DATE_FORMAT, e);
+        }
+    }
+    
+    /**
+     * 格式化当前时间为VIP日期格式
+     * @return 格式化后的当前时间字符串
+     */
+    public static String formatCurrentTime() {
+        return FORMATTER.format(LocalDateTime.now());
+    }
+    
+    /**
+     * 格式化指定时间为VIP日期格式
+     * @param dateTime 要格式化的时间
+     * @return 格式化后的时间字符串
+     */
+    public static String formatDateTime(LocalDateTime dateTime) {
+        if (dateTime == null) {
+            return null;
+        }
+        return FORMATTER.format(dateTime);
+    }
+    
+    /**
+     * 获取指定天数后的时间并格式化为VIP日期格式
+     * 用于计算VIP有效期延长
+     * @param days 天数
+     * @return 格式化后的未来时间字符串
+     */
+    public static String getFutureTimeWithDays(int days) {
+        LocalDateTime futureDateTime = LocalDateTime.now().plusDays(days);
+        return FORMATTER.format(futureDateTime);
+    }
+}

+ 40 - 0
src/main/resources/mapper/member/SpecialFavorStudentMapper.xml

@@ -0,0 +1,40 @@
+<?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.sqx.modules.member.dao.SpecialFavorStudentDao">
+
+    <select id="pages" resultType="com.sqx.modules.member.vo.SpecialFavorStudentVO">
+        select
+            sfs.id,
+            sfs.name,
+            sfs.student_number,
+            sfs.verify_status,
+            sfs.create_time,
+            sfs.verify_user_id,
+            tu.user_name as verify_user_name,
+            tu.phone as verify_user_phone,
+            sfs.last_verify_time
+        from
+            special_favor_student sfs
+            left join
+                tb_user tu on sfs.verify_user_id = tu.user_id
+        <where>
+            and sfs.is_deleted = 0
+            <if test="query.name != null and query.name != ''">
+                and sfs.name like concat('%', #{query.name}, '%')
+            </if>
+            <if test="query.studentNumber != null and query.studentNumber != ''">
+                and sfs.student_number like concat('%', #{query.studentNumber}, '%')
+            </if>
+            <if test="query.verifyStatus != null and query.verifyStatus != ''">
+                and sfs.verify_status = #{query.verifyStatus}
+            </if>
+            <if test="query.startTime != null">
+                and sfs.create_time >= #{query.startTime}
+            </if>
+            <if test="query.endTime != null">
+                and sfs.create_time <![CDATA[<=]]> #{query.endTime}
+            </if>
+        </where>
+        order by sfs.create_time desc
+    </select>
+</mapper>

+ 8 - 0
src/main/resources/mapper/member/VipPromoCodeMapper.xml

@@ -0,0 +1,8 @@
+<?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.sqx.modules.member.dao.VipPromoCodeDao">
+
+    <select id="incrementPromoCountById">
+        UPDATE vip_promo_code SET promo_count = promo_count + 1 WHERE id = #{promoCodeId}
+    </select>
+</mapper>

+ 40 - 0
src/main/resources/mapper/member/VipPromoRecordMapper.xml

@@ -0,0 +1,40 @@
+<?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.sqx.modules.member.dao.VipPromoRecordDao">
+
+    <select id="pages" resultType="com.sqx.modules.member.vo.VipPromoRecordVO">
+        select
+            vpr.id,
+            vpr.promo_code_id,
+            vpr.user_id,
+            vpr.user_name,
+            vpr.user_phone,
+            vpr.create_time,
+            vpr.create_time,
+            vpr.pay_type,
+            vpr.pay_amount,
+            t.vip_expiration_time expire_time
+        from
+            vip_promo_record vpr
+            left join tb_user t on vpr.user_id = t.id
+        <where>
+            and vpr.is_deleted = 0
+            <if test="query.promoCodeId != null">
+                and vpr.promo_code_id = #{query.promoCodeId}
+            </if>
+            <if test="query.userPhone != null and query.userPhone != ''">
+                and vpr.user_phone like concat('%', #{query.userPhone}, '%')
+            </if>
+            <if test="query.payType != null and query.payType != ''">
+                and vpr.pay_type = #{query.payType}
+            </if>
+            <if test="query.startTime != null">
+                and vpr.create_time >= #{query.startTime}
+            </if>
+            <if test="query.endTime != null">
+                and vpr.create_time <![CDATA[<=]]>  #{query.endTime}
+            </if>
+        </where>
+        order by vpr.create_time desc
+    </select>
+</mapper>