8
0

49 Коммиты d34a0380da ... d58273f0c1

Автор SHA1 Сообщение Дата
  codingliang d58273f0c1 Merge remote-tracking branch 'origin/dev/order-type-up' into dev/order-type-up 9 месяцев назад
  codingliang 1fe9139474 骑手送达时短信改为非必须 9 месяцев назад
  codingliang 666bc22829 修复获取最近30天销售数据报错问题 9 месяцев назад
  codingliang 0d10d5fb25 修改店铺默认排序规则 9 месяцев назад
  codingliang 723df4daa7 修改店铺默认排序规则 9 месяцев назад
  codingliang b7e596b778 会员立减金额规则调整 9 месяцев назад
  codingliang 29e868db1b 修改会员优惠描述 9 месяцев назад
  codingliang 83b26856b8 新增会员优惠和活动不同时享受限制 9 месяцев назад
  codingliang 29c8b514d4 调整get-can-reduce接口 9 месяцев назад
  codingliang 42227d3827 Merge remote-tracking branch 'refs/remotes/origin/dev/vip-up' into dev/order-type-up 9 месяцев назад
  codingliang d3297c8ef3 会员优惠新增最低金额判断 9 месяцев назад
  codingliang 7fae66562b 修复小票打印订单打包费单独计算问题 9 месяцев назад
  codingliang d775235203 修复小票打印订单打包费单独计算问题 9 месяцев назад
  codingliang 7d337297c8 Merge branch 'refs/heads/dev/vip-up' into dev/order-type-up 9 месяцев назад
  codingliang 0ba755c9aa 修复构建店铺列表缓存时缺失VIP优惠筛选字段问题 9 месяцев назад
  codingliang 09ffc774d1 新增到店堂食额外订单类型,同步调整打包费、打印逻辑 9 месяцев назад
  codingliang 94bc5c25fa 调整骑手申请认证接口 9 месяцев назад
  codingliang 5542f5e9c9 去除拒绝骑手认证时的退款逻辑 9 месяцев назад
  codingliang e7f2e25581 小程序跑腿师傅实名认证取消支付步骤 9 месяцев назад
  codingliang b4ad19c6cc 修复商家缴纳保证金后没有钱包明细问题 9 месяцев назад
  codingliang 71212e29a3 兼容商户端小程序获取支付参数 9 месяцев назад
  codingliang 1f8a29cd90 修复只定义异常未抛出异常问题 9 месяцев назад
  codingliang b4b230e71e 新增商户缴纳保证金(新)接口 9 месяцев назад
  codingliang c76d99aa99 修复推广码推广失败问题 9 месяцев назад
  codingliang 6adcf72ad8 购买会员新增推荐码校验 9 месяцев назад
  codingliang 843a1faa0e 修复会员记录查询条件不生效问题 9 месяцев назад
  codingliang 8b197446ab 新增获取用户是否可以会员立减接口 9 месяцев назад
  codingliang 3b8fe243a5 新增推广码导出接口; 9 месяцев назад
  codingliang c5190b6f78 修复vip导出文件失败问题 10 месяцев назад
  codingliang e804dff08f vip_promo_record表pay_rel_id长度调整 10 месяцев назад
  codingliang 8e810d5eba 修复vip导出文件失败问题 10 месяцев назад
  codingliang fb530e62fd 修复会员购买获取支付参数场景选择错误问题 10 месяцев назад
  codingliang 9685931a0f 修复店铺查询异常 10 месяцев назад
  codingliang 4e14861b78 分页查询会员推广记录新增特殊照顾查询接口 10 месяцев назад
  codingliang 668537176f 文件导出部分接口userId获取方式调整 10 месяцев назад
  codingliang b9de92501c 会员推广记录导出接口调整 10 месяцев назад
  wanxl c956adbf46 解决循环依赖问题 10 месяцев назад
  codingliang a58f832871 tbOrder新增vipPromotion字段,获取支付参数和完成订单时加入会员优惠逻辑 10 месяцев назад
  codingliang 5b5a6a61cd goodsShop新增vipPromotion字段 10 месяцев назад
  codingliang e298a57cf4 会员推广管理、特殊照顾学生管理新增admin窄化路径 10 месяцев назад
  codingliang 0ec0a54bf1 del_flag设置默认值 10 месяцев назад
  codingliang 1e3fa3826b 特殊学生导出接口调整 10 месяцев назад
  codingliang 05193a81e8 恢复被删除的低版本poi依赖 10 месяцев назад
  codingliang c69d9766ec 删除低版本的poi 10 месяцев назад
  codingliang 53e86ee92a 学号字段类型调整 10 месяцев назад
  codingliang 15a61dfab7 修复sql中删除字段字段名写错问题 10 месяцев назад
  codingliang edad3a8d0f 修复AppOrderService引用自身问题 10 месяцев назад
  codingliang b6e2f11829 新增vip会员管理功能接口 10 месяцев назад
  codingliang 7502c3cae3 vip会员升级基础代码生成 10 месяцев назад
82 измененных файлов с 2859 добавлено и 562 удалено
  1. 55 0
      db/update_251111.sql
  2. 2 0
      db/update_251128.sql
  3. 2 0
      db/update_251205.sql
  4. 7 0
      src/main/java/com/sqx/modules/app/service/UserService.java
  5. 2 0
      src/main/java/com/sqx/modules/app/service/impl/UserMoneyDetailsServiceImpl.java
  6. 5 3
      src/main/java/com/sqx/modules/app/service/impl/UserMoneyServiceImpl.java
  7. 9 0
      src/main/java/com/sqx/modules/app/service/impl/UserServiceImpl.java
  8. 9 0
      src/main/java/com/sqx/modules/coupon/service/TbCouponUserService.java
  9. 18 0
      src/main/java/com/sqx/modules/coupon/service/impl/TbTbCouponUserServiceImpl.java
  10. 5 11
      src/main/java/com/sqx/modules/datacentre/controller/DataCentreController.java
  11. 28 107
      src/main/java/com/sqx/modules/datacentre/service/impl/DataCentreServiceImpl.java
  12. 17 12
      src/main/java/com/sqx/modules/errand/service/impl/TbIndentServiceImpl.java
  13. 41 9
      src/main/java/com/sqx/modules/exportExecl/controller/ExportExeclController.java
  14. 2 2
      src/main/java/com/sqx/modules/goods/controller/GoodsController.java
  15. 6 2
      src/main/java/com/sqx/modules/goods/controller/GoodsShopController.java
  16. 5 18
      src/main/java/com/sqx/modules/goods/controller/app/AppGoodsController.java
  17. 2 2
      src/main/java/com/sqx/modules/goods/dao/GoodsShopDao.java
  18. 6 0
      src/main/java/com/sqx/modules/goods/dto/ShopQueryDTO.java
  19. 17 15
      src/main/java/com/sqx/modules/goods/entity/GoodsShop.java
  20. 1 2
      src/main/java/com/sqx/modules/goods/service/GoodsService.java
  21. 14 0
      src/main/java/com/sqx/modules/goods/service/GoodsShopService.java
  22. 22 7
      src/main/java/com/sqx/modules/goods/service/impl/GoodsServiceImpl.java
  23. 34 0
      src/main/java/com/sqx/modules/goods/service/impl/GoodsShopServiceImpl.java
  24. 74 0
      src/main/java/com/sqx/modules/member/controller/SpecialFavorStudentController.java
  25. 81 0
      src/main/java/com/sqx/modules/member/controller/VipPromoCodeController.java
  26. 33 0
      src/main/java/com/sqx/modules/member/controller/app/AppSpecialFavorStudentController.java
  27. 52 0
      src/main/java/com/sqx/modules/member/controller/app/AppVipController.java
  28. 15 0
      src/main/java/com/sqx/modules/member/dao/SpecialFavorStudentDao.java
  29. 12 0
      src/main/java/com/sqx/modules/member/dao/VipPromoCodeDao.java
  30. 15 0
      src/main/java/com/sqx/modules/member/dao/VipPromoRecordDao.java
  31. 27 0
      src/main/java/com/sqx/modules/member/dto/CanReduceQueryDTO.java
  32. 30 0
      src/main/java/com/sqx/modules/member/dto/SpecialFavorStudentDTO.java
  33. 25 0
      src/main/java/com/sqx/modules/member/dto/SpecialFavorStudentVerifyDTO.java
  34. 34 0
      src/main/java/com/sqx/modules/member/dto/VipPromoCodeDTO.java
  35. 41 0
      src/main/java/com/sqx/modules/member/dto/VipPromoRecordDTO.java
  36. 63 0
      src/main/java/com/sqx/modules/member/entity/SpecialFavorStudent.java
  37. 63 0
      src/main/java/com/sqx/modules/member/entity/VipPromoCode.java
  38. 73 0
      src/main/java/com/sqx/modules/member/entity/VipPromoRecord.java
  39. 82 0
      src/main/java/com/sqx/modules/member/query/SpecialFavorStudentQuery.java
  40. 50 0
      src/main/java/com/sqx/modules/member/query/VipPromoCodeQuery.java
  41. 105 0
      src/main/java/com/sqx/modules/member/query/VipPromoRecordQuery.java
  42. 27 0
      src/main/java/com/sqx/modules/member/service/SpecialFavorStudentService.java
  43. 36 0
      src/main/java/com/sqx/modules/member/service/VipPromoCodeService.java
  44. 18 0
      src/main/java/com/sqx/modules/member/service/VipPromoRecordService.java
  45. 40 0
      src/main/java/com/sqx/modules/member/service/VipService.java
  46. 152 0
      src/main/java/com/sqx/modules/member/service/impl/SpecialFavorStudentServiceImpl.java
  47. 97 0
      src/main/java/com/sqx/modules/member/service/impl/VipPromoCodeServiceImpl.java
  48. 55 0
      src/main/java/com/sqx/modules/member/service/impl/VipPromoRecordServiceImpl.java
  49. 227 0
      src/main/java/com/sqx/modules/member/service/impl/VipServiceImpl.java
  50. 50 0
      src/main/java/com/sqx/modules/member/vo/SpecialFavorStudentVO.java
  51. 42 0
      src/main/java/com/sqx/modules/member/vo/VipPromoCodeVO.java
  52. 51 0
      src/main/java/com/sqx/modules/member/vo/VipPromoRecordVO.java
  53. 26 0
      src/main/java/com/sqx/modules/member/vo/VipReduceVO.java
  54. 7 0
      src/main/java/com/sqx/modules/order/dao/AppOrderDao.java
  55. 13 10
      src/main/java/com/sqx/modules/order/entity/TbOrder.java
  56. 14 0
      src/main/java/com/sqx/modules/order/service/AppOrderService.java
  57. 110 19
      src/main/java/com/sqx/modules/order/service/impl/AppAppOrderServiceImpl.java
  58. 4 19
      src/main/java/com/sqx/modules/pay/controller/app/ApiWeiXinErrRiderPayController.java
  59. 0 2
      src/main/java/com/sqx/modules/pay/controller/app/ApiWeiXinPayController.java
  60. 4 0
      src/main/java/com/sqx/modules/pay/dto/GetPayParamDTO.java
  61. 8 0
      src/main/java/com/sqx/modules/pay/dto/PayOrderDTO.java
  62. 42 0
      src/main/java/com/sqx/modules/pay/dto/RiderCertificationDTO.java
  63. 5 0
      src/main/java/com/sqx/modules/pay/entity/PayDetails.java
  64. 2 2
      src/main/java/com/sqx/modules/pay/service/WxErrRiderService.java
  65. 23 60
      src/main/java/com/sqx/modules/pay/service/impl/NewPayServiceImpl.java
  66. 1 0
      src/main/java/com/sqx/modules/pay/service/impl/PayDetailsServiceImpl.java
  67. 28 25
      src/main/java/com/sqx/modules/pay/service/impl/WxErrRiderServiceImpl.java
  68. 8 62
      src/main/java/com/sqx/modules/pay/service/impl/WxServiceImpl.java
  69. 2 0
      src/main/java/com/sqx/modules/printInfo/entity/eOrderTypeExtra.java
  70. 51 21
      src/main/java/com/sqx/modules/shop/controller/app/ShopMoneyController.java
  71. 142 0
      src/main/java/com/sqx/modules/utils/PromotionCodeUtil.java
  72. 108 0
      src/main/java/com/sqx/modules/utils/VipExpirationUtil.java
  73. 19 7
      src/main/java/com/sqx/modules/utils/fieYun/FeiYunUtils.java
  74. 5 0
      src/main/java/com/sqx/scheduler/config/SchedulerLock.java
  75. 107 11
      src/main/java/com/sqx/scheduler/export/ExportScheduler.java
  76. 30 5
      src/main/java/com/sqx/scheduler/order/OrderScheduler.java
  77. 7 1
      src/main/resources/mapper/goods/GoodsShopMapper.xml
  78. 40 0
      src/main/resources/mapper/member/SpecialFavorStudentMapper.xml
  79. 8 0
      src/main/resources/mapper/member/VipPromoCodeMapper.xml
  80. 49 0
      src/main/resources/mapper/member/VipPromoRecordMapper.xml
  81. 13 0
      src/main/resources/mapper/order/OrderMapper.xml
  82. 4 128
      src/test/java/OrderTest.java

+ 55 - 0
db/update_251111.sql

@@ -0,0 +1,55 @@
+DROP TABLE IF EXISTS special_favor_student;
+CREATE TABLE special_favor_student(
+    `id` bigint NOT NULL AUTO_INCREMENT  COMMENT 'id' ,
+    `del_flag` VARCHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标识;0未删除、1已删除' ,
+    `name` VARCHAR(32) NOT NULL   COMMENT '真实姓名' ,
+    `student_number` VARCHAR(32) NOT NULL   COMMENT '学号' ,
+    `verify_status` VARCHAR(1) NOT NULL  DEFAULT 0 COMMENT '验证状态;0未认证、1已认证' ,
+    `create_time` DATETIME NOT NULL   COMMENT '创建时间' ,
+    `verify_user_id` bigint    COMMENT '验证用户id' ,
+    `last_verify_time` DATETIME    COMMENT '最后验证时间' ,
+    PRIMARY KEY (id)
+)  COMMENT = '特殊照顾学生';
+
+DROP TABLE IF EXISTS vip_promo_code;
+CREATE TABLE vip_promo_code(
+    `id` bigint NOT NULL AUTO_INCREMENT  COMMENT 'id' ,
+    `del_flag` VARCHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标识;0未删除、1已删除' ,
+    `user_name` VARCHAR(32) NOT NULL   COMMENT '推广人名称' ,
+    `promo_code` VARCHAR(32) NOT NULL   COMMENT '推广码' ,
+    `id_card` VARCHAR(32) NOT NULL   COMMENT '推广人身份证号码' ,
+    `phone` VARCHAR(32) NOT NULL   COMMENT '推广人手机号码' ,
+    `promo_count` INT NOT NULL  DEFAULT 0 COMMENT '推广人次' ,
+    `create_time` DATETIME NOT NULL   COMMENT '创建时间' ,
+    PRIMARY KEY (id)
+)  COMMENT = '会员推广码';
+
+
+DROP TABLE IF EXISTS vip_promo_record;
+CREATE TABLE vip_promo_record(
+    `id` bigint NOT NULL AUTO_INCREMENT  COMMENT 'id' ,
+    `del_flag` VARCHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标识;0未删除、1已删除' ,
+    `promo_code_id` bigint    COMMENT '推广码id' ,
+    `user_id` bigint NOT NULL   COMMENT '用户id' ,
+    `user_name` VARCHAR(32) NOT NULL   COMMENT '用户名称' ,
+    `user_phone` VARCHAR(32) NOT NULL   COMMENT '用户手机号' ,
+    `create_time` DATETIME NOT NULL   COMMENT '创建时间' ,
+    `pay_type` VARCHAR(1) NOT NULL   COMMENT '支付类型;1微信支付、2余额支付、3暖心照顾' ,
+    `pay_amount` DECIMAL(24,6) NOT NULL  DEFAULT 0 COMMENT '支付金额' ,
+    `pay_rel_id` VARCHAR(64)    COMMENT '支付关联id' ,
+    PRIMARY KEY (id)
+)  COMMENT = '会员推广记录';
+
+-- 支付详情添加额外参数字段
+alter table pay_details add extra varchar(100) comment '额外备用参数' after remark;
+
+-- 商品添加参与vip优惠标识 0否 1是
+alter table goods_shop add vip_promotion varchar(1) not null default 0 comment '是否参与vip优惠 0否 1是';
+
+-- 新增会员每单立减金额(元)、会员每天优惠单数(个)配置
+INSERT INTO `tcwm2.5`.`common_info`(`id`, `create_at`, `max`, `min`, `type`, `value`, `condition_from`) VALUES (444, '2025-11-13 14:47:44', NULL, '会员每单立减金额(元)', 444, '2', 'huiyuan');
+INSERT INTO `tcwm2.5`.`common_info`(`id`, `create_at`, `max`, `min`, `type`, `value`, `condition_from`) VALUES (445, '2025-11-13 14:47:44', NULL, '会员每天优惠单数(个)', 445, '2', 'huiyuan');
+
+-- 订单添加是否会员优惠字段 0否 1是
+alter table tb_order add vip_promotion varchar(1) not null default '0' comment '是否会员优惠 0否 1是';
+CREATE INDEX idx_user_vip_pay_time ON tb_order(user_id, vip_promotion, is_pay, pay_time);

