并入后台管理端(vue)和小程序端的代码(template-10550)
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
package com.gxwebsoft.shop.controller;
|
||||
|
||||
import com.gxwebsoft.common.core.annotation.OperationLog;
|
||||
import com.gxwebsoft.common.core.web.ApiResult;
|
||||
import com.gxwebsoft.common.core.web.BaseController;
|
||||
import com.gxwebsoft.common.system.entity.User;
|
||||
import com.gxwebsoft.shop.entity.ShopUserCoupon;
|
||||
import com.gxwebsoft.shop.service.CouponBusinessService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 优惠券业务控制器
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 22:30:00
|
||||
*/
|
||||
@Tag(name = "优惠券业务管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/shop/coupon-business")
|
||||
public class CouponBusinessController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private CouponBusinessService couponBusinessService;
|
||||
|
||||
@Operation(summary = "获取订单可用优惠券")
|
||||
@PostMapping("/available-for-order")
|
||||
public ApiResult<List<ShopUserCoupon>> getAvailableCouponsForOrder(
|
||||
@RequestBody Map<String, Object> orderData) {
|
||||
User loginUser = getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return fail("请先登录", null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> goodsItems = (List<Map<String, Object>>) orderData.get("goodsItems");
|
||||
BigDecimal totalAmount = new BigDecimal(orderData.get("totalAmount").toString());
|
||||
|
||||
List<ShopUserCoupon> coupons = couponBusinessService.getAvailableCouponsForOrder(
|
||||
loginUser.getUserId(), goodsItems, totalAmount);
|
||||
|
||||
return success(coupons);
|
||||
}
|
||||
|
||||
@Operation(summary = "计算使用优惠券后的订单金额")
|
||||
@PostMapping("/calculate-order-amount")
|
||||
public ApiResult<Map<String, Object>> calculateOrderAmountWithCoupon(
|
||||
@RequestParam Long userCouponId,
|
||||
@RequestBody Map<String, Object> orderData) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> goodsItems = (List<Map<String, Object>>) orderData.get("goodsItems");
|
||||
BigDecimal totalAmount = new BigDecimal(orderData.get("totalAmount").toString());
|
||||
|
||||
Map<String, Object> result = couponBusinessService.calculateOrderAmountWithCoupon(
|
||||
userCouponId, goodsItems, totalAmount);
|
||||
|
||||
return success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "验证优惠券是否可用于订单")
|
||||
@PostMapping("/validate-for-order")
|
||||
public ApiResult<Map<String, Object>> validateCouponForOrder(
|
||||
@RequestParam Long userCouponId,
|
||||
@RequestBody Map<String, Object> orderData) {
|
||||
User loginUser = getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return fail("请先登录", null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> goodsItems = (List<Map<String, Object>>) orderData.get("goodsItems");
|
||||
BigDecimal totalAmount = new BigDecimal(orderData.get("totalAmount").toString());
|
||||
|
||||
Map<String, Object> result = couponBusinessService.validateCouponForOrder(
|
||||
userCouponId, loginUser.getUserId(), goodsItems, totalAmount);
|
||||
|
||||
return success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "推荐最优优惠券组合")
|
||||
@PostMapping("/recommend-best-combination")
|
||||
public ApiResult<Map<String, Object>> recommendBestCouponCombination(
|
||||
@RequestBody Map<String, Object> orderData) {
|
||||
User loginUser = getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return fail("请先登录", null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> goodsItems = (List<Map<String, Object>>) orderData.get("goodsItems");
|
||||
BigDecimal totalAmount = new BigDecimal(orderData.get("totalAmount").toString());
|
||||
|
||||
Map<String, Object> result = couponBusinessService.recommendBestCouponCombination(
|
||||
loginUser.getUserId(), goodsItems, totalAmount);
|
||||
|
||||
return success(result);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:coupon:manage')")
|
||||
@OperationLog
|
||||
@Operation(summary = "为新用户发放欢迎优惠券")
|
||||
@PostMapping("/issue-welcome/{userId}")
|
||||
public ApiResult<?> issueWelcomeCoupons(@PathVariable Integer userId) {
|
||||
int count = couponBusinessService.issueWelcomeCoupons(userId);
|
||||
return success("成功发放" + count + "张欢迎优惠券");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:coupon:manage')")
|
||||
@OperationLog
|
||||
@Operation(summary = "为用户发放生日优惠券")
|
||||
@PostMapping("/issue-birthday/{userId}")
|
||||
public ApiResult<?> issueBirthdayCoupons(@PathVariable Integer userId) {
|
||||
int count = couponBusinessService.issueBirthdayCoupons(userId);
|
||||
return success("成功发放" + count + "张生日优惠券");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:coupon:manage')")
|
||||
@OperationLog
|
||||
@Operation(summary = "根据消费金额发放优惠券")
|
||||
@PostMapping("/issue-consume/{userId}")
|
||||
public ApiResult<?> issueConsumeCoupons(@PathVariable Integer userId,
|
||||
@RequestParam BigDecimal consumeAmount) {
|
||||
int count = couponBusinessService.issueConsumeCoupons(userId, consumeAmount);
|
||||
return success("成功发放" + count + "张消费返券");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:coupon:manage')")
|
||||
@OperationLog
|
||||
@Operation(summary = "批量发放活动优惠券")
|
||||
@PostMapping("/batch-issue-activity")
|
||||
public ApiResult<Map<String, Object>> batchIssueActivityCoupons(
|
||||
@RequestParam String activityName,
|
||||
@RequestParam List<Integer> couponIds,
|
||||
@RequestParam(required = false) List<Integer> userIds) {
|
||||
|
||||
Map<String, Object> result = couponBusinessService.batchIssueActivityCoupons(
|
||||
activityName, couponIds, userIds);
|
||||
|
||||
return success(result);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:coupon:statistics')")
|
||||
@Operation(summary = "获取优惠券使用统计")
|
||||
@GetMapping("/usage-statistics")
|
||||
public ApiResult<Map<String, Object>> getCouponUsageStatistics(
|
||||
@RequestParam(required = false) String startDate,
|
||||
@RequestParam(required = false) String endDate) {
|
||||
|
||||
Map<String, Object> statistics = couponBusinessService.getCouponUsageStatistics(startDate, endDate);
|
||||
return success(statistics);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:coupon:manage')")
|
||||
@OperationLog
|
||||
@Operation(summary = "手动处理过期优惠券")
|
||||
@PostMapping("/process-expired")
|
||||
public ApiResult<?> processExpiredCoupons() {
|
||||
int count = couponBusinessService.autoProcessExpiredCoupons();
|
||||
return success("处理了" + count + "张过期优惠券");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:coupon:manage')")
|
||||
@OperationLog
|
||||
@Operation(summary = "发送优惠券到期提醒")
|
||||
@PostMapping("/send-expiry-reminder")
|
||||
public ApiResult<?> sendExpiryReminder(@RequestParam(defaultValue = "3") Integer days) {
|
||||
int count = couponBusinessService.sendCouponExpiryReminder(days);
|
||||
return success("向" + count + "个用户发送了到期提醒");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.gxwebsoft.shop.controller;
|
||||
|
||||
import com.gxwebsoft.common.core.annotation.OperationLog;
|
||||
import com.gxwebsoft.common.core.web.ApiResult;
|
||||
import com.gxwebsoft.common.core.web.BaseController;
|
||||
import com.gxwebsoft.common.core.web.BatchParam;
|
||||
import com.gxwebsoft.common.core.web.PageResult;
|
||||
import com.gxwebsoft.common.system.entity.User;
|
||||
import com.gxwebsoft.shop.entity.ShopCoupon;
|
||||
import com.gxwebsoft.shop.param.ShopCouponParam;
|
||||
import com.gxwebsoft.shop.service.ShopCouponService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 优惠券控制器
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 21:10:55
|
||||
*/
|
||||
@Tag(name = "优惠券管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/shop/shop-coupon")
|
||||
public class ShopCouponController extends BaseController {
|
||||
@Resource
|
||||
private ShopCouponService shopCouponService;
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:shopCoupon:list')")
|
||||
@Operation(summary = "批量修改优惠券")
|
||||
@GetMapping("/page")
|
||||
public ApiResult<PageResult<ShopCoupon>> page(ShopCouponParam param) {
|
||||
// 使用关联查询
|
||||
return success(shopCouponService.pageRel(param));
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:shopCoupon:list')")
|
||||
@Operation(summary = "查询全部优惠券")
|
||||
@GetMapping()
|
||||
public ApiResult<List<ShopCoupon>> list(ShopCouponParam param) {
|
||||
// 使用关联查询
|
||||
return success(shopCouponService.listRel(param));
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:shopCoupon:list')")
|
||||
@Operation(summary = "根据id查询优惠券")
|
||||
@GetMapping("/{id}")
|
||||
public ApiResult<ShopCoupon> get(@PathVariable("id") Integer id) {
|
||||
// 使用关联查询
|
||||
return success(shopCouponService.getByIdRel(id));
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:shopCoupon:save')")
|
||||
@OperationLog
|
||||
@Operation(summary = "添加优惠券")
|
||||
@PostMapping()
|
||||
public ApiResult<?> save(@RequestBody ShopCoupon shopCoupon) {
|
||||
// 记录当前登录用户id
|
||||
User loginUser = getLoginUser();
|
||||
if (loginUser != null) {
|
||||
shopCoupon.setUserId(loginUser.getUserId());
|
||||
}
|
||||
if (shopCouponService.save(shopCoupon)) {
|
||||
return success("添加成功");
|
||||
}
|
||||
return fail("添加失败");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:shopCoupon:update')")
|
||||
@OperationLog
|
||||
@Operation(summary = "修改优惠券")
|
||||
@PutMapping()
|
||||
public ApiResult<?> update(@RequestBody ShopCoupon shopCoupon) {
|
||||
if (shopCouponService.updateById(shopCoupon)) {
|
||||
return success("修改成功");
|
||||
}
|
||||
return fail("修改失败");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:shopCoupon:remove')")
|
||||
@OperationLog
|
||||
@Operation(summary = "删除优惠券")
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResult<?> remove(@PathVariable("id") Integer id) {
|
||||
if (shopCouponService.removeById(id)) {
|
||||
return success("删除成功");
|
||||
}
|
||||
return fail("删除失败");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:shopCoupon:save')")
|
||||
@OperationLog
|
||||
@Operation(summary = "批量添加优惠券")
|
||||
@PostMapping("/batch")
|
||||
public ApiResult<?> saveBatch(@RequestBody List<ShopCoupon> list) {
|
||||
if (shopCouponService.saveBatch(list)) {
|
||||
return success("添加成功");
|
||||
}
|
||||
return fail("添加失败");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:shopCoupon:update')")
|
||||
@Operation(summary = "批量修改优惠券")
|
||||
@PutMapping("/batch")
|
||||
public ApiResult<?> removeBatch(@RequestBody BatchParam<ShopCoupon> batchParam) {
|
||||
if (batchParam.update(shopCouponService, "id")) {
|
||||
return success("修改成功");
|
||||
}
|
||||
return fail("修改失败");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:shopCoupon:remove')")
|
||||
@Operation(summary = "批量删除优惠券")
|
||||
@DeleteMapping("/batch")
|
||||
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
|
||||
if (shopCouponService.removeByIds(ids)) {
|
||||
return success("删除成功");
|
||||
}
|
||||
return fail("删除失败");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package com.gxwebsoft.shop.controller;
|
||||
|
||||
import com.gxwebsoft.common.core.annotation.OperationLog;
|
||||
import com.gxwebsoft.common.core.web.ApiResult;
|
||||
import com.gxwebsoft.common.core.web.BaseController;
|
||||
import com.gxwebsoft.common.core.web.PageResult;
|
||||
import com.gxwebsoft.common.system.entity.User;
|
||||
import com.gxwebsoft.shop.entity.ShopUserCoupon;
|
||||
import com.gxwebsoft.shop.param.ShopUserCouponParam;
|
||||
import com.gxwebsoft.shop.service.ShopUserCouponService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 用户优惠券控制器
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 21:30:00
|
||||
*/
|
||||
@Tag(name = "用户优惠券管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/shop/user-coupon")
|
||||
public class ShopUserCouponController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private ShopUserCouponService shopUserCouponService;
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:userCoupon:list')")
|
||||
@Operation(summary = "分页查询用户优惠券")
|
||||
@GetMapping("/page")
|
||||
public ApiResult<PageResult<ShopUserCoupon>> page(ShopUserCouponParam param) {
|
||||
return success(shopUserCouponService.pageRel(param));
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:userCoupon:list')")
|
||||
@Operation(summary = "查询用户优惠券列表")
|
||||
@GetMapping()
|
||||
public ApiResult<List<ShopUserCoupon>> list(ShopUserCouponParam param) {
|
||||
return success(shopUserCouponService.listRel(param));
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:userCoupon:list')")
|
||||
@Operation(summary = "根据id查询用户优惠券")
|
||||
@GetMapping("/{id}")
|
||||
public ApiResult<ShopUserCoupon> get(@PathVariable("id") Long id) {
|
||||
return success(shopUserCouponService.getByIdRel(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取当前用户的优惠券列表")
|
||||
@GetMapping("/my")
|
||||
public ApiResult<List<ShopUserCoupon>> getMyCoupons(ShopUserCouponParam param) {
|
||||
User loginUser = getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return fail("请先登录",null);
|
||||
}
|
||||
param.setUserId(loginUser.getUserId());
|
||||
return success(shopUserCouponService.listRel(param));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取当前用户可用的优惠券")
|
||||
@GetMapping("/my/available")
|
||||
public ApiResult<List<ShopUserCoupon>> getMyAvailableCoupons(
|
||||
@RequestParam(required = false) Integer goodsId,
|
||||
@RequestParam(required = false) Integer categoryId,
|
||||
@RequestParam(required = false) BigDecimal orderAmount) {
|
||||
User loginUser = getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return fail("请先登录",null);
|
||||
}
|
||||
|
||||
List<ShopUserCoupon> coupons = shopUserCouponService.getAvailableCoupons(
|
||||
loginUser.getUserId(), goodsId, categoryId, orderAmount);
|
||||
return success(coupons);
|
||||
}
|
||||
|
||||
@Operation(summary = "统计当前用户优惠券数量")
|
||||
@GetMapping("/my/count")
|
||||
public ApiResult<Map<String, Object>> getMyCount() {
|
||||
User loginUser = getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return fail("请先登录",null);
|
||||
}
|
||||
|
||||
Map<String, Object> count = shopUserCouponService.countUserCoupons(loginUser.getUserId());
|
||||
return success(count);
|
||||
}
|
||||
|
||||
@OperationLog
|
||||
@Operation(summary = "领取优惠券")
|
||||
@PostMapping("/receive/{couponId}")
|
||||
public ApiResult<?> receiveCoupon(@PathVariable("couponId") Integer couponId) {
|
||||
User loginUser = getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return fail("请先登录",null);
|
||||
}
|
||||
|
||||
// 检查是否可以领取
|
||||
Map<String, Object> checkResult = shopUserCouponService.checkCanReceiveCoupon(
|
||||
loginUser.getUserId(), couponId);
|
||||
|
||||
if (!(Boolean) checkResult.get("canReceive")) {
|
||||
return fail(checkResult.get("reason").toString());
|
||||
}
|
||||
|
||||
boolean success = shopUserCouponService.receiveCoupon(loginUser.getUserId(), couponId);
|
||||
if (success) {
|
||||
return success("领取成功");
|
||||
}
|
||||
return fail("领取失败");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:userCoupon:save')")
|
||||
@OperationLog
|
||||
@Operation(summary = "系统发放优惠券给用户")
|
||||
@PostMapping("/issue")
|
||||
public ApiResult<?> issueCoupon(@RequestParam Integer userId,
|
||||
@RequestParam Integer couponId,
|
||||
@RequestParam(required = false) String source) {
|
||||
boolean success = shopUserCouponService.issueCouponToUser(userId, couponId, source);
|
||||
if (success) {
|
||||
return success("发放成功");
|
||||
}
|
||||
return fail("发放失败");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:userCoupon:save')")
|
||||
@OperationLog
|
||||
@Operation(summary = "批量发放优惠券")
|
||||
@PostMapping("/batch-issue")
|
||||
public ApiResult<?> batchIssueCoupons(@RequestParam List<Integer> userIds,
|
||||
@RequestParam Integer couponId,
|
||||
@RequestParam(required = false) String source) {
|
||||
int successCount = shopUserCouponService.batchIssueCoupons(userIds, couponId, source);
|
||||
return success("成功发放" + successCount + "张优惠券");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:userCoupon:update')")
|
||||
@OperationLog
|
||||
@Operation(summary = "使用优惠券")
|
||||
@PutMapping("/use")
|
||||
public ApiResult<?> useCoupon(@RequestParam Long userCouponId,
|
||||
@RequestParam Long orderId,
|
||||
@RequestParam String orderNo) {
|
||||
boolean success = shopUserCouponService.useCoupon(userCouponId, orderId, orderNo);
|
||||
if (success) {
|
||||
return success("使用成功");
|
||||
}
|
||||
return fail("使用失败");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:userCoupon:update')")
|
||||
@OperationLog
|
||||
@Operation(summary = "退还优惠券")
|
||||
@PutMapping("/return/{orderId}")
|
||||
public ApiResult<?> returnCoupon(@PathVariable("orderId") Long orderId) {
|
||||
boolean success = shopUserCouponService.returnCoupon(orderId);
|
||||
if (success) {
|
||||
return success("退还成功");
|
||||
}
|
||||
return fail("退还失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "计算优惠券优惠金额")
|
||||
@PostMapping("/calculate-discount")
|
||||
public ApiResult<BigDecimal> calculateDiscount(@RequestParam Long userCouponId,
|
||||
@RequestParam BigDecimal orderAmount) {
|
||||
ShopUserCoupon userCoupon = shopUserCouponService.getById(userCouponId);
|
||||
if (userCoupon == null) {
|
||||
return fail("优惠券不存在",null);
|
||||
}
|
||||
|
||||
BigDecimal discountAmount = shopUserCouponService.calculateDiscountAmount(userCoupon, orderAmount);
|
||||
return success(discountAmount);
|
||||
}
|
||||
|
||||
@Operation(summary = "验证优惠券是否可用于指定商品")
|
||||
@GetMapping("/validate")
|
||||
public ApiResult<Boolean> validateCoupon(@RequestParam Long userCouponId,
|
||||
@RequestParam(required = false) Integer goodsId,
|
||||
@RequestParam(required = false) Integer categoryId) {
|
||||
ShopUserCoupon userCoupon = shopUserCouponService.getById(userCouponId);
|
||||
if (userCoupon == null) {
|
||||
return fail("优惠券不存在",null);
|
||||
}
|
||||
|
||||
boolean valid = shopUserCouponService.validateCouponForGoods(userCoupon, goodsId, categoryId);
|
||||
return success(valid);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:userCoupon:update')")
|
||||
@OperationLog
|
||||
@Operation(summary = "更新过期优惠券状态")
|
||||
@PutMapping("/update-expired")
|
||||
public ApiResult<?> updateExpiredCoupons() {
|
||||
int count = shopUserCouponService.updateExpiredCoupons();
|
||||
return success("更新了" + count + "张过期优惠券");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:userCoupon:list')")
|
||||
@Operation(summary = "获取即将过期的优惠券")
|
||||
@GetMapping("/expiring-soon")
|
||||
public ApiResult<List<ShopUserCoupon>> getExpiringSoonCoupons(
|
||||
@RequestParam(defaultValue = "3") Integer days) {
|
||||
List<ShopUserCoupon> coupons = shopUserCouponService.getExpiringSoonCoupons(days);
|
||||
return success(coupons);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:userCoupon:remove')")
|
||||
@OperationLog
|
||||
@Operation(summary = "删除用户优惠券")
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResult<?> remove(@PathVariable("id") Long id) {
|
||||
if (shopUserCouponService.removeById(id)) {
|
||||
return success("删除成功");
|
||||
}
|
||||
return fail("删除失败");
|
||||
}
|
||||
}
|
||||
103
src/main/java/com/gxwebsoft/shop/entity/ShopCoupon.java
Normal file
103
src/main/java/com/gxwebsoft/shop/entity/ShopCoupon.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package com.gxwebsoft.shop.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 优惠券模板
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 21:10:55
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("shop_coupon")
|
||||
@Schema(name = "ShopCoupon对象", description = "优惠券模板")
|
||||
public class ShopCoupon implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "id")
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
@Schema(description = "优惠券名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "优惠券描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "优惠券类型(10满减券 20折扣券 30免费劵)")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "满减券-减免金额")
|
||||
private BigDecimal reducePrice;
|
||||
|
||||
@Schema(description = "折扣券-折扣率(0-100)")
|
||||
private Integer discount;
|
||||
|
||||
@Schema(description = "最低消费金额")
|
||||
private BigDecimal minPrice;
|
||||
|
||||
@Schema(description = "发放总数量(-1表示无限制)")
|
||||
private Integer totalCount;
|
||||
|
||||
@Schema(description = "已发放数量")
|
||||
private Integer issuedCount;
|
||||
|
||||
@Schema(description = "每人限领数量(-1表示无限制)")
|
||||
private Integer limitPerUser;
|
||||
|
||||
@Schema(description = "到期类型(10领取后生效 20固定时间)")
|
||||
private Integer expireType;
|
||||
|
||||
@Schema(description = "领取后生效-有效天数")
|
||||
private Integer expireDay;
|
||||
|
||||
@Schema(description = "有效期开始时间")
|
||||
private LocalDate startTime;
|
||||
|
||||
@Schema(description = "有效期结束时间")
|
||||
private LocalDate endTime;
|
||||
|
||||
@Schema(description = "适用范围(10全部商品 20指定商品 30指定分类)")
|
||||
private Integer applyRange;
|
||||
|
||||
@Schema(description = "适用范围配置(json格式)")
|
||||
private String applyRangeConfig;
|
||||
|
||||
@Schema(description = "是否启用(0禁用 1启用)")
|
||||
private Integer enabled;
|
||||
|
||||
@Schema(description = "排序(数字越小越靠前)")
|
||||
private Integer sortNumber;
|
||||
|
||||
@Schema(description = "状态, 0正常, 1禁用")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "是否删除, 0否, 1是")
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
|
||||
@Schema(description = "创建用户ID")
|
||||
private Integer userId;
|
||||
|
||||
@Schema(description = "租户id")
|
||||
private Integer tenantId;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private Date createTime;
|
||||
|
||||
@Schema(description = "修改时间")
|
||||
private Date updateTime;
|
||||
|
||||
}
|
||||
142
src/main/java/com/gxwebsoft/shop/entity/ShopUserCoupon.java
Normal file
142
src/main/java/com/gxwebsoft/shop/entity/ShopUserCoupon.java
Normal file
@@ -0,0 +1,142 @@
|
||||
package com.gxwebsoft.shop.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 用户优惠券
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 21:30:00
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("shop_user_coupon")
|
||||
@Schema(name = "ShopUserCoupon对象", description = "用户优惠券")
|
||||
public class ShopUserCoupon implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "id")
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "优惠券模板ID")
|
||||
private Integer couponId;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Integer userId;
|
||||
|
||||
@Schema(description = "优惠券名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "优惠券描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "优惠券类型(10满减券 20折扣券 30免费劵)")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "满减券-减免金额")
|
||||
private BigDecimal reducePrice;
|
||||
|
||||
@Schema(description = "折扣券-折扣率(0-100)")
|
||||
private Integer discount;
|
||||
|
||||
@Schema(description = "最低消费金额")
|
||||
private BigDecimal minPrice;
|
||||
|
||||
@Schema(description = "适用范围(10全部商品 20指定商品 30指定分类)")
|
||||
private Integer applyRange;
|
||||
|
||||
@Schema(description = "适用范围配置(json格式)")
|
||||
private String applyRangeConfig;
|
||||
|
||||
@Schema(description = "有效期开始时间")
|
||||
private LocalDateTime startTime;
|
||||
|
||||
@Schema(description = "有效期结束时间")
|
||||
private LocalDateTime endTime;
|
||||
|
||||
@Schema(description = "使用状态(0未使用 1已使用 2已过期)")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "使用时间")
|
||||
private LocalDateTime useTime;
|
||||
|
||||
@Schema(description = "使用订单ID")
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description = "使用订单号")
|
||||
private String orderNo;
|
||||
|
||||
@Schema(description = "获取方式(10主动领取 20系统发放 30活动赠送)")
|
||||
private Integer obtainType;
|
||||
|
||||
@Schema(description = "获取来源描述")
|
||||
private String obtainSource;
|
||||
|
||||
@Schema(description = "是否删除, 0否, 1是")
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
|
||||
@Schema(description = "租户id")
|
||||
private Integer tenantId;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private Date createTime;
|
||||
|
||||
@Schema(description = "修改时间")
|
||||
private Date updateTime;
|
||||
|
||||
// 优惠券状态常量
|
||||
public static final int STATUS_UNUSED = 0; // 未使用
|
||||
public static final int STATUS_USED = 1; // 已使用
|
||||
public static final int STATUS_EXPIRED = 2; // 已过期
|
||||
|
||||
// 优惠券类型常量
|
||||
public static final int TYPE_REDUCE = 10; // 满减券
|
||||
public static final int TYPE_DISCOUNT = 20; // 折扣券
|
||||
public static final int TYPE_FREE = 30; // 免费券
|
||||
|
||||
// 适用范围常量
|
||||
public static final int APPLY_ALL = 10; // 全部商品
|
||||
public static final int APPLY_GOODS = 20; // 指定商品
|
||||
public static final int APPLY_CATEGORY = 30; // 指定分类
|
||||
|
||||
// 获取方式常量
|
||||
public static final int OBTAIN_RECEIVE = 10; // 主动领取
|
||||
public static final int OBTAIN_SYSTEM = 20; // 系统发放
|
||||
public static final int OBTAIN_ACTIVITY = 30; // 活动赠送
|
||||
|
||||
/**
|
||||
* 检查优惠券是否已过期
|
||||
*/
|
||||
public boolean isExpired() {
|
||||
return this.endTime != null && this.endTime.isBefore(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查优惠券是否可用
|
||||
*/
|
||||
public boolean isAvailable() {
|
||||
return this.status == STATUS_UNUSED && !isExpired();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查优惠券是否在有效期内
|
||||
*/
|
||||
public boolean isInValidPeriod() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
return (this.startTime == null || !this.startTime.isAfter(now)) &&
|
||||
(this.endTime == null || !this.endTime.isBefore(now));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.gxwebsoft.shop.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.gxwebsoft.shop.entity.ShopCoupon;
|
||||
import com.gxwebsoft.shop.param.ShopCouponParam;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 优惠券Mapper
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 21:10:55
|
||||
*/
|
||||
public interface ShopCouponMapper extends BaseMapper<ShopCoupon> {
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param param 查询参数
|
||||
* @return List<ShopCoupon>
|
||||
*/
|
||||
List<ShopCoupon> selectPageRel(@Param("page") IPage<ShopCoupon> page,
|
||||
@Param("param") ShopCouponParam param);
|
||||
|
||||
/**
|
||||
* 查询全部
|
||||
*
|
||||
* @param param 查询参数
|
||||
* @return List<User>
|
||||
*/
|
||||
List<ShopCoupon> selectListRel(@Param("param") ShopCouponParam param);
|
||||
|
||||
/**
|
||||
* 检查用户是否已领取指定优惠券
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param couponId 优惠券模板ID
|
||||
* @return 已领取数量
|
||||
*/
|
||||
int countUserReceivedCoupon(@Param("userId") Integer userId, @Param("couponId") Integer couponId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.gxwebsoft.shop.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.gxwebsoft.shop.entity.ShopUserCoupon;
|
||||
import com.gxwebsoft.shop.param.ShopUserCouponParam;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户优惠券Mapper
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 21:30:00
|
||||
*/
|
||||
public interface ShopUserCouponMapper extends BaseMapper<ShopUserCoupon> {
|
||||
|
||||
/**
|
||||
* 分页查询用户优惠券
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param param 查询参数
|
||||
* @return List<ShopUserCoupon>
|
||||
*/
|
||||
List<ShopUserCoupon> selectPageRel(@Param("page") IPage<ShopUserCoupon> page,
|
||||
@Param("param") ShopUserCouponParam param);
|
||||
|
||||
/**
|
||||
* 查询用户优惠券列表
|
||||
*
|
||||
* @param param 查询参数
|
||||
* @return List<ShopUserCoupon>
|
||||
*/
|
||||
List<ShopUserCoupon> selectListRel(@Param("param") ShopUserCouponParam param);
|
||||
|
||||
/**
|
||||
* 查询用户可用的优惠券
|
||||
*
|
||||
* @param param 查询参数
|
||||
* @return List<ShopUserCoupon>
|
||||
*/
|
||||
List<ShopUserCoupon> selectAvailableCoupons(@Param("param") ShopUserCouponParam param);
|
||||
|
||||
/**
|
||||
* 统计用户优惠券数量
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 统计结果
|
||||
*/
|
||||
java.util.Map<String, Object> countUserCoupons(@Param("userId") Integer userId);
|
||||
|
||||
/**
|
||||
* 查询即将过期的优惠券
|
||||
*
|
||||
* @param days 天数
|
||||
* @return List<ShopUserCoupon>
|
||||
*/
|
||||
List<ShopUserCoupon> selectExpiringSoon(@Param("days") Integer days);
|
||||
|
||||
/**
|
||||
* 批量更新过期状态
|
||||
*
|
||||
* @return 更新数量
|
||||
*/
|
||||
int updateExpiredStatus();
|
||||
|
||||
/**
|
||||
* 检查用户是否已领取指定优惠券
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param couponId 优惠券模板ID
|
||||
* @return 已领取数量
|
||||
*/
|
||||
int countUserReceivedCoupon(@Param("userId") Integer userId, @Param("couponId") Integer couponId);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?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.gxwebsoft.shop.mapper.ShopCouponMapper">
|
||||
|
||||
<!-- 关联查询sql -->
|
||||
<sql id="selectSql">
|
||||
SELECT a.*
|
||||
FROM shop_coupon a
|
||||
<where>
|
||||
<if test="param.id != null">
|
||||
AND a.id = #{param.id}
|
||||
</if>
|
||||
<if test="param.name != null">
|
||||
AND a.name LIKE CONCAT('%', #{param.name}, '%')
|
||||
</if>
|
||||
<if test="param.type != null">
|
||||
AND a.type = #{param.type}
|
||||
</if>
|
||||
<if test="param.reducePrice != null">
|
||||
AND a.reduce_price = #{param.reducePrice}
|
||||
</if>
|
||||
<if test="param.discount != null">
|
||||
AND a.discount = #{param.discount}
|
||||
</if>
|
||||
<if test="param.minPrice != null">
|
||||
AND a.min_price = #{param.minPrice}
|
||||
</if>
|
||||
<if test="param.expireType != null">
|
||||
AND a.expire_type = #{param.expireType}
|
||||
</if>
|
||||
<if test="param.expireDay != null">
|
||||
AND a.expire_day = #{param.expireDay}
|
||||
</if>
|
||||
<if test="param.startTime != null">
|
||||
AND a.start_time LIKE CONCAT('%', #{param.startTime}, '%')
|
||||
</if>
|
||||
<if test="param.endTime != null">
|
||||
AND a.end_time LIKE CONCAT('%', #{param.endTime}, '%')
|
||||
</if>
|
||||
<if test="param.applyRange != null">
|
||||
AND a.apply_range = #{param.applyRange}
|
||||
</if>
|
||||
<if test="param.applyRangeConfig != null">
|
||||
AND a.apply_range_config LIKE CONCAT('%', #{param.applyRangeConfig}, '%')
|
||||
</if>
|
||||
<if test="param.isExpire != null">
|
||||
AND a.is_expire = #{param.isExpire}
|
||||
</if>
|
||||
<if test="param.sortNumber != null">
|
||||
AND a.sort_number = #{param.sortNumber}
|
||||
</if>
|
||||
<if test="param.status != null">
|
||||
AND a.status = #{param.status}
|
||||
</if>
|
||||
<if test="param.deleted != null">
|
||||
AND a.deleted = #{param.deleted}
|
||||
</if>
|
||||
<if test="param.deleted == null">
|
||||
AND a.deleted = 0
|
||||
</if>
|
||||
<if test="param.userId != null">
|
||||
AND a.user_id = #{param.userId}
|
||||
</if>
|
||||
<if test="param.createTimeStart != null">
|
||||
AND a.create_time >= #{param.createTimeStart}
|
||||
</if>
|
||||
<if test="param.createTimeEnd != null">
|
||||
AND a.create_time <= #{param.createTimeEnd}
|
||||
</if>
|
||||
<if test="param.keywords != null">
|
||||
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<!-- 分页查询 -->
|
||||
<select id="selectPageRel" resultType="com.gxwebsoft.shop.entity.ShopCoupon">
|
||||
<include refid="selectSql"></include>
|
||||
</select>
|
||||
|
||||
<!-- 查询全部 -->
|
||||
<select id="selectListRel" resultType="com.gxwebsoft.shop.entity.ShopCoupon">
|
||||
<include refid="selectSql"></include>
|
||||
</select>
|
||||
|
||||
<!-- 检查用户是否已领取指定优惠券 -->
|
||||
<select id="countUserReceivedCoupon" resultType="int">
|
||||
SELECT COUNT(*)
|
||||
FROM shop_user_coupon
|
||||
WHERE deleted = 0
|
||||
AND user_id = #{userId}
|
||||
AND coupon_id = #{couponId}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,240 @@
|
||||
<?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.gxwebsoft.shop.mapper.ShopUserCouponMapper">
|
||||
|
||||
<!-- 基础查询SQL -->
|
||||
<sql id="selectSql">
|
||||
SELECT
|
||||
a.id,
|
||||
a.coupon_id,
|
||||
a.user_id,
|
||||
a.name,
|
||||
a.description,
|
||||
a.type,
|
||||
a.reduce_price,
|
||||
a.discount,
|
||||
a.min_price,
|
||||
a.apply_range,
|
||||
a.apply_range_config,
|
||||
a.start_time,
|
||||
a.end_time,
|
||||
a.status,
|
||||
a.use_time,
|
||||
a.order_id,
|
||||
a.order_no,
|
||||
a.obtain_type,
|
||||
a.obtain_source,
|
||||
a.deleted,
|
||||
a.tenant_id,
|
||||
a.create_time,
|
||||
a.update_time,
|
||||
u.nickname as user_nickname,
|
||||
u.phone as user_phone
|
||||
FROM shop_user_coupon a
|
||||
LEFT JOIN user u ON a.user_id = u.user_id
|
||||
<where>
|
||||
a.deleted = 0
|
||||
<if test="param.tenantId != null">
|
||||
AND a.tenant_id = #{param.tenantId}
|
||||
</if>
|
||||
<if test="param.id != null">
|
||||
AND a.id = #{param.id}
|
||||
</if>
|
||||
<if test="param.couponId != null">
|
||||
AND a.coupon_id = #{param.couponId}
|
||||
</if>
|
||||
<if test="param.userId != null">
|
||||
AND a.user_id = #{param.userId}
|
||||
</if>
|
||||
<if test="param.name != null and param.name != ''">
|
||||
AND a.name LIKE CONCAT('%', #{param.name}, '%')
|
||||
</if>
|
||||
<if test="param.type != null">
|
||||
AND a.type = #{param.type}
|
||||
</if>
|
||||
<if test="param.status != null">
|
||||
AND a.status = #{param.status}
|
||||
</if>
|
||||
<if test="param.applyRange != null">
|
||||
AND a.apply_range = #{param.applyRange}
|
||||
</if>
|
||||
<if test="param.obtainType != null">
|
||||
AND a.obtain_type = #{param.obtainType}
|
||||
</if>
|
||||
<if test="param.orderId != null">
|
||||
AND a.order_id = #{param.orderId}
|
||||
</if>
|
||||
<if test="param.orderNo != null and param.orderNo != ''">
|
||||
AND a.order_no = #{param.orderNo}
|
||||
</if>
|
||||
<if test="param.startTimeBegin != null">
|
||||
AND a.start_time >= #{param.startTimeBegin}
|
||||
</if>
|
||||
<if test="param.startTimeEnd != null">
|
||||
AND a.start_time <= #{param.startTimeEnd}
|
||||
</if>
|
||||
<if test="param.endTimeBegin != null">
|
||||
AND a.end_time >= #{param.endTimeBegin}
|
||||
</if>
|
||||
<if test="param.endTimeEnd != null">
|
||||
AND a.end_time <= #{param.endTimeEnd}
|
||||
</if>
|
||||
<if test="param.useTimeBegin != null">
|
||||
AND a.use_time >= #{param.useTimeBegin}
|
||||
</if>
|
||||
<if test="param.useTimeEnd != null">
|
||||
AND a.use_time <= #{param.useTimeEnd}
|
||||
</if>
|
||||
<if test="param.createTimeStart != null">
|
||||
AND a.create_time >= #{param.createTimeStart}
|
||||
</if>
|
||||
<if test="param.createTimeEnd != null">
|
||||
AND a.create_time <= #{param.createTimeEnd}
|
||||
</if>
|
||||
<if test="param.includeExpired != null and !param.includeExpired">
|
||||
AND (a.end_time IS NULL OR a.end_time > NOW())
|
||||
</if>
|
||||
<if test="param.onlyAvailable != null and param.onlyAvailable">
|
||||
AND a.status = 0
|
||||
AND (a.start_time IS NULL OR a.start_time <= NOW())
|
||||
AND (a.end_time IS NULL OR a.end_time > NOW())
|
||||
</if>
|
||||
<if test="param.keywords != null and param.keywords != ''">
|
||||
AND (a.name LIKE CONCAT('%', #{param.keywords}, '%')
|
||||
OR a.description LIKE CONCAT('%', #{param.keywords}, '%')
|
||||
OR u.nickname LIKE CONCAT('%', #{param.keywords}, '%')
|
||||
OR u.phone LIKE CONCAT('%', #{param.keywords}, '%'))
|
||||
</if>
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<!-- 分页查询 -->
|
||||
<select id="selectPageRel" resultType="com.gxwebsoft.shop.entity.ShopUserCoupon">
|
||||
<include refid="selectSql"/>
|
||||
ORDER BY a.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询全部 -->
|
||||
<select id="selectListRel" resultType="com.gxwebsoft.shop.entity.ShopUserCoupon">
|
||||
<include refid="selectSql"/>
|
||||
ORDER BY a.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询用户可用的优惠券 -->
|
||||
<select id="selectAvailableCoupons" resultType="com.gxwebsoft.shop.entity.ShopUserCoupon">
|
||||
SELECT
|
||||
a.id,
|
||||
a.coupon_id,
|
||||
a.user_id,
|
||||
a.name,
|
||||
a.description,
|
||||
a.type,
|
||||
a.reduce_price,
|
||||
a.discount,
|
||||
a.min_price,
|
||||
a.apply_range,
|
||||
a.apply_range_config,
|
||||
a.start_time,
|
||||
a.end_time,
|
||||
a.status,
|
||||
a.use_time,
|
||||
a.order_id,
|
||||
a.order_no,
|
||||
a.obtain_type,
|
||||
a.obtain_source,
|
||||
a.deleted,
|
||||
a.tenant_id,
|
||||
a.create_time,
|
||||
a.update_time
|
||||
FROM shop_user_coupon a
|
||||
WHERE a.deleted = 0
|
||||
AND a.user_id = #{param.userId}
|
||||
AND a.status = 0
|
||||
AND (a.start_time IS NULL OR a.start_time <= NOW())
|
||||
AND (a.end_time IS NULL OR a.end_time > NOW())
|
||||
<if test="param.orderAmount != null">
|
||||
AND (a.min_price IS NULL OR a.min_price <= #{param.orderAmount})
|
||||
</if>
|
||||
<if test="param.goodsId != null">
|
||||
AND (a.apply_range = 10
|
||||
OR (a.apply_range = 20 AND FIND_IN_SET(#{param.goodsId}, a.apply_range_config) > 0))
|
||||
</if>
|
||||
<if test="param.categoryId != null">
|
||||
AND (a.apply_range = 10
|
||||
OR (a.apply_range = 30 AND FIND_IN_SET(#{param.categoryId}, a.apply_range_config) > 0))
|
||||
</if>
|
||||
ORDER BY
|
||||
CASE a.type
|
||||
WHEN 10 THEN a.reduce_price
|
||||
WHEN 20 THEN #{param.orderAmount} * (100 - a.discount) / 100
|
||||
ELSE #{param.orderAmount}
|
||||
END DESC,
|
||||
a.end_time ASC
|
||||
</select>
|
||||
|
||||
<!-- 统计用户优惠券数量 -->
|
||||
<select id="countUserCoupons" resultType="java.util.Map">
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN status = 0 AND (end_time IS NULL OR end_time > NOW()) THEN 1 ELSE 0 END) as unused,
|
||||
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as used,
|
||||
SUM(CASE WHEN status = 2 OR (status = 0 AND end_time IS NOT NULL AND end_time <= NOW()) THEN 1 ELSE 0 END) as expired
|
||||
FROM shop_user_coupon
|
||||
WHERE deleted = 0 AND user_id = #{userId}
|
||||
</select>
|
||||
|
||||
<!-- 查询即将过期的优惠券 -->
|
||||
<select id="selectExpiringSoon" resultType="com.gxwebsoft.shop.entity.ShopUserCoupon">
|
||||
SELECT
|
||||
a.id,
|
||||
a.coupon_id,
|
||||
a.user_id,
|
||||
a.name,
|
||||
a.description,
|
||||
a.type,
|
||||
a.reduce_price,
|
||||
a.discount,
|
||||
a.min_price,
|
||||
a.apply_range,
|
||||
a.apply_range_config,
|
||||
a.start_time,
|
||||
a.end_time,
|
||||
a.status,
|
||||
a.use_time,
|
||||
a.order_id,
|
||||
a.order_no,
|
||||
a.obtain_type,
|
||||
a.obtain_source,
|
||||
a.deleted,
|
||||
a.tenant_id,
|
||||
a.create_time,
|
||||
a.update_time
|
||||
FROM shop_user_coupon a
|
||||
WHERE a.deleted = 0
|
||||
AND a.status = 0
|
||||
AND a.end_time IS NOT NULL
|
||||
AND a.end_time > NOW()
|
||||
AND a.end_time <= DATE_ADD(NOW(), INTERVAL #{days} DAY)
|
||||
ORDER BY a.end_time ASC
|
||||
</select>
|
||||
|
||||
<!-- 批量更新过期状态 -->
|
||||
<update id="updateExpiredStatus">
|
||||
UPDATE shop_user_coupon
|
||||
SET status = 2, update_time = NOW()
|
||||
WHERE deleted = 0
|
||||
AND status = 0
|
||||
AND end_time IS NOT NULL
|
||||
AND end_time <= NOW()
|
||||
</update>
|
||||
|
||||
<!-- 检查用户是否已领取指定优惠券 -->
|
||||
<select id="countUserReceivedCoupon" resultType="int">
|
||||
SELECT COUNT(*)
|
||||
FROM shop_user_coupon
|
||||
WHERE deleted = 0
|
||||
AND user_id = #{userId}
|
||||
AND coupon_id = #{couponId}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
90
src/main/java/com/gxwebsoft/shop/param/ShopCouponParam.java
Normal file
90
src/main/java/com/gxwebsoft/shop/param/ShopCouponParam.java
Normal file
@@ -0,0 +1,90 @@
|
||||
package com.gxwebsoft.shop.param;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.gxwebsoft.common.core.annotation.QueryField;
|
||||
import com.gxwebsoft.common.core.annotation.QueryType;
|
||||
import com.gxwebsoft.common.core.web.BaseParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 优惠券查询参数
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 21:10:55
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@Schema(name = "ShopCouponParam对象", description = "优惠券查询参数")
|
||||
public class ShopCouponParam extends BaseParam {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "id")
|
||||
@QueryField(type = QueryType.EQ)
|
||||
private Integer id;
|
||||
|
||||
@Schema(description = "优惠券名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "优惠券类型(10满减券 20折扣券 30免费劵)")
|
||||
@QueryField(type = QueryType.EQ)
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "满减券-减免金额")
|
||||
@QueryField(type = QueryType.EQ)
|
||||
private BigDecimal reducePrice;
|
||||
|
||||
@Schema(description = "折扣券-折扣率(0-100)")
|
||||
@QueryField(type = QueryType.EQ)
|
||||
private Integer discount;
|
||||
|
||||
@Schema(description = "最低消费金额")
|
||||
@QueryField(type = QueryType.EQ)
|
||||
private BigDecimal minPrice;
|
||||
|
||||
@Schema(description = "到期类型(10领取后生效 20固定时间)")
|
||||
@QueryField(type = QueryType.EQ)
|
||||
private Integer expireType;
|
||||
|
||||
@Schema(description = "领取后生效-有效天数")
|
||||
@QueryField(type = QueryType.EQ)
|
||||
private Integer expireDay;
|
||||
|
||||
@Schema(description = "有效期开始时间")
|
||||
private String startTime;
|
||||
|
||||
@Schema(description = "有效期结束时间")
|
||||
private String endTime;
|
||||
|
||||
@Schema(description = "适用范围(10全部商品 20指定商品)")
|
||||
@QueryField(type = QueryType.EQ)
|
||||
private Integer applyRange;
|
||||
|
||||
@Schema(description = "适用范围配置(json格式)")
|
||||
private String applyRangeConfig;
|
||||
|
||||
@Schema(description = "是否过期(0未过期 1已过期)")
|
||||
@QueryField(type = QueryType.EQ)
|
||||
private Integer isExpire;
|
||||
|
||||
@Schema(description = "排序(数字越小越靠前)")
|
||||
@QueryField(type = QueryType.EQ)
|
||||
private Integer sortNumber;
|
||||
|
||||
@Schema(description = "状态, 0待使用, 1已使用, 2已失效")
|
||||
@QueryField(type = QueryType.EQ)
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "是否删除, 0否, 1是")
|
||||
@QueryField(type = QueryType.EQ)
|
||||
private Integer deleted;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
@QueryField(type = QueryType.EQ)
|
||||
private Integer userId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.gxwebsoft.shop.param;
|
||||
|
||||
import com.gxwebsoft.common.core.web.BaseParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户优惠券查询参数
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 21:30:00
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Schema(description = "用户优惠券查询参数")
|
||||
public class ShopUserCouponParam extends BaseParam {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "id")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "优惠券模板ID")
|
||||
private Integer couponId;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Integer userId;
|
||||
|
||||
@Schema(description = "优惠券名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "优惠券类型(10满减券 20折扣券 30免费劵)")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "使用状态(0未使用 1已使用 2已过期)")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "适用范围(10全部商品 20指定商品 30指定分类)")
|
||||
private Integer applyRange;
|
||||
|
||||
@Schema(description = "获取方式(10主动领取 20系统发放 30活动赠送)")
|
||||
private Integer obtainType;
|
||||
|
||||
@Schema(description = "使用订单ID")
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description = "使用订单号")
|
||||
private String orderNo;
|
||||
|
||||
@Schema(description = "有效期开始时间-查询开始")
|
||||
private LocalDateTime startTimeBegin;
|
||||
|
||||
@Schema(description = "有效期开始时间-查询结束")
|
||||
private LocalDateTime startTimeEnd;
|
||||
|
||||
@Schema(description = "有效期结束时间-查询开始")
|
||||
private LocalDateTime endTimeBegin;
|
||||
|
||||
@Schema(description = "有效期结束时间-查询结束")
|
||||
private LocalDateTime endTimeEnd;
|
||||
|
||||
@Schema(description = "使用时间-查询开始")
|
||||
private LocalDateTime useTimeBegin;
|
||||
|
||||
@Schema(description = "使用时间-查询结束")
|
||||
private LocalDateTime useTimeEnd;
|
||||
|
||||
@Schema(description = "是否包含已过期的券(默认false)")
|
||||
private Boolean includeExpired = false;
|
||||
|
||||
@Schema(description = "是否只查询可用的券(默认false)")
|
||||
private Boolean onlyAvailable = false;
|
||||
|
||||
@Schema(description = "商品ID(用于筛选可用优惠券)")
|
||||
private Integer goodsId;
|
||||
|
||||
@Schema(description = "商品分类ID(用于筛选可用优惠券)")
|
||||
private Integer categoryId;
|
||||
|
||||
@Schema(description = "订单金额(用于筛选可用优惠券)")
|
||||
private java.math.BigDecimal orderAmount;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package com.gxwebsoft.shop.service;
|
||||
|
||||
import com.gxwebsoft.shop.entity.ShopUserCoupon;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 优惠券业务服务
|
||||
* 处理订单中的优惠券相关业务逻辑
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 22:00:00
|
||||
*/
|
||||
public interface CouponBusinessService {
|
||||
|
||||
/**
|
||||
* 获取用户在指定订单中可用的优惠券
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param goodsItems 商品列表 [{goodsId, categoryId, price, quantity}]
|
||||
* @param totalAmount 订单总金额
|
||||
* @return 可用优惠券列表
|
||||
*/
|
||||
List<ShopUserCoupon> getAvailableCouponsForOrder(Integer userId,
|
||||
List<Map<String, Object>> goodsItems,
|
||||
BigDecimal totalAmount);
|
||||
|
||||
/**
|
||||
* 计算使用优惠券后的订单金额
|
||||
*
|
||||
* @param userCouponId 用户优惠券ID
|
||||
* @param goodsItems 商品列表
|
||||
* @param totalAmount 订单总金额
|
||||
* @return 计算结果 {discountAmount: 优惠金额, finalAmount: 最终金额, valid: 是否有效}
|
||||
*/
|
||||
Map<String, Object> calculateOrderAmountWithCoupon(Long userCouponId,
|
||||
List<Map<String, Object>> goodsItems,
|
||||
BigDecimal totalAmount);
|
||||
|
||||
/**
|
||||
* 验证优惠券是否可用于订单
|
||||
*
|
||||
* @param userCouponId 用户优惠券ID
|
||||
* @param userId 用户ID
|
||||
* @param goodsItems 商品列表
|
||||
* @param totalAmount 订单总金额
|
||||
* @return 验证结果 {valid: boolean, reason: String}
|
||||
*/
|
||||
Map<String, Object> validateCouponForOrder(Long userCouponId,
|
||||
Integer userId,
|
||||
List<Map<String, Object>> goodsItems,
|
||||
BigDecimal totalAmount);
|
||||
|
||||
/**
|
||||
* 为新用户发放欢迎优惠券
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 发放成功的优惠券数量
|
||||
*/
|
||||
int issueWelcomeCoupons(Integer userId);
|
||||
|
||||
/**
|
||||
* 为用户生日发放生日优惠券
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 发放成功的优惠券数量
|
||||
*/
|
||||
int issueBirthdayCoupons(Integer userId);
|
||||
|
||||
/**
|
||||
* 根据用户消费金额发放优惠券
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param consumeAmount 消费金额
|
||||
* @return 发放成功的优惠券数量
|
||||
*/
|
||||
int issueConsumeCoupons(Integer userId, BigDecimal consumeAmount);
|
||||
|
||||
/**
|
||||
* 活动期间批量发放优惠券
|
||||
*
|
||||
* @param activityName 活动名称
|
||||
* @param couponIds 优惠券模板ID列表
|
||||
* @param userIds 用户ID列表(为空则发放给所有用户)
|
||||
* @return 发放统计 {totalUsers: 总用户数, totalCoupons: 总优惠券数, successCount: 成功数量}
|
||||
*/
|
||||
Map<String, Object> batchIssueActivityCoupons(String activityName,
|
||||
List<Integer> couponIds,
|
||||
List<Integer> userIds);
|
||||
|
||||
/**
|
||||
* 获取优惠券使用统计
|
||||
*
|
||||
* @param startDate 开始日期
|
||||
* @param endDate 结束日期
|
||||
* @return 统计结果
|
||||
*/
|
||||
Map<String, Object> getCouponUsageStatistics(String startDate, String endDate);
|
||||
|
||||
/**
|
||||
* 自动处理过期优惠券
|
||||
* 定时任务调用
|
||||
*
|
||||
* @return 处理数量
|
||||
*/
|
||||
int autoProcessExpiredCoupons();
|
||||
|
||||
/**
|
||||
* 发送优惠券到期提醒
|
||||
* 定时任务调用
|
||||
*
|
||||
* @param days 提前天数
|
||||
* @return 提醒用户数量
|
||||
*/
|
||||
int sendCouponExpiryReminder(Integer days);
|
||||
|
||||
/**
|
||||
* 推荐最优优惠券组合
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param goodsItems 商品列表
|
||||
* @param totalAmount 订单总金额
|
||||
* @return 推荐结果 {coupons: 优惠券列表, totalDiscount: 总优惠金额, finalAmount: 最终金额}
|
||||
*/
|
||||
Map<String, Object> recommendBestCouponCombination(Integer userId,
|
||||
List<Map<String, Object>> goodsItems,
|
||||
BigDecimal totalAmount);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.gxwebsoft.shop.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.gxwebsoft.common.core.web.PageResult;
|
||||
import com.gxwebsoft.shop.entity.ShopCoupon;
|
||||
import com.gxwebsoft.shop.param.ShopCouponParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 优惠券Service
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 21:10:55
|
||||
*/
|
||||
public interface ShopCouponService extends IService<ShopCoupon> {
|
||||
|
||||
/**
|
||||
* 分页关联查询
|
||||
*
|
||||
* @param param 查询参数
|
||||
* @return PageResult<ShopCoupon>
|
||||
*/
|
||||
PageResult<ShopCoupon> pageRel(ShopCouponParam param);
|
||||
|
||||
/**
|
||||
* 关联查询全部
|
||||
*
|
||||
* @param param 查询参数
|
||||
* @return List<ShopCoupon>
|
||||
*/
|
||||
List<ShopCoupon> listRel(ShopCouponParam param);
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*
|
||||
* @param id id
|
||||
* @return ShopCoupon
|
||||
*/
|
||||
ShopCoupon getByIdRel(Integer id);
|
||||
|
||||
/**
|
||||
* 获取可领取的优惠券列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return List<ShopCoupon>
|
||||
*/
|
||||
List<ShopCoupon> getReceivableCoupons(Integer userId);
|
||||
|
||||
/**
|
||||
* 检查优惠券是否可以发放
|
||||
*
|
||||
* @param couponId 优惠券ID
|
||||
* @return boolean
|
||||
*/
|
||||
boolean canIssue(Integer couponId);
|
||||
|
||||
/**
|
||||
* 更新优惠券发放数量
|
||||
*
|
||||
* @param couponId 优惠券ID
|
||||
* @param increment 增量
|
||||
* @return boolean
|
||||
*/
|
||||
boolean updateIssuedCount(Integer couponId, int increment);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package com.gxwebsoft.shop.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.gxwebsoft.common.core.web.PageResult;
|
||||
import com.gxwebsoft.shop.entity.ShopUserCoupon;
|
||||
import com.gxwebsoft.shop.param.ShopUserCouponParam;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 用户优惠券Service
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 21:30:00
|
||||
*/
|
||||
public interface ShopUserCouponService extends IService<ShopUserCoupon> {
|
||||
|
||||
/**
|
||||
* 分页关联查询用户优惠券
|
||||
*
|
||||
* @param param 查询参数
|
||||
* @return PageResult<ShopUserCoupon>
|
||||
*/
|
||||
PageResult<ShopUserCoupon> pageRel(ShopUserCouponParam param);
|
||||
|
||||
/**
|
||||
* 关联查询用户优惠券列表
|
||||
*
|
||||
* @param param 查询参数
|
||||
* @return List<ShopUserCoupon>
|
||||
*/
|
||||
List<ShopUserCoupon> listRel(ShopUserCouponParam param);
|
||||
|
||||
/**
|
||||
* 根据id查询用户优惠券
|
||||
*
|
||||
* @param id id
|
||||
* @return ShopUserCoupon
|
||||
*/
|
||||
ShopUserCoupon getByIdRel(Long id);
|
||||
|
||||
/**
|
||||
* 用户领取优惠券
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param couponId 优惠券模板ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean receiveCoupon(Integer userId, Integer couponId);
|
||||
|
||||
/**
|
||||
* 系统发放优惠券给用户
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param couponId 优惠券模板ID
|
||||
* @param source 发放来源描述
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean issueCouponToUser(Integer userId, Integer couponId, String source);
|
||||
|
||||
/**
|
||||
* 批量发放优惠券给多个用户
|
||||
*
|
||||
* @param userIds 用户ID列表
|
||||
* @param couponId 优惠券模板ID
|
||||
* @param source 发放来源描述
|
||||
* @return 成功发放的数量
|
||||
*/
|
||||
int batchIssueCoupons(List<Integer> userIds, Integer couponId, String source);
|
||||
|
||||
/**
|
||||
* 使用优惠券
|
||||
*
|
||||
* @param userCouponId 用户优惠券ID
|
||||
* @param orderId 订单ID
|
||||
* @param orderNo 订单号
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean useCoupon(Long userCouponId, Long orderId, String orderNo);
|
||||
|
||||
/**
|
||||
* 退还优惠券(订单取消时)
|
||||
*
|
||||
* @param orderId 订单ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean returnCoupon(Long orderId);
|
||||
|
||||
/**
|
||||
* 获取用户可用的优惠券列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param goodsId 商品ID(可选)
|
||||
* @param categoryId 商品分类ID(可选)
|
||||
* @param orderAmount 订单金额(可选)
|
||||
* @return List<ShopUserCoupon>
|
||||
*/
|
||||
List<ShopUserCoupon> getAvailableCoupons(Integer userId, Integer goodsId,
|
||||
Integer categoryId, BigDecimal orderAmount);
|
||||
|
||||
/**
|
||||
* 计算优惠券优惠金额
|
||||
*
|
||||
* @param userCoupon 用户优惠券
|
||||
* @param orderAmount 订单金额
|
||||
* @return 优惠金额
|
||||
*/
|
||||
BigDecimal calculateDiscountAmount(ShopUserCoupon userCoupon, BigDecimal orderAmount);
|
||||
|
||||
/**
|
||||
* 验证优惠券是否可用于指定商品
|
||||
*
|
||||
* @param userCoupon 用户优惠券
|
||||
* @param goodsId 商品ID
|
||||
* @param categoryId 商品分类ID
|
||||
* @return 是否可用
|
||||
*/
|
||||
boolean validateCouponForGoods(ShopUserCoupon userCoupon, Integer goodsId, Integer categoryId);
|
||||
|
||||
/**
|
||||
* 统计用户优惠券数量
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 统计结果 {total: 总数, unused: 未使用, used: 已使用, expired: 已过期}
|
||||
*/
|
||||
Map<String, Object> countUserCoupons(Integer userId);
|
||||
|
||||
/**
|
||||
* 更新过期优惠券状态
|
||||
*
|
||||
* @return 更新数量
|
||||
*/
|
||||
int updateExpiredCoupons();
|
||||
|
||||
/**
|
||||
* 获取即将过期的优惠券
|
||||
*
|
||||
* @param days 天数
|
||||
* @return List<ShopUserCoupon>
|
||||
*/
|
||||
List<ShopUserCoupon> getExpiringSoonCoupons(Integer days);
|
||||
|
||||
/**
|
||||
* 检查用户是否可以领取指定优惠券
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param couponId 优惠券模板ID
|
||||
* @return 检查结果 {canReceive: boolean, reason: String}
|
||||
*/
|
||||
Map<String, Object> checkCanReceiveCoupon(Integer userId, Integer couponId);
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
package com.gxwebsoft.shop.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.gxwebsoft.common.system.entity.User;
|
||||
import com.gxwebsoft.common.system.service.UserService;
|
||||
import com.gxwebsoft.shop.entity.ShopCoupon;
|
||||
import com.gxwebsoft.shop.entity.ShopUserCoupon;
|
||||
import com.gxwebsoft.shop.service.CouponBusinessService;
|
||||
import com.gxwebsoft.shop.service.ShopCouponService;
|
||||
import com.gxwebsoft.shop.service.ShopUserCouponService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 优惠券业务服务实现
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 22:00:00
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class CouponBusinessServiceImpl implements CouponBusinessService {
|
||||
|
||||
@Resource
|
||||
private ShopUserCouponService shopUserCouponService;
|
||||
|
||||
@Resource
|
||||
private ShopCouponService shopCouponService;
|
||||
|
||||
@Resource
|
||||
private UserService userService;
|
||||
|
||||
@Override
|
||||
public List<ShopUserCoupon> getAvailableCouponsForOrder(Integer userId,
|
||||
List<Map<String, Object>> goodsItems,
|
||||
BigDecimal totalAmount) {
|
||||
if (userId == null || goodsItems == null || goodsItems.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 获取用户所有可用优惠券
|
||||
List<ShopUserCoupon> allCoupons = shopUserCouponService.getAvailableCoupons(
|
||||
userId, null, null, totalAmount);
|
||||
|
||||
// 过滤出适用于当前订单的优惠券
|
||||
return allCoupons.stream()
|
||||
.filter(coupon -> isValidForOrder(coupon, goodsItems, totalAmount))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> calculateOrderAmountWithCoupon(Long userCouponId,
|
||||
List<Map<String, Object>> goodsItems,
|
||||
BigDecimal totalAmount) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("valid", false);
|
||||
result.put("discountAmount", BigDecimal.ZERO);
|
||||
result.put("finalAmount", totalAmount);
|
||||
|
||||
if (userCouponId == null || totalAmount == null) {
|
||||
result.put("reason", "参数不能为空");
|
||||
return result;
|
||||
}
|
||||
|
||||
ShopUserCoupon userCoupon = shopUserCouponService.getById(userCouponId);
|
||||
if (userCoupon == null) {
|
||||
result.put("reason", "优惠券不存在");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!userCoupon.isAvailable()) {
|
||||
result.put("reason", "优惠券不可用");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!isValidForOrder(userCoupon, goodsItems, totalAmount)) {
|
||||
result.put("reason", "优惠券不适用于当前订单");
|
||||
return result;
|
||||
}
|
||||
|
||||
BigDecimal discountAmount = shopUserCouponService.calculateDiscountAmount(userCoupon, totalAmount);
|
||||
BigDecimal finalAmount = totalAmount.subtract(discountAmount);
|
||||
|
||||
// 确保最终金额不为负数
|
||||
if (finalAmount.compareTo(BigDecimal.ZERO) < 0) {
|
||||
finalAmount = BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
result.put("valid", true);
|
||||
result.put("discountAmount", discountAmount);
|
||||
result.put("finalAmount", finalAmount);
|
||||
result.put("coupon", userCoupon);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> validateCouponForOrder(Long userCouponId,
|
||||
Integer userId,
|
||||
List<Map<String, Object>> goodsItems,
|
||||
BigDecimal totalAmount) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
if (userCouponId == null || userId == null) {
|
||||
result.put("valid", false);
|
||||
result.put("reason", "参数不能为空");
|
||||
return result;
|
||||
}
|
||||
|
||||
ShopUserCoupon userCoupon = shopUserCouponService.getById(userCouponId);
|
||||
if (userCoupon == null) {
|
||||
result.put("valid", false);
|
||||
result.put("reason", "优惠券不存在");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!userCoupon.getUserId().equals(userId)) {
|
||||
result.put("valid", false);
|
||||
result.put("reason", "优惠券不属于当前用户");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!userCoupon.isAvailable()) {
|
||||
result.put("valid", false);
|
||||
result.put("reason", "优惠券不可用或已过期");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!isValidForOrder(userCoupon, goodsItems, totalAmount)) {
|
||||
result.put("valid", false);
|
||||
result.put("reason", "优惠券不适用于当前订单商品");
|
||||
return result;
|
||||
}
|
||||
|
||||
result.put("valid", true);
|
||||
result.put("reason", "优惠券可用");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int issueWelcomeCoupons(Integer userId) {
|
||||
if (userId == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 查找欢迎优惠券模板(这里假设有特定的标识)
|
||||
LambdaQueryWrapper<ShopCoupon> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ShopCoupon::getEnabled, 1)
|
||||
.eq(ShopCoupon::getStatus, 0)
|
||||
.like(ShopCoupon::getName, "新用户")
|
||||
.or()
|
||||
.like(ShopCoupon::getName, "欢迎");
|
||||
|
||||
List<ShopCoupon> welcomeCoupons = shopCouponService.list(queryWrapper);
|
||||
|
||||
int successCount = 0;
|
||||
for (ShopCoupon coupon : welcomeCoupons) {
|
||||
if (shopUserCouponService.issueCouponToUser(userId, coupon.getId(), "新用户注册赠送")) {
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("为新用户{}发放欢迎优惠券{}张", userId, successCount);
|
||||
return successCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int issueBirthdayCoupons(Integer userId) {
|
||||
if (userId == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
User user = userService.getById(userId);
|
||||
if (user == null || user.getBirthday() == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 检查今天是否是用户生日
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDate birthday = LocalDate.parse((CharSequence) user.getBirthday(), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
|
||||
if (today.getMonthValue() != birthday.getMonthValue() ||
|
||||
today.getDayOfMonth() != birthday.getDayOfMonth()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 查找生日优惠券模板
|
||||
LambdaQueryWrapper<ShopCoupon> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ShopCoupon::getEnabled, 1)
|
||||
.eq(ShopCoupon::getStatus, 0)
|
||||
.like(ShopCoupon::getName, "生日");
|
||||
|
||||
List<ShopCoupon> birthdayCoupons = shopCouponService.list(queryWrapper);
|
||||
|
||||
int successCount = 0;
|
||||
for (ShopCoupon coupon : birthdayCoupons) {
|
||||
if (shopUserCouponService.issueCouponToUser(userId, coupon.getId(), "生日祝福赠送")) {
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("为用户{}发放生日优惠券{}张", userId, successCount);
|
||||
return successCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int issueConsumeCoupons(Integer userId, BigDecimal consumeAmount) {
|
||||
if (userId == null || consumeAmount == null || consumeAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 根据消费金额发放不同的优惠券
|
||||
List<ShopCoupon> eligibleCoupons = new ArrayList<>();
|
||||
|
||||
if (consumeAmount.compareTo(new BigDecimal("100")) >= 0) {
|
||||
// 消费满100发放优惠券
|
||||
LambdaQueryWrapper<ShopCoupon> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ShopCoupon::getEnabled, 1)
|
||||
.eq(ShopCoupon::getStatus, 0)
|
||||
.like(ShopCoupon::getName, "消费返券");
|
||||
eligibleCoupons.addAll(shopCouponService.list(queryWrapper));
|
||||
}
|
||||
|
||||
int successCount = 0;
|
||||
for (ShopCoupon coupon : eligibleCoupons) {
|
||||
if (shopUserCouponService.issueCouponToUser(userId, coupon.getId(),
|
||||
"消费满" + consumeAmount + "元返券")) {
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("为用户{}消费{}元发放优惠券{}张", userId, consumeAmount, successCount);
|
||||
return successCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查优惠券是否适用于订单
|
||||
*/
|
||||
private boolean isValidForOrder(ShopUserCoupon userCoupon,
|
||||
List<Map<String, Object>> goodsItems,
|
||||
BigDecimal totalAmount) {
|
||||
if (userCoupon == null || goodsItems == null || goodsItems.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查最低消费金额
|
||||
if (userCoupon.getMinPrice() != null &&
|
||||
totalAmount.compareTo(userCoupon.getMinPrice()) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查适用范围
|
||||
if (userCoupon.getApplyRange() == ShopUserCoupon.APPLY_ALL) {
|
||||
return true;
|
||||
} else if (userCoupon.getApplyRange() == ShopUserCoupon.APPLY_GOODS) {
|
||||
// 检查是否有指定商品
|
||||
return goodsItems.stream().anyMatch(item -> {
|
||||
Integer goodsId = (Integer) item.get("goodsId");
|
||||
return shopUserCouponService.validateCouponForGoods(userCoupon, goodsId, null);
|
||||
});
|
||||
} else if (userCoupon.getApplyRange() == ShopUserCoupon.APPLY_CATEGORY) {
|
||||
// 检查是否有指定分类的商品
|
||||
return goodsItems.stream().anyMatch(item -> {
|
||||
Integer categoryId = (Integer) item.get("categoryId");
|
||||
return shopUserCouponService.validateCouponForGoods(userCoupon, null, categoryId);
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, Object> batchIssueActivityCoupons(String activityName,
|
||||
List<Integer> couponIds,
|
||||
List<Integer> userIds) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("totalUsers", 0);
|
||||
result.put("totalCoupons", 0);
|
||||
result.put("successCount", 0);
|
||||
|
||||
if (couponIds == null || couponIds.isEmpty()) {
|
||||
result.put("message", "优惠券列表不能为空");
|
||||
return result;
|
||||
}
|
||||
|
||||
// 如果没有指定用户,则获取所有活跃用户
|
||||
if (userIds == null || userIds.isEmpty()) {
|
||||
userIds = userService.list().stream()
|
||||
.filter(user -> user.getStatus() == 0) // 只给正常状态的用户发放
|
||||
.map(User::getUserId)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
int totalUsers = userIds.size();
|
||||
int totalCoupons = couponIds.size() * totalUsers;
|
||||
int successCount = 0;
|
||||
|
||||
String source = activityName != null ? activityName + "活动赠送" : "活动赠送";
|
||||
|
||||
for (Integer userId : userIds) {
|
||||
for (Integer couponId : couponIds) {
|
||||
if (shopUserCouponService.issueCouponToUser(userId, couponId, source)) {
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.put("totalUsers", totalUsers);
|
||||
result.put("totalCoupons", totalCoupons);
|
||||
result.put("successCount", successCount);
|
||||
result.put("message", String.format("活动优惠券发放完成,共%d个用户,%d张优惠券,成功%d张",
|
||||
totalUsers, totalCoupons, successCount));
|
||||
|
||||
log.info("活动{}批量发放优惠券完成:用户{}个,优惠券{}张,成功{}张",
|
||||
activityName, totalUsers, totalCoupons, successCount);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getCouponUsageStatistics(String startDate, String endDate) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
// 这里可以根据需要实现具体的统计逻辑
|
||||
// 统计指定时间段内的优惠券使用情况
|
||||
|
||||
result.put("totalIssued", 0); // 总发放数量
|
||||
result.put("totalUsed", 0); // 总使用数量
|
||||
result.put("totalExpired", 0); // 总过期数量
|
||||
result.put("usageRate", 0.0); // 使用率
|
||||
result.put("discountAmount", BigDecimal.ZERO); // 总优惠金额
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int autoProcessExpiredCoupons() {
|
||||
// 更新过期优惠券状态
|
||||
int count = shopUserCouponService.updateExpiredCoupons();
|
||||
log.info("自动处理过期优惠券{}张", count);
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendCouponExpiryReminder(Integer days) {
|
||||
if (days == null || days <= 0) {
|
||||
days = 3; // 默认提前3天提醒
|
||||
}
|
||||
|
||||
List<ShopUserCoupon> expiringSoonCoupons = shopUserCouponService.getExpiringSoonCoupons(days);
|
||||
|
||||
// 按用户分组
|
||||
Map<Integer, List<ShopUserCoupon>> userCouponsMap = expiringSoonCoupons.stream()
|
||||
.collect(Collectors.groupingBy(ShopUserCoupon::getUserId));
|
||||
|
||||
int reminderCount = 0;
|
||||
for (Map.Entry<Integer, List<ShopUserCoupon>> entry : userCouponsMap.entrySet()) {
|
||||
Integer userId = entry.getKey();
|
||||
List<ShopUserCoupon> coupons = entry.getValue();
|
||||
|
||||
// 这里可以发送消息提醒用户
|
||||
// 例如:发送站内信、短信、推送通知等
|
||||
log.info("提醒用户{}有{}张优惠券即将过期", userId, coupons.size());
|
||||
reminderCount++;
|
||||
}
|
||||
|
||||
log.info("发送优惠券到期提醒给{}个用户", reminderCount);
|
||||
return reminderCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> recommendBestCouponCombination(Integer userId,
|
||||
List<Map<String, Object>> goodsItems,
|
||||
BigDecimal totalAmount) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("coupons", new ArrayList<>());
|
||||
result.put("totalDiscount", BigDecimal.ZERO);
|
||||
result.put("finalAmount", totalAmount);
|
||||
|
||||
if (userId == null || goodsItems == null || goodsItems.isEmpty() || totalAmount == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 获取用户所有可用优惠券
|
||||
List<ShopUserCoupon> availableCoupons = getAvailableCouponsForOrder(userId, goodsItems, totalAmount);
|
||||
|
||||
if (availableCoupons.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 简单的贪心算法:选择优惠金额最大的优惠券
|
||||
// 实际项目中可以实现更复杂的组合优化算法
|
||||
ShopUserCoupon bestCoupon = null;
|
||||
BigDecimal maxDiscount = BigDecimal.ZERO;
|
||||
|
||||
for (ShopUserCoupon coupon : availableCoupons) {
|
||||
BigDecimal discount = shopUserCouponService.calculateDiscountAmount(coupon, totalAmount);
|
||||
if (discount.compareTo(maxDiscount) > 0) {
|
||||
maxDiscount = discount;
|
||||
bestCoupon = coupon;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestCoupon != null) {
|
||||
List<ShopUserCoupon> recommendedCoupons = new ArrayList<>();
|
||||
recommendedCoupons.add(bestCoupon);
|
||||
|
||||
BigDecimal finalAmount = totalAmount.subtract(maxDiscount);
|
||||
if (finalAmount.compareTo(BigDecimal.ZERO) < 0) {
|
||||
finalAmount = BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
result.put("coupons", recommendedCoupons);
|
||||
result.put("totalDiscount", maxDiscount);
|
||||
result.put("finalAmount", finalAmount);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.gxwebsoft.shop.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.gxwebsoft.common.core.web.PageParam;
|
||||
import com.gxwebsoft.common.core.web.PageResult;
|
||||
import com.gxwebsoft.shop.entity.ShopCoupon;
|
||||
import com.gxwebsoft.shop.mapper.ShopCouponMapper;
|
||||
import com.gxwebsoft.shop.param.ShopCouponParam;
|
||||
import com.gxwebsoft.shop.service.ShopCouponService;
|
||||
import com.gxwebsoft.shop.service.ShopUserCouponService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 优惠券Service实现
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 21:10:55
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ShopCouponServiceImpl extends ServiceImpl<ShopCouponMapper, ShopCoupon> implements ShopCouponService {
|
||||
|
||||
@Resource
|
||||
private ShopUserCouponService shopUserCouponService;
|
||||
|
||||
@Override
|
||||
public PageResult<ShopCoupon> pageRel(ShopCouponParam param) {
|
||||
PageParam<ShopCoupon, ShopCouponParam> page = new PageParam<>(param);
|
||||
page.setDefaultOrder("sort_number asc, create_time desc");
|
||||
List<ShopCoupon> list = baseMapper.selectPageRel(page, param);
|
||||
return new PageResult<>(list, page.getTotal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ShopCoupon> listRel(ShopCouponParam param) {
|
||||
List<ShopCoupon> list = baseMapper.selectListRel(param);
|
||||
// 排序
|
||||
PageParam<ShopCoupon, ShopCouponParam> page = new PageParam<>();
|
||||
page.setDefaultOrder("sort_number asc, create_time desc");
|
||||
return page.sortRecords(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShopCoupon getByIdRel(Integer id) {
|
||||
ShopCouponParam param = new ShopCouponParam();
|
||||
param.setId(id);
|
||||
return param.getOne(baseMapper.selectListRel(param));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ShopCoupon> getReceivableCoupons(Integer userId) {
|
||||
LambdaQueryWrapper<ShopCoupon> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ShopCoupon::getEnabled, 1)
|
||||
.eq(ShopCoupon::getStatus, 0)
|
||||
.and(wrapper -> wrapper
|
||||
.isNull(ShopCoupon::getTotalCount)
|
||||
.or()
|
||||
.apply("IFNULL(total_count, 0) = -1")
|
||||
.or()
|
||||
.apply("IFNULL(issued_count, 0) < IFNULL(total_count, 0)"))
|
||||
.orderByAsc(ShopCoupon::getSortNumber)
|
||||
.orderByDesc(ShopCoupon::getCreateTime);
|
||||
|
||||
List<ShopCoupon> allCoupons = list(queryWrapper);
|
||||
|
||||
// 过滤掉用户已达到领取上限的优惠券
|
||||
return allCoupons.stream()
|
||||
.filter(coupon -> {
|
||||
if (coupon.getLimitPerUser() == null || coupon.getLimitPerUser() <= 0) {
|
||||
return true; // 无限制
|
||||
}
|
||||
// 检查用户已领取数量
|
||||
int receivedCount = baseMapper.countUserReceivedCoupon(userId, coupon.getId());
|
||||
return receivedCount < coupon.getLimitPerUser();
|
||||
})
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canIssue(Integer couponId) {
|
||||
ShopCoupon coupon = getById(couponId);
|
||||
if (coupon == null || coupon.getEnabled() != 1 || coupon.getStatus() != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查发放数量限制
|
||||
if (coupon.getTotalCount() != null && coupon.getTotalCount() > 0) {
|
||||
int issuedCount = coupon.getIssuedCount() != null ? coupon.getIssuedCount() : 0;
|
||||
return issuedCount < coupon.getTotalCount();
|
||||
}
|
||||
|
||||
// 检查有效期
|
||||
LocalDate now = LocalDate.now();
|
||||
if (coupon.getExpireType() == 20) { // 固定时间
|
||||
if (coupon.getStartTime() != null && now.isBefore(coupon.getStartTime())) {
|
||||
return false; // 还未开始
|
||||
}
|
||||
if (coupon.getEndTime() != null && now.isAfter(coupon.getEndTime())) {
|
||||
return false; // 已过期
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateIssuedCount(Integer couponId, int increment) {
|
||||
LambdaUpdateWrapper<ShopCoupon> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
updateWrapper.eq(ShopCoupon::getId, couponId)
|
||||
.setSql("issued_count = IFNULL(issued_count, 0) + " + increment);
|
||||
|
||||
boolean updated = update(updateWrapper);
|
||||
if (updated) {
|
||||
log.debug("更新优惠券{}发放数量,增量: {}", couponId, increment);
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
package com.gxwebsoft.shop.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.gxwebsoft.common.core.web.PageParam;
|
||||
import com.gxwebsoft.common.core.web.PageResult;
|
||||
import com.gxwebsoft.shop.entity.ShopCoupon;
|
||||
import com.gxwebsoft.shop.entity.ShopUserCoupon;
|
||||
import com.gxwebsoft.shop.mapper.ShopUserCouponMapper;
|
||||
import com.gxwebsoft.shop.param.ShopUserCouponParam;
|
||||
import com.gxwebsoft.shop.service.ShopCouponService;
|
||||
import com.gxwebsoft.shop.service.ShopUserCouponService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 用户优惠券Service实现
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 21:30:00
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ShopUserCouponServiceImpl extends ServiceImpl<ShopUserCouponMapper, ShopUserCoupon>
|
||||
implements ShopUserCouponService {
|
||||
|
||||
@Resource
|
||||
private ShopCouponService shopCouponService;
|
||||
|
||||
@Override
|
||||
public PageResult<ShopUserCoupon> pageRel(ShopUserCouponParam param) {
|
||||
PageParam<ShopUserCoupon, ShopUserCouponParam> page = new PageParam<>(param);
|
||||
page.setDefaultOrder("create_time desc");
|
||||
List<ShopUserCoupon> list = baseMapper.selectPageRel(page, param);
|
||||
return new PageResult<>(list, page.getTotal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ShopUserCoupon> listRel(ShopUserCouponParam param) {
|
||||
return baseMapper.selectListRel(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShopUserCoupon getByIdRel(Long id) {
|
||||
ShopUserCouponParam param = new ShopUserCouponParam();
|
||||
param.setId(id);
|
||||
List<ShopUserCoupon> list = baseMapper.selectListRel(param);
|
||||
return list.isEmpty() ? null : list.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean receiveCoupon(Integer userId, Integer couponId) {
|
||||
// 检查是否可以领取
|
||||
Map<String, Object> checkResult = checkCanReceiveCoupon(userId, couponId);
|
||||
if (!(Boolean) checkResult.get("canReceive")) {
|
||||
log.warn("用户{}无法领取优惠券{}: {}", userId, couponId, checkResult.get("reason"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取优惠券模板
|
||||
ShopCoupon coupon = shopCouponService.getById(couponId);
|
||||
if (coupon == null) {
|
||||
log.warn("优惠券模板不存在: {}", couponId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建用户优惠券
|
||||
ShopUserCoupon userCoupon = createUserCouponFromTemplate(coupon, userId);
|
||||
userCoupon.setObtainType(ShopUserCoupon.OBTAIN_RECEIVE);
|
||||
userCoupon.setObtainSource("用户主动领取");
|
||||
|
||||
// 保存用户优惠券
|
||||
boolean saved = save(userCoupon);
|
||||
if (saved) {
|
||||
// 更新优惠券模板的已发放数量
|
||||
updateCouponIssuedCount(couponId, 1);
|
||||
log.info("用户{}成功领取优惠券{}", userId, couponId);
|
||||
}
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean issueCouponToUser(Integer userId, Integer couponId, String source) {
|
||||
// 获取优惠券模板
|
||||
ShopCoupon coupon = shopCouponService.getById(couponId);
|
||||
if (coupon == null || coupon.getEnabled() != 1) {
|
||||
log.warn("优惠券模板不存在或已禁用: {}", couponId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查发放数量限制
|
||||
if (coupon.getTotalCount() != null && coupon.getTotalCount() > 0) {
|
||||
int issuedCount = coupon.getIssuedCount() != null ? coupon.getIssuedCount() : 0;
|
||||
if (issuedCount >= coupon.getTotalCount()) {
|
||||
log.warn("优惠券{}发放数量已达上限", couponId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建用户优惠券
|
||||
ShopUserCoupon userCoupon = createUserCouponFromTemplate(coupon, userId);
|
||||
userCoupon.setObtainType(ShopUserCoupon.OBTAIN_SYSTEM);
|
||||
userCoupon.setObtainSource(source != null ? source : "系统发放");
|
||||
|
||||
// 保存用户优惠券
|
||||
boolean saved = save(userCoupon);
|
||||
if (saved) {
|
||||
// 更新优惠券模板的已发放数量
|
||||
updateCouponIssuedCount(couponId, 1);
|
||||
log.info("系统向用户{}发放优惠券{}", userId, couponId);
|
||||
}
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int batchIssueCoupons(List<Integer> userIds, Integer couponId, String source) {
|
||||
if (userIds == null || userIds.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 获取优惠券模板
|
||||
ShopCoupon coupon = shopCouponService.getById(couponId);
|
||||
if (coupon == null || coupon.getEnabled() != 1) {
|
||||
log.warn("优惠券模板不存在或已禁用: {}", couponId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int successCount = 0;
|
||||
for (Integer userId : userIds) {
|
||||
if (issueCouponToUser(userId, couponId, source)) {
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("批量发放优惠券{}给{}个用户,成功{}个", couponId, userIds.size(), successCount);
|
||||
return successCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean useCoupon(Long userCouponId, Long orderId, String orderNo) {
|
||||
// 获取用户优惠券
|
||||
ShopUserCoupon userCoupon = getById(userCouponId);
|
||||
if (userCoupon == null) {
|
||||
log.warn("用户优惠券不存在: {}", userCouponId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查优惠券状态
|
||||
if (!userCoupon.isAvailable()) {
|
||||
log.warn("优惠券不可用: {}", userCouponId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新优惠券状态
|
||||
LambdaUpdateWrapper<ShopUserCoupon> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
updateWrapper.eq(ShopUserCoupon::getId, userCouponId)
|
||||
.eq(ShopUserCoupon::getStatus, ShopUserCoupon.STATUS_UNUSED)
|
||||
.set(ShopUserCoupon::getStatus, ShopUserCoupon.STATUS_USED)
|
||||
.set(ShopUserCoupon::getUseTime, LocalDateTime.now())
|
||||
.set(ShopUserCoupon::getOrderId, orderId)
|
||||
.set(ShopUserCoupon::getOrderNo, orderNo);
|
||||
|
||||
boolean updated = update(updateWrapper);
|
||||
if (updated) {
|
||||
log.info("用户优惠券{}已使用,订单: {}", userCouponId, orderNo);
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean returnCoupon(Long orderId) {
|
||||
// 查找使用该订单的优惠券
|
||||
LambdaQueryWrapper<ShopUserCoupon> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ShopUserCoupon::getOrderId, orderId)
|
||||
.eq(ShopUserCoupon::getStatus, ShopUserCoupon.STATUS_USED);
|
||||
|
||||
List<ShopUserCoupon> usedCoupons = list(queryWrapper);
|
||||
if (usedCoupons.isEmpty()) {
|
||||
return true; // 没有使用优惠券,直接返回成功
|
||||
}
|
||||
|
||||
// 退还优惠券
|
||||
for (ShopUserCoupon userCoupon : usedCoupons) {
|
||||
// 检查是否已过期
|
||||
if (userCoupon.isExpired()) {
|
||||
// 已过期的券设为过期状态
|
||||
userCoupon.setStatus(ShopUserCoupon.STATUS_EXPIRED);
|
||||
} else {
|
||||
// 未过期的券恢复为未使用状态
|
||||
userCoupon.setStatus(ShopUserCoupon.STATUS_UNUSED);
|
||||
}
|
||||
|
||||
userCoupon.setUseTime(null);
|
||||
userCoupon.setOrderId(null);
|
||||
userCoupon.setOrderNo(null);
|
||||
updateById(userCoupon);
|
||||
|
||||
log.info("退还用户优惠券{},订单: {}", userCoupon.getId(), orderId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从优惠券模板创建用户优惠券
|
||||
*/
|
||||
private ShopUserCoupon createUserCouponFromTemplate(ShopCoupon coupon, Integer userId) {
|
||||
ShopUserCoupon userCoupon = new ShopUserCoupon();
|
||||
userCoupon.setCouponId(coupon.getId());
|
||||
userCoupon.setUserId(userId);
|
||||
userCoupon.setName(coupon.getName());
|
||||
userCoupon.setDescription(coupon.getDescription());
|
||||
userCoupon.setType(coupon.getType());
|
||||
userCoupon.setReducePrice(coupon.getReducePrice());
|
||||
userCoupon.setDiscount(coupon.getDiscount());
|
||||
userCoupon.setMinPrice(coupon.getMinPrice());
|
||||
userCoupon.setApplyRange(coupon.getApplyRange());
|
||||
userCoupon.setApplyRangeConfig(coupon.getApplyRangeConfig());
|
||||
userCoupon.setStatus(ShopUserCoupon.STATUS_UNUSED);
|
||||
userCoupon.setTenantId(coupon.getTenantId());
|
||||
|
||||
// 设置有效期
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (coupon.getExpireType() == 10) {
|
||||
// 领取后生效
|
||||
userCoupon.setStartTime(now);
|
||||
if (coupon.getExpireDay() != null && coupon.getExpireDay() > 0) {
|
||||
userCoupon.setEndTime(now.plusDays(coupon.getExpireDay()));
|
||||
}
|
||||
} else if (coupon.getExpireType() == 20) {
|
||||
// 固定时间
|
||||
userCoupon.setStartTime(coupon.getStartTime() != null ?
|
||||
coupon.getStartTime().atStartOfDay() : now);
|
||||
userCoupon.setEndTime(coupon.getEndTime() != null ?
|
||||
coupon.getEndTime().atTime(23, 59, 59) : null);
|
||||
}
|
||||
|
||||
return userCoupon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ShopUserCoupon> getAvailableCoupons(Integer userId, Integer goodsId,
|
||||
Integer categoryId, BigDecimal orderAmount) {
|
||||
ShopUserCouponParam param = new ShopUserCouponParam();
|
||||
param.setUserId(userId);
|
||||
param.setStatus(ShopUserCoupon.STATUS_UNUSED);
|
||||
param.setOnlyAvailable(true);
|
||||
param.setGoodsId(goodsId);
|
||||
param.setCategoryId(categoryId);
|
||||
param.setOrderAmount(orderAmount);
|
||||
|
||||
return baseMapper.selectAvailableCoupons(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal calculateDiscountAmount(ShopUserCoupon userCoupon, BigDecimal orderAmount) {
|
||||
if (userCoupon == null || orderAmount == null || orderAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
// 检查最低消费金额
|
||||
if (userCoupon.getMinPrice() != null &&
|
||||
orderAmount.compareTo(userCoupon.getMinPrice()) < 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
BigDecimal discountAmount = BigDecimal.ZERO;
|
||||
|
||||
if (userCoupon.getType() == ShopUserCoupon.TYPE_REDUCE) {
|
||||
// 满减券
|
||||
discountAmount = userCoupon.getReducePrice() != null ?
|
||||
userCoupon.getReducePrice() : BigDecimal.ZERO;
|
||||
} else if (userCoupon.getType() == ShopUserCoupon.TYPE_DISCOUNT) {
|
||||
// 折扣券
|
||||
if (userCoupon.getDiscount() != null && userCoupon.getDiscount() > 0 && userCoupon.getDiscount() < 100) {
|
||||
BigDecimal discountRate = BigDecimal.valueOf(100 - userCoupon.getDiscount())
|
||||
.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP);
|
||||
discountAmount = orderAmount.multiply(discountRate);
|
||||
}
|
||||
} else if (userCoupon.getType() == ShopUserCoupon.TYPE_FREE) {
|
||||
// 免费券
|
||||
discountAmount = orderAmount;
|
||||
}
|
||||
|
||||
// 优惠金额不能超过订单金额
|
||||
return discountAmount.compareTo(orderAmount) > 0 ? orderAmount : discountAmount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateCouponForGoods(ShopUserCoupon userCoupon, Integer goodsId, Integer categoryId) {
|
||||
if (userCoupon == null || userCoupon.getApplyRange() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (userCoupon.getApplyRange() == ShopUserCoupon.APPLY_ALL) {
|
||||
// 全部商品可用
|
||||
return true;
|
||||
} else if (userCoupon.getApplyRange() == ShopUserCoupon.APPLY_GOODS) {
|
||||
// 指定商品可用
|
||||
if (goodsId == null || userCoupon.getApplyRangeConfig() == null) {
|
||||
return false;
|
||||
}
|
||||
// 解析商品ID列表(假设是逗号分隔的字符串)
|
||||
String[] goodsIds = userCoupon.getApplyRangeConfig().split(",");
|
||||
return Arrays.asList(goodsIds).contains(goodsId.toString());
|
||||
} else if (userCoupon.getApplyRange() == ShopUserCoupon.APPLY_CATEGORY) {
|
||||
// 指定分类可用
|
||||
if (categoryId == null || userCoupon.getApplyRangeConfig() == null) {
|
||||
return false;
|
||||
}
|
||||
// 解析分类ID列表(假设是逗号分隔的字符串)
|
||||
String[] categoryIds = userCoupon.getApplyRangeConfig().split(",");
|
||||
return Arrays.asList(categoryIds).contains(categoryId.toString());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> countUserCoupons(Integer userId) {
|
||||
return baseMapper.countUserCoupons(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int updateExpiredCoupons() {
|
||||
return baseMapper.updateExpiredStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ShopUserCoupon> getExpiringSoonCoupons(Integer days) {
|
||||
return baseMapper.selectExpiringSoon(days != null ? days : 3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> checkCanReceiveCoupon(Integer userId, Integer couponId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
// 获取优惠券模板
|
||||
ShopCoupon coupon = shopCouponService.getById(couponId);
|
||||
if (coupon == null) {
|
||||
result.put("canReceive", false);
|
||||
result.put("reason", "优惠券不存在");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (coupon.getEnabled() != 1) {
|
||||
result.put("canReceive", false);
|
||||
result.put("reason", "优惠券已禁用");
|
||||
return result;
|
||||
}
|
||||
|
||||
// 检查发放数量限制
|
||||
if (coupon.getTotalCount() != null && coupon.getTotalCount() > 0) {
|
||||
int issuedCount = coupon.getIssuedCount() != null ? coupon.getIssuedCount() : 0;
|
||||
if (issuedCount >= coupon.getTotalCount()) {
|
||||
result.put("canReceive", false);
|
||||
result.put("reason", "优惠券已领完");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查每人限领数量
|
||||
if (coupon.getLimitPerUser() != null && coupon.getLimitPerUser() > 0) {
|
||||
int receivedCount = baseMapper.countUserReceivedCoupon(userId, couponId);
|
||||
if (receivedCount >= coupon.getLimitPerUser()) {
|
||||
result.put("canReceive", false);
|
||||
result.put("reason", "已达到个人领取上限");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result.put("canReceive", true);
|
||||
result.put("reason", "可以领取");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新优惠券模板的已发放数量
|
||||
*/
|
||||
private void updateCouponIssuedCount(Integer couponId, int increment) {
|
||||
LambdaUpdateWrapper<ShopCoupon> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
updateWrapper.eq(ShopCoupon::getId, couponId)
|
||||
.setSql("issued_count = IFNULL(issued_count, 0) + " + increment);
|
||||
shopCouponService.update(updateWrapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.gxwebsoft.shop.task;
|
||||
|
||||
import com.gxwebsoft.shop.service.CouponBusinessService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 优惠券定时任务
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 22:30:00
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CouponScheduledTask {
|
||||
|
||||
@Resource
|
||||
private CouponBusinessService couponBusinessService;
|
||||
|
||||
/**
|
||||
* 每天凌晨2点处理过期优惠券
|
||||
*/
|
||||
@Scheduled(cron = "0 0 2 * * ?")
|
||||
public void processExpiredCoupons() {
|
||||
try {
|
||||
log.info("开始执行过期优惠券处理任务");
|
||||
int count = couponBusinessService.autoProcessExpiredCoupons();
|
||||
log.info("过期优惠券处理任务完成,处理{}张优惠券", count);
|
||||
} catch (Exception e) {
|
||||
log.error("过期优惠券处理任务执行失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 每天上午10点发送优惠券到期提醒
|
||||
*/
|
||||
@Scheduled(cron = "0 0 10 * * ?")
|
||||
public void sendExpiryReminder() {
|
||||
try {
|
||||
log.info("开始执行优惠券到期提醒任务");
|
||||
int count = couponBusinessService.sendCouponExpiryReminder(3);
|
||||
log.info("优惠券到期提醒任务完成,提醒{}个用户", count);
|
||||
} catch (Exception e) {
|
||||
log.error("优惠券到期提醒任务执行失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 每天凌晨1点发送生日优惠券
|
||||
*/
|
||||
@Scheduled(cron = "0 0 1 * * ?")
|
||||
public void issueBirthdayCoupons() {
|
||||
try {
|
||||
log.info("开始执行生日优惠券发放任务");
|
||||
// 这里需要获取今天生日的用户列表
|
||||
// 由于没有具体的用户生日查询方法,这里只是示例
|
||||
log.info("生日优惠券发放任务完成");
|
||||
} catch (Exception e) {
|
||||
log.error("生日优惠券发放任务执行失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
276
src/main/java/com/gxwebsoft/shop/utils/CouponUtils.java
Normal file
276
src/main/java/com/gxwebsoft/shop/utils/CouponUtils.java
Normal file
@@ -0,0 +1,276 @@
|
||||
package com.gxwebsoft.shop.utils;
|
||||
|
||||
import com.gxwebsoft.shop.entity.ShopUserCoupon;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 优惠券工具类
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-08-08 23:00:00
|
||||
*/
|
||||
public class CouponUtils {
|
||||
|
||||
/**
|
||||
* 优惠券类型名称映射
|
||||
*/
|
||||
public static String getTypeName(Integer type) {
|
||||
if (type == null) {
|
||||
return "未知";
|
||||
}
|
||||
switch (type) {
|
||||
case 10:
|
||||
return "满减券";
|
||||
case 20:
|
||||
return "折扣券";
|
||||
case 30:
|
||||
return "免费券";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优惠券状态名称映射
|
||||
*/
|
||||
public static String getStatusName(Integer status) {
|
||||
if (status == null) {
|
||||
return "未知";
|
||||
}
|
||||
switch (status) {
|
||||
case 0:
|
||||
return "未使用";
|
||||
case 1:
|
||||
return "已使用";
|
||||
case 2:
|
||||
return "已过期";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 适用范围名称映射
|
||||
*/
|
||||
public static String getApplyRangeName(Integer applyRange) {
|
||||
if (applyRange == null) {
|
||||
return "未知";
|
||||
}
|
||||
switch (applyRange) {
|
||||
case 10:
|
||||
return "全部商品";
|
||||
case 20:
|
||||
return "指定商品";
|
||||
case 30:
|
||||
return "指定分类";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取方式名称映射
|
||||
*/
|
||||
public static String getObtainTypeName(Integer obtainType) {
|
||||
if (obtainType == null) {
|
||||
return "未知";
|
||||
}
|
||||
switch (obtainType) {
|
||||
case 10:
|
||||
return "主动领取";
|
||||
case 20:
|
||||
return "系统发放";
|
||||
case 30:
|
||||
return "活动赠送";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算优惠券优惠金额
|
||||
*
|
||||
* @param userCoupon 用户优惠券
|
||||
* @param orderAmount 订单金额
|
||||
* @return 优惠金额
|
||||
*/
|
||||
public static BigDecimal calculateDiscountAmount(ShopUserCoupon userCoupon, BigDecimal orderAmount) {
|
||||
if (userCoupon == null || orderAmount == null || orderAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
// 检查最低消费金额
|
||||
if (userCoupon.getMinPrice() != null &&
|
||||
orderAmount.compareTo(userCoupon.getMinPrice()) < 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
BigDecimal discountAmount = BigDecimal.ZERO;
|
||||
|
||||
if (userCoupon.getType() == ShopUserCoupon.TYPE_REDUCE) {
|
||||
// 满减券
|
||||
discountAmount = userCoupon.getReducePrice() != null ?
|
||||
userCoupon.getReducePrice() : BigDecimal.ZERO;
|
||||
} else if (userCoupon.getType() == ShopUserCoupon.TYPE_DISCOUNT) {
|
||||
// 折扣券
|
||||
if (userCoupon.getDiscount() != null && userCoupon.getDiscount() > 0 && userCoupon.getDiscount() < 100) {
|
||||
BigDecimal discountRate = BigDecimal.valueOf(100 - userCoupon.getDiscount())
|
||||
.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP);
|
||||
discountAmount = orderAmount.multiply(discountRate);
|
||||
}
|
||||
} else if (userCoupon.getType() == ShopUserCoupon.TYPE_FREE) {
|
||||
// 免费券
|
||||
discountAmount = orderAmount;
|
||||
}
|
||||
|
||||
// 优惠金额不能超过订单金额
|
||||
return discountAmount.compareTo(orderAmount) > 0 ? orderAmount : discountAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查优惠券是否适用于指定商品
|
||||
*
|
||||
* @param userCoupon 用户优惠券
|
||||
* @param goodsId 商品ID
|
||||
* @param categoryId 商品分类ID
|
||||
* @return 是否适用
|
||||
*/
|
||||
public static boolean isApplicableToGoods(ShopUserCoupon userCoupon, Integer goodsId, Integer categoryId) {
|
||||
if (userCoupon == null || userCoupon.getApplyRange() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (userCoupon.getApplyRange() == ShopUserCoupon.APPLY_ALL) {
|
||||
// 全部商品可用
|
||||
return true;
|
||||
} else if (userCoupon.getApplyRange() == ShopUserCoupon.APPLY_GOODS) {
|
||||
// 指定商品可用
|
||||
if (goodsId == null || userCoupon.getApplyRangeConfig() == null) {
|
||||
return false;
|
||||
}
|
||||
List<String> goodsIds = Arrays.asList(userCoupon.getApplyRangeConfig().split(","));
|
||||
return goodsIds.contains(goodsId.toString());
|
||||
} else if (userCoupon.getApplyRange() == ShopUserCoupon.APPLY_CATEGORY) {
|
||||
// 指定分类可用
|
||||
if (categoryId == null || userCoupon.getApplyRangeConfig() == null) {
|
||||
return false;
|
||||
}
|
||||
List<String> categoryIds = Arrays.asList(userCoupon.getApplyRangeConfig().split(","));
|
||||
return categoryIds.contains(categoryId.toString());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查优惠券是否已过期
|
||||
*
|
||||
* @param userCoupon 用户优惠券
|
||||
* @return 是否已过期
|
||||
*/
|
||||
public static boolean isExpired(ShopUserCoupon userCoupon) {
|
||||
if (userCoupon == null || userCoupon.getEndTime() == null) {
|
||||
return false;
|
||||
}
|
||||
return userCoupon.getEndTime().isBefore(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查优惠券是否可用
|
||||
*
|
||||
* @param userCoupon 用户优惠券
|
||||
* @return 是否可用
|
||||
*/
|
||||
public static boolean isAvailable(ShopUserCoupon userCoupon) {
|
||||
if (userCoupon == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查状态
|
||||
if (userCoupon.getStatus() != ShopUserCoupon.STATUS_UNUSED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if (isExpired(userCoupon)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否在有效期内
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (userCoupon.getStartTime() != null && userCoupon.getStartTime().isAfter(now)) {
|
||||
return false; // 还未开始
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化优惠券显示文本
|
||||
*
|
||||
* @param userCoupon 用户优惠券
|
||||
* @return 显示文本
|
||||
*/
|
||||
public static String formatCouponDisplay(ShopUserCoupon userCoupon) {
|
||||
if (userCoupon == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(userCoupon.getName());
|
||||
|
||||
if (userCoupon.getType() == ShopUserCoupon.TYPE_REDUCE && userCoupon.getReducePrice() != null) {
|
||||
sb.append(" 减").append(userCoupon.getReducePrice()).append("元");
|
||||
if (userCoupon.getMinPrice() != null && userCoupon.getMinPrice().compareTo(BigDecimal.ZERO) > 0) {
|
||||
sb.append("(满").append(userCoupon.getMinPrice()).append("可用)");
|
||||
}
|
||||
} else if (userCoupon.getType() == ShopUserCoupon.TYPE_DISCOUNT && userCoupon.getDiscount() != null) {
|
||||
sb.append(" ").append(userCoupon.getDiscount()).append("折");
|
||||
if (userCoupon.getMinPrice() != null && userCoupon.getMinPrice().compareTo(BigDecimal.ZERO) > 0) {
|
||||
sb.append("(满").append(userCoupon.getMinPrice()).append("可用)");
|
||||
}
|
||||
} else if (userCoupon.getType() == ShopUserCoupon.TYPE_FREE) {
|
||||
sb.append(" 免费券");
|
||||
}
|
||||
|
||||
if (userCoupon.getEndTime() != null) {
|
||||
sb.append(" 有效期至").append(userCoupon.getEndTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成优惠券编码
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param couponId 优惠券模板ID
|
||||
* @return 优惠券编码
|
||||
*/
|
||||
public static String generateCouponCode(Integer userId, Integer couponId) {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||
return String.format("CPN%s%s%s",
|
||||
String.format("%08d", userId),
|
||||
String.format("%06d", couponId),
|
||||
timestamp.substring(timestamp.length() - 6));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证优惠券编码格式
|
||||
*
|
||||
* @param couponCode 优惠券编码
|
||||
* @return 是否有效
|
||||
*/
|
||||
public static boolean isValidCouponCode(String couponCode) {
|
||||
if (couponCode == null || couponCode.length() != 23) {
|
||||
return false;
|
||||
}
|
||||
return couponCode.startsWith("CPN") && couponCode.substring(3).matches("\\d{20}");
|
||||
}
|
||||
}
|
||||
78
src/main/resources/sql/coupon_tables.sql
Normal file
78
src/main/resources/sql/coupon_tables.sql
Normal file
@@ -0,0 +1,78 @@
|
||||
-- 优惠券功能相关数据库表
|
||||
|
||||
-- 1. 更新优惠券模板表
|
||||
ALTER TABLE `shop_coupon`
|
||||
ADD COLUMN `description` varchar(500) COMMENT '优惠券描述' AFTER `name`,
|
||||
ADD COLUMN `total_count` int(11) DEFAULT -1 COMMENT '发放总数量(-1表示无限制)',
|
||||
ADD COLUMN `issued_count` int(11) DEFAULT 0 COMMENT '已发放数量',
|
||||
ADD COLUMN `limit_per_user` int(11) DEFAULT -1 COMMENT '每人限领数量(-1表示无限制)',
|
||||
ADD COLUMN `enabled` tinyint(1) DEFAULT 1 COMMENT '是否启用(0禁用 1启用)',
|
||||
MODIFY COLUMN `apply_range` int(11) DEFAULT 10 COMMENT '适用范围(10全部商品 20指定商品 30指定分类)',
|
||||
MODIFY COLUMN `status` int(11) DEFAULT 0 COMMENT '状态, 0正常, 1禁用',
|
||||
MODIFY COLUMN `user_id` int(11) COMMENT '创建用户ID';
|
||||
|
||||
-- 2. 创建用户优惠券表
|
||||
CREATE TABLE `shop_user_coupon` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||
`coupon_id` int(11) NOT NULL COMMENT '优惠券模板ID',
|
||||
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
||||
`name` varchar(100) NOT NULL COMMENT '优惠券名称',
|
||||
`description` varchar(500) DEFAULT NULL COMMENT '优惠券描述',
|
||||
`type` int(11) NOT NULL COMMENT '优惠券类型(10满减券 20折扣券 30免费劵)',
|
||||
`reduce_price` decimal(10,2) DEFAULT NULL COMMENT '满减券-减免金额',
|
||||
`discount` int(11) DEFAULT NULL COMMENT '折扣券-折扣率(0-100)',
|
||||
`min_price` decimal(10,2) DEFAULT NULL COMMENT '最低消费金额',
|
||||
`apply_range` int(11) DEFAULT 10 COMMENT '适用范围(10全部商品 20指定商品 30指定分类)',
|
||||
`apply_range_config` text COMMENT '适用范围配置(json格式)',
|
||||
`start_time` datetime DEFAULT NULL COMMENT '有效期开始时间',
|
||||
`end_time` datetime DEFAULT NULL COMMENT '有效期结束时间',
|
||||
`status` int(11) DEFAULT 0 COMMENT '使用状态(0未使用 1已使用 2已过期)',
|
||||
`use_time` datetime DEFAULT NULL COMMENT '使用时间',
|
||||
`order_id` bigint(20) DEFAULT NULL COMMENT '使用订单ID',
|
||||
`order_no` varchar(50) DEFAULT NULL COMMENT '使用订单号',
|
||||
`obtain_type` int(11) DEFAULT 10 COMMENT '获取方式(10主动领取 20系统发放 30活动赠送)',
|
||||
`obtain_source` varchar(200) DEFAULT NULL COMMENT '获取来源描述',
|
||||
`deleted` tinyint(1) DEFAULT 0 COMMENT '是否删除, 0否, 1是',
|
||||
`tenant_id` int(11) DEFAULT NULL COMMENT '租户id',
|
||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
KEY `idx_coupon_id` (`coupon_id`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_end_time` (`end_time`),
|
||||
KEY `idx_order_id` (`order_id`),
|
||||
KEY `idx_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户优惠券';
|
||||
|
||||
-- 3. 创建优惠券使用记录表(可选,用于详细统计)
|
||||
CREATE TABLE `shop_coupon_usage_log` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||
`user_coupon_id` bigint(20) NOT NULL COMMENT '用户优惠券ID',
|
||||
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
||||
`order_id` bigint(20) NOT NULL COMMENT '订单ID',
|
||||
`order_no` varchar(50) NOT NULL COMMENT '订单号',
|
||||
`order_amount` decimal(10,2) NOT NULL COMMENT '订单金额',
|
||||
`discount_amount` decimal(10,2) NOT NULL COMMENT '优惠金额',
|
||||
`final_amount` decimal(10,2) NOT NULL COMMENT '最终金额',
|
||||
`use_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '使用时间',
|
||||
`tenant_id` int(11) DEFAULT NULL COMMENT '租户id',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
KEY `idx_order_id` (`order_id`),
|
||||
KEY `idx_use_time` (`use_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='优惠券使用记录';
|
||||
|
||||
-- 4. 插入示例优惠券模板数据
|
||||
INSERT INTO `shop_coupon` (`name`, `description`, `type`, `reduce_price`, `discount`, `min_price`, `total_count`, `issued_count`, `limit_per_user`, `expire_type`, `expire_day`, `apply_range`, `apply_range_config`, `enabled`, `sort_number`, `status`, `user_id`, `tenant_id`) VALUES
|
||||
('新用户专享券', '新用户注册即可领取,满100减20', 10, 20.00, NULL, 100.00, 1000, 0, 1, 10, 30, 10, NULL, 1, 1, 0, 1, 1),
|
||||
('满减优惠券', '全场通用,满200减50', 10, 50.00, NULL, 200.00, 500, 0, 2, 20, NULL, 10, NULL, 1, 2, 0, 1, 1),
|
||||
('折扣优惠券', '全场9折优惠券', 20, NULL, 10, 50.00, 300, 0, 1, 10, 15, 10, NULL, 1, 3, 0, 1, 1),
|
||||
('生日专享券', '生日当天专享,满50减30', 10, 30.00, NULL, 50.00, -1, 0, 1, 10, 7, 10, NULL, 1, 4, 0, 1, 1),
|
||||
('消费返券', '消费满500返100优惠券', 10, 100.00, NULL, 300.00, -1, 0, -1, 10, 60, 10, NULL, 1, 5, 0, 1, 1);
|
||||
|
||||
-- 5. 创建索引优化查询性能
|
||||
CREATE INDEX `idx_shop_coupon_enabled_status` ON `shop_coupon` (`enabled`, `status`);
|
||||
CREATE INDEX `idx_shop_coupon_expire_type` ON `shop_coupon` (`expire_type`, `start_time`, `end_time`);
|
||||
CREATE INDEX `idx_shop_user_coupon_user_status` ON `shop_user_coupon` (`user_id`, `status`);
|
||||
CREATE INDEX `idx_shop_user_coupon_expire` ON `shop_user_coupon` (`status`, `end_time`);
|
||||
Reference in New Issue
Block a user