Jelajahi Sumber

1、领取优惠券(校验逻辑)
2、优惠券活动终止逻辑

skyline 1 tahun lalu
induk
melakukan
e7861ff767

+ 10 - 0
entity/src/main/java/com/kym/entity/admin/Coupon.java

@@ -97,6 +97,16 @@ public class Coupon extends BaseEntity {
      */
     private Integer quantity;
 
+    /**
+     * 已领取/发放数量
+     */
+    private Integer claimedQuantity;
+
+    /**
+     * 已使用数量
+     */
+    private Integer usedQuantity;
+
     /**
      * 活动状态:0-未开始,1-进行中,2-已结束,3-已取消
      */

+ 10 - 0
entity/src/main/java/com/kym/entity/admin/vo/CouponVo.java

@@ -75,6 +75,16 @@ public class CouponVo extends BaseEntity {
      */
     private Integer quantity;
 
+    /**
+     * 已领取/发放数量
+     */
+    private Integer claimedQuantity;
+
+    /**
+     * 已使用数量
+     */
+    private Integer usedQuantity;
+
     /**
      * 活动状态:0-未开始,1-进行中,2-已结束,3-已取消
      */

+ 2 - 0
entity/src/main/java/com/kym/entity/common/RedisKeys.java

@@ -14,4 +14,6 @@ public interface RedisKeys {
     String STATION_ID_TO_NAME = "STATION_ID_TO_NAME:";
     String CONNECTOR_ID_TO_PARKING_NO = "CONNECTOR_ID_TO_PARKING_NO:";
     String ADMIN_USER_STATION_IDS = "ADMIN_USER_STATION_IDS:";
+
+    String COUPON_ID_TO_USERS = "COUPON_ID_TO_USER_ID:";
 }

+ 3 - 2
entity/src/main/java/com/kym/entity/miniapp/UserCoupon.java

@@ -27,13 +27,14 @@ import java.time.LocalDateTime;
 @Accessors(chain = true)
 public class UserCoupon extends BaseEntity {
 
-
-
     private static final long serialVersionUID = 1L;
 
     public final static int STATUS_无效 = 0;
     public final static int STATUS_有效 = 1;
 
+    public static final int USAGE_STATUS_未使用 = 0;
+    public static final int USAGE_STATUS_已使用 = 1;
+
     /**
      * 优惠券id
      */

+ 3 - 1
mapper/src/main/resources/mappers/admin/CouponMapper.xml

@@ -17,6 +17,8 @@
         <result column="discount" property="discount" />
         <result column="allow_stacke" property="allowStacke" />
         <result column="quantity" property="quantity" />
+        <result column="claimed_quantity" property="claimedQuantity" />
+        <result column="used_quantity" property="usedQuantity" />
         <result column="status" property="status" />
         <result column="remark" property="remark" />
         <result column="create_time" property="createTime" />
@@ -25,7 +27,7 @@
 
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id,activity_id,name, coupon_desc, start_time, end_time, validity,receive_type,coupon_type, min_service_money, discount, allow_stacke, quantity, status, remark,create_time,update_time
+        id,activity_id,name, coupon_desc, start_time, end_time, validity,receive_type,coupon_type, min_service_money, discount, allow_stacke, quantity, claimed_quantity, used_quantity, status, remark,create_time,update_time
     </sql>
 
 </mapper>

+ 16 - 2
miniapp/src/main/java/com/kym/miniapp/controller/CouponController.java

@@ -1,9 +1,9 @@
 package com.kym.miniapp.controller;
 
 import com.kym.common.R;
-import com.kym.entity.admin.queryParams.CommonQueryParam;
 import com.kym.entity.admin.queryParams.CouponQueryParam;
 import com.kym.service.admin.CouponService;
+import com.kym.service.miniapp.UserCouponService;
 import org.springframework.web.bind.annotation.*;
 
 /**
@@ -20,8 +20,11 @@ public class CouponController {
 
     private final CouponService couponService;
 
-    public CouponController(CouponService couponService) {
+    private final UserCouponService userCouponService;
+
+    public CouponController(CouponService couponService, UserCouponService userCouponService) {
         this.couponService = couponService;
+        this.userCouponService = userCouponService;
     }
 
     /**
@@ -35,4 +38,15 @@ public class CouponController {
         return R.success(couponService.listRightsAndCoupons(params));
     }
 
+    /**
+     * 领取优惠券
+     *
+     * @param couponId
+     * @return
+     */
+    public R<?> collectCoupon(@RequestParam("couponId") Long couponId) {
+        userCouponService.collectCoupon(couponId);
+        return R.success();
+    }
+
 }

+ 42 - 12
service/src/main/java/com/kym/service/admin/impl/ActivityServiceImpl.java

@@ -4,7 +4,6 @@ import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.dynamic.datasource.annotation.DSTransactional;
 import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
 import com.github.pagehelper.PageHelper;
-import com.github.yulichang.base.MPJBaseServiceImpl;
 import com.kym.common.utils.CommUtil;
 import com.kym.common.utils.IDGenerator;
 import com.kym.entity.admin.*;
@@ -12,14 +11,17 @@ import com.kym.entity.admin.delay.DelayActivity;
 import com.kym.entity.admin.queryParams.ActivityQueryParam;
 import com.kym.entity.admin.vo.ActivityVo;
 import com.kym.entity.common.PageBean;
+import com.kym.entity.common.RedisKeys;
 import com.kym.entity.miniapp.UserRechargeRights;
 import com.kym.mapper.admin.ActivityMapper;
+import com.kym.service.MyBaseServiceImpl;
 import com.kym.service.admin.*;
 import com.kym.service.jobs.DelayService;
 import com.kym.service.miniapp.UserRechargeRightsService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.context.annotation.Lazy;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -43,27 +45,30 @@ import static com.kym.entity.admin.Activity.*;
 @Service
 @DS("db-admin")
 @Slf4j
-public class ActivityServiceImpl extends MPJBaseServiceImpl<ActivityMapper, Activity> implements ActivityService {
+public class ActivityServiceImpl extends MyBaseServiceImpl<ActivityMapper, Activity> implements ActivityService {
 
     private final ActivityStationService activityStationService;
     private final RechargeRightsService rechargeRightsService;
-
-    private final CouponService couponService;
     private final UserRechargeRightsService userRechargeRightsService;
     private final StationService stationService;
     private final DelayService<DelayActivity> activityDelayService;
     private final BannerService bannerService;
+    private final CouponService couponService;
+
+    private final RedisTemplate<String, String> redisTemplate;
 
-    public ActivityServiceImpl(ActivityStationService activityStationService, RechargeRightsService rechargeRightsService, CouponService couponService,
-                               UserRechargeRightsService userRechargeRightsService, StationService stationService,
-                               @Lazy DelayService<DelayActivity> activityDelayService, BannerService bannerService) {
+
+    public ActivityServiceImpl(ActivityStationService activityStationService, RechargeRightsService rechargeRightsService,
+                               UserRechargeRightsService userRechargeRightsService, StationService stationService, @Lazy DelayService<DelayActivity> activityDelayService,
+                               BannerService bannerService, CouponService couponService, RedisTemplate<String, String> redisTemplate) {
         this.activityStationService = activityStationService;
         this.rechargeRightsService = rechargeRightsService;
-        this.couponService = couponService;
         this.userRechargeRightsService = userRechargeRightsService;
         this.stationService = stationService;
         this.activityDelayService = activityDelayService;
         this.bannerService = bannerService;
+        this.couponService = couponService;
+        this.redisTemplate = redisTemplate;
     }
 
     /**
@@ -173,10 +178,35 @@ public class ActivityServiceImpl extends MPJBaseServiceImpl<ActivityMapper, Acti
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void terminateActivity(String activityId) {
-        lambdaUpdate().set(Activity::getStatus, Activity.STATUS_已结束).eq(Activity::getId, activityId).update();
-        rechargeRightsService.lambdaUpdate().set(RechargeRights::getStatus, Activity.STATUS_已结束).eq(RechargeRights::getActivityId, activityId).update();
-        // 修改banner状态为失效
-        bannerService.lambdaUpdate().set(Banner::getStatus, Banner.STATUS_有效).eq(Banner::getActivityId, activityId).update();
+        var activity = lambdaQuery().eq(Activity::getId, activityId).one();
+        if (activity != null) {
+            // 根据活动类型分别处理
+            if (activity.getDiscountType().equals(Activity.DISCOUNT_TYPE_服务费折扣权益)) {
+                lambdaUpdate().set(Activity::getStatus, Activity.STATUS_已结束).eq(Activity::getId, activityId).update();
+                rechargeRightsService.lambdaUpdate().set(RechargeRights::getStatus, Activity.STATUS_已结束).eq(RechargeRights::getActivityId, activityId).update();
+                // 修改banner状态为失效
+                bannerService.lambdaUpdate().set(Banner::getStatus, Banner.STATUS_有效).eq(Banner::getActivityId, activityId).update();
+            }
+            if (activity.getDiscountType().equals(Activity.DISCOUNT_TYPE_优惠券)) {
+                var couponList = couponService.lambdaQuery().eq(Coupon::getActivityId, activityId).list();
+                // 优惠券状态修改为已结束
+                if (CommUtil.isNotEmptyAndNull(couponList)) {
+                    couponService.updateBatchById(couponList.stream().peek(coupon -> coupon.setStatus(Activity.STATUS_已结束)).toList());
+
+                    // redis 优惠券失效,删除数据
+                    var userCoupons = redisTemplate.opsForSet().members(RedisKeys.COUPON_ID_TO_USERS);
+                    if (userCoupons != null) {
+                        couponList.forEach(coupon -> {
+                            // 筛选出活动相关的数据
+                            var coupon2user = userCoupons.stream().filter(userCoupon -> userCoupon.startsWith(String.valueOf(coupon.getId())));
+                            redisTemplate.opsForSet().remove(RedisKeys.COUPON_ID_TO_USERS, coupon2user.toArray());
+                        });
+                    }
+
+                }
+            }
+        }
+
     }
 
     /**

+ 2 - 0
service/src/main/java/com/kym/service/miniapp/UserCouponService.java

@@ -17,4 +17,6 @@ import com.kym.entity.miniapp.vo.UserCouponVo;
 public interface UserCouponService extends IService<UserCoupon> {
 
     PageBean<UserCouponVo> listUserCoupons(CouponQueryParam params);
+
+    void collectCoupon(Long couponId);
 }

+ 81 - 2
service/src/main/java/com/kym/service/miniapp/impl/UserCouponServiceImpl.java

@@ -1,21 +1,32 @@
 package com.kym.service.miniapp.impl;
 
+import cn.dev33.satoken.stp.StpUtil;
 import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
 import com.github.pagehelper.PageHelper;
 import com.github.yulichang.base.MPJBaseServiceImpl;
+import com.kym.common.exception.BusinessException;
 import com.kym.common.utils.CommUtil;
+import com.kym.entity.admin.Coupon;
 import com.kym.entity.admin.queryParams.CouponQueryParam;
 import com.kym.entity.common.PageBean;
+import com.kym.entity.common.RedisKeys;
 import com.kym.entity.miniapp.User;
 import com.kym.entity.miniapp.UserCoupon;
 import com.kym.entity.miniapp.vo.UserCouponVo;
 import com.kym.mapper.miniapp.UserCouponMapper;
+import com.kym.service.admin.ActivityService;
+import com.kym.service.admin.CouponService;
 import com.kym.service.miniapp.UserCouponService;
 import com.kym.service.miniapp.UserService;
+import com.kym.service.mq.producer.UserCouponSender;
 import org.springframework.beans.BeanUtils;
-import org.springframework.context.annotation.Lazy;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
 /**
  * <p>
  * 用户优惠券关联表 服务实现类
@@ -28,10 +39,22 @@ import org.springframework.stereotype.Service;
 @DS("db-miniapp")
 public class UserCouponServiceImpl extends MPJBaseServiceImpl<UserCouponMapper, UserCoupon> implements UserCouponService {
 
+    private final RedisTemplate<String, String> redisTemplate;
+
+    private final ActivityService activityService;
+
     private final UserService userService;
 
-    public UserCouponServiceImpl(@Lazy UserService userService) {
+    private final CouponService couponService;
+
+    private final UserCouponSender userCouponSender;
+
+    public UserCouponServiceImpl(RedisTemplate<String, String> redisTemplate, ActivityService activityService, UserService userService, CouponService couponService, UserCouponSender userCouponSender) {
+        this.redisTemplate = redisTemplate;
+        this.activityService = activityService;
         this.userService = userService;
+        this.couponService = couponService;
+        this.userCouponSender = userCouponSender;
     }
 
     @Override
@@ -60,4 +83,60 @@ public class UserCouponServiceImpl extends MPJBaseServiceImpl<UserCouponMapper,
         var res = new PageBean<>(voList);
         return new PageBean<>(voList).setList(res);
     }
+
+    @Override
+    public void collectCoupon(Long couponId) {
+        // 手动切换数据源
+        DynamicDataSourceContextHolder.push("db-admin");
+        // 优惠券状态校验
+        var coupon = couponService.lambdaQuery()
+                .eq(Coupon::getId, couponId)
+                .eq(Coupon::getStatus, Coupon.STATUS_有效)
+                .one();
+
+        if (coupon == null) {
+            DynamicDataSourceContextHolder.poll();
+            throw new BusinessException("优惠券不存在或已失效!");
+        }
+
+        if (coupon.getClaimedQuantity() >= coupon.getQuantity()) {
+            DynamicDataSourceContextHolder.poll();
+            throw new BusinessException("优惠券已领完!");
+        }
+
+        // 重复领取校验
+        var userId = StpUtil.getLoginIdAsLong();
+        var exist = redisTemplate.opsForSet().isMember(RedisKeys.COUPON_ID_TO_USERS, couponId + ":" + userId);
+        if (Boolean.TRUE.equals(exist)) {
+            DynamicDataSourceContextHolder.poll();
+            throw new BusinessException("优惠券已领取,请勿重复领取!");
+        }
+
+        // 活动
+        var activity = activityService.getById(coupon.getActivityId());
+        DynamicDataSourceContextHolder.poll();
+
+        // 存入缓存和数据库
+        redisTemplate.opsForSet().add(RedisKeys.COUPON_ID_TO_USERS, couponId + ":" + userId);
+
+        var userCoupon = new UserCoupon();
+        userCoupon.setActivityId(coupon.getActivityId());
+        userCoupon.setActivityName(activity.getName());
+        userCoupon.setCouponId(coupon.getId());
+        userCoupon.setCouponName(coupon.getName());
+        userCoupon.setUserId(userId);
+        userCoupon.setStartTime(LocalDateTime.now());
+        userCoupon.setEndTime(LocalDateTime.now().with(LocalTime.MAX).plusDays(coupon.getValidity() - 1));
+        userCoupon.setValidity(coupon.getValidity());
+        userCoupon.setCouponType(coupon.getCouponType());
+        userCoupon.setDiscount(coupon.getDiscount());
+        userCoupon.setMinServiceMoney(coupon.getMinServiceMoney());
+        userCoupon.setAllowStacke(coupon.getAllowStacke());
+        userCoupon.setRemark(coupon.getRemark());
+//        userCouponSender.sendMessage(userCoupon);
+        save(userCoupon);
+
+        // todo 优惠券活动终止时处理redis数据,优惠券失效或者使用之后该做什么操作?
+    }
+
 }

+ 13 - 13
service/src/main/java/com/kym/service/miniapp/impl/UserServiceImpl.java

@@ -32,13 +32,13 @@ import lombok.SneakyThrows;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDateTime;
 import java.util.List;
-import java.util.Map;
 import java.util.stream.Collectors;
 
 import static java.util.Map.of;
@@ -56,27 +56,28 @@ import static java.util.Map.of;
 public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implements UserService {
     private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);
     private final WxConfig wxConfig;
-    private final AccountService accountService;
     private final RefundLogService refundLogService;
     private final CarsService carsService;
     private final UserRechargeRightsService userRechargeRightsService;
     private final RechargeRightsService rechargeRightsService;
-    private final ActivityService activityService;
     private final BannerService bannerService;
 
+    private final AccountService accountService;
+    private final ActivityService activityService;
     private final UserCouponService userCouponService;
 
-    public UserServiceImpl(WxConfig wxConfig, AccountService accountService, RefundLogService refundLogService,
+    public UserServiceImpl(WxConfig wxConfig, RefundLogService refundLogService,
                            CarsService carsService, UserRechargeRightsService userRechargeRightsService,
-                           RechargeRightsService rechargeRightsService, @Lazy ActivityService activityService, BannerService bannerService, UserCouponService userCouponService) {
+                           RechargeRightsService rechargeRightsService, BannerService bannerService,
+                           @Lazy AccountService accountService, @Lazy ActivityService activityService,@Lazy UserCouponService userCouponService) {
         this.wxConfig = wxConfig;
-        this.accountService = accountService;
         this.refundLogService = refundLogService;
         this.carsService = carsService;
         this.userRechargeRightsService = userRechargeRightsService;
         this.rechargeRightsService = rechargeRightsService;
-        this.activityService = activityService;
         this.bannerService = bannerService;
+        this.accountService = accountService;
+        this.activityService = activityService;
         this.userCouponService = userCouponService;
     }
 
@@ -86,7 +87,7 @@ public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implem
     @Override
     public R<?> wxLogin(WxLoginParams params) {
         // 微信登录
-        var json = HttpUtil.getJson(WxApi.WX_MP_LOGIN.getApi(), Map.of(
+        var json = HttpUtil.getJson(WxApi.WX_MP_LOGIN.getApi(), of(
                 "appid", wxConfig.getAppid(),
                 "secret", wxConfig.getSecret(),
                 "js_code", params.getCode()
@@ -110,9 +111,9 @@ public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implem
                 newUser.setUnionid(unionid);
                 // 手机号解密:先获取access_token,再请求手机号信息
                 // access_token获取
-                var accessTokenJson = HttpUtil.getJson(WxApi.WX_GET_ACCESS_TOKEN.getApi(), Map.of("appid", wxConfig.getAppid(), "secret", wxConfig.getSecret()));
+                var accessTokenJson = HttpUtil.getJson(WxApi.WX_GET_ACCESS_TOKEN.getApi(), of("appid", wxConfig.getAppid(), "secret", wxConfig.getSecret()));
                 var accessToken = accessTokenJson.getString("access_token");
-                var wxPhoneNum = HttpUtil.post(WxApi.WX_MP_GET_PHONE.getApi().replace("ACCESS_TOKEN", accessToken), Map.of("code", params.getPhoneCode()), WxPhoneNum.class);
+                var wxPhoneNum = HttpUtil.post(WxApi.WX_MP_GET_PHONE.getApi().replace("ACCESS_TOKEN", accessToken), of("code", params.getPhoneCode()), WxPhoneNum.class);
                 var mobilePhone = wxPhoneNum.getPhone_info().getPurePhoneNumber();
                 newUser.setMobilePhone(mobilePhone);
                 newUser.setUsername(mobilePhone);
@@ -217,7 +218,7 @@ public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implem
                 car.setVin(userVo.getVin());
             }
             // 将用户名下其他车辆设为非默认
-            var cars = carsService.listByMap(Map.of("user_id", userVo.getId()));
+            var cars = carsService.listByMap(of("user_id", userVo.getId()));
             cars.stream().filter(c -> !userVo.getDefaultPlateNo().equals(c.getEngineNo())).peek(s -> s.setIsDefault(false)).collect(Collectors.toList());
             carsService.updateBatchById(cars);
             var wrapper = new QueryWrapper<Cars>();
@@ -250,7 +251,7 @@ public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implem
         if (!CommUtil.isEmptyOrNull(params.getMobilePhone())) {
             userIds = lambdaQuery().
                     eq(User::getMobilePhone, params.getMobilePhone())
-                    .eq(!CommUtil.isEmptyOrNull(params.getStatus()),User::getStatus, params.getStatus()).list().stream().map(User::getId).toList();
+                    .eq(!CommUtil.isEmptyOrNull(params.getStatus()), User::getStatus, params.getStatus()).list().stream().map(User::getId).toList();
         }
         PageHelper.startPage(params.getPageNum(), params.getPageSize());
         var result = baseMapper.listUser(userIds);
@@ -277,5 +278,4 @@ public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implem
         return page;
     }
 
-
 }