+ 2 - 0
db/update_251128.sql

@@ -0,0 +1,2 @@
+
+INSERT INTO `tcwm2.5`.`common_info`(`id`, `create_at`, `max`, `min`, `type`, `value`, `condition_from`) VALUES (446, '2025-11-28 14:47:44', NULL, '会员立减最低金额(元)', 446, '10', 'huiyuan');

+ 2 - 0
db/update_251205.sql

@@ -0,0 +1,2 @@
+-- 创建订单表订单状态、支付时间、店铺ID索引 用于查询按店铺分组的订单状态、支付时间的统计信息
+CREATE INDEX idx_status_pay_time_shop ON tb_order(status, pay_time, shop_id);

+ 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);
 }

+ 2 - 0
src/main/java/com/sqx/modules/app/service/impl/UserMoneyDetailsServiceImpl.java

@@ -22,6 +22,7 @@ import com.sqx.modules.pay.controller.query.WalletDetailQuery;
 import com.sqx.modules.pay.vo.WalletDetailVO;
 import com.sqx.modules.utils.excel.ExcelData;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
@@ -38,6 +39,7 @@ public class UserMoneyDetailsServiceImpl extends ServiceImpl<UserMoneyDetailsDao
     @Autowired
     private CommonInfoDao commonInfoDao;
 
+    @Lazy
     @Autowired
     private TbIndentService tbIndentService;
 

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

@@ -12,6 +12,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.sqx.common.exception.SqxException;
 import com.sqx.common.utils.PageUtils;
 import com.sqx.common.utils.Result;
+import com.sqx.common.utils.SpringContextUtils;
 import com.sqx.modules.app.bo.UpdateMoneyBO;
 import com.sqx.modules.app.dao.UserDao;
 import com.sqx.modules.app.dao.UserMoneyDao;
@@ -32,6 +33,7 @@ import com.sqx.modules.pay.service.NewPayService;
 import com.sqx.modules.utils.SenInfoCheckUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
@@ -57,10 +59,9 @@ public class UserMoneyServiceImpl extends ServiceImpl<UserMoneyDao, UserMoney> i
     private CommonInfoService commonInfoService;
     @Autowired
     private RechargeRecordService rechargeRecordService;
+    @Lazy
     @Autowired
     private GoodsShopService goodsShopService;
-    @Autowired
-    private NewPayService newPayService;
 
     @Override
     public void updateMoney(int i, Long userId, double money) {
@@ -273,7 +274,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("用户钱包余额不足,扣减失败!");
             }
         }
 
@@ -322,6 +323,7 @@ public class UserMoneyServiceImpl extends ServiceImpl<UserMoneyDao, UserMoney> i
         UserMoneyDetails userMoneyDetails = updateUserMoney(updateMoneyBO);
 
         String traceNo = IdWorker.getTimeId();
+        NewPayService newPayService = SpringContextUtils.getBean(NewPayService.class);
         newPayService.wechatSplitBill(traceNo, mchId, amount);
 
         userMoneyDetails.setRemark(traceNo);

+ 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);
     }
 

+ 28 - 107
src/main/java/com/sqx/modules/datacentre/service/impl/DataCentreServiceImpl.java

@@ -1,7 +1,5 @@
 package com.sqx.modules.datacentre.service.impl;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.sqx.common.utils.DateUtils;
@@ -10,12 +8,10 @@ import com.sqx.common.utils.Result;
 import com.sqx.modules.app.dao.UserDao;
 import com.sqx.modules.app.entity.UserEntity;
 import com.sqx.modules.app.entity.UserMoneyDetails;
-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;
@@ -25,48 +21,34 @@ import com.sqx.modules.errand.entity.Feedback;
 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 UserService userService;
+    private final CommonInfoService commonInfoService;
+    private final TbCouponUserService tbCouponUserService;
 
     @Override
     public Result dataCentre() {
@@ -181,44 +163,6 @@ public class DataCentreServiceImpl implements DataCentreService {
     @Transactional
     @Override
     public Result checkCertification(Long userId, String checkCertification, String checkCertificationMessage) {
-        if("2".equals(checkCertification)){
-            UserEntity userEntity = userService.selectUserById(userId);
-            PayDetails payDetails = payDetailsDao.selectByOrderId(userEntity.getCheckNumber());
-            if(payDetails.getType().equals(4)){
-                String code=AliPayController.alipayRefund(payDetails);
-                if(StringUtils.isNotBlank(code)){
-                    log.error(code);
-                    JSONObject jsonObject = JSON.parseObject(code);
-                    JSONObject alipay_trade_refund_response = jsonObject.getJSONObject("alipay_trade_refund_response");
-                    String code1 = alipay_trade_refund_response.getString("code");
-                    if(!"10000".equals(code1)){
-                        return Result.error("退款失败!"+alipay_trade_refund_response.getString("sub_msg"));
-                    }
-                }else{
-                    return Result.error("退款失败!");
-                }
-            }else{
-                boolean flag= wxErrRiderService.wxRefund(payDetails);
-                if(!flag){
-                    return Result.error("退款失败,请联系客服!");
-                }
-            }
-            UserEntity userEntity1 = new UserEntity();
-            userEntity1.setUserId(userId);
-            userEntity1.setCheckCertification("2");
-            userEntity1.setCashDeposit(BigDecimal.valueOf(0));
-            userDao.updateById(userEntity1);
-            //退保证金的记录
-            UserMoneyDetails userMoneyDetails=new UserMoneyDetails();
-            userMoneyDetails.setUserId(userId);
-            userMoneyDetails.setTitle("退还保证金");
-            userMoneyDetails.setContent("实名认证审核未通过,保证金:"+payDetails.getMoney()+"元已退还至原支付途径");
-            userMoneyDetails.setType(2);
-            userMoneyDetails.setClassify(1);
-            userMoneyDetails.setMoney(BigDecimal.valueOf(payDetails.getMoney()));
-            userMoneyDetails.setCreateTime(new SimpleDateFormat("yyyy-MM-dd :HH:mm:ss").format(new Date()));
-            userMoneyDetailsService.save(userMoneyDetails);
-        }
         dataCentreDao.checkCertification(userId, checkCertification, checkCertificationMessage);
         return Result.success();
     }
@@ -383,46 +327,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 +384,4 @@ public class DataCentreServiceImpl implements DataCentreService {
         userDao.updateById(userEntity);
         return Result.success();
     }
-
-
 }

+ 17 - 12
src/main/java/com/sqx/modules/errand/service/impl/TbIndentServiceImpl.java

@@ -1558,8 +1558,9 @@ public class TbIndentServiceImpl extends ServiceImpl<TbIndentDao, TbIndent> impl
      */
     private void orderHandTakeoutOrder(TbIndent indentOrder, Long userId, OrderRiderDeliveryDTO deliveryDTO) {
         Long smsTemplateId = deliveryDTO.getSmsTemplateId();
+        // 短信模板为空,不发送短信
         if (ObjectUtil.isNull(smsTemplateId)) {
-            throw new SqxException("短信模板id不能为空");
+            return;
         }
 
         MyGlobalThreadPool.execute(() -> {
@@ -1856,13 +1857,23 @@ public class TbIndentServiceImpl extends ServiceImpl<TbIndentDao, TbIndent> impl
      */
     private void handTakeoutOrder(TbIndent indentOrder, Long userId, RiderDeliveryDTO deliveryDTO) {
         // 6表示跑腿订单处于已完成状态
-        //锁内用锁外数据进行判断会拿等待锁前的数据判断
+        // 锁内用锁外数据进行判断会拿等待锁前的数据判断
         if ("6".equals(indentOrder.getIndentState())) {
             return;
         }
+
+        // 订单新增送达图片
+        String imgStr = deliveryDTO.getImgs();
+        if (StrUtil.isNotBlank(imgStr)) {
+            TbOrder tbOrder = appOrderService.getById(indentOrder.getOrderId());
+            tbOrder.setDeliveryImgs(imgStr);
+            appOrderService.updateById(tbOrder);
+        }
+
+        // 短信模板为空,不发送短信
         Long smsTemplateId = deliveryDTO.getSmsTemplateId();
         if (ObjectUtil.isNull(smsTemplateId)) {
-            throw new SqxException("短信模板id不能为空");
+            return;
         }
 
         // 收货人手机
@@ -1880,15 +1891,9 @@ public class TbIndentServiceImpl extends ServiceImpl<TbIndentDao, TbIndent> impl
             return;
         }
 
-        // 订单新增送达图片
-        String imgs = deliveryDTO.getImgs();
-        if (StrUtil.isNotBlank(imgs)) {
-            TbOrder tbOrder = appOrderService.getById(indentOrder.getOrderId());
-            tbOrder.setDeliveryImgs(imgs);
-            appOrderService.updateById(tbOrder);
-        }
         TbIndentSmsSendLog tbIndentSmsSendLog=smsSendLogService.getSendSuccessByOrderId(indentOrder.getIndentId());
-//        有发送成功记录则不再发送
+
+        // 有发送成功记录则不再发送
         if(tbIndentSmsSendLog!=null){
             log.error("外卖跑腿订单【{}】短信已发送成功,短信id【{}】", deliveryDTO.getIndentNumber(),tbIndentSmsSendLog.getId());
             return;
@@ -2290,7 +2295,7 @@ public class TbIndentServiceImpl extends ServiceImpl<TbIndentDao, TbIndent> impl
                 goodsShopRelevancy1.setSales(goodsShopRelevancy.getSales() - goodsNum);//加销量
                 goodsShopRelevancy1.setInventory(goodsShopRelevancy.getInventory() + goodsNum);//减库存
                 goodsShopRelevancyDao.updateById(goodsShopRelevancy1);
-                goodsShopDao.updateShopSales(2, goodsNum, goodsShopRelevancy.getShopId());
+                // goodsShopDao.updateShopSales(2, goodsNum, goodsShopRelevancy.getShopId());
             }
         }
 

+ 41 - 9
src/main/java/com/sqx/modules/exportExecl/controller/ExportExeclController.java

@@ -1,21 +1,24 @@
 package com.sqx.modules.exportExecl.controller;
 
+import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.jcraft.jsch.SftpException;
 import com.sqx.common.utils.PageUtils;
 import com.sqx.common.utils.Result;
 import com.sqx.common.utils.SftpUtil;
+import com.sqx.common.utils.ShiroUtils;
 import com.sqx.modules.address.query.InsideAddressQuery;
 import com.sqx.modules.common.service.CommonInfoService;
 import com.sqx.modules.datacentre.query.RankListQuery;
 import com.sqx.modules.datacentre.query.ShopCenterQuery;
 import com.sqx.modules.exportExecl.dto.AccountEntry;
-import com.sqx.modules.exportExecl.mapper.ExportJobMapper;
 import com.sqx.modules.exportExecl.model.ExportJob;
 import com.sqx.modules.exportExecl.service.ExportJobService;
+import com.sqx.modules.member.query.SpecialFavorStudentQuery;
+import com.sqx.modules.member.query.VipPromoCodeQuery;
+import com.sqx.modules.member.query.VipPromoRecordQuery;
 import com.sqx.modules.order.dao.AppOrderDao;
 import com.sqx.modules.order.dto.OrderQueryDto;
 import com.sqx.modules.pay.controller.query.CashOutQueryDTO;
@@ -27,21 +30,15 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.poi.util.IOUtils;
-import org.jetbrains.annotations.NotNull;
 import org.springframework.util.StringUtils;
 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;
 
 import javax.servlet.http.HttpServletResponse;
-import javax.validation.Valid;
-import java.io.BufferedInputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.net.URLEncoder;
-import java.nio.file.Files;
-import java.nio.file.Paths;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.Date;
@@ -319,4 +316,39 @@ public class ExportExeclController {
         }
     }
 
+    @GetMapping("exportSpecialFavorStudent")
+    @ApiOperation("导出特殊照顾学生列表")
+    public Result exportSpecialFavorStudent(SpecialFavorStudentQuery queryDTO) {
+        Long userId = ShiroUtils.getUserId();
+        ExportJob exportJob = buildExportJob("特殊照顾学生列表.xlsx","specialFavorStudent", userId.toString(),
+                queryDTO.buildQueryConditionString(),
+                JSONUtil.toJsonStr(queryDTO));
+        exportJobService.save(exportJob);
+
+        return Result.success("特殊照顾学生列表导出成功");
+    }
+
+    @GetMapping("exportVipPromoCode")
+    @ApiOperation("导出会员推广码记录列表")
+    public Result exportVipPromoCode(VipPromoCodeQuery queryDTO) {
+        Long userId = ShiroUtils.getUserId();
+        ExportJob exportJob = buildExportJob("会员码广码记录列表.xlsx","vipPromoCode", userId.toString(),
+                queryDTO.buildQueryConditionString(),
+                JSONUtil.toJsonStr(queryDTO));
+        exportJobService.save(exportJob);
+
+        return Result.success("会员码广码记录列表导出成功");
+    }
+
+    @GetMapping("exportVipPromoRecord")
+    @ApiOperation("导出会员充值记录列表")
+    public Result exportVipPromoRecord(VipPromoRecordQuery queryDTO) {
+        Long userId = ShiroUtils.getUserId();
+        ExportJob exportJob = buildExportJob("会员充值记录列表.xlsx","vipPromoRecord", userId.toString(),
+                queryDTO.buildQueryConditionString(),
+                JSONUtil.toJsonStr(queryDTO));
+        exportJobService.save(exportJob);
+
+        return Result.success("会员充值记录列表导出成功");
+    }
 }

+ 2 - 2
src/main/java/com/sqx/modules/goods/controller/GoodsController.java

@@ -103,9 +103,9 @@ public class GoodsController extends AbstractController {
 
     @ApiOperation("查看所有商铺")
     @GetMapping(value = "selectAllShop")
-    public Result selectAllShop(Integer page, Integer limit, String shopName,String region,Integer isActivity){
+    public Result selectAllShop(Integer page, Integer limit, String shopName,String region,Integer isActivity, String vipPromotion){
 
-        return goodsService.selectAllShop(page, limit, shopName,region,isActivity);
+        return goodsService.selectAllShop(page, limit, shopName,region,isActivity,vipPromotion);
     }
 
     @ApiOperation("查看商品评价详情")

+ 6 - 2
src/main/java/com/sqx/modules/goods/controller/GoodsShopController.java

@@ -136,7 +136,11 @@ public class GoodsShopController {
         return Result.success();
     }
 
-
-
+    @ApiOperation(value = "修改商品是否参与vip优惠", notes = "vipPromotion 是否参与vip优惠活动,0否1是")
+    @PutMapping("/vip-promotion/{shopId}/{vipPromotion}")
+    public Result updateVipPromotion(@PathVariable Long shopId, @PathVariable String vipPromotion){
+        goodsShopService.updateVipPromotion(shopId, vipPromotion);
+        return Result.success();
+    }
 
 }

+ 5 - 18
src/main/java/com/sqx/modules/goods/controller/app/AppGoodsController.java

@@ -2,7 +2,6 @@ package com.sqx.modules.goods.controller.app;
 
 import cn.hutool.core.util.StrUtil;
 import com.sqx.common.utils.Result;
-import com.sqx.modules.app.annotation.Login;
 import com.sqx.modules.goods.dto.ShopQueryDTO;
 import com.sqx.modules.goods.entity.GoodsShop;
 import com.sqx.modules.goods.service.GoodsService;
@@ -11,7 +10,11 @@ import com.sqx.modules.shop.service.SearchHistoryService;
 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;
 
 @Api(tags={"用户端-商品"})
 @RestController
@@ -46,22 +49,6 @@ public class AppGoodsController {
         return goodsService.selectGoodsById(goodsId);
     }
 
-    /*@Login
-    @ApiOperation("获取该商品规格名")
-    @GetMapping(value = "selectSkuByGoodsId")
-    public Result selectSkuByGoodsId(Long goodsId){
-        //goodsService.selectSkuByGoodsId(goodsId)
-        return null;
-    }
-
-    @Login
-    @ApiOperation("获取该商品详细规格")
-    @GetMapping(value = "selectAttrByGoodsId")
-    public Result selectAttrByGoodsId(Long goodsId){
-        //goodsService.selectAttrByGoodsId(goodsId);
-        return null;
-    }*/
-
     @ApiOperation("条件筛选店铺")
     @GetMapping(value = "selectShop")
     public Result selectShop(ShopQueryDTO queryDTO){

+ 2 - 2
src/main/java/com/sqx/modules/goods/dao/GoodsShopDao.java

@@ -17,7 +17,7 @@ import java.util.List;
 @Mapper
 public interface GoodsShopDao extends BaseMapper<GoodsShop> {
 
-    IPage<GoodsShop> selectAllShop(@Param("pages") Page<GoodsShop> pages, @Param("shopName") String shopName, @Param("region") String region, @Param("isActivity") Integer isActivity);
+    IPage<GoodsShop> selectAllShop(@Param("pages") Page<GoodsShop> pages, @Param("shopName") String shopName, @Param("region") String region, @Param("isActivity") Integer isActivity, @Param("vipPromotion") String vipPromotion);
 
     IPage<GoodsShop> selectAllShopByAdmin(Page<GoodsShop> pages,@Param("shopName") String shopName,@Param("userId") Long userId);
 
@@ -45,7 +45,7 @@ public interface GoodsShopDao extends BaseMapper<GoodsShop> {
 
     IPage<GoodsShop> selectSearch(Page<GoodsShop> pages, String impotr, Double lng, Double lat, String city);
 
-    IPage<GoodsShop> selectShop(Page<GoodsShop> pages, Integer screen, Integer shopTypeId, Double lng, Double lat, String city, String impotr,Long activityId);
+    IPage<GoodsShop> selectShop(@Param("pages") Page<GoodsShop> pages, @Param("screen") Integer screen, @Param("shopTypeId") Integer shopTypeId, @Param("lng") Double lng, @Param("lat") Double lat, @Param("city") String city, @Param("impotr") String impotr, @Param("activityId") Long activityId, @Param("vipPromotion") String vipPromotion);
 
     IPage<Evaluate> selectEvaluate(Page<Evaluate> pages, Long shopId, String goodsId, Integer grade);
 

+ 6 - 0
src/main/java/com/sqx/modules/goods/dto/ShopQueryDTO.java

@@ -1,6 +1,8 @@
 package com.sqx.modules.goods.dto;
 
 import com.sqx.common.query.PageQuery;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 /**
@@ -10,6 +12,7 @@ import lombok.Data;
  * @date : 2024-08-28 13:45
  */
 @Data
+@ApiModel("app店铺查询参数")
 public class ShopQueryDTO extends PageQuery {
     private Long userId;
     /**
@@ -27,4 +30,7 @@ public class ShopQueryDTO extends PageQuery {
 
     private String impotr;
     private Long activityId;
+
+    @ApiModelProperty("是否参与vip优惠 0否 1是")
+    private String vipPromotion;
 }

+ 17 - 15
src/main/java/com/sqx/modules/goods/entity/GoodsShop.java

@@ -204,11 +204,20 @@ public class GoodsShop implements Serializable {
     @ApiModelProperty("商家佣金比例")
     private BigDecimal shopRate;
 
-   /* @ApiModelProperty("商户缴纳保证金订单号")
-    private Integer checkNumber;*/
-
+    @ApiModelProperty("排序")
     private Integer sort;
 
+    @ApiModelProperty("是否供应商 0是  1不是")
+    private Integer isSupplier;
+
+    @ApiModelProperty("消息配置  0开启  1关闭")
+    private Integer messageConfiguration;
+
+    @ApiModelProperty("是否隐藏 0不隐藏 1是隐藏")
+    private Integer isConceal;
+
+    @ApiModelProperty("是否参与vip优惠 0否 1是")
+    private String vipPromotion;
 
     @ApiModelProperty("距离")
     @TableField(exist = false)
@@ -218,7 +227,6 @@ public class GoodsShop implements Serializable {
     @TableField(exist = false)
     private Double errandTime;
 
-
     @ApiModelProperty("商铺类型名称")
     @TableField(exist = false)
     private String shopTypeName;
@@ -257,16 +265,10 @@ public class GoodsShop implements Serializable {
     @ApiModelProperty("参与活动列表")
     private List<ShopActivityVO>  shopActivityList;
 
+    @TableField(exist = false)
+    @ApiModelProperty("会员优惠描述")
+    private String vipPromotionDesc;
+
     public GoodsShop() {
     }
-
-
-    @ApiModelProperty("是否供应商 0是  1不是")
-    private Integer isSupplier;
-
-    @ApiModelProperty("消息配置  0开启  1关闭")
-    private Integer messageConfiguration;
-
-    @ApiModelProperty("是否隐藏 0不隐藏 1是隐藏")
-    private Integer isConceal;
-}
+}

+ 1 - 2
src/main/java/com/sqx/modules/goods/service/GoodsService.java

@@ -1,6 +1,5 @@
 package com.sqx.modules.goods.service;
 
-import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.sqx.common.utils.Result;
 import com.sqx.modules.goods.dto.ShopQueryDTO;
@@ -30,7 +29,7 @@ public interface GoodsService extends IService<Goods> {
 
     Result selectShortShop(double lng, double lat, String shopName, Integer page, Integer limit, Long shopId);
 
-    Result selectAllShop(Integer page, Integer limit, String shopName,String region,Integer isActivity);
+    Result selectAllShop(Integer page, Integer limit, String shopName, String region, Integer isActivity, String vipPromotion);
 
     Result selectAdminGoodsList(Integer page, Integer limit, Integer classifyId, String goodsName, Integer status, Long shopId, String shopName);
 

+ 14 - 0
src/main/java/com/sqx/modules/goods/service/GoodsShopService.java

@@ -8,6 +8,7 @@ import com.sqx.modules.goods.entity.GoodsShopRelevancy;
 import com.sqx.modules.goods.vo.GoodsShopVo;
 
 import java.util.List;
+import java.util.Map;
 
 public interface GoodsShopService extends IService<GoodsShop> {
 
@@ -63,4 +64,17 @@ public interface GoodsShopService extends IService<GoodsShop> {
      * @return 店铺列表
      */
     List<GoodsShop> getAllShops();
+
+    /**
+     * 修改商品是否参与vip优惠
+     * @param shopId 店铺id
+     * @param vipPromotion 是否参与vip优惠活动,0否1是
+     */
+    void updateVipPromotion(Long shopId, String vipPromotion);
+
+    /**
+     * 更新店铺销售数量
+     * @param shopSalesMap 店铺销售数量映射表,key为店铺id,value为销售数量
+     */
+    void updateShopSales(Map<Long, Integer> shopSalesMap);
 }

+ 22 - 7
src/main/java/com/sqx/modules/goods/service/impl/GoodsServiceImpl.java

@@ -452,9 +452,9 @@ public class GoodsServiceImpl extends ServiceImpl<GoodsDao, Goods> implements Go
     }
 
     @Override
-    public Result selectAllShop(Integer page, Integer limit, String shopName, String region, Integer isActivity) {
+    public Result selectAllShop(Integer page, Integer limit, String shopName, String region, Integer isActivity, String vipPromotion) {
         Page<GoodsShop> pages = new Page<>(page, limit);
-        PageUtils pageUtils = new PageUtils(goodsShopDao.selectAllShop(pages, shopName, region, isActivity));
+        PageUtils pageUtils = new PageUtils(goodsShopDao.selectAllShop(pages, shopName, region, isActivity,vipPromotion));
         return Result.success().put("data", pageUtils);
     }
 
@@ -534,12 +534,10 @@ public class GoodsServiceImpl extends ServiceImpl<GoodsDao, Goods> implements Go
             city = getCity(lat, lng);
         }
 
-        IPage<GoodsShop> goodsShopIPage = goodsShopDao.selectShop(pages, queryDTO.getScreen(), queryDTO.getShopTypeId(), lng, lat, city, queryDTO.getImpotr(), queryDTO.getActivityId());
+        IPage<GoodsShop> goodsShopIPage = goodsShopDao.selectShop(pages, queryDTO.getScreen(), queryDTO.getShopTypeId(), lng, lat, city, queryDTO.getImpotr(), queryDTO.getActivityId(), queryDTO.getVipPromotion());
         List<GoodsShop> records = goodsShopIPage.getRecords();
 
         if (CollUtil.isNotEmpty(records)) {
-            CommonInfo one1 = commonInfoService.findOne(292);
-            CommonInfo one2 = commonInfoService.findOne(293);
             List<Long> shopIdList = records.stream().map(GoodsShop::getShopId).collect(Collectors.toList());
             List<Goods> goodsList = goodsDao.selectGoodsBySalesAndGoodsNameAndShopIds(shopIdList, queryDTO.getImpotr());
             Map<Long, List<Goods>> goodsMap = goodsList.stream().collect(Collectors.groupingBy(Goods::getShopId));
@@ -551,13 +549,27 @@ public class GoodsServiceImpl extends ServiceImpl<GoodsDao, Goods> implements Go
             List<ShopActivityVO> shopActivityVOS = activityShopService.getActivityByShopIds(shopIdList);
             Map<Long, List<ShopActivityVO>> shopActivityMap = shopActivityVOS.stream().collect(Collectors.groupingBy(ShopActivityVO::getShopId));
 
+            CommonInfo vipPromotionMinAmount = commonInfoService.findOne(446);
+            CommonInfo vipPromotionAmount = commonInfoService.findOne(444);
+            String vipPromotionDesc = "满" + vipPromotionMinAmount.getValue() + "元减" + vipPromotionAmount.getValue() + "元";
+
+            CommonInfo one1 = commonInfoService.findOne(292);
+            CommonInfo one2 = commonInfoService.findOne(293);
             for (int a = 0; a < goodsShopIPage.getRecords().size(); a++) {
                 GoodsShop goodsShop = goodsShopIPage.getRecords().get(a);
                 Long shopId = goodsShop.getShopId();
                 goodsShop.setCouponList(couponMap.get(shopId));
 
-                // 设置活动信息
-                goodsShop.setShopActivityList(shopActivityMap.get(shopId));
+                // 会员专区查询
+                if (StrUtil.equals(queryDTO.getVipPromotion(), goodsShop.getVipPromotion())) {
+                    // 设置会员优惠描述
+                    goodsShop.setVipPromotionDesc(vipPromotionDesc);
+                }
+                // 非会员专区查询
+                else {
+                    // 设置活动信息
+                    goodsShop.setShopActivityList(shopActivityMap.get(shopId));
+                }
 
                 if (StringUtils.isNotEmpty(queryDTO.getImpotr())) {
                     goodsShop.setGoodsList(goodsMap.get(shopId));
@@ -611,6 +623,9 @@ public class GoodsServiceImpl extends ServiceImpl<GoodsDao, Goods> implements Go
         if (StrUtil.isNotBlank(queryDTO.getCity())) {
             keyBuilder.append("_city_").append(queryDTO.getCity());
         }
+        if (StrUtil.isNotBlank(queryDTO.getVipPromotion())) {
+            keyBuilder.append("_vip_").append(queryDTO.getVipPromotion());
+        }
 
         return keyBuilder.toString();
     }

+ 34 - 0
src/main/java/com/sqx/modules/goods/service/impl/GoodsShopServiceImpl.java

@@ -1,5 +1,7 @@
 package com.sqx.modules.goods.service.impl;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
@@ -62,6 +64,8 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 @Service
 public class GoodsShopServiceImpl extends ServiceImpl<GoodsShopDao, GoodsShop> implements GoodsShopService {
@@ -606,4 +610,34 @@ public class GoodsShopServiceImpl extends ServiceImpl<GoodsShopDao, GoodsShop> i
         queryWrapper.eq(GoodsShop::getStatus, 1);
         return list(queryWrapper);
     }
+
+    @Override
+    public void updateVipPromotion(Long shopId, String vipPromotion) {
+        GoodsShop goodsShop = new GoodsShop();
+        goodsShop.setShopId(shopId);
+        goodsShop.setVipPromotion(StrUtil.equals(vipPromotion,"1") ? "1" : "0");
+        baseMapper.updateById(goodsShop);
+    }
+
+    @Override
+    public void updateShopSales(Map<Long, Integer> shopSalesMap) {
+        LambdaQueryWrapper<GoodsShop> queryWrapper = Wrappers.lambdaQuery();
+        // 通过审核
+        queryWrapper.eq(GoodsShop::getStatus, 1);
+        // 已上架
+        queryWrapper.eq(GoodsShop::getPutawayFlag, 0);
+        // 未被封禁
+        queryWrapper.eq(GoodsShop::getBannedFlag, 0);
+        List<GoodsShop> goodsShops = list(queryWrapper);
+        if (CollUtil.isNotEmpty(goodsShops)) {
+            List<GoodsShop> newGoodsShops = goodsShops.stream().map(e -> {
+                GoodsShop goodsShop = new GoodsShop();
+                goodsShop.setShopId(e.getShopId());
+                goodsShop.setShopSales(shopSalesMap.getOrDefault(e.getShopId(), 0));
+                return goodsShop;
+            }).collect(Collectors.toList());
+
+            updateBatchById(newGoodsShops);
+        }
+    }
 }

+ 74 - 0
src/main/java/com/sqx/modules/member/controller/SpecialFavorStudentController.java

@@ -0,0 +1,74 @@
+package com.sqx.modules.member.controller;
+
+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 org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+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 javax.validation.Valid;
+import java.util.Arrays;
+
+@Api(tags = "特殊照顾学生")
+@RestController
+@RequestMapping("admin/special-favor-student")
+@RequiredArgsConstructor
+public class SpecialFavorStudentController{
+    
+    private final SpecialFavorStudentService specialFavorStudentService;
+
+    @GetMapping("pages")
+    @ApiOperation("分页查询特殊照顾学生列表")
+    public Result pages(@Valid SpecialFavorStudentQuery query) {
+        return Result.success().put("data", specialFavorStudentService.pages(query));
+    }
+
+    @PostMapping
+    @ApiOperation("添加特殊照顾学生")
+    public Result add(@RequestBody @Valid SpecialFavorStudentDTO dto) {
+        specialFavorStudentService.save(dto);
+        return Result.success();
+    }
+
+    @PutMapping
+    @ApiOperation("修改特殊照顾学生")
+    public Result update(@RequestBody @Valid SpecialFavorStudentDTO dto) {
+        if (ObjectUtil.isNull(dto.getId())) {
+            throw new SqxException("id不能为空");
+        }
+        specialFavorStudentService.update(dto);
+        return Result.success();
+    }
+
+    @DeleteMapping
+    @ApiOperation("删除特殊照顾学生")
+    public Result delete(@RequestBody @ApiParam("id的集合") Long[] ids) {
+        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();
+    }
+}

+ 81 - 0
src/main/java/com/sqx/modules/member/controller/VipPromoCodeController.java

@@ -0,0 +1,81 @@
+package com.sqx.modules.member.controller;
+
+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 org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+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 javax.validation.Valid;
+import java.util.Arrays;
+
+@Api(tags = "会员推广管理")
+@RestController
+@RequestMapping("admin/vip-promo")
+@RequiredArgsConstructor
+public class VipPromoCodeController{
+    
+    private final VipPromoCodeService vipPromoCodeService;
+    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 VipPromoCodeDTO dto) {
+        vipPromoCodeService.save(dto);
+        return Result.success();
+    }
+
+    @PutMapping
+    @ApiOperation("修改会员推广码")
+    public Result update(@RequestBody @Valid VipPromoCodeDTO dto) {
+        if (ObjectUtil.isNull(dto.getId())) {
+            throw new SqxException("id不能为空");
+        }
+        vipPromoCodeService.update(dto);
+        return Result.success();
+    }
+
+    @DeleteMapping
+    @ApiOperation("删除会员推广码")
+    public Result delete(@RequestBody @ApiParam("id的集合") Long[] ids) {
+        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);
+    }
+}

+ 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();
+    }
+}

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

@@ -0,0 +1,52 @@
+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.dto.CanReduceQueryDTO;
+import com.sqx.modules.member.service.VipService;
+import com.sqx.modules.member.vo.VipReduceVO;
+import com.sqx.modules.pay.vo.PayTransactionsVO;
+import icu.xuyijie.secureapi.annotation.DecryptParam;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+/**
+ * @author codingliang
+ * @date 2025-11-12
+ */
+@RestController
+@RequestMapping(value = "app/vip")
+@RequiredArgsConstructor
+public class AppVipController {
+
+    private final VipService vipService;
+
+    @Login
+    @ApiOperation(value = "获取用户是否可以会员立减")
+    @GetMapping(value = "get-can-reduce")
+    public Result getCanReduce(@RequestAttribute("userId") Long userId, @Valid CanReduceQueryDTO canReduceQueryDTO){
+        VipReduceVO vipReduceVO = vipService.getVipReduceInfo(userId, canReduceQueryDTO);
+        return Result.success().put("data", vipReduceVO);
+    }
+
+    @Login
+    @ApiOperation(value = "获取购买vip支付参数", notes = "promoCode 为推荐码")
+    @GetMapping(value = "get-pay-param")
+    public Result getPayParam(@RequestAttribute("userId") Long userId, @DecryptParam(value = "promoCode") 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, @DecryptParam(value = "promoCode") String promoCode){
+        vipService.balancePay(userId, promoCode);
+        return Result.success();
+    }
+}

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

@@ -0,0 +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);
+}

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

@@ -0,0 +1,12 @@
+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);
+}

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

@@ -0,0 +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);
+}

+ 27 - 0
src/main/java/com/sqx/modules/member/dto/CanReduceQueryDTO.java

@@ -0,0 +1,27 @@
+package com.sqx.modules.member.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+
+/**
+ * 查询是否可以减免参数
+ * @author codingliang
+ * @date 2025-11-30
+ */
+@Data
+public class CanReduceQueryDTO {
+
+    /**
+     * 店铺id
+     */
+    @NotNull(message = "店铺id不能为空")
+    private Long shopId;
+
+    /**
+     * 订单原始价格
+     */
+    @NotNull(message = "订单原始价格不能为空")
+    private BigDecimal originalPrice;
+}

+ 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 String 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;
+}

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

@@ -0,0 +1,63 @@
+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;
+
+import java.io.Serializable;
+import java.util.Date;
+
+ /**
+ * 特殊照顾学生
+ * @author : codingliang
+ * @date : 2025-11-11
+ */
+@Data
+@TableName("special_favor_student")
+public class SpecialFavorStudent implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 删除标识;0未删除、1已删除
+     */
+    @TableLogic(value = "0", delval = "1")
+    private String delFlag;
+    
+    /**
+     * 真实姓名
+     */
+    private String name;
+    
+    /**
+     * 学号
+     */
+    private String studentNumber;
+    
+    /**
+     * 验证状态;0未认证、1已认证
+     */
+    private String verifyStatus;
+    
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+    
+    /**
+     * 验证用户id
+     */
+    private Long verifyUserId;
+    
+    /**
+     * 最后验证时间
+     */
+    private Date lastVerifyTime;
+}

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

@@ -0,0 +1,63 @@
+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;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+* 会员推广码
+* @author : codingliang
+* @date : 2025-11-11
+*/
+@Data
+@TableName("vip_promo_code")
+public class VipPromoCode implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 删除标识;0未删除、1已删除
+     */
+    @TableLogic(value = "0", delval = "1")
+    private String delFlag;
+
+    /**
+     * 推广人名称
+     */
+    private String userName;
+
+    /**
+     * 推广码
+     */
+    private String promoCode;
+
+    /**
+     * 推广人身份证号码
+     */
+    private String idCard;
+
+    /**
+     * 推广人手机号码
+     */
+    private String phone;
+
+    /**
+     * 推广人次
+     */
+    private Integer promoCount;
+
+     /**
+      * 创建时间
+      */
+     private Date createTime;
+}

+ 73 - 0
src/main/java/com/sqx/modules/member/entity/VipPromoRecord.java

@@ -0,0 +1,73 @@
+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;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+* 会员推广记录
+* @author : codingliang
+* @date : 2025-11-11
+*/
+@Data
+@TableName("vip_promo_record")
+public class VipPromoRecord implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 删除标识;0未删除、1已删除
+     */
+    @TableLogic(value = "0", delval = "1")
+    private String delFlag;
+
+    /**
+     * 推广码id
+     */
+    private Long promoCodeId;
+
+    /**
+     * 用户id
+     */
+    private Long userId;
+    
+    /**
+     * 用户名称
+     */
+    private String userName;
+    
+    /**
+     * 用户手机号
+     */
+    private String userPhone;
+    
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 支付类型;1微信支付、2余额支付、3暖心照顾
+     */
+    private String payType;
+    
+    /**
+     * 支付金额
+     */
+    private Double payAmount;
+    
+    /**
+     * 支付关联id
+     */
+    private String payRelId;
+}

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

@@ -0,0 +1,82 @@
+package com.sqx.modules.member.query;
+
+import cn.hutool.core.util.StrUtil;
+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.text.SimpleDateFormat;
+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;
+
+    /**
+     * 拼接查询条件字符串
+     * @return 拼接后的查询条件字符串
+     */
+    public String buildQueryConditionString() {
+        StringBuilder conditionBuilder = new StringBuilder();
+
+        // 拼接真实姓名条件
+        if (StrUtil.isNotBlank(this.name)) {
+            conditionBuilder.append("真实姓名: ").append(this.name).append(",");
+        }
+
+        // 拼接学号条件
+        if (StrUtil.isNotBlank(this.studentNumber)) {
+            conditionBuilder.append("学号: ").append(this.studentNumber).append(",");
+        }
+
+        // 拼接验证状态条件(转换为中文)
+        if (StrUtil.isNotBlank(this.verifyStatus)) {
+            String verifyStatusText = "1".equals(this.verifyStatus) ? "已验证" : "未验证";
+            conditionBuilder.append("验证状态: ").append(verifyStatusText).append(",");
+        }
+
+        // 拼接认证开始时间条件
+        if (this.startTime != null) {
+            conditionBuilder.append("认证开始时间: ")
+                    .append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.startTime))
+                    .append(",");
+        }
+
+        // 拼接认证结束时间条件
+        if (this.endTime != null) {
+            conditionBuilder.append("认证结束时间: ")
+                    .append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.endTime))
+                    .append(",");
+        }
+
+        // 拼接分页信息
+        conditionBuilder.append("当前页码: ").append(getPage())
+                .append(",每页条数: ").append(getLimit());
+
+        return conditionBuilder.toString();
+    }
+}

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

@@ -0,0 +1,50 @@
+package com.sqx.modules.member.query;
+
+import cn.hutool.core.util.StrUtil;
+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;
+
+    public String buildQueryConditionString() {
+        StringBuilder sb = new StringBuilder();
+
+        // 会员用户名
+        if (StrUtil.isNotBlank(userName)) {
+            sb.append("会员用户名:").append(userName).append(", ");
+        }
+
+        // 推广码
+        if (StrUtil.isNotBlank(promoCode)) {
+            sb.append("推广码:").append(promoCode).append(", ");
+        }
+
+        // 推广人手机号码
+        if (StrUtil.isNotBlank(phone)) {
+            sb.append("推广人手机号码:").append(phone).append(", ");
+        }
+
+        sb.append("页码:").append(getPage()).append(", ");
+        sb.append("每页条数:").append(getLimit());
+
+        return sb.toString();
+    }
+}

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

@@ -0,0 +1,105 @@
+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.text.SimpleDateFormat;
+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 = "是否暖心照顾 1是、0否")
+    private String specialFlag;
+    
+    @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;
+
+    /**
+     * 拼接查询条件字符串
+     * @return 拼接后的查询条件字符串
+     */
+    public String buildQueryConditionString() {
+        StringBuilder sb = new StringBuilder();
+
+        // 推广码id
+        if (getPromoCodeId() != null) {
+            sb.append("推广码id:").append(getPromoCodeId()).append(", ");
+        }
+
+        // 用户手机号
+        if (getUserPhone() != null && !getUserPhone().trim().isEmpty()) {
+            sb.append("用户手机号:").append(getUserPhone()).append(", ");
+        }
+
+        // 是否暖心照顾
+        if (getSpecialFlag() != null && !getSpecialFlag().trim().isEmpty()) {
+            String specialFlagText = "";
+            switch (getSpecialFlag()) {
+                case "1":
+                    specialFlagText = "是";
+                    break;
+                case "0":
+                    specialFlagText = "否";
+                    break;
+                default:
+                    specialFlagText = getSpecialFlag();
+            }
+            sb.append("是否暖心照顾:").append(specialFlagText).append(", ");
+        }
+
+        // 支付类型
+        // if (getPayType() != null && !getPayType().trim().isEmpty()) {
+        //     String payTypeText = "";
+        //     switch (getPayType()) {
+        //         case "1":
+        //             payTypeText = "微信支付";
+        //             break;
+        //         case "2":
+        //             payTypeText = "余额支付";
+        //             break;
+        //         case "3":
+        //             payTypeText = "暖心照顾";
+        //             break;
+        //         default:
+        //             payTypeText = getPayType();
+        //     }
+        //     sb.append("支付类型:").append(payTypeText).append(", ");
+        // }
+
+        // 开始时间
+        if (getStartTime() != null) {
+            sb.append("开始时间:").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(getStartTime())).append(", ");
+        }
+
+        // 结束时间
+        if (getEndTime() != null) {
+            sb.append("结束时间:").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(getEndTime())).append(", ");
+        }
+
+        // 分页信息
+        sb.append("页码:").append(getPage()).append(", ");
+        sb.append("每页条数:").append(getLimit());
+
+        return sb.toString();
+    }
+}

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

@@ -0,0 +1,27 @@
+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);
+
+    /**
+     * 特殊照顾学生认证审核
+     * @param userId 登录用户id
+     * @param dto    特殊照顾学生认证审核参数
+     */
+    void verify(Long userId, SpecialFavorStudentVerifyDTO dto);
+}

+ 36 - 0
src/main/java/com/sqx/modules/member/service/VipPromoCodeService.java

@@ -0,0 +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);
+}

+ 18 - 0
src/main/java/com/sqx/modules/member/service/VipPromoRecordService.java

@@ -0,0 +1,18 @@
+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);
+
+    /**
+     * 新增会员推广记录
+     * @param recordDTO 会员推广记录dto
+     */
+    void add(VipPromoRecordDTO recordDTO);
+}

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

@@ -0,0 +1,40 @@
+package com.sqx.modules.member.service;
+
+import com.sqx.modules.member.dto.CanReduceQueryDTO;
+import com.sqx.modules.member.vo.VipReduceVO;
+import com.sqx.modules.pay.entity.PayDetails;
+import com.sqx.modules.pay.vo.PayTransactionsVO;
+
+import javax.validation.Valid;
+
+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);
+
+     /**
+      * 获取用户是否可以会员立减
+      * @param userId 用户id
+      * @param canReduceQueryDTO 查询是否可以减免参数
+      * @return vip 立减信息
+      */
+    VipReduceVO getVipReduceInfo(Long userId, @Valid CanReduceQueryDTO canReduceQueryDTO);
+}

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

@@ -0,0 +1,152 @@
+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 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
+    @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())) {
+            throw 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() + "]已存在");
+        }
+    }
+}

+ 97 - 0
src/main/java/com/sqx/modules/member/service/impl/VipPromoCodeServiceImpl.java

@@ -0,0 +1,97 @@
+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.app.service.UserService;
+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 com.sqx.modules.member.vo.VipPromoCodeVO;
+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 {
+
+    private final UserService userService;
+
+    @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<VipPromoCodeVO> vos = page.getRecords().stream().map(e -> {
+            VipPromoCodeVO vo = new VipPromoCodeVO();
+            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("推广码已存在");
+        }
+
+        if (ObjectUtil.isNull(userService.queryByPhone(dto.getPhone()))) {
+            throw new SqxException("手机号码不存在,请确认!");
+        }
+    }
+}

+ 55 - 0
src/main/java/com/sqx/modules/member/service/impl/VipPromoRecordServiceImpl.java

@@ -0,0 +1,55 @@
+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 com.sqx.modules.member.vo.VipPromoRecordVO;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+
+@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
+    @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.isNotNull(promoCode)) {
+                record.setPromoCodeId(promoCode.getId());
+
+                vipPromoCodeService.increasePromoCount(promoCode.getId());
+            }
+        }
+
+        save(record);
+    }
+}

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

@@ -0,0 +1,227 @@
+package com.sqx.modules.member.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.sqx.common.exception.SqxException;
+import com.sqx.common.utils.Constant;
+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.goods.entity.GoodsShop;
+import com.sqx.modules.goods.service.GoodsShopService;
+import com.sqx.modules.member.dto.CanReduceQueryDTO;
+import com.sqx.modules.member.dto.VipPromoRecordDTO;
+import com.sqx.modules.member.entity.VipPromoCode;
+import com.sqx.modules.member.service.VipPromoCodeService;
+import com.sqx.modules.member.service.VipPromoRecordService;
+import com.sqx.modules.member.service.VipService;
+import com.sqx.modules.member.vo.VipReduceVO;
+import com.sqx.modules.order.entity.TbOrder;
+import com.sqx.modules.order.service.AppOrderService;
+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 javax.validation.Valid;
+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 VipPromoCodeService vipPromoCodeService;
+    private final VipPromoRecordService vipPromoRecordService;
+    private final UserMoneyService userMoneyService;
+    private final GoodsShopService goodsShopService;
+    private final AppOrderService appOrderService;
+
+
+    @Override
+    public PayTransactionsVO getPayParam(Long userId, String promoCode) {
+        UserEntity user = userService.getById(userId);
+        beforePay(user, promoCode);
+
+        // 查询会员价格
+        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.VIP_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);
+        beforePay(user, promoCode);
+
+        // 会员价格
+        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 beforePay(UserEntity user, String promoCode) {
+        if(ObjectUtil.isNotNull(user.getIsVip()) && user.getIsVip() == 1 && VipExpirationUtil.isVipValid(user.getVipExpirationTime())) {
+            throw new SqxException("已经是会员,不能重复购买!");
+        }
+
+        VipPromoCode vipPromoCode = vipPromoCodeService.getByCode(promoCode);
+        if (ObjectUtil.isNull(vipPromoCode)) {
+            throw new SqxException("无效的推广码!");
+        }
+    }
+
+    @Override
+    public VipReduceVO getVipReduceInfo(Long userId, @Valid CanReduceQueryDTO canReduceQueryDTO) {
+        // 查询订单原始金额是否达到会员立减金额
+        CommonInfo vipPromotionMinAmount = commonInfoService.findOne(446);
+        if (canReduceQueryDTO.getOriginalPrice().compareTo(new BigDecimal(vipPromotionMinAmount.getValue())) < 0) {
+            return VipReduceVO.builder().reduceAmount(BigDecimal.ZERO)
+                    .canReduceFlag(Constant.NO)
+                    .vipReduceDesc("订单金额未达到会员立减金额")
+                    .build();
+        }
+
+        // 查询店铺是否参与会员立减
+        GoodsShop goodsShop = goodsShopService.getById(canReduceQueryDTO.getShopId());
+        if (ObjectUtil.isNull(goodsShop)) {
+            throw new SqxException("无效的店铺id!");
+        }
+        String goodsShopVipPromotion = goodsShop.getVipPromotion();
+        if (!StrUtil.equals(goodsShopVipPromotion, Constant.YES)) {
+            return VipReduceVO.builder()
+                    .reduceAmount(BigDecimal.ZERO)
+                    .canReduceFlag(Constant.NO)
+                    .vipReduceDesc("店铺未参与会员立减")
+                    .build();
+        }
+
+        // 查询用户是否为会员
+        UserEntity user = userService.getById(userId);
+        String vipFlag = ObjectUtil.isNotNull(user.getIsVip()) && user.getIsVip() == 1 && VipExpirationUtil.isVipValid(user.getVipExpirationTime())
+                ? Constant.YES : Constant.NO;
+        if (!StrUtil.equals(vipFlag, Constant.YES)) {
+            return VipReduceVO.builder().reduceAmount(BigDecimal.ZERO)
+                    .canReduceFlag(Constant.NO)
+                    .vipReduceDesc("用户非会员,不能立减")
+                    .build();
+        }
+
+        // 会员每天限制优惠单数(单)
+        int count = appOrderService.getCurDayVipPromotionByUserCount(userId);
+        CommonInfo vipPromotionCount = commonInfoService.findOne(445);
+        if (count >= Integer.parseInt(vipPromotionCount.getValue())) {
+            return VipReduceVO.builder().reduceAmount(BigDecimal.ZERO)
+                    .canReduceFlag(Constant.NO)
+                    .vipReduceDesc("会员当日优惠单数已达上限")
+                    .build();
+        }
+
+        // 会员每单立减金额(元)
+        CommonInfo vipPromotionAmount = commonInfoService.findOne(444);
+        BigDecimal vipPromotionAmountDecimal = new BigDecimal(vipPromotionAmount.getValue());
+        return  VipReduceVO.builder().reduceAmount(vipPromotionAmountDecimal)
+                .canReduceFlag(Constant.YES)
+                .vipReduceDesc("会员每单立减金额" + vipPromotionAmountDecimal + "元")
+                .build();
+    }
+
+    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);
+        }
+    }
+}

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

@@ -0,0 +1,50 @@
+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 = "真实姓名")
+   private String name;
+
+   @ApiModelProperty(value = "学号")
+   private String studentNumber;
+
+   @ApiModelProperty(value = "验证状态;0未认证、1已认证")
+   private String verifyStatus;
+
+   @ApiModelProperty(value = "创建时间")
+   @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+   private Date createTime;
+
+   @ApiModelProperty(value = "验证用户id")
+   private Long verifyUserId;
+
+   @ApiModelProperty(value = "验证用户名")
+   private String verifyUserName;
+
+   @ApiModelProperty(value = "验证用户手机号")
+   private String verifyUserPhone;
+
+   @ApiModelProperty(value = "最后验证时间")
+   @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+   private Date lastVerifyTime;
+}

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

@@ -0,0 +1,42 @@
+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 = "会员推广码")
+public class VipPromoCodeVO 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;
+
+    @ApiModelProperty(value = "推广人次")
+    private Integer promoCount;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+}

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

@@ -0,0 +1,51 @@
+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 = "推广码")
+    private String promoCode;
+
+    @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;
+}

+ 26 - 0
src/main/java/com/sqx/modules/member/vo/VipReduceVO.java

@@ -0,0 +1,26 @@
+package com.sqx.modules.member.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * vip 立减信息
+ * @author codingliang
+ * @date 2025-11-16
+ */
+@Data
+@Builder
+public class VipReduceVO {
+
+    @ApiModelProperty(value = "用户是否可以会员立减")
+    private String canReduceFlag;
+
+    @ApiModelProperty(value = "立减金额")
+    private BigDecimal reduceAmount;
+
+    @ApiModelProperty(value = "立减描述")
+    private String vipReduceDesc;
+}

+ 7 - 0
src/main/java/com/sqx/modules/order/dao/AppOrderDao.java

@@ -15,6 +15,7 @@ import org.apache.ibatis.annotations.Param;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 
 @Mapper
 public interface AppOrderDao extends BaseMapper<TbOrder> {
@@ -114,4 +115,10 @@ public interface AppOrderDao extends BaseMapper<TbOrder> {
     IPage<GoodsSkuSalesCountVO> getGoodsSkuSalesCount(Page<GoodsSkuSalesCountVO> pages, GoodsSkuQuery query);
 
     List<TbOrder> temp();
+
+    /**
+     * 统计近近30天每个店铺的已完成的订单数量
+     * @return 店铺id-订单数量映射列表
+     */
+    List<Map<String, Object>> countFinishByShopIdWithLast30Days();
 }

+ 13 - 10
src/main/java/com/sqx/modules/order/entity/TbOrder.java

@@ -66,7 +66,7 @@ public class TbOrder implements Serializable {
     @ApiModelProperty("订单类型 1上门 2外卖")
     private Integer orderType;
 
-    @ApiModelProperty("订单类型拓展 1上门 2骑手配送 3商家配送")
+    @ApiModelProperty("订单类型拓展 0上门堂食 1上门打包 2骑手配送 3商家配送")
     private Integer orderTypeExtra;
 
     @ApiModelProperty("订单父id")
@@ -135,6 +135,18 @@ public class TbOrder implements Serializable {
     @ApiModelProperty("打印状态  已打印:1 未打印:0")
     private Integer isPrint;
 
+    @ApiModelProperty("超时时间")
+    private Long timeOut;
+
+    @ApiModelProperty("身份证号")
+    private String idCard;
+
+    @ApiModelProperty("快跑者回调订单号")
+    private String tradeNo;
+
+    @ApiModelProperty("是否会员优惠 0否 1是")
+    private String vipPromotion;
+
     @ApiModelProperty("商铺名")
     @TableField(exist = false)
     private String shopName;
@@ -267,9 +279,6 @@ public class TbOrder implements Serializable {
     @ApiModelProperty("站点名称")
     private String stationName;
 
-    @ApiModelProperty("超时时间")
-    private Long timeOut;
-
     @ApiModelProperty("交易号")
     @TableField(exist = false)
     private String transactionId;
@@ -291,10 +300,4 @@ public class TbOrder implements Serializable {
     @ApiModelProperty("完成订单时间")
     @TableField(exist = false)
     private String finishTime;
-
-    @ApiModelProperty("身份证号")
-    private String idCard;
-
-    @ApiModelProperty("快跑者回调订单号")
-    private String tradeNo;
 }

+ 14 - 0
src/main/java/com/sqx/modules/order/service/AppOrderService.java

@@ -16,6 +16,7 @@ import com.sqx.modules.utils.excel.ExcelData;
 
 import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 
 public interface AppOrderService extends IService<TbOrder> {
 
@@ -198,4 +199,17 @@ public interface AppOrderService extends IService<TbOrder> {
      * @param query 查询参数
      */
     void exportGoodsSkuSalesCount(GoodsSkuQuery query);
+
+     /**
+     * 查询用户当天会员优惠单数量
+     * @param userId 用户id
+     * @return 会员优惠单数量
+     */
+    int getCurDayVipPromotionByUserCount(Long userId);
+
+     /**
+      * 统计最近30天内每个店铺的订单完成数量
+      * @return 店铺id-订单完成数量映射
+      */
+    Map<Long, Integer> countFinishByShopIdWithLast30Days();
 }

+ 110 - 19
src/main/java/com/sqx/modules/order/service/impl/AppAppOrderServiceImpl.java

@@ -103,6 +103,7 @@ import com.sqx.modules.shop.service.ShopTypeService;
 import com.sqx.modules.sys.entity.SysUserEntity;
 import com.sqx.modules.sys.service.SysUserService;
 import com.sqx.modules.utils.SenInfoCheckUtil;
+import com.sqx.modules.utils.VipExpirationUtil;
 import com.sqx.modules.utils.excel.EasyExcelUtil;
 import com.sqx.modules.utils.excel.ExcelData;
 import com.sqx.modules.utils.fieYun.FeiYunUtils;
@@ -134,6 +135,7 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 @Service
 @Slf4j
@@ -150,8 +152,6 @@ public class AppAppOrderServiceImpl extends ServiceImpl<AppOrderDao, TbOrder> im
     @Autowired
     private UserService userService;
     @Autowired
-    private AppOrderService orderService;
-    @Autowired
     private PayDetailsService payDetailsService;
     @Autowired
     private OrderGoodsDao orderGoodsDao;
@@ -499,9 +499,6 @@ public class AppAppOrderServiceImpl extends ServiceImpl<AppOrderDao, TbOrder> im
         // 给支付的时候商品描述用
         parentOrder.setShopName(goodsShop.getShopName());
 
-        // // 设置订单类型
-        // parentOrder.setOrderType(payOrderDTO.getOrderType());
-
         // 校验订单类型
         checkOrderType(parentOrder, payOrderDTO, goodsShop);
 
@@ -514,6 +511,9 @@ public class AppAppOrderServiceImpl extends ServiceImpl<AppOrderDao, TbOrder> im
         // 校验地址、配送费信息
         checkAddress(parentOrder, payOrderDTO.getAddressId(), goodsShop);
 
+        // 校验是否参与vip优惠
+        checkVipPromotion(parentOrder, goodsShop, userId);
+
         // 检查活动优惠信息
         checkActivity(payOrderDTO.getActivityId(), parentOrder);
 
@@ -562,8 +562,10 @@ public class AppAppOrderServiceImpl extends ServiceImpl<AppOrderDao, TbOrder> im
             } else {
                 order.setOrderTypeExtra(2);
             }
-        } else {
-            order.setOrderTypeExtra(1);
+        }
+        // 其他 到店订单
+        else {
+            order.setOrderTypeExtra(payOrderDTO.getPackType());
         }
 
         // 骑手配送订单
@@ -601,6 +603,11 @@ public class AppAppOrderServiceImpl extends ServiceImpl<AppOrderDao, TbOrder> im
      * @param parentOrder 订单信息
      */
     private void checkActivity(Long activityId, TbOrder parentOrder) {
+        // 参加了会员优惠,则不参与活动优惠
+        if (StrUtil.equals(parentOrder.getVipPromotion(), Constant.YES)) {
+            return;
+        }
+
         OrderSuitActivityVO orderBestActivity = null;
 
         // 获取当前订单适用的活动列表
@@ -651,7 +658,7 @@ public class AppAppOrderServiceImpl extends ServiceImpl<AppOrderDao, TbOrder> im
             //减库存
             goodsShopRelevancy1.setInventory(goodsShopRelevancy.getInventory() - goodsNum);
             goodsShopRelevancyDao.updateById(goodsShopRelevancy1);
-            goodsShopDao.updateShopSales(1, goodsNum, goodsShopRelevancy.getShopId());
+            // goodsShopDao.updateShopSales(1, goodsNum, goodsShopRelevancy.getShopId());
         }
     }
 
@@ -1220,7 +1227,12 @@ public class AppAppOrderServiceImpl extends ServiceImpl<AppOrderDao, TbOrder> im
                 OrderGoods orderGoods = orderGoodsList.get(a);
                 BigDecimal goodsNum = new BigDecimal(orderGoods.getGoodsNum());
                 BigDecimal goodsPrice = orderGoods.getGoodsPrice();
-                BigDecimal goodsPacks = orderGoods.getGoodsPack();
+
+                // orderTypeExtra不为0时需要计算打包费,为0时不计算打包费
+                BigDecimal goodsPacks = BigDecimal.valueOf(0);
+                if (parentOrder.getOrderTypeExtra() != 0) {
+                    goodsPacks = orderGoods.getGoodsPack();
+                }
 
                 // 当前子订单总打包费
                 BigDecimal sumGoodsPack = goodsNum.multiply(goodsPacks);
@@ -2007,7 +2019,7 @@ public class AppAppOrderServiceImpl extends ServiceImpl<AppOrderDao, TbOrder> im
                 goodsShopRelevancy1.setSales(goodsShopRelevancy.getSales() - goodsNum);//加销量
                 goodsShopRelevancy1.setInventory(goodsShopRelevancy.getInventory() + goodsNum);//减库存
                 goodsShopRelevancyDao.updateById(goodsShopRelevancy1);
-                goodsShopDao.updateShopSales(2, goodsNum, goodsShopRelevancy.getShopId());
+                // goodsShopDao.updateShopSales(2, goodsNum, goodsShopRelevancy.getShopId());
             }
 
             return Result.success("取消订单成功!");
@@ -2228,14 +2240,16 @@ public class AppAppOrderServiceImpl extends ServiceImpl<AppOrderDao, TbOrder> im
         contentBuffer.append("平台服务费:").append(pingRate.setScale(2, BigDecimal.ROUND_DOWN)).append(",");
         contentBuffer.append("短信服务费:").append(smsSendMoney.setScale(2, BigDecimal.ROUND_DOWN)).append(",");
         if (ObjectUtils.isNotEmpty(tbOrder.getOrderTypeExtra())&&tbOrder.getOrderTypeExtra()==2) {
-            //        配送费大于等于1.5就不需要骑手额外配送费
-//            if (tbOrder.getErrandMoney().doubleValue()<1.5) {
-                contentBuffer.append("骑手额外配送费:").append(rider.setScale(2, BigDecimal.ROUND_DOWN)).append(",");
-//            }
+            contentBuffer.append("骑手额外配送费:").append(rider.setScale(2, BigDecimal.ROUND_DOWN)).append(",");
         }
-        contentBuffer.append("到账金额:").append(shopMoney.setScale(2, BigDecimal.ROUND_DOWN));
 
+        // 会员优惠
+        if (StrUtil.equals(tbOrder.getVipPromotion(), Constant.YES)) {
+            CommonInfo commonInfo = commonInfoService.findOne(444);
+            contentBuffer.append("会员优惠金额:").append(commonInfo.getValue()).append("元,");
+        }
 
+        contentBuffer.append("到账金额:").append(shopMoney.setScale(2, BigDecimal.ROUND_DOWN));
 
 
         if (shopIds == 0) {
@@ -2519,7 +2533,7 @@ public class AppAppOrderServiceImpl extends ServiceImpl<AppOrderDao, TbOrder> im
                 goodsShopRelevancy1.setSales(goodsShopRelevancy.getSales() - goodsNum);//加销量
                 goodsShopRelevancy1.setInventory(goodsShopRelevancy.getInventory() + goodsNum);//减库存
                 goodsShopRelevancyDao.updateById(goodsShopRelevancy1);
-                goodsShopDao.updateShopSales(2, goodsNum, goodsShopRelevancy.getShopId());
+                // goodsShopDao.updateShopSales(2, goodsNum, goodsShopRelevancy.getShopId());
             }
 
             // 取消跑腿订单
@@ -2677,7 +2691,7 @@ public class AppAppOrderServiceImpl extends ServiceImpl<AppOrderDao, TbOrder> im
                 goodsShopRelevancy1.setSales(goodsShopRelevancy.getSales() - goodsNum);//加销量
                 goodsShopRelevancy1.setInventory(goodsShopRelevancy.getInventory() + goodsNum);//减库存
                 goodsShopRelevancyDao.updateById(goodsShopRelevancy1);
-                goodsShopDao.updateShopSales(2, goodsNum, goodsShopRelevancy.getShopId());
+                // goodsShopDao.updateShopSales(2, goodsNum, goodsShopRelevancy.getShopId());
             }
 
             // 打印退款小票
@@ -2817,7 +2831,7 @@ public class AppAppOrderServiceImpl extends ServiceImpl<AppOrderDao, TbOrder> im
                             tbOrder.setStatus(5);
                         } else if ("SUCCESS".equals(trade_state)) {
                             tbOrder.setPayType(1);
-                            orderService.updateOrderAfterPaySuccess(tbOrder);
+                            updateOrderAfterPaySuccess(tbOrder);
                         }
                     } else {
                         RhtQrcodePayApi qrcodePay = new RhtQrcodePayApi(wechatMchConfig.getMchId(), wechatMchConfig.getMchKey(), RHT_PAY_BASE_URL);
@@ -2841,7 +2855,7 @@ public class AppAppOrderServiceImpl extends ServiceImpl<AppOrderDao, TbOrder> im
                                 log.error("超时任务处理订单【{}】支付成功,但未获取到支付订单!支付通知信息:{}", orderNumber, response2);
                             }
                             tbOrder.setPayType(1);
-                            orderService.updateOrderAfterPaySuccess(tbOrder);
+                            updateOrderAfterPaySuccess(tbOrder);
                         }
 
                     }
@@ -2972,6 +2986,34 @@ public class AppAppOrderServiceImpl extends ServiceImpl<AppOrderDao, TbOrder> im
         EasyExcelUtil.exportExcel(records, GoodsSkuSalesCountVO.class, "sku销售记录", "sheet1");
     }
 
+    @Override
+    public int getCurDayVipPromotionByUserCount(Long userId) {
+        // 查询用户当天会员优惠单数量
+        QueryWrapper<TbOrder> wrapper = new QueryWrapper<>();
+        wrapper.eq("user_id", userId)
+                .eq("vip_promotion", Constant.YES)
+                .eq("is_pay", Constant.YES)
+                // 8商家拒绝接单 5订单已取消 不纳入统计
+                .notIn("status", "5", "8")
+                .ge("pay_time", LocalDateTime.now().toLocalDate().atStartOfDay())
+                .le("pay_time", LocalDateTime.now().toLocalDate().atTime(23, 59, 59));
+        return count(wrapper);
+    }
+
+    @Override
+    public Map<Long, Integer> countFinishByShopIdWithLast30Days() {
+        // 统计近近30天每个店铺的已完成订单数量
+        List<Map<String, Object>> shopSalesList = baseMapper.countFinishByShopIdWithLast30Days();
+        // 转换为ShopId -> OrderCount的Map返回
+        return shopSalesList.stream()
+                .filter(map -> map.containsKey("shop_id") && map.containsKey("order_count"))
+                .filter(map -> map.get("shop_id") != null && map.get("order_count") != null)
+                .collect(Collectors.toMap(
+                        map -> Long.valueOf(map.get("shop_id").toString()),
+                        map -> Integer.valueOf(map.get("order_count").toString())
+                ));
+    }
+
     /**
      * 更新订单状态和支付顺序
      *
@@ -2990,4 +3032,53 @@ public class AppAppOrderServiceImpl extends ServiceImpl<AppOrderDao, TbOrder> im
             lock.unlock();
         }
     }
+
+    /**
+     * 校验是否参与vip优惠
+     * @param order 订单信息
+     * @param goodsShop 店铺信息
+     * @param userId 用户id
+     */
+    private void checkVipPromotion(TbOrder order, GoodsShop goodsShop, Long userId) {
+        // 检查店铺是否支持会员优惠
+        if (!StrUtil.equals(goodsShop.getVipPromotion(), Constant.YES)) {
+            return;
+        }
+
+        // 判断订单是否达到会员立减最低金额(元)
+        CommonInfo vipPromotionMinAmount = commonInfoService.findOne(446);
+        if (order.getPayMoney().compareTo(new BigDecimal(vipPromotionMinAmount.getValue())) < 0) {
+            return;
+        }
+
+        // 检查用户是否为会员,且会员是否过期
+        UserEntity user = userService.getById(userId);
+        if(ObjectUtil.isNotNull(user.getIsVip()) && user.getIsVip() == 1 && VipExpirationUtil.isVipValid(user.getVipExpirationTime())) {
+            // 查询用户当天会员优惠单数量
+            int count = getCurDayVipPromotionByUserCount(userId);
+
+            // 会员每天限制优惠单数(单)
+            CommonInfo vipPromotionCount = commonInfoService.findOne(445);
+            if (count >= Integer.parseInt(vipPromotionCount.getValue())) {
+                return;
+            }
+
+            // 会员每单立减金额(元)
+            CommonInfo vipPromotionAmount = commonInfoService.findOne(444);
+            BigDecimal payMoney = order.getPayMoney().subtract(new BigDecimal(vipPromotionAmount.getValue()));
+
+            //如果使用红包后,订单价格小于0,则改为0.01元
+            if (payMoney.doubleValue() <= 0) {
+                payMoney = new BigDecimal("0.01");
+            }
+
+            // 设置订单支付金额
+            order.setPayMoney(payMoney);
+
+            // 订单设置为vip优惠订单
+            order.setVipPromotion(Constant.YES);
+
+            log.info("preOrder==>[{}],订单参与会员优惠,优惠后金额为:{}元", order.getOrderId(), payMoney);
+        }
+    }
 }

+ 4 - 19
src/main/java/com/sqx/modules/pay/controller/app/ApiWeiXinErrRiderPayController.java

@@ -4,6 +4,7 @@ import com.sqx.common.utils.Result;
 import com.sqx.modules.app.annotation.Login;
 import com.sqx.modules.app.dao.UserDao;
 import com.sqx.modules.app.entity.UserEntity;
+import com.sqx.modules.pay.dto.RiderCertificationDTO;
 import com.sqx.modules.pay.service.WxErrRiderService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -12,6 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
@@ -70,28 +72,11 @@ public class ApiWeiXinErrRiderPayController {
     @Login
     @ApiOperation("小程序跑腿师傅实名认证")
     @PostMapping("/wxPayJsApiRiderCertification/{type}")
-    public Result wxPayJsApiRiderCertification(@RequestAttribute("userId") Long userId, @PathVariable("type") Integer type, @RequestBody UserEntity userEntity) throws Exception{
-        Integer userType = 2;
-        return wxService.wxPayJsApiRiderCertification(userId,type,userEntity, userType);
-    }
-
-    /*@Login
-    @ApiOperation("公众号跑腿师傅实名认证")
-    @PostMapping("/wxPayMpRiderCertification")
-    public Result wxPayMpRiderCertification(@RequestAttribute("userId") Long userId, @RequestBody UserEntity userEntity) throws Exception{
-        Integer userType = 2;
-        return wxService.wxPayJsApiRiderCertification(userId,2,userEntity, userType);
+    public Result wxPayJsApiRiderCertification(@RequestAttribute("userId") Long userId, @PathVariable("type") Integer type, @RequestBody @Valid RiderCertificationDTO dto) {
+        return wxService.wxPayJsApiRiderCertification(userId, dto);
     }
 
     @Login
-    @ApiOperation("app跑腿师傅实名认证")
-    @PostMapping("/appRiderCertification")
-    public Result appRiderCertification(@RequestAttribute("userId") Long userId, @RequestBody UserEntity userEntity) throws Exception{
-        Integer userType = 2;
-        return wxService.wxPayJsApiRiderCertification(userId,1,userEntity, userType);
-    }*/
-
-    @Login
     @ApiOperation("判断用户是否已实名认证成功")
     @GetMapping(value = "selectCertificationFlag")
     public Result selectCertificationFlag(@RequestAttribute Long userId){

+ 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;
 }

+ 8 - 0
src/main/java/com/sqx/modules/pay/dto/PayOrderDTO.java

@@ -5,6 +5,8 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
 
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Pattern;
@@ -34,6 +36,12 @@ public class PayOrderDTO {
     @NotNull(message = "订单类型不能为空")
     private Integer orderType;
 
+    @ApiModelProperty("打包类型 0堂食 1打包")
+    @NotNull(message = "打包类型不能为空")
+    @Max(value = 1, message = "打包类型只能为0或1")
+    @Min(value = 0, message = "打包类型只能为0或1")
+    private Integer packType;
+
     @ApiModelProperty("活动id")
     private Long activityId;
 

+ 42 - 0
src/main/java/com/sqx/modules/pay/dto/RiderCertificationDTO.java

@@ -0,0 +1,42 @@
+package com.sqx.modules.pay.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 骑手认证dto
+ * @author codingliang
+ * @date 2025-11-25
+ */
+@Data
+@ApiModel(value = "RiderCertificationDTO", description = "骑手认证dto")
+public class RiderCertificationDTO {
+
+    @ApiModelProperty("用户名")
+    @NotBlank(message = "用户名不能为空")
+    private String userName;
+
+    @ApiModelProperty("证件号码")
+    @NotBlank(message = "证件号码不能为空")
+    private String identityCardNumber;
+
+    @ApiModelProperty("身份证正面图片")
+    @NotBlank(message = "身份证正面图片不能为空")
+    private String identityCardFront;
+
+    @ApiModelProperty("身份证反面图片")
+    @NotBlank(message = "身份证反面图片不能为空")
+    private String identityCardVerso;
+
+    @ApiModelProperty("手持身份证正面图片")
+    @NotBlank(message = "手持身份证正面图片不能为空")
+    private String selfIdentityCard;
+
+    @ApiModelProperty("站点id")
+    @NotNull(message = "站点id不能为空")
+    private Integer riderStationId;
+}

+ 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;
+
     /**
      * 支付系统交易订单号
      */

+ 2 - 2
src/main/java/com/sqx/modules/pay/service/WxErrRiderService.java

@@ -1,7 +1,7 @@
 package com.sqx.modules.pay.service;
 
 import com.sqx.common.utils.Result;
-import com.sqx.modules.app.entity.UserEntity;
+import com.sqx.modules.pay.dto.RiderCertificationDTO;
 import com.sqx.modules.pay.entity.PayDetails;
 
 import java.math.BigDecimal;
@@ -17,7 +17,7 @@ public interface WxErrRiderService {
 
     Result payOrder(Long userId, String indentNumber, Integer type, Integer userType) throws Exception;
 
-    Result wxPayJsApiRiderCertification(Long userId,Integer type, UserEntity userEntity, Integer userType) throws  Exception;
+    Result wxPayJsApiRiderCertification(Long userId, RiderCertificationDTO dto);
 
     String payBack(String resXml,Integer type);;
 

+ 23 - 60
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,14 @@ 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 +31,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
@@ -99,7 +54,6 @@ public class NewPayServiceImpl implements NewPayService {
     private final CommonInfoService commonInfoService;
     private final UserMoneyService userMoneyService;
     private final GoodsShopService goodsShopService;
-    private final DataCentreService dataCentreService;
 
     /**
      * 润海通支付接口地址
@@ -132,7 +86,7 @@ public class NewPayServiceImpl implements NewPayService {
             return JSONUtil.toBean(payDetails.getRemark(), WechatTransactionsParamVO.class);
         }
 
-        WechatPayConfig wechatMchConfig = getWechatMchConfig();
+        WechatPayConfig wechatMchConfig = getWechatMchConfig(dto);
         RhtQrcodePayApi qrcodePay = new RhtQrcodePayApi(wechatMchConfig.getMchId(), wechatMchConfig.getMchKey(), RHT_PAY_BASE_URL);
         QrcodeOpenPayRequestBean openRequest = getQrcodeOpenPayRequestBean(dto, wechatMchConfig);
 
@@ -183,7 +137,7 @@ public class NewPayServiceImpl implements NewPayService {
 
     @Override
     public boolean wechatRefund(String orderNo) {
-        WechatPayConfig wechatMchConfig = getWechatMchConfig();
+        WechatPayConfig wechatMchConfig = getWechatMchConfig(null);
         RhtQrcodePayApi qrcodePay = new RhtQrcodePayApi(wechatMchConfig.getMchId(), wechatMchConfig.getMchKey(), RHT_PAY_BASE_URL);
         QrcodeRefundRequestBean request = new QrcodeRefundRequestBean();
         // 商户流水号
@@ -208,7 +162,7 @@ public class NewPayServiceImpl implements NewPayService {
 
     @Override
     public void wechatSplitBill(String traceNo, String inMchId, BigDecimal amount) {
-        WechatPayConfig wechatMchConfig = getWechatMchConfig();
+        WechatPayConfig wechatMchConfig = getWechatMchConfig(null);
         RhtSplitApi splitApi = new RhtSplitApi(wechatMchConfig.getMchId(), wechatMchConfig.getMchKey(), RHT_PAY_BASE_URL);
 
         String outAmount = amount.toString();
@@ -303,9 +257,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);
     }
 
     /**
@@ -351,12 +304,22 @@ public class NewPayServiceImpl implements NewPayService {
      * 获取微信支付商户配置
      * @return 商户配置
      */
-    private WechatPayConfig getWechatMchConfig() {
-        return WechatPayConfig.builder()
-                .appId(commonInfoService.findOne(45).getValue())
-                .mchId(commonInfoService.findOne(434).getValue())
+    private WechatPayConfig getWechatMchConfig(GetPayParamDTO dto) {
+        WechatPayConfig.WechatPayConfigBuilder builder = WechatPayConfig.builder();
+
+        // 保证金缴纳场景,使用商户端小程序appId
+        if (ObjectUtil.isNotNull(dto) && PaySceneEnum.BOND_PAY == dto.getPaySceneEnum()) {
+            builder.appId(commonInfoService.findOne(305).getValue());
+        }
+        // 其他支付场景,使用客户端appId
+        else {
+            builder.appId(commonInfoService.findOne(45).getValue());
+        }
+
+        builder.mchId(commonInfoService.findOne(434).getValue())
                 .mchKey(commonInfoService.findOne(435).getValue())
-                .h5Url(commonInfoService.findOne(19).getValue())
-                .build();
+                .h5Url(commonInfoService.findOne(19).getValue());
+
+        return builder.build();
     }
-}
+}

+ 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);
     }
 

+ 28 - 25
src/main/java/com/sqx/modules/pay/service/impl/WxErrRiderServiceImpl.java

@@ -1,13 +1,16 @@
 package com.sqx.modules.pay.service.impl;
 
-import com.alibaba.fastjson.JSONObject;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
 import com.github.wxpay.sdk.WXPay;
 import com.github.wxpay.sdk.WXPayConstants;
 import com.github.wxpay.sdk.WXPayUtil;
+import com.sqx.common.exception.SqxException;
+import com.sqx.common.utils.Constant;
 import com.sqx.common.utils.Result;
 import com.sqx.modules.app.dao.UserDao;
 import com.sqx.modules.app.entity.UserEntity;
-import com.sqx.modules.app.entity.UserMoneyDetails;
 import com.sqx.modules.app.service.UserMoneyDetailsService;
 import com.sqx.modules.app.service.UserService;
 import com.sqx.modules.common.entity.CommonInfo;
@@ -17,6 +20,7 @@ import com.sqx.modules.errand.service.TbIndentService;
 import com.sqx.modules.pay.config.WXConfig;
 import com.sqx.modules.pay.controller.app.AliPayController;
 import com.sqx.modules.pay.dao.PayDetailsDao;
+import com.sqx.modules.pay.dto.RiderCertificationDTO;
 import com.sqx.modules.pay.entity.PayDetails;
 import com.sqx.modules.pay.service.WxErrRiderService;
 import com.sqx.modules.pay.service.WxService;
@@ -104,36 +108,35 @@ public class WxErrRiderServiceImpl implements WxErrRiderService {
     }
 
     @Override
-    public Result wxPayJsApiRiderCertification(Long userId,Integer type, UserEntity userEntity, Integer userType) throws Exception {
+    public Result wxPayJsApiRiderCertification(Long userId, RiderCertificationDTO dto) {
+        UserEntity user = userService.getById(userId);
+        if(ObjectUtil.isNull(user)){
+            throw new SqxException("用户不存在!");
+        }
+
+        if (StrUtil.equals(Constant.YES, user.getCheckCertification())){
+            return Result.error("您已认证成功,无需重复认证!");
+        }
+
         CommonInfo isVerify = commonInfoService.findOne(260);
-        String generalOrder = getGeneralOrder();
         if("是".equals(isVerify.getValue())){
-            String result = VerifyIdCardUtils.verifyIdCard(userEntity.getIdentityCardNumber(),userEntity.getUserName());
+            String result = VerifyIdCardUtils.verifyIdCard(dto.getIdentityCardNumber(),dto.getUserName());
             if(!"ok".equals(result)){
                 return Result.error("您填写的信息有误,请检查后重试!");
             }
         }
-        userEntity.setUserId(userId);
-        userEntity.setCheckNumber(generalOrder);
-//        因为添加站点,所以需要更新tb_user表
-        userDao.updateById(userEntity);
 
-        CommonInfo one = commonInfoService.findOne(273);
-        double money = Double.parseDouble(one.getValue());
-        PayDetails payDetails = new PayDetails();
-        payDetails.setState(0);
-        payDetails.setCreateTime(sdf.format(new Date()));
-        payDetails.setOrderId(generalOrder);
-        payDetails.setUserId(userId);
-        payDetails.setMoney(money);
-        payDetails.setClassify(3);
-        payDetails.setType(type);
-        payDetails.setRemark(JSONObject.toJSONString(userEntity));
-        payDetailsDao.insert(payDetails);
-        if(type==4||type==5){
-            return aliPayController.aliRiderCertification(money, type, generalOrder);
-        }
-        return pay(money, type, userId, generalOrder,3, userType);
+        UserEntity newUser = new UserEntity();
+        BeanUtil.copyProperties(dto, newUser);
+        newUser.setUserId(userId);
+        // 2 骑手
+        newUser.setType(2);
+        // 设置为待审核状态
+        newUser.setCheckCertification(Constant.NO);
+        newUser.setCheckCertificationMessage("待审核");
+        userService.updateById(newUser);
+
+        return Result.success();
     }
 
 

+ 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;
         }
     }
-
-
-}
+}

+ 2 - 0
src/main/java/com/sqx/modules/printInfo/entity/eOrderTypeExtra.java

@@ -55,6 +55,8 @@ public enum eOrderTypeExtra {
 
     public static String stringOf(Integer value) {
         switch (value) {
+            case 0:
+                return "到店堂食(不打包)";
             case 1:
                 return "到店取餐";
             case 2:

+ 51 - 21
src/main/java/com/sqx/modules/shop/controller/app/ShopMoneyController.java

@@ -1,6 +1,9 @@
 package com.sqx.modules.shop.controller.app;
 
+import cn.hutool.core.bean.BeanUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.sqx.common.exception.SqxException;
 import com.sqx.common.utils.PageUtils;
 import com.sqx.common.utils.Result;
 import com.sqx.modules.app.dao.UserDao;
@@ -14,14 +17,20 @@ import com.sqx.modules.goods.dao.GoodsShopDao;
 import com.sqx.modules.goods.entity.GoodsShop;
 import com.sqx.modules.pay.controller.app.AliPayController;
 import com.sqx.modules.pay.controller.query.CashOutQueryDTO;
+import com.sqx.modules.pay.dto.GetPayParamDTO;
+import com.sqx.modules.pay.enums.PayChannelEnum;
+import com.sqx.modules.pay.enums.PaySceneEnum;
 import com.sqx.modules.pay.service.CashOutService;
+import com.sqx.modules.pay.service.NewPayService;
 import com.sqx.modules.pay.service.WxService;
+import com.sqx.modules.pay.vo.WechatTransactionsParamVO;
 import com.sqx.modules.sys.controller.AbstractController;
 import com.sqx.modules.sys.dao.SysUserDao;
 import com.sqx.modules.sys.entity.SysUserEntity;
+import icu.xuyijie.secureapi.annotation.DecryptParam;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
-import org.springframework.beans.factory.annotation.Autowired;
+import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
@@ -31,30 +40,24 @@ import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 
 @Api(tags={"商户端-商户钱包"})
 @RestController
 @RequestMapping("/shop/shopmoney")
+@RequiredArgsConstructor
 public class ShopMoneyController extends AbstractController {
 
-    @Autowired
-    private UserMoneyService userMoneyService;
-    @Autowired
-    private UserMoneyDetailsService userMoneyDetailsService;
-    @Autowired
-    private UserDao userDao;
-    @Autowired
-    private CashOutService cashOutService;
-    @Autowired
-    private UserService userService;
-    @Autowired
-    private WxService wxService;
-    @Autowired
-    private SysUserDao sysUserDao;
-    @Autowired
-    private AliPayController aliPayController;
-    @Autowired
-    private GoodsShopDao goodsShopDao;
+    private final UserMoneyService userMoneyService;
+    private final UserMoneyDetailsService userMoneyDetailsService;
+    private final UserDao userDao;
+    private final CashOutService cashOutService;
+    private final UserService userService;
+    private final WxService wxService;
+    private final SysUserDao sysUserDao;
+    private final AliPayController aliPayController;
+    private final GoodsShopDao goodsShopDao;
+    private final NewPayService newPayService;
 
     @GetMapping("/selectShopMoney")
     @ApiOperation("商户钱包余额")
@@ -72,9 +75,36 @@ public class ShopMoneyController extends AbstractController {
         return Result.success().put("data", userMoneyService.selectUserMoneyByUserId(goodsShop.getUserId()));
     }
 
-    @ApiOperation("商户缴纳保证金")
+    @ApiOperation("商户缴纳保证金(新)")
     @PostMapping(value = "shopCashDeposit")
-    public Result shopCashDeposit(String openId, Double money,Integer type) throws Exception {
+    public Result shopCashDeposit(@DecryptParam(value = "openId") String openId, @DecryptParam(value = "money") BigDecimal money) {
+        SysUserEntity user = getUser();
+        Long adminUserId = user.getUserId();
+        SysUserShop sysUserShop = sysUserDao.selectUserShop(adminUserId);
+        GoodsShop goodsShop = goodsShopDao.selectById(sysUserShop.getShopId());
+        if(goodsShop.getStatus() != 1){
+            throw new SqxException("管理端审核成功后才能交保证金");
+        }
+
+        // 构建获取支付参数dto
+        GetPayParamDTO dto = GetPayParamDTO.builder()
+                .orderNo(IdWorker.getTimeId())
+                .orderDesc("墨轩商场-商户缴纳保证金")
+                .amount(money.setScale(2, RoundingMode.HALF_UP))
+                .shopId(goodsShop.getShopId())
+                .userThirdId(openId)
+                .payChannel(PayChannelEnum.WECHAT_JS_API)
+                .paySceneEnum(PaySceneEnum.BOND_PAY)
+                .build();
+
+        WechatTransactionsParamVO wechatTransactionsParamVO = newPayService.getWechatTransactionsParamVO(dto);
+
+        return Result.success(BeanUtil.beanToMap(wechatTransactionsParamVO, false, true));
+    }
+
+    @ApiOperation("商户缴纳保证金(废弃)")
+    @PostMapping(value = "shopCashDeposit-bak")
+    public Result shopCashDepositBak(String openId, Double money,Integer type) throws Exception {
         SysUserEntity user = getUser();
         Long adminUserId = user.getUserId();
         SysUserShop sysUserShop = sysUserDao.selectUserShop(adminUserId);

+ 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);
+    }
+}

+ 19 - 7
src/main/java/com/sqx/modules/utils/fieYun/FeiYunUtils.java

@@ -1,27 +1,35 @@
 package com.sqx.modules.utils.fieYun;
 
+import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson.JSONObject;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.sqx.common.utils.Constant;
 import com.sqx.common.utils.Result;
 import com.sqx.modules.activity.service.ActivityService;
 import com.sqx.modules.activity.vo.ActivityOrderVO;
-import com.sqx.modules.activity.vo.ActivityVO;
 import com.sqx.modules.app.entity.UserEntity;
 import com.sqx.modules.app.service.UserService;
 import com.sqx.modules.common.service.CommonInfoService;
 import com.sqx.modules.coupon.entity.TbCoupon;
-import com.sqx.modules.coupon.entity.TbCouponUser;
 import com.sqx.modules.coupon.service.TbCouponService;
 import com.sqx.modules.goods.entity.GoodsShop;
 import com.sqx.modules.goods.service.GoodsShopService;
 import com.sqx.modules.order.entity.OrderGoods;
 import com.sqx.modules.order.entity.TbOrder;
 import com.sqx.modules.order.service.AppOrderService;
-import com.sqx.modules.printInfo.entity.*;
+import com.sqx.modules.printInfo.entity.PrintInfo;
+import com.sqx.modules.printInfo.entity.PrintInfoDetail;
+import com.sqx.modules.printInfo.entity.PrintInfoShop;
+import com.sqx.modules.printInfo.entity.PrintModelDetailResult;
+import com.sqx.modules.printInfo.entity.PrintModelResult;
+import com.sqx.modules.printInfo.entity.PrintModelTypeResult;
+import com.sqx.modules.printInfo.entity.eOrderStatus;
+import com.sqx.modules.printInfo.entity.eOrderTypeExtra;
+import com.sqx.modules.printInfo.entity.ePrintStatu;
+import com.sqx.modules.printInfo.entity.eWordSize;
 import com.sqx.modules.printInfo.service.PrintInfoDetailService;
 import com.sqx.modules.printInfo.service.PrintInfoService;
 import com.sqx.modules.printInfo.service.PrintInfoShopService;
-import com.sqx.modules.utils.fieYun.model.OrderForm;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.lang.StringUtils;
@@ -990,12 +998,10 @@ public class FeiYunUtils {
                 stringBuilder.append("<BR>");
 
                 stringBuilder.append("--------------------------------<BR>");
-                BigDecimal packMoney = BigDecimal.ZERO;
+                BigDecimal packMoney = tbOrder.getPackMoney();
                 BigDecimal goodsMoney = BigDecimal.ZERO;
                 for (OrderGoods orderGoods : orderGoodsList) {
                     BigDecimal sumMoney = orderGoods.getGoodsPrice().multiply(BigDecimal.valueOf(orderGoods.getGoodsNum()));
-                    BigDecimal sumPack = orderGoods.getGoodsPack().multiply(BigDecimal.valueOf(orderGoods.getGoodsNum()));
-                    packMoney = packMoney.add(sumPack);
                     goodsMoney = goodsMoney.add(sumMoney);
                     String name;
                     if (StringUtils.isNotEmpty(orderGoods.getSkuMessage())) {
@@ -1279,6 +1285,12 @@ public class FeiYunUtils {
                 stringBuilder.append(errand_money_start).append("跑腿费:").append(errandMoneyT).append("元").append(errand_money_end + "<BR>");
                 //从这个表里拿优惠活动 activity_part_record 活动参与记录表
                 stringBuilder.append(activity_title_start).append("优惠活动:").append(activity == null ? "" : activity.getTitle()).append(activity_title_end + "<BR>");
+
+                // 会员优惠
+                if (StrUtil.equals(tbOrder.getVipPromotion(), Constant.YES)) {
+                    stringBuilder.append(activity_title_start).append("会员优惠:").append("-" + commonInfoService.findOne(444).getValue()).append("元").append(activity_title_end + "<BR>");
+                }
+
                 BigDecimal patMoney = tbOrder.getPayMoney().setScale(2, BigDecimal.ROUND_HALF_UP);
                 stringBuilder.append(pay_money_start).append("合计:").append(patMoney).append("元").append(pay_money_end + "<BR>");
                 stringBuilder.append(payStringBuilder.toString());

+ 5 - 0
src/main/java/com/sqx/scheduler/config/SchedulerLock.java

@@ -47,4 +47,9 @@ public interface SchedulerLock {
      * 用户优惠券自动过期锁
      */
     String INTEGRAL_OF_USER_EXPIRATION_LOCK = "wm:lock:integral:exp";
+
+    /**
+     * 更新店铺销量锁
+     */
+    String UPDATE_SHOP_SALES_LOCK = "wm:lock:shop:sales:update";
 }

+ 107 - 11
src/main/java/com/sqx/scheduler/export/ExportScheduler.java

@@ -1,9 +1,10 @@
 package com.sqx.scheduler.export;
 
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
-import com.sqx.common.utils.Constant;
-import com.sqx.common.utils.Result;
+import com.sqx.common.utils.PageUtils;
 import com.sqx.common.utils.SftpUtil;
 import com.sqx.modules.address.query.InsideAddressQuery;
 import com.sqx.modules.address.service.InsideAddressService;
@@ -18,6 +19,15 @@ import com.sqx.modules.datacentre.service.DataCentreService;
 import com.sqx.modules.exportExecl.dto.AccountEntry;
 import com.sqx.modules.exportExecl.model.ExportJob;
 import com.sqx.modules.exportExecl.service.ExportJobService;
+import com.sqx.modules.member.query.SpecialFavorStudentQuery;
+import com.sqx.modules.member.query.VipPromoCodeQuery;
+import com.sqx.modules.member.query.VipPromoRecordQuery;
+import com.sqx.modules.member.service.SpecialFavorStudentService;
+import com.sqx.modules.member.service.VipPromoCodeService;
+import com.sqx.modules.member.service.VipPromoRecordService;
+import com.sqx.modules.member.vo.SpecialFavorStudentVO;
+import com.sqx.modules.member.vo.VipPromoCodeVO;
+import com.sqx.modules.member.vo.VipPromoRecordVO;
 import com.sqx.modules.order.dto.OrderQueryDto;
 import com.sqx.modules.order.service.AppOrderService;
 import com.sqx.modules.pay.controller.query.CashOutQueryDTO;
@@ -28,7 +38,6 @@ import com.sqx.modules.reconciliation.service.PlatformBillService;
 import com.sqx.modules.utils.excel.ExcelData;
 import com.sqx.modules.utils.excel.ExportExcelUtils;
 import com.sqx.scheduler.config.SchedulerLock;
-import io.swagger.annotations.ApiOperation;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.jetbrains.annotations.NotNull;
@@ -39,7 +48,6 @@ import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
-import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 
 import java.io.ByteArrayInputStream;
@@ -49,11 +57,14 @@ import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
+import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * 优惠券定时任务
@@ -78,9 +89,12 @@ public class ExportScheduler {
     private final UserMoneyDetailsService userMoneyDetailsService;
     private final DataCentreService dataCentreService;
     private final PlatformBillService platformBillService;
-
     private final InsideAddressService insideAddressService;
+    private final SpecialFavorStudentService specialFavorStudentService;
+    private final VipPromoRecordService vipPromoRecordService;
+    private final VipPromoCodeService vipPromoCodeService;
 
+    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
     /**
      * 将所有超过失效时间的优惠券改为失效状态
@@ -123,7 +137,8 @@ public class ExportScheduler {
                 InputStream inputStream=null;
                 ByteArrayOutputStream os = new ByteArrayOutputStream();
                 try {
-                    ExcelData excelData=new ExcelData();
+                    new ExcelData();
+                    ExcelData excelData;
                     if("order".equals(fileType)){
                         excelData=getOrderExcelData(exportJob);
                     }else if("recharge".equals(fileType)){
@@ -146,7 +161,13 @@ public class ExportScheduler {
                         excelData=getPlatformBillExcelData(exportJob);
                     }else if ("shippingAddress".equals(fileType)){
                         excelData=getShippingAddressExcelData(exportJob);
-                    }else{
+                    } else if("specialFavorStudent".equals(fileType)){
+                        excelData = getSpecialFavorStudentExcelData(exportJob);
+                    }else if("vipPromoRecord".equals(fileType)){
+                        excelData = getVipPromoRecordExcelData(exportJob);
+                    } else if ("vipPromoCode".equals(fileType)){
+                        excelData = getVipPromoCodeExcelData(exportJob);
+                    } else{
                         continue;
                     }
                     String value=commonInfoService.findOne(430).getValue();
@@ -190,8 +211,83 @@ public class ExportScheduler {
         }
     }
 
+    private ExcelData getVipPromoCodeExcelData(ExportJob exportJob) {
+        VipPromoCodeQuery query = JSONUtil.toBean(exportJob.getConditions(), VipPromoCodeQuery.class);
+
+        ExcelData excelData = new ExcelData();
+        excelData.setName("会员推广码列表");
+        excelData.setTitles(Arrays.asList("推广人名称", "推广码", "推广人身份证号码", "推广人手机号码", "推广人次", "创建时间"));
+
+        PageUtils page = vipPromoCodeService.pages(query);
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        List<List<Object>> rows = page.getList().stream().map(e -> {
+            VipPromoCodeVO vo = (VipPromoCodeVO) e;
+            List<Object> row = new ArrayList<>();
+            row.add(vo.getUserName());
+            row.add(vo.getPromoCode());
+            row.add(vo.getIdCard());
+            row.add(vo.getPhone());
+            row.add(vo.getPromoCount());
+            row.add(ObjectUtils.isNull(vo.getCreateTime()) ? "" : sdf.format(vo.getCreateTime()));
+            return row;
+        }).collect(Collectors.toList());
+
+        excelData.setRows(rows);
+        return excelData;
+    }
+
+    private ExcelData getVipPromoRecordExcelData(ExportJob exportJob) {
+        VipPromoRecordQuery query = JSONUtil.toBean(exportJob.getConditions(), VipPromoRecordQuery.class);
+
+        ExcelData data = new ExcelData();
+        data.setName("特殊照顾学生列表");
+        data.setTitles(Arrays.asList("推广码id", "用户id", "用户名称", "用户手机号", "创建时间", "支付类型", "支付金额", "会员过期时间"));
+
+        PageUtils pages = vipPromoRecordService.pages(query);
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        List<List<Object>> rows = pages.getList().stream().map(e -> {
+            VipPromoRecordVO vo = (VipPromoRecordVO) e;
+            List<Object> row = new ArrayList<>();
+            row.add(vo.getPromoCodeId());
+            row.add(vo.getUserId());
+            row.add(vo.getUserName());
+            row.add(vo.getUserPhone());
+            row.add(ObjectUtils.isNull(vo.getCreateTime()) ? "" : sdf.format(vo.getCreateTime()));
+            row.add(StrUtil.equals(vo.getPayType(), "1") ? "微信支付" : (StrUtil.equals(vo.getPayType(), "2") ? "余额支付" : "暖心照顾"));
+            row.add(vo.getPayAmount());
+            row.add(ObjectUtils.isNull(vo.getExpireTime()) ? "" : sdf.format(vo.getExpireTime()));
+            return row;
+        }).collect(Collectors.toList());
+        data.setRows(rows);
+
+        return data;
+    }
+
+    private ExcelData getSpecialFavorStudentExcelData(ExportJob exportJob) {
+        SpecialFavorStudentQuery query = JSONUtil.toBean(exportJob.getConditions(), SpecialFavorStudentQuery.class);
+        ExcelData data = new ExcelData();
+        data.setName("特殊照顾学生列表");
+        data.setTitles(Arrays.asList("姓名", "学号", "验证状态", "创建时间", "验证用户名", "验证手机号", "验证时间"));
+        PageUtils pages = specialFavorStudentService.pages(query);
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        List<List<Object>> rows = pages.getList().stream().map(e -> {
+            SpecialFavorStudentVO vo = (SpecialFavorStudentVO) e;
+            List<Object> row = new ArrayList<>();
+            row.add(vo.getName());
+            row.add(vo.getStudentNumber());
+            row.add(StrUtil.equals(vo.getVerifyStatus(), "0") ? "未认证" : "已认证");
+            row.add(ObjectUtils.isNull(vo.getCreateTime()) ? "" : sdf.format(vo.getCreateTime()));
+            row.add(vo.getVerifyUserName());
+            row.add(vo.getVerifyUserPhone());
+            row.add(ObjectUtils.isNull(vo.getLastVerifyTime()) ? "" : sdf.format(vo.getLastVerifyTime()));
+            return row;
+        }).collect(Collectors.toList());
+        data.setRows(rows);
+
+        return data;
+    }
+
     private ExcelData getShippingAddressExcelData(ExportJob exportJob) {
-        DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
         String[] conditions=exportJob.getConditions().split(",",-1);
         InsideAddressQuery query = new InsideAddressQuery();
         query.setAddressDetail(conditions[1]);
@@ -200,12 +296,12 @@ public class ExportScheduler {
             query.setStationId(Long.valueOf(conditions[3]));
         }
         if(!"".equals(conditions[4])){
-            Date start = Date.from(LocalDateTime.parse(conditions[4], dateTimeFormatter1).atZone(ZoneId.systemDefault()).toInstant());
+            Date start = Date.from(LocalDateTime.parse(conditions[4], dateTimeFormatter).atZone(ZoneId.systemDefault()).toInstant());
             log.info("start:"+start);
             query.setStartTime(start);
         }
         if(!"".equals(conditions[5])){
-            Date end = Date.from(LocalDateTime.parse(conditions[5], dateTimeFormatter1).atZone(ZoneId.systemDefault()).toInstant());
+            Date end = Date.from(LocalDateTime.parse(conditions[5], dateTimeFormatter).atZone(ZoneId.systemDefault()).toInstant());
             query.setEndTime(end);
             log.info("end:"+end);
         }
@@ -253,7 +349,7 @@ public class ExportScheduler {
             String start="";
             if (ObjectUtils.isNotEmpty(createTime)) {
                 LocalDateTime localDateTime = LocalDateTime.ofInstant(createTime.toInstant(), ZoneId.systemDefault());
-                start = localDateTime.format(dateTimeFormatter1);
+                start = localDateTime.format(dateTimeFormatter);
             }
             row.add(start);
             rows.add(row);

+ 30 - 5
src/main/java/com/sqx/scheduler/order/OrderScheduler.java

@@ -2,8 +2,6 @@ package com.sqx.scheduler.order;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
-import com.baomidou.mybatisplus.core.conditions.Wrapper;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.ekyong.www.pay.pay.qrcode.api.RhtQrcodePayApi;
@@ -24,7 +22,6 @@ import com.sqx.modules.pay.service.PayDetailsService;
 import com.sqx.modules.shop.entity.ShopType;
 import com.sqx.modules.shop.service.ShopTypeService;
 import com.sqx.scheduler.config.SchedulerLock;
-import io.swagger.models.auth.In;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.redisson.api.RLock;
@@ -41,6 +38,7 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -58,9 +56,7 @@ public class OrderScheduler {
     private final AppOrderService orderService;
     private final CommonInfoService commonInfoService;
     private final PayDetailsService payDetailsService;
-
     private final ShopTypeService shopTypeService;
-
     private final GoodsShopService goodsShopService;
 
     private final static String RHT_PAY_BASE_URL = "https://api.ekbuyclub.com";
@@ -309,4 +305,33 @@ public class OrderScheduler {
             lock.unlock();
         }
     }
+
+    /**
+     * 更新店铺销量
+     *  每天1点、10点、13点、22点执行
+     */
+    @Async
+    @Scheduled(cron = "0 0 1,10,13,22 * * ?")
+    public void updateShopSales() {
+        RLock lock = null;
+        try {
+            // 尝试获取锁,最多等待0秒(即立即返回),锁的过期时间为2分钟
+            lock = redissonClient.getLock(SchedulerLock.UPDATE_SHOP_SALES_LOCK);
+            boolean isLocked = lock.tryLock(0, 2, TimeUnit.MINUTES);
+            if (!isLocked) {
+                log.info("更新店铺销量任务:当前有其他服务实例正在执行,本次跳过");
+                return;
+            }
+            log.info("更新店铺销量任务开始运行");
+            Map<Long, Integer> shopSalesMap = orderService.countFinishByShopIdWithLast30Days();
+            goodsShopService.updateShopSales(shopSalesMap);
+            log.info("更新店铺销量任务运行成功");
+        } catch (Exception e) {
+            log.error("更新店铺销量任务运行失败,失败原因:{}", e.getMessage());
+        } finally {
+            if (lock != null && lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        }
+    }
 }

+ 7 - 1
src/main/resources/mapper/goods/GoodsShopMapper.xml

@@ -26,6 +26,9 @@
                 and gs.activity_id is not null
             </if>
         </if>
+        <if test="vipPromotion != null and vipPromotion != ''">
+            and gs.vip_promotion = #{vipPromotion}
+        </if>
         order by create_time desc
     </select>
 
@@ -249,10 +252,13 @@
                 <if test="shopTypeId!=null">
                     and FIND_IN_SET(#{shopTypeId},gs.shop_type_id)
                 </if>
+                <if test="vipPromotion !=null and vipPromotion != ''">
+                    and gs.vip_promotion = #{vipPromotion}
+                </if>
                 order by
                     gs.is_recommend desc
                 <if test="screen == 1">
-                    ,gs.sort,gs.shop_score desc, distance asc
+                    ,gs.sort,gs.shop_score desc, gs.shop_sales desc, gs.create_time desc
                 </if>
                 <if test="screen == 2">
                     ,gs.shop_score desc

+ 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.del_flag = 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>

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

@@ -0,0 +1,49 @@
+<?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,
+            vpc.promo_code,
+            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 vip_promo_code vpc on vpc.id = vpr.promo_code_id
+            left join tb_user t on vpr.user_id = t.user_id
+        <where>
+            and vpr.del_flag = 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.specialFlag != null and query.specialFlag == '1'.toString()">
+                and vpr.pay_type = '3'
+            </if>
+            <if test="query.specialFlag != null and query.specialFlag != '1'.toString()">
+                and vpr.pay_type != '3'
+            </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>

+ 13 - 0
src/main/resources/mapper/order/OrderMapper.xml

@@ -812,4 +812,17 @@
 
     </select>
 
+    <select id="countFinishByShopIdWithLast30Days" resultType="java.util.Map">
+        SELECT
+            shop_id,
+            COUNT(*) AS order_count
+        FROM
+            tb_order
+        WHERE
+            status in (3, 4, 6)
+            AND pay_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
+        GROUP BY
+            shop_id
+    </select>
+
 </mapper>

+ 4 - 128
src/test/java/OrderTest.java

@@ -80,136 +80,12 @@ public class OrderTest {
     @Autowired
     private AppOrderDao appOrderDao;
 
+    @Autowired
+    private AppOrderService orderService;
+
     @Test
     public void test() {
-        List<TbOrder> orderList = appOrderDao.temp();
-        for (TbOrder tbOrder : orderList) {
-            QueryWrapper<UserMoneyDetails> queryWrapper = new QueryWrapper<>();
-            queryWrapper.eq("title", "商户订单收入,订单号:" + tbOrder.getOrderNumber());
-            if (userMoneyDetailsService.count(queryWrapper) > 0) {
-                log.info("订单id【{}】,商户用户钱包收入记录已存在,跳过", tbOrder.getOrderId());
-                continue;
-            }
-
-            log.info("订单id【{}】,开始计算商户用户钱包收入记录", tbOrder.getOrderId());
-
-
-            GoodsShop goodsShop = shopMessageService.selectShopId(tbOrder.getShopId());
-
-            // 计算商户应得金额
-            BigDecimal shopRate = goodsShop.getShopRate();
-            BigDecimal sumMoney = tbOrder.getPayMoney();
-            BigDecimal couponMoney = BigDecimal.ZERO;
-            Long shopIds = -1L;
-            if (tbOrder.getCouponId() != null) {
-                TbCouponUser tbCouponUser = tbCouponUserDao.selectById(tbOrder.getCouponId());
-                sumMoney = tbOrder.getPayMoney().add(tbCouponUser.getMoney());
-                couponMoney = tbCouponUser.getMoney();
-                shopIds = tbCouponUser.getShopId();
-            }
-
-            BigDecimal errandMoney = BigDecimal.ZERO;
-            if (tbOrder.getOrderType() != 1) {
-                //判断是否达到商家满减  如果达到商家满减 跑腿费让商家承担
-                if (tbOrder.getAutoSendOrder() != null && tbOrder.getAutoSendOrder() == 0) {
-                    errandMoney = tbOrder.getErrandMoney();
-                    sumMoney = sumMoney.subtract(tbOrder.getErrandMoney());
-                } else {
-                    sumMoney = sumMoney.subtract(tbOrder.getErrandMoney());
-                    errandMoney = tbOrder.getErrandMoney();
-                }
-            }
-
-            // 再减去校内配送费
-            sumMoney = sumMoney.subtract(tbOrder.getInsideDeliveryFee());
-
-            // 店铺收入
-            BigDecimal shopMoney = sumMoney.multiply(shopRate);
-            shopMoney = shopMoney.setScale(2, BigDecimal.ROUND_DOWN);
-            BigDecimal pingRate = sumMoney.subtract(shopMoney);
-
-            // 商家配送短信服务费 为0
-            BigDecimal smsSendMoney = BigDecimal.ZERO;
-
-            // 这里再判断 是否是商家优惠券  如果是商家优惠券  这笔钱商家承担
-            if (shopIds != -1 && shopIds != 0) {
-                shopMoney = shopMoney.subtract(couponMoney);
-            }
-
-            // 判断订单是否参与活动,如果参与活动,则优惠金额由商家承担
-            ActivityPartRecord activityPartRecord = activityPartRecordService.getByOrderId(tbOrder.getOrderId());
-            BigDecimal activityDiscountAmount = BigDecimal.ZERO;
-            if (ObjectUtil.isNotNull(activityPartRecord)) {
-                activityDiscountAmount = activityPartRecord.getDiscountAmount();
-            }
-
-            if (tbOrder.getAutoSendOrder() != null && tbOrder.getAutoSendOrder() == 1) {
-                //如果达到商家满减 则商家不加上配送费
-                if (!"本单已达到商家满减金额,跑腿费由商家承担".equals(tbOrder.getErrandMoneyIsShop())) {
-                    shopMoney = shopMoney.add(tbOrder.getErrandMoney());
-                }
-            }
-
-            StringBuffer contentBuffer = new StringBuffer();
-            BigDecimal rider = new BigDecimal(0);
-            if (ObjectUtils.isNotEmpty(tbOrder.getOrderTypeExtra())&&tbOrder.getOrderTypeExtra()==2) {
-                //        获取骑手额外配送费
-                CommonInfo commonInfo = commonInfoService.findOne(443);
-                if (ObjectUtils.isNotEmpty(commonInfo)) {
-                    rider =rider.add(new BigDecimal(commonInfo.getValue()));
-                    shopMoney=shopMoney.subtract(rider);
-                }
-                log.info("订单id【{}】,骑手额外配送费【{}】", tbOrder.getOrderId(), rider);
-            }
-
-            // 添加商户用户钱包收入记录
-            UserMoneyDetails userMoneyDetails1 = new UserMoneyDetails();
-            userMoneyDetails1.setShopId(goodsShop.getShopId());
-            userMoneyDetails1.setTitle("商户订单收入,订单号:" + tbOrder.getOrderNumber());
-
-            // 订单金额
-            BigDecimal orderAmount = tbOrder.getPayMoney().add(couponMoney).add(activityDiscountAmount);
-            contentBuffer.append("订单金额:").append(orderAmount.setScale(2, BigDecimal.ROUND_DOWN));
-            if (!couponMoney.equals(BigDecimal.ZERO)) {
-                contentBuffer.append(",优惠券金额:").append(couponMoney.setScale(2, BigDecimal.ROUND_DOWN));
-            }
-            if (!activityDiscountAmount.equals(BigDecimal.ZERO)) {
-                contentBuffer.append(",活动优惠金额:").append(activityDiscountAmount.setScale(2, BigDecimal.ROUND_DOWN));
-            }
-
-            contentBuffer.append(",用户实付金额:").append(tbOrder.getPayMoney()).append(",其中");
-            if (tbOrder.getOrderType() != 1) {
-                contentBuffer.append("跑腿费:").append(errandMoney.setScale(2, BigDecimal.ROUND_DOWN)).append("(").append(tbOrder.getErrandMoneyIsShop()).append(")").append(",");
-            }
-
-            contentBuffer.append("特殊地址跑腿费:").append(tbOrder.getInsideDeliveryFee().setScale(2, BigDecimal.ROUND_DOWN)).append(",");
-            contentBuffer.append("平台服务费:").append(pingRate.setScale(2, BigDecimal.ROUND_DOWN)).append(",");
-            contentBuffer.append("短信服务费:").append(smsSendMoney.setScale(2, BigDecimal.ROUND_DOWN)).append(",");
-            if (ObjectUtils.isNotEmpty(tbOrder.getOrderTypeExtra())&&tbOrder.getOrderTypeExtra()==2) {
-                contentBuffer.append("骑手额外配送费:").append(rider.setScale(2, BigDecimal.ROUND_DOWN)).append(",");
-            }
-            contentBuffer.append("到账金额:").append(shopMoney.setScale(2, BigDecimal.ROUND_DOWN));
-
-            if (shopIds == 0) {
-                contentBuffer.append("(本单为平台优惠券,平台补贴)");
-            } else if (shopIds != -1) {
-                contentBuffer.append("(本单为商家优惠券,商家补贴)");
-            }
-
-            userMoneyDetails1.setContent(contentBuffer.toString());
-
-            userMoneyDetails1.setType(1);
-            userMoneyDetails1.setClassify(3);
-            userMoneyDetails1.setMoney(shopMoney);
-            DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
-            userMoneyDetails1.setCreateTime(LocalDateTime.now().format(dateTimeFormatter1));
-            userMoneyDetails1.setState(2);
-            userMoneyDetailsDao.insert(userMoneyDetails1);
-            log.info("订单id【{}】,商户用户钱包收入记录新增成功", tbOrder.getOrderId());
-
-            //将用户支付的金额转入商户用户钱包
-            userMoneyService.updateMoney(1, goodsShop.getUserId(), shopMoney.doubleValue());
-        }
+        orderService.accomplishOrder(1521747L, 2);
     }
 
     @Test