Merge branch 'dev_market'
This commit is contained in:
commit
921a83d5c3
39
jzo2o-api/src/main/java/com/jzo2o/api/market/CouponApi.java
Normal file
39
jzo2o-api/src/main/java/com/jzo2o/api/market/CouponApi.java
Normal file
@ -0,0 +1,39 @@
|
||||
package com.jzo2o.api.market;
|
||||
|
||||
import com.jzo2o.api.market.dto.request.CouponUseBackReqDTO;
|
||||
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
|
||||
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
|
||||
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 内部接口 - 优惠卷相关接口
|
||||
* @author JIAN
|
||||
*/
|
||||
@FeignClient(contextId = "jzo2o-market", value = "jzo2o-market", path = "/market/inner/coupon")
|
||||
public interface CouponApi {
|
||||
/**
|
||||
* 根据订单金额获取当前用户可用优惠卷
|
||||
*/
|
||||
@GetMapping("/getAvailable")
|
||||
List<AvailableCouponsResDTO> getAvailableCoupon(@RequestParam BigDecimal totalAmount);
|
||||
|
||||
/**
|
||||
* 用户核销优惠卷返回优惠金额
|
||||
*/
|
||||
@PostMapping("/use")
|
||||
CouponUseResDTO useCoupon(@RequestBody CouponUseReqDTO couponUseReqDTO);
|
||||
|
||||
/**
|
||||
* 用户取消订单退回使用优惠卷
|
||||
*/
|
||||
@PostMapping("/useBack")
|
||||
void useBack(@RequestBody CouponUseBackReqDTO couponUseBackReqDTO);
|
||||
}
|
||||
@ -4,8 +4,8 @@ import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@ApiModel("优惠券使用退回请求模型")
|
||||
@Data
|
||||
@ApiModel("优惠券使用退回请求模型")
|
||||
public class CouponUseBackReqDTO {
|
||||
@ApiModelProperty("优惠券id")
|
||||
private Long id;
|
||||
@ -13,4 +13,4 @@ public class CouponUseBackReqDTO {
|
||||
private Long ordersId;
|
||||
@ApiModelProperty("用户id")
|
||||
private Long userId;
|
||||
}
|
||||
}
|
||||
@ -2,11 +2,13 @@ package com.jzo2o.api.market.dto.request;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@ApiModel("优惠券使用模型")
|
||||
public class CouponUseReqDTO {
|
||||
@ApiModelProperty("优惠券id")
|
||||
@ -15,4 +17,4 @@ public class CouponUseReqDTO {
|
||||
private Long ordersId;
|
||||
@ApiModelProperty("总金额")
|
||||
private BigDecimal totalAmount;
|
||||
}
|
||||
}
|
||||
@ -3,12 +3,17 @@ package com.jzo2o.api.market.dto.response;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author JIAN
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("优惠券信息")
|
||||
@Accessors(chain = true)
|
||||
public class AvailableCouponsResDTO {
|
||||
@ApiModelProperty("优惠券id")
|
||||
private Long id;
|
||||
@ -16,34 +21,34 @@ public class AvailableCouponsResDTO {
|
||||
/**
|
||||
* 优惠券名称
|
||||
*/
|
||||
@ApiModelProperty(value = "活动名称",required = true)
|
||||
@ApiModelProperty(value = "活动名称", required = true)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 活动id
|
||||
*/
|
||||
@ApiModelProperty(value = "活动id",required = true)
|
||||
@ApiModelProperty(value = "活动id", required = true)
|
||||
private Long activityId;
|
||||
|
||||
@ApiModelProperty(value = "使用类型,1:满减,2:折扣",required = true)
|
||||
@ApiModelProperty(value = "使用类型,1:满减,2:折扣", required = true)
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 折扣
|
||||
*/
|
||||
@ApiModelProperty(value = "折扣",required = false)
|
||||
@ApiModelProperty(value = "折扣")
|
||||
private Integer discountRate;
|
||||
|
||||
/**
|
||||
* 优惠金额
|
||||
*/
|
||||
@ApiModelProperty(value = "优惠金额",required = false)
|
||||
@ApiModelProperty(value = "优惠金额")
|
||||
private BigDecimal discountAmount;
|
||||
|
||||
/**
|
||||
* 满减金额
|
||||
*/
|
||||
@ApiModelProperty(value = "满减条件,0:表示无门槛",required = true)
|
||||
@ApiModelProperty(value = "满减条件,0:表示无门槛", required = true)
|
||||
private BigDecimal amountCondition;
|
||||
|
||||
/**
|
||||
@ -51,4 +56,4 @@ public class AvailableCouponsResDTO {
|
||||
*/
|
||||
@ApiModelProperty("优惠券过期时间")
|
||||
private LocalDateTime validityTime;
|
||||
}
|
||||
}
|
||||
@ -2,13 +2,17 @@ package com.jzo2o.api.market.dto.response;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@ApiModel("优惠券使用返回信息模型")
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CouponUseResDTO {
|
||||
@ApiModelProperty("优惠金额")
|
||||
private BigDecimal discountAmount;
|
||||
}
|
||||
}
|
||||
9
jzo2o-market/Dockerfile
Normal file
9
jzo2o-market/Dockerfile
Normal file
@ -0,0 +1,9 @@
|
||||
FROM openjdk:11-jdk
|
||||
LABEL maintainer="研究院研发组 <research-maint@itcast.cn>"
|
||||
RUN echo "Asia/Shanghai" > /etc/timezone
|
||||
ARG PACKAGE_PATH=./target/jzo2o-market.jar
|
||||
|
||||
ADD ${PACKAGE_PATH:-./} app.jar
|
||||
|
||||
EXPOSE 11510
|
||||
ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS app.jar"]
|
||||
103
jzo2o-market/pom.xml
Normal file
103
jzo2o-market/pom.xml
Normal file
@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>jzo2o-market</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<parent>
|
||||
<artifactId>jzo2o-parent</artifactId>
|
||||
<groupId>com.jzo2o</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jzo2o</groupId>
|
||||
<artifactId>jzo2o-mvc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.jzo2o</groupId>
|
||||
<artifactId>jzo2o-knife4j-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.jzo2o</groupId>
|
||||
<artifactId>jzo2o-mysql</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.jzo2o</groupId>
|
||||
<artifactId>jzo2o-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.jzo2o</groupId>
|
||||
<artifactId>jzo2o-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--单元测试-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!--canal-->
|
||||
<dependency>
|
||||
<groupId>com.jzo2o</groupId>
|
||||
<artifactId>jzo2o-canal-sync</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.jzo2o</groupId>
|
||||
<artifactId>jzo2o-xxl-job</artifactId>
|
||||
</dependency>
|
||||
<!-- <dependency>
|
||||
<groupId>com.jzo2o</groupId>
|
||||
<artifactId>jzo2o-seata</artifactId>
|
||||
</dependency>-->
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>build-info</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<mainClass>com.jzo2o.market.MarketApplication</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,20 @@
|
||||
package com.jzo2o.market;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
@EnableAspectJAutoProxy
|
||||
@SpringBootApplication
|
||||
@Slf4j
|
||||
@MapperScan("com.jzo2o.market.mapper")
|
||||
public class MarketApplication {
|
||||
public static void main(String[] args) {
|
||||
new SpringApplicationBuilder(MarketApplication.class)
|
||||
.build(args)
|
||||
.run(args);
|
||||
log.info("家政服务-营销中心启动");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package com.jzo2o.market.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.scripting.support.ResourceScriptSource;
|
||||
|
||||
/**
|
||||
* 加载Redis的Lua脚本配置类
|
||||
* @author JIAN
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisLuaConfiguration {
|
||||
@Bean("seizeCouponScript")
|
||||
public DefaultRedisScript<Integer> seizeCouponScript() {
|
||||
DefaultRedisScript<Integer> redisScript = new DefaultRedisScript<>();
|
||||
// resource目录下的scripts文件下的seizeCouponScript.lua文件
|
||||
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("scripts/seizeCouponScript.lua")));
|
||||
redisScript.setResultType(Integer.class);
|
||||
return redisScript;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.jzo2o.market.config;
|
||||
|
||||
import com.jzo2o.redis.properties.RedisSyncProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 线程池配置
|
||||
* @author JIAN
|
||||
*/
|
||||
@Configuration
|
||||
public class TreadPoolConfiguration {
|
||||
@Bean("syncThreadPool")
|
||||
public ThreadPoolExecutor threadPoolExecutor(RedisSyncProperties redisSyncProperties) {
|
||||
return new ThreadPoolExecutor(
|
||||
1,
|
||||
redisSyncProperties.getQueueNum(),
|
||||
120,
|
||||
TimeUnit.SECONDS,
|
||||
new SynchronousQueue<>(),
|
||||
new ThreadPoolExecutor.DiscardPolicy()
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.jzo2o.market.constants;
|
||||
|
||||
public class ErrorInfo {
|
||||
public static class Msg {
|
||||
public static final String SEIZE_COUPON_FAILD = "单子已经被抢走了";
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package com.jzo2o.market.constants;
|
||||
|
||||
public class RedisConstants {
|
||||
|
||||
public static final class RedisKey {
|
||||
/**
|
||||
* 优惠券库存表 由Canal同步程序写入
|
||||
* redis key格式:COUPON:RESOURCE:STOCK:{序号} 序号=活动id% 10
|
||||
* hash结构 (hashKey:活动id,hashValue:库存数量)
|
||||
*/
|
||||
public static final String COUPON_RESOURCE_STOCK = "COUPON:RESOURCE:STOCK:{%s}";
|
||||
|
||||
/**
|
||||
* 抢券同步队列,存储抢券成功记录由抢券程序(Lua)写入
|
||||
* redis key格式 QUEUE:COUPON:SEIZE:SYNC:{序号} 序号=活动id% 10
|
||||
* hash结构 (hashKey:活动id,hashValue:用户id)
|
||||
*/
|
||||
public static final String COUPON_SEIZE_SYNC_QUEUE_NAME = "COUPON:SEIZE:SYNC";
|
||||
|
||||
/**
|
||||
* 抢券成功列表,用户抢券成功写入记录
|
||||
* redis key格式 :COUPON:SEIZE:LIST:活动id_{序号} 序号=活动id% 10
|
||||
* hash结构 (hashKey:用户id,hashValue:1)
|
||||
*/
|
||||
public static final String COUPON_SEIZE_LIST = "COUPON:SEIZE:LIST:%s_{%s}";
|
||||
|
||||
/**
|
||||
* 活动列表 由于活动预热程序写入,待开始及进行中的活动
|
||||
* redis key格式:ACTIVITY:LIST
|
||||
* string 结构: value=活动列表json串
|
||||
*/
|
||||
public static final String ACTIVITY_CACHE_LIST = "ACTIVITY:LIST";
|
||||
}
|
||||
|
||||
public static final class Formatter {
|
||||
/**
|
||||
* 优惠券抢券同步
|
||||
*/
|
||||
public static final String COUPON_SEIZE_HANDLE_LOCK = "COUPON:SEIZE:RESULT_PROCESS";
|
||||
|
||||
/**
|
||||
* 活动预热
|
||||
*/
|
||||
public static final String ACTIVITY_PREHEAT = "ACTIVITY:PREHEAT";
|
||||
|
||||
/**
|
||||
* 活动结束
|
||||
*/
|
||||
public static final String ACTIVITY_FINISHED = "ACTIVITY:FINISHED";
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.jzo2o.market.constants;
|
||||
|
||||
public class TabTypeConstants {
|
||||
/**
|
||||
* 抢单中
|
||||
*/
|
||||
public static final int SEIZING = 1;
|
||||
|
||||
/**
|
||||
* 未开始
|
||||
*/
|
||||
public static final int NO_START = 2;
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package com.jzo2o.market.controller.consumer;
|
||||
|
||||
import com.jzo2o.common.expcetions.BadRequestException;
|
||||
import com.jzo2o.market.enums.ActivityStatusEnum;
|
||||
import com.jzo2o.market.model.dto.response.SeizeCouponInfoResDTO;
|
||||
import com.jzo2o.market.service.IActivityService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户端 - 活动管理控制器
|
||||
* @author JIAN
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController("consumerActivityController")
|
||||
@RequestMapping("/consumer/activity")
|
||||
public class ActivityController {
|
||||
@Resource
|
||||
private IActivityService activityService;
|
||||
|
||||
/**
|
||||
* 用户端查询缓存中的活动信息
|
||||
* @param tabType 1 进行中 2 待生效
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public List<SeizeCouponInfoResDTO> listActivity(@RequestParam Integer tabType) {
|
||||
ActivityStatusEnum activityStatusEnum;
|
||||
if (tabType == 1) {
|
||||
activityStatusEnum = ActivityStatusEnum.DISTRIBUTING;
|
||||
} else if (tabType == 2) {
|
||||
activityStatusEnum = ActivityStatusEnum.NO_DISTRIBUTE;
|
||||
} else {
|
||||
throw new BadRequestException("请求的状态出错");
|
||||
}
|
||||
|
||||
return activityService.getCachedActivity(activityStatusEnum);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package com.jzo2o.market.controller.consumer;
|
||||
|
||||
import com.jzo2o.common.expcetions.BadRequestException;
|
||||
import com.jzo2o.common.utils.ObjectUtils;
|
||||
import com.jzo2o.market.enums.CouponStatusEnum;
|
||||
import com.jzo2o.market.model.dto.request.SeizeCouponReqDTO;
|
||||
import com.jzo2o.market.model.dto.response.CouponSimpleInfoResDTO;
|
||||
import com.jzo2o.market.service.IActivityService;
|
||||
import com.jzo2o.market.service.ICouponService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户端 - 优惠卷管理控制器
|
||||
* @author JIAN
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController("consumerCouponController")
|
||||
@RequestMapping("/consumer/coupon")
|
||||
public class CouponController {
|
||||
@Resource
|
||||
private IActivityService activityService;
|
||||
@Resource
|
||||
private ICouponService couponService;
|
||||
|
||||
/**
|
||||
* 获取当前用户的优惠卷(滚动查询)
|
||||
*/
|
||||
@GetMapping("/my")
|
||||
public List<CouponSimpleInfoResDTO> currentUserCoupon(@RequestParam Integer status,
|
||||
@RequestParam(required = false) Integer lastId) {
|
||||
CouponStatusEnum couponStatusEnum = CouponStatusEnum.statusOf(status);
|
||||
if (ObjectUtils.isEmpty(couponStatusEnum)) {
|
||||
throw new BadRequestException("优惠卷状态出错");
|
||||
}
|
||||
|
||||
return couponService.getCurrentUserCoupon(couponStatusEnum, lastId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户抢卷接口
|
||||
*/
|
||||
@PostMapping("/seize")
|
||||
public void seizeCoupon(@RequestBody SeizeCouponReqDTO seizeCouponReqDTO) {
|
||||
activityService.seizeCoupon(seizeCouponReqDTO);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
package com.jzo2o.market.controller.inner;
|
||||
|
||||
import com.jzo2o.api.market.CouponApi;
|
||||
import com.jzo2o.api.market.dto.request.CouponUseBackReqDTO;
|
||||
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
|
||||
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
|
||||
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
|
||||
import com.jzo2o.market.service.ICouponService;
|
||||
import com.jzo2o.market.service.ICouponUseBackService;
|
||||
import com.jzo2o.market.service.ICouponWriteOffService;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 内部接口 - 优惠卷管理控制器
|
||||
* @author JIAN
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController("innerCouponController")
|
||||
@RequestMapping("/inner/coupon")
|
||||
public class CouponController implements CouponApi {
|
||||
@Resource
|
||||
private ICouponService couponService;
|
||||
@Resource
|
||||
private ICouponWriteOffService couponWriteOffService;
|
||||
@Resource
|
||||
private ICouponUseBackService couponUseBackService;
|
||||
|
||||
/**
|
||||
* 根据订单金额获取当前用户可用优惠卷
|
||||
*/
|
||||
@Override
|
||||
@GetMapping("/getAvailable")
|
||||
@ApiOperation("获取可用优惠券列表")
|
||||
@ApiImplicitParam(name = "totalAmount", value = "总金额,单位分", required = true, dataTypeClass = BigDecimal.class)
|
||||
public List<AvailableCouponsResDTO> getAvailableCoupon(@RequestParam BigDecimal totalAmount) {
|
||||
return couponService.getAvailableCoupon(totalAmount);
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostMapping("/use")
|
||||
@ApiOperation("使用优惠卷并返回优惠金额")
|
||||
public CouponUseResDTO useCoupon(@RequestBody CouponUseReqDTO couponUseReqDTO) {
|
||||
return couponWriteOffService.use(couponUseReqDTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostMapping("/useBack")
|
||||
@ApiOperation("优惠券退回接口")
|
||||
public void useBack(@RequestBody CouponUseBackReqDTO couponUseBackReqDTO) {
|
||||
couponUseBackService.useBack(couponUseBackReqDTO);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package com.jzo2o.market.controller.operation;
|
||||
|
||||
import com.jzo2o.common.model.PageResult;
|
||||
import com.jzo2o.market.model.dto.request.ActivityPageQueryDTO;
|
||||
import com.jzo2o.market.model.dto.request.ActivitySaveReqDTO;
|
||||
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
|
||||
import com.jzo2o.market.service.IActivityService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 运营端 - 活动管理控制器
|
||||
* @author JIAN
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController("operationActivityController")
|
||||
@RequestMapping("/operation/activity")
|
||||
public class ActivityController {
|
||||
@Resource
|
||||
private IActivityService activityService;
|
||||
|
||||
@GetMapping("/page")
|
||||
public PageResult<ActivityInfoResDTO> pageActivity(ActivityPageQueryDTO activityPageQueryDTO) {
|
||||
return activityService.page(activityPageQueryDTO);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ActivityInfoResDTO getActivity(@PathVariable Long id) {
|
||||
return activityService.getDetailById(id);
|
||||
}
|
||||
|
||||
@PostMapping("/save")
|
||||
public void saveOrUpdateActivity(@RequestBody ActivitySaveReqDTO activitySaveReqDTO) {
|
||||
activityService.saveOrUpdate(activitySaveReqDTO);
|
||||
}
|
||||
|
||||
@PostMapping("/revoke/{id}")
|
||||
public void revokeActivity(@PathVariable Long id) {
|
||||
activityService.revoke(id);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package com.jzo2o.market.controller.operation;
|
||||
|
||||
import com.jzo2o.common.model.PageResult;
|
||||
import com.jzo2o.market.model.dto.request.CouponPageQueryDTO;
|
||||
import com.jzo2o.market.model.dto.response.CouponPageInfoResDTO;
|
||||
import com.jzo2o.market.service.ICouponService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 运营端 - 优惠卷管理控制器
|
||||
* @author JIAN
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController("operationCouponController")
|
||||
@RequestMapping("/operation/coupon")
|
||||
public class CouponController {
|
||||
@Resource
|
||||
private ICouponService couponService;
|
||||
|
||||
@GetMapping("/page")
|
||||
public PageResult<CouponPageInfoResDTO> pageCoupon(CouponPageQueryDTO couponPageQueryDTO) {
|
||||
return couponService.page(couponPageQueryDTO);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.jzo2o.market.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 活动状态枚举类
|
||||
* @author JIAN
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ActivityStatusEnum {
|
||||
NO_DISTRIBUTE(1, "待生效"),
|
||||
DISTRIBUTING(2, "进行中"),
|
||||
LOSE_EFFICACY(3, "已失效"),
|
||||
VOIDED(4, "作废");
|
||||
|
||||
@EnumValue
|
||||
@JsonValue
|
||||
private int status;
|
||||
private String name;
|
||||
|
||||
public boolean equals(Integer status) {
|
||||
return status != null && this.status == status;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.jzo2o.market.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 优惠卷状态枚举类
|
||||
* @author JIAN
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CouponStatusEnum {
|
||||
NO_USE(1, "未使用"),
|
||||
USED(2, "已使用"),
|
||||
INVALID(3, "已失效"),
|
||||
VOIDED(4, "已作废");
|
||||
|
||||
@EnumValue
|
||||
@JsonValue
|
||||
private int status;
|
||||
private String name;
|
||||
|
||||
public static CouponStatusEnum statusOf(Integer status) {
|
||||
if (status == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (CouponStatusEnum couponStatusEnum : CouponStatusEnum.values()) {
|
||||
if (couponStatusEnum.getStatus() == status) {
|
||||
return couponStatusEnum;
|
||||
}
|
||||
}
|
||||
|
||||
// not found
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
package com.jzo2o.market.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 优惠券类型枚举类
|
||||
* @author JIAN
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CouponTypeEnum {
|
||||
AMOUNT_DISCOUNT(1, "满减"),
|
||||
RATE_DISCOUNT(2, "打折");
|
||||
|
||||
@EnumValue
|
||||
@JsonValue
|
||||
private int type;
|
||||
private String name;
|
||||
|
||||
public boolean equals(Integer type) {
|
||||
return type != null && type.equals(this.type);
|
||||
}
|
||||
|
||||
public static CouponTypeEnum typeOf(Integer type) {
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (CouponTypeEnum couponTypeEnum : CouponTypeEnum.values()) {
|
||||
if (couponTypeEnum.getType() == type) {
|
||||
return couponTypeEnum;
|
||||
}
|
||||
}
|
||||
|
||||
// not found
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.jzo2o.market.handler;
|
||||
|
||||
import com.jzo2o.common.expcetions.CommonException;
|
||||
import com.jzo2o.market.service.ICouponService;
|
||||
import com.jzo2o.redis.handler.SyncProcessHandler;
|
||||
import com.jzo2o.redis.model.SyncMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
import static com.jzo2o.market.constants.RedisConstants.RedisKey.COUPON_SEIZE_SYNC_QUEUE_NAME;
|
||||
|
||||
/**
|
||||
* 抢卷同步队列同步处理器
|
||||
* @author JIAN
|
||||
*/
|
||||
@Slf4j
|
||||
@Component(COUPON_SEIZE_SYNC_QUEUE_NAME)
|
||||
public class SeizeCouponSyncProcessHandler implements SyncProcessHandler<Object> {
|
||||
@Resource
|
||||
private ICouponService couponService;
|
||||
|
||||
@Override
|
||||
public void batchProcess(List<SyncMessage<Object>> multiData) {
|
||||
throw new CommonException("不支持批量处理");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void singleProcess(SyncMessage<Object> singleData) {
|
||||
long userId = Long.parseLong(singleData.getKey());
|
||||
long activityId = Long.parseLong(singleData.getValue().toString());
|
||||
|
||||
log.info("同步优惠卷信息, 活动id: {}, 用户id: {}", activityId, userId);
|
||||
couponService.syncCouponRecord(activityId, userId);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package com.jzo2o.market.handler;
|
||||
|
||||
import com.jzo2o.market.service.IActivityService;
|
||||
import com.jzo2o.market.service.ICouponService;
|
||||
import com.jzo2o.redis.sync.SyncManager;
|
||||
import com.xxl.job.core.handler.annotation.XxlJob;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
import static com.jzo2o.market.constants.RedisConstants.RedisKey.COUPON_SEIZE_SYNC_QUEUE_NAME;
|
||||
import static com.jzo2o.redis.constants.RedisSyncQueueConstants.MODE_SINGLE;
|
||||
import static com.jzo2o.redis.constants.RedisSyncQueueConstants.STORAGE_TYPE_HASH;
|
||||
|
||||
/**
|
||||
* XxlJob任务处理器
|
||||
* @author JIAN
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@SuppressWarnings("unused")
|
||||
public class XxlJobHandler {
|
||||
@Resource
|
||||
private IActivityService activityService;
|
||||
@Resource
|
||||
private ICouponService couponService;
|
||||
@Resource(name = "syncThreadPool")
|
||||
private ThreadPoolExecutor syncThreadPool;
|
||||
@Resource
|
||||
private SyncManager syncManager;
|
||||
|
||||
/**
|
||||
* 自动修改活动状态(1分钟1次)
|
||||
* 1.活动进行中状态修改
|
||||
* 2.活动已失效状态修改
|
||||
*/
|
||||
@XxlJob("updateActivityStatus")
|
||||
@Transactional
|
||||
public void updateActivityStatus() {
|
||||
log.info("自动修改活动状态任务开始");
|
||||
activityService.updateActivityStatus();
|
||||
log.info("自动修改活动状态任务完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动过期已领取优惠券(1小时1次)
|
||||
*/
|
||||
@XxlJob("processExpireCoupon")
|
||||
public void processExpireCoupon() {
|
||||
log.info("自动过期已领取优惠券任务开始");
|
||||
couponService.invalidExpiredCoupon();
|
||||
log.info("自动过期已领取优惠券任务完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动预热(缓存)1个月内的活动
|
||||
*/
|
||||
@XxlJob("activityPreheat")
|
||||
public void activityPreheat() {
|
||||
log.info("自动预热1个月内的活动任务开始");
|
||||
activityService.cacheComingActivity();
|
||||
log.info("自动预热1个月内的活动任务完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动从Redis同步抢卷结果到数据库
|
||||
*/
|
||||
@XxlJob("seizeCouponSyncJob")
|
||||
public void seizeCouponSyncJob() {
|
||||
log.info("自动从Redis同步抢卷结果到数据库任务开始");
|
||||
syncManager.start(COUPON_SEIZE_SYNC_QUEUE_NAME, STORAGE_TYPE_HASH, MODE_SINGLE, syncThreadPool);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.jzo2o.market.mapper;
|
||||
|
||||
import com.jzo2o.market.model.domain.Activity;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author itcast
|
||||
* @since 2023-09-16
|
||||
*/
|
||||
public interface ActivityMapper extends BaseMapper<Activity> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.jzo2o.market.mapper;
|
||||
|
||||
import com.jzo2o.market.model.domain.Coupon;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author itcast
|
||||
* @since 2023-09-16
|
||||
*/
|
||||
public interface CouponMapper extends BaseMapper<Coupon> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.jzo2o.market.mapper;
|
||||
|
||||
import com.jzo2o.market.model.domain.CouponUseBack;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 优惠券使用回退记录 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author itcast
|
||||
* @since 2023-09-18
|
||||
*/
|
||||
public interface CouponUseBackMapper extends BaseMapper<CouponUseBack> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.jzo2o.market.mapper;
|
||||
|
||||
import com.jzo2o.market.model.domain.CouponWriteOff;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 优惠券核销表 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author itcast
|
||||
* @since 2023-09-22
|
||||
*/
|
||||
public interface CouponWriteOffMapper extends BaseMapper<CouponWriteOff> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
package com.jzo2o.market.model.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.jzo2o.market.enums.ActivityStatusEnum;
|
||||
import com.jzo2o.market.enums.CouponTypeEnum;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 活动实体类
|
||||
* @author itcast
|
||||
* @since 2023-09-16
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
public class Activity implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 优惠券配置id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 优惠券名称,可以和活动名称保持一致
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 使用类型,1:满减,2:折扣
|
||||
*/
|
||||
private CouponTypeEnum type;
|
||||
|
||||
/**
|
||||
* 使用条件,0:表示无门槛,其他值:最低消费金额
|
||||
*/
|
||||
private BigDecimal amountCondition;
|
||||
|
||||
/**
|
||||
* 折扣率,折扣类型的折扣率,8折就是存80
|
||||
*/
|
||||
private Integer discountRate;
|
||||
|
||||
/**
|
||||
* 优惠金额,满减或无门槛的优惠金额
|
||||
*/
|
||||
private BigDecimal discountAmount;
|
||||
|
||||
/**
|
||||
* 优惠券有效期天数
|
||||
*/
|
||||
private Integer validityDays;
|
||||
|
||||
/**
|
||||
* 发放开始时间
|
||||
*/
|
||||
private LocalDateTime distributeStartTime;
|
||||
|
||||
/**
|
||||
* 发放结束时间
|
||||
*/
|
||||
private LocalDateTime distributeEndTime;
|
||||
|
||||
/**
|
||||
* 优惠券配置状态,1:待生效,2:进行中,3:已失效
|
||||
*/
|
||||
private ActivityStatusEnum status;
|
||||
|
||||
/**
|
||||
* 发放数量,0:表示无限量,其他正数表示最大发放量
|
||||
*/
|
||||
private Integer totalNum;
|
||||
|
||||
/**
|
||||
* 库存数量
|
||||
*/
|
||||
private Integer stockNum;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 逻辑删除
|
||||
*/
|
||||
private Integer isDeleted;
|
||||
}
|
||||
@ -0,0 +1,116 @@
|
||||
package com.jzo2o.market.model.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.jzo2o.market.enums.CouponTypeEnum;
|
||||
import com.jzo2o.market.enums.CouponStatusEnum;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
*
|
||||
* </p>
|
||||
* @author itcast
|
||||
* @since 2023-09-16
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
public class Coupon implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 优惠券id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 优惠券名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 优惠券的拥有者
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户姓名
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户手机号
|
||||
*/
|
||||
private String userPhone;
|
||||
|
||||
/**
|
||||
* 活动id
|
||||
*/
|
||||
private Long activityId;
|
||||
|
||||
/**
|
||||
* 使用类型,1:满减,2:折扣
|
||||
*/
|
||||
private CouponTypeEnum type;
|
||||
|
||||
/**
|
||||
* 折扣
|
||||
*/
|
||||
private Integer discountRate;
|
||||
|
||||
/**
|
||||
* 优惠金额
|
||||
*/
|
||||
private BigDecimal discountAmount;
|
||||
|
||||
/**
|
||||
* 满减金额
|
||||
*/
|
||||
private BigDecimal amountCondition;
|
||||
|
||||
/**
|
||||
* 有效期
|
||||
*/
|
||||
private LocalDateTime validityTime;
|
||||
|
||||
/**
|
||||
* 使用时间
|
||||
*/
|
||||
private LocalDateTime useTime;
|
||||
|
||||
/**
|
||||
* 优惠券状态,1:未使用,2:已使用,3:已过期
|
||||
*/
|
||||
private CouponStatusEnum status;
|
||||
|
||||
/**
|
||||
* 订单id
|
||||
*/
|
||||
private String ordersId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 逻辑删除
|
||||
*/
|
||||
private Integer isDeleted;
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package com.jzo2o.market.model.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 优惠券使用回退记录
|
||||
* </p>
|
||||
* @author itcast
|
||||
* @since 2023-09-18
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
public class CouponUseBack implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 回退记录id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 优惠券id
|
||||
*/
|
||||
private Long couponId;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 回退时间
|
||||
*/
|
||||
private LocalDateTime useBackTime;
|
||||
|
||||
/**
|
||||
* 核销时间
|
||||
*/
|
||||
private LocalDateTime writeOffTime;
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
package com.jzo2o.market.model.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 优惠券核销表
|
||||
* </p>
|
||||
* @author itcast
|
||||
* @since 2023-09-22
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class CouponWriteOff implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 优惠券id
|
||||
*/
|
||||
private Long couponId;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 核销时使用的订单号
|
||||
*/
|
||||
private Long ordersId;
|
||||
|
||||
/**
|
||||
* 活动id
|
||||
*/
|
||||
private Long activityId;
|
||||
|
||||
/**
|
||||
* 核销时间
|
||||
*/
|
||||
private LocalDateTime writeOffTime;
|
||||
|
||||
/**
|
||||
* 核销人手机号
|
||||
*/
|
||||
private String writeOffManPhone;
|
||||
|
||||
/**
|
||||
* 核销人姓名
|
||||
*/
|
||||
private String writeOffManName;
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.jzo2o.market.model.dto.request;
|
||||
|
||||
import com.jzo2o.common.model.dto.PageQueryDTO;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@ApiModel("活动分页查询模型")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ActivityPageQueryDTO extends PageQueryDTO {
|
||||
@ApiModelProperty("活动id")
|
||||
private Long id;
|
||||
@ApiModelProperty("活动名称")
|
||||
private String name;
|
||||
@ApiModelProperty("类型,,1:满减,2:折扣")
|
||||
private Integer type;
|
||||
@ApiModelProperty("优惠券配置状态,1:待生效,2:进行中,3:已失效")
|
||||
private Integer status;
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
package com.jzo2o.market.model.dto.request;
|
||||
|
||||
import com.jzo2o.common.expcetions.BadRequestException;
|
||||
import com.jzo2o.common.utils.DateUtils;
|
||||
import com.jzo2o.common.utils.ObjectUtils;
|
||||
import com.jzo2o.market.enums.CouponTypeEnum;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.Null;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@ApiModel("活动保存请求模型")
|
||||
@Validated
|
||||
public class ActivitySaveReqDTO {
|
||||
@ApiModelProperty(value = "活动id,新增时不填,修改时必填")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty(value = "活动名称", required = true)
|
||||
@Size(max = 20, message = "活动名称超出20个字符无法输入")
|
||||
@Null(message = "活动名称为空,请输入活动名称")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty(value = "优惠券类型,1:满减,2:折扣", required = true)
|
||||
private Integer type;
|
||||
@ApiModelProperty(value = "满减限额,0:表示无门槛,其他值:最低消费金额")
|
||||
@Min(value = 0, message = "满额限制请输入大于/等于 0的整数")
|
||||
@Null(message = "满额限制为空,请输入满额限制")
|
||||
private BigDecimal amountCondition;
|
||||
|
||||
@ApiModelProperty(value = "折扣率,折扣类型的折扣率,例如:8,打8折, type为2时必填")
|
||||
private Integer discountRate;
|
||||
|
||||
@ApiModelProperty(value = "优惠金额,满减或无门槛的优惠金额", required = true)
|
||||
private BigDecimal discountAmount;
|
||||
|
||||
@ApiModelProperty(value = "发放开始时间", required = true)
|
||||
@Null(message = "发放时间为空,请输入发放时间")
|
||||
private LocalDateTime distributeStartTime;
|
||||
@Null(message = "发放时间为空,请输入发放时间")
|
||||
@ApiModelProperty(value = "发放结束时间", required = true)
|
||||
private LocalDateTime distributeEndTime;
|
||||
|
||||
@ApiModelProperty(value = "发放数量,0:表示无限量,其他正数表示最大发放量")
|
||||
private Integer totalNum = 0;
|
||||
|
||||
@ApiModelProperty(value = "有效期天数", required = true)
|
||||
@Null(message = "使用期限请输入大于0的整数")
|
||||
@Min(value = 0, message = "使用期限请输入大于0的整数")
|
||||
private Integer validityDays;
|
||||
|
||||
public void check() {
|
||||
if (CouponTypeEnum.AMOUNT_DISCOUNT.equals(type)) {
|
||||
// 满减
|
||||
//discountAmount字段不能为空,且值为正数
|
||||
if (ObjectUtils.isNull(discountAmount)) {
|
||||
throw new BadRequestException("折扣金额为空,请输入折扣金额");
|
||||
} else if (discountAmount.compareTo(BigDecimal.ZERO) < 0) {
|
||||
throw new BadRequestException("折扣金额请输入大于0的整数");
|
||||
}
|
||||
} else if (CouponTypeEnum.RATE_DISCOUNT.equals(type)) {
|
||||
// 折扣
|
||||
if (ObjectUtils.isNull(discountRate)) {
|
||||
throw new BadRequestException("折扣比例为空,请输入折扣比例");
|
||||
} else if (discountRate.compareTo(0) < 0 || discountRate.compareTo(100) > 0) {
|
||||
throw new BadRequestException("折扣比例请输入大于0,小于10的整数");
|
||||
}
|
||||
} else {
|
||||
throw new BadRequestException("优惠券类型不存在");
|
||||
}
|
||||
// 发放时间
|
||||
if (distributeStartTime.isAfter(distributeEndTime)) {
|
||||
throw new BadRequestException("结束时间不能早于开始时间");
|
||||
}
|
||||
if (distributeEndTime.isBefore(DateUtils.now())) {
|
||||
throw new BadRequestException("发放时间已过期");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.jzo2o.market.model.dto.request;
|
||||
|
||||
import com.jzo2o.common.model.dto.PageQueryDTO;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.validation.constraints.Null;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Validated
|
||||
@ApiModel("运营端优惠券查询模型")
|
||||
public class CouponPageQueryDTO extends PageQueryDTO {
|
||||
@ApiModelProperty(value = "活动id",required = true)
|
||||
@Null(message = "请先选择活动")
|
||||
private Long activityId;
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package com.jzo2o.market.model.dto.request;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Null;
|
||||
|
||||
@Data
|
||||
@ApiModel
|
||||
public class SeizeCouponReqDTO {
|
||||
@ApiModelProperty("活动id")
|
||||
@Null(message = "请求失败")
|
||||
private Long id;
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package com.jzo2o.market.model.dto.response;
|
||||
|
||||
import com.jzo2o.market.enums.ActivityStatusEnum;
|
||||
import com.jzo2o.market.enums.CouponTypeEnum;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@ApiModel("活动分页字段模型")
|
||||
@Accessors(chain = true)
|
||||
public class ActivityInfoResDTO {
|
||||
@ApiModelProperty("活动id")
|
||||
private Long id;
|
||||
@ApiModelProperty("活动名称")
|
||||
private String name;
|
||||
@ApiModelProperty("优惠券类型,1:满减,2:折扣")
|
||||
private CouponTypeEnum type;
|
||||
@ApiModelProperty("满减限额,0:表示无门槛,其他值:最低消费金额")
|
||||
private BigDecimal amountCondition;
|
||||
@ApiModelProperty("折扣率,折扣类型的折扣率,例如:8,打8折")
|
||||
private Integer discountRate;
|
||||
@ApiModelProperty("优惠金额,满减或无门槛的优惠金额")
|
||||
private BigDecimal discountAmount;
|
||||
@ApiModelProperty("发放开始时间")
|
||||
private LocalDateTime distributeStartTime;
|
||||
@ApiModelProperty("发放结束时间")
|
||||
private LocalDateTime distributeEndTime;
|
||||
@ApiModelProperty("优惠券配置状态,1:待生效,2:进行中,3:已失效")
|
||||
private ActivityStatusEnum status;
|
||||
@ApiModelProperty("发放数量,0:表示无限量,其他正数表示最大发放量")
|
||||
private Integer totalNum;
|
||||
@ApiModelProperty("领取数量")
|
||||
private Integer receiveNum;
|
||||
@ApiModelProperty("核销数量")
|
||||
private Integer writeOffNum;
|
||||
@ApiModelProperty("有效期天数")
|
||||
private Integer validityDays;
|
||||
@ApiModelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
@ApiModelProperty("更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package com.jzo2o.market.model.dto.response;
|
||||
|
||||
import com.jzo2o.market.enums.CouponStatusEnum;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 优惠卷分页模型
|
||||
* @author itcast
|
||||
* @since 2023-09-16
|
||||
*/
|
||||
@Data
|
||||
public class CouponPageInfoResDTO implements Serializable {
|
||||
@ApiModelProperty(value = "优惠券id", required = true)
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("用户姓名")
|
||||
private String userName;
|
||||
|
||||
@ApiModelProperty("用户手机号")
|
||||
private String userPhone;
|
||||
|
||||
/**
|
||||
* 活动id
|
||||
*/
|
||||
@ApiModelProperty(value = "活动id", required = true)
|
||||
private Long activityId;
|
||||
|
||||
@ApiModelProperty("使用时间")
|
||||
private LocalDateTime useTime;
|
||||
|
||||
/**
|
||||
* 优惠券状态,1:未使用,2:已使用,3:已过期
|
||||
*/
|
||||
@ApiModelProperty("优惠券状态,1:未使用,2:已使用,3:已过期")
|
||||
private CouponStatusEnum status;
|
||||
|
||||
/**
|
||||
* 订单id
|
||||
*/
|
||||
private String ordersId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@ApiModelProperty(value = "创建时间", required = true)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@ApiModelProperty(value = "更新时间", required = true)
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package com.jzo2o.market.model.dto.response;
|
||||
|
||||
import com.jzo2o.market.enums.CouponStatusEnum;
|
||||
import com.jzo2o.market.enums.CouponTypeEnum;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 优惠卷用户查询模型
|
||||
* @author itcast
|
||||
* @since 2023-09-16
|
||||
*/
|
||||
@Data
|
||||
public class CouponSimpleInfoResDTO implements Serializable {
|
||||
@ApiModelProperty(value = "优惠券id", required = true)
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty(value = "活动名称", required = true)
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty(value = "活动id", required = true)
|
||||
private Long activityId;
|
||||
|
||||
@ApiModelProperty(value = "使用类型,1:满减,2:折扣", required = true)
|
||||
private CouponTypeEnum type;
|
||||
|
||||
@ApiModelProperty(value = "折扣")
|
||||
private Integer discountRate;
|
||||
|
||||
@ApiModelProperty(value = "优惠金额")
|
||||
private BigDecimal discountAmount;
|
||||
|
||||
@ApiModelProperty(value = "满减条件,0:表示无门槛", required = true)
|
||||
private BigDecimal amountCondition;
|
||||
|
||||
@ApiModelProperty("优惠券过期时间")
|
||||
private LocalDateTime validityTime;
|
||||
|
||||
@ApiModelProperty("使用时间")
|
||||
private LocalDateTime useTime;
|
||||
|
||||
@ApiModelProperty("优惠券状态,1:未使用,2:已使用,3:已过期")
|
||||
private CouponStatusEnum status;
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.jzo2o.market.model.dto.response;
|
||||
|
||||
import com.jzo2o.market.enums.ActivityStatusEnum;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@ApiModel("抢券列表信息")
|
||||
@Accessors(chain = true)
|
||||
public class SeizeCouponInfoResDTO implements Serializable {
|
||||
@ApiModelProperty("活动id")
|
||||
private Long id;
|
||||
@ApiModelProperty("活动名称")
|
||||
private String name;
|
||||
@ApiModelProperty("优惠券类型,1:满减,2:折扣")
|
||||
private Integer type;
|
||||
@ApiModelProperty("满减限额,0:表示无门槛,其他值:最低消费金额")
|
||||
private BigDecimal amountCondition;
|
||||
@ApiModelProperty("折扣率,折扣类型的折扣率,例如:8,打8折")
|
||||
private Integer discountRate;
|
||||
@ApiModelProperty("优惠金额,满减或无门槛的优惠金额")
|
||||
private BigDecimal discountAmount;
|
||||
@ApiModelProperty("发放开始时间")
|
||||
private LocalDateTime distributeStartTime;
|
||||
@ApiModelProperty("发放结束时间")
|
||||
private LocalDateTime distributeEndTime;
|
||||
@ApiModelProperty("优惠券配置状态,1:待生效,2:进行中,3:已失效")
|
||||
private ActivityStatusEnum status;
|
||||
@ApiModelProperty("发放数量")
|
||||
private Integer totalNum;
|
||||
@ApiModelProperty("优惠券剩余数量(库存数量)")
|
||||
private Integer remainNum;
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
package com.jzo2o.market.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.jzo2o.common.model.PageResult;
|
||||
import com.jzo2o.market.enums.ActivityStatusEnum;
|
||||
import com.jzo2o.market.model.domain.Activity;
|
||||
import com.jzo2o.market.model.dto.request.ActivityPageQueryDTO;
|
||||
import com.jzo2o.market.model.dto.request.ActivitySaveReqDTO;
|
||||
import com.jzo2o.market.model.dto.request.SeizeCouponReqDTO;
|
||||
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
|
||||
import com.jzo2o.market.model.dto.response.SeizeCouponInfoResDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 服务类
|
||||
* </p>
|
||||
* @author itcast
|
||||
* @since 2023-09-16
|
||||
*/
|
||||
public interface IActivityService extends IService<Activity> {
|
||||
/**
|
||||
* 分页查询活动数据
|
||||
*/
|
||||
PageResult<ActivityInfoResDTO> page(ActivityPageQueryDTO activityPageQueryDTO);
|
||||
|
||||
/**
|
||||
* 查询活动详细数据
|
||||
*/
|
||||
ActivityInfoResDTO getDetailById(Long id);
|
||||
|
||||
/**
|
||||
* 获取缓存的活动信息
|
||||
* @param status 筛选的活动状态
|
||||
*/
|
||||
List<SeizeCouponInfoResDTO> getCachedActivity(ActivityStatusEnum status);
|
||||
|
||||
/**
|
||||
* 新增/插入活动信息
|
||||
*/
|
||||
void saveOrUpdate(ActivitySaveReqDTO activitySaveReqDTO);
|
||||
|
||||
/**
|
||||
* 撤销活动
|
||||
*/
|
||||
void revoke(Long id);
|
||||
|
||||
/**
|
||||
* 根据当前时间更新活动状态
|
||||
*/
|
||||
void updateActivityStatus();
|
||||
|
||||
/**
|
||||
* 缓存近1个月生效的活动信息
|
||||
*/
|
||||
void cacheComingActivity();
|
||||
|
||||
/**
|
||||
* 用户端进行抢卷操作
|
||||
*/
|
||||
void seizeCoupon(SeizeCouponReqDTO seizeCouponReqDTO);
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
package com.jzo2o.market.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
|
||||
import com.jzo2o.common.model.PageResult;
|
||||
import com.jzo2o.market.enums.CouponStatusEnum;
|
||||
import com.jzo2o.market.model.domain.Coupon;
|
||||
import com.jzo2o.market.model.dto.request.CouponPageQueryDTO;
|
||||
import com.jzo2o.market.model.dto.response.CouponPageInfoResDTO;
|
||||
import com.jzo2o.market.model.dto.response.CouponSimpleInfoResDTO;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 服务类
|
||||
* </p>
|
||||
* @author itcast
|
||||
* @since 2023-09-16
|
||||
*/
|
||||
public interface ICouponService extends IService<Coupon> {
|
||||
/**
|
||||
* 分页查询优惠卷信息(通过活动id)
|
||||
*/
|
||||
PageResult<CouponPageInfoResDTO> page(CouponPageQueryDTO couponPageQueryDTO);
|
||||
|
||||
/**
|
||||
* 用户端滚动查询(抢卷时间降序)
|
||||
*/
|
||||
List<CouponSimpleInfoResDTO> getCurrentUserCoupon(CouponStatusEnum status, Integer lastId);
|
||||
|
||||
/**
|
||||
* 更新过期优惠卷的状态为失效中
|
||||
*/
|
||||
void invalidExpiredCoupon();
|
||||
|
||||
/**
|
||||
* 同步优惠卷数据到数据库中
|
||||
*/
|
||||
void syncCouponRecord(long activityId, long userId);
|
||||
|
||||
/**
|
||||
* 根据订单金额获取当前用户可用优惠卷
|
||||
*/
|
||||
List<AvailableCouponsResDTO> getAvailableCoupon(BigDecimal totalAmount);
|
||||
|
||||
/**
|
||||
* 计算优惠卷的优惠价格
|
||||
* @param coupon 优惠卷信息
|
||||
* @param totalAmount 订单总金额
|
||||
*/
|
||||
BigDecimal calcDiscountAmount(Coupon coupon, BigDecimal totalAmount);
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.jzo2o.market.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.jzo2o.api.market.dto.request.CouponUseBackReqDTO;
|
||||
import com.jzo2o.market.model.domain.CouponUseBack;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 优惠券使用回退记录 服务类
|
||||
* </p>
|
||||
* @author itcast
|
||||
* @since 2023-09-18
|
||||
*/
|
||||
public interface ICouponUseBackService extends IService<CouponUseBack> {
|
||||
/**
|
||||
* 用户退回优惠卷
|
||||
*/
|
||||
void useBack(CouponUseBackReqDTO couponUseBackReqDTO);
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.jzo2o.market.service;
|
||||
|
||||
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
|
||||
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
|
||||
import com.jzo2o.market.model.domain.CouponWriteOff;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 优惠券核销表 服务类
|
||||
* </p>
|
||||
* @author itcast
|
||||
* @since 2023-09-22
|
||||
*/
|
||||
public interface ICouponWriteOffService extends IService<CouponWriteOff> {
|
||||
/**
|
||||
* 核销指定的优惠卷并返回优惠金额
|
||||
*/
|
||||
CouponUseResDTO use(CouponUseReqDTO couponUseReqDTO);
|
||||
}
|
||||
@ -0,0 +1,296 @@
|
||||
package com.jzo2o.market.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
|
||||
import com.jzo2o.common.constants.ErrorInfo;
|
||||
import com.jzo2o.common.expcetions.CommonException;
|
||||
import com.jzo2o.common.expcetions.DBException;
|
||||
import com.jzo2o.common.expcetions.ForbiddenOperationException;
|
||||
import com.jzo2o.common.model.PageResult;
|
||||
import com.jzo2o.common.utils.BeanUtils;
|
||||
import com.jzo2o.common.utils.CollUtils;
|
||||
import com.jzo2o.common.utils.JsonUtils;
|
||||
import com.jzo2o.market.constants.RedisConstants;
|
||||
import com.jzo2o.market.enums.ActivityStatusEnum;
|
||||
import com.jzo2o.market.enums.CouponStatusEnum;
|
||||
import com.jzo2o.market.enums.CouponTypeEnum;
|
||||
import com.jzo2o.market.mapper.ActivityMapper;
|
||||
import com.jzo2o.market.model.domain.Activity;
|
||||
import com.jzo2o.market.model.domain.Coupon;
|
||||
import com.jzo2o.market.model.dto.request.ActivityPageQueryDTO;
|
||||
import com.jzo2o.market.model.dto.request.ActivitySaveReqDTO;
|
||||
import com.jzo2o.market.model.dto.request.SeizeCouponReqDTO;
|
||||
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
|
||||
import com.jzo2o.market.model.dto.response.SeizeCouponInfoResDTO;
|
||||
import com.jzo2o.market.service.IActivityService;
|
||||
import com.jzo2o.market.service.ICouponService;
|
||||
import com.jzo2o.mvc.utils.UserContext;
|
||||
import com.jzo2o.mysql.utils.PageUtils;
|
||||
import com.jzo2o.redis.properties.RedisSyncProperties;
|
||||
import com.jzo2o.redis.utils.RedisSyncQueueUtils;
|
||||
import org.springframework.data.redis.core.HashOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.RedisScript;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.jzo2o.market.constants.RedisConstants.RedisKey.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 服务实现类
|
||||
* </p>
|
||||
* @author itcast
|
||||
* @since 2023-09-16
|
||||
*/
|
||||
@Service
|
||||
public class ActivityServiceImpl extends ServiceImpl<ActivityMapper, Activity> implements IActivityService {
|
||||
@Resource
|
||||
private ICouponService couponService;
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
@Resource(name = "seizeCouponScript")
|
||||
private RedisScript<Integer> seizeCouponScript;
|
||||
@Resource
|
||||
private RedisSyncProperties redisSyncProperties;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public PageResult<ActivityInfoResDTO> page(ActivityPageQueryDTO activityPageQueryDTO) {
|
||||
// 分页查询
|
||||
Page<Activity> activityPage = PageUtils.parsePageQuery(activityPageQueryDTO, Activity.class);
|
||||
LambdaQueryWrapper<Activity> queryWrapper = Wrappers.<Activity>lambdaQuery()
|
||||
.eq(ObjectUtils.isNotEmpty(activityPageQueryDTO.getId()), Activity::getId, activityPageQueryDTO.getId())
|
||||
.like(ObjectUtils.isNotEmpty(activityPageQueryDTO.getName()), Activity::getName, activityPageQueryDTO.getName())
|
||||
.eq(ObjectUtils.isNotEmpty(activityPageQueryDTO.getType()), Activity::getType, activityPageQueryDTO.getType())
|
||||
.eq(ObjectUtils.isNotEmpty(activityPageQueryDTO.getStatus()), Activity::getStatus, activityPageQueryDTO.getStatus())
|
||||
// 新创建的活动显示在列表前面
|
||||
.orderByDesc(Activity::getCreateTime);
|
||||
Page<Activity> pageAns = baseMapper.selectPage(activityPage, queryWrapper);
|
||||
|
||||
return PageUtils.toPage(pageAns, ActivityInfoResDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivityInfoResDTO getDetailById(Long id) {
|
||||
Activity activity = baseMapper.selectById(id);
|
||||
if (ObjectUtils.isEmpty(activity)) {
|
||||
return new ActivityInfoResDTO();
|
||||
}
|
||||
|
||||
ActivityInfoResDTO activityInfoResDTO = BeanUtils.toBean(activity, ActivityInfoResDTO.class);
|
||||
|
||||
List<Coupon> coupons = couponService.lambdaQuery()
|
||||
.eq(Coupon::getActivityId, id)
|
||||
.select(Coupon::getStatus)
|
||||
.list();
|
||||
|
||||
activityInfoResDTO.setReceiveNum(coupons.size());
|
||||
activityInfoResDTO.setWriteOffNum((int) coupons.stream()
|
||||
.filter(coupon -> coupon.getStatus() == CouponStatusEnum.USED)
|
||||
.count());
|
||||
|
||||
return activityInfoResDTO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SeizeCouponInfoResDTO> getCachedActivity(ActivityStatusEnum status) {
|
||||
List<SeizeCouponInfoResDTO> couponInfoList = JsonUtils.toList((String) redisTemplate.opsForValue()
|
||||
.get(ACTIVITY_CACHE_LIST), SeizeCouponInfoResDTO.class);
|
||||
|
||||
if (CollUtils.isEmpty(couponInfoList)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
LocalDateTime nowTime = LocalDateTime.now();
|
||||
return couponInfoList.stream()
|
||||
.peek(coupon -> {
|
||||
// 防止缓存中的状态出错
|
||||
if (coupon.getDistributeEndTime().isBefore(nowTime)) {
|
||||
coupon.setStatus(ActivityStatusEnum.LOSE_EFFICACY);
|
||||
} else if (coupon.getDistributeStartTime().isBefore(nowTime)) {
|
||||
coupon.setStatus(ActivityStatusEnum.DISTRIBUTING);
|
||||
}
|
||||
})
|
||||
.filter(coupon -> coupon.getStatus() == status)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void saveOrUpdate(ActivitySaveReqDTO activitySaveReqDTO) {
|
||||
// 参数检验
|
||||
activitySaveReqDTO.check();
|
||||
|
||||
Activity activity = BeanUtils.toBean(activitySaveReqDTO, Activity.class);
|
||||
activity.setType(CouponTypeEnum.typeOf(activitySaveReqDTO.getType()));
|
||||
|
||||
if (ObjectUtils.isEmpty(activity.getId())) {
|
||||
// 新增记录初始化相关状态
|
||||
activity.setStatus(ActivityStatusEnum.NO_DISTRIBUTE)
|
||||
.setStockNum(activity.getTotalNum());
|
||||
} else {
|
||||
// 更新需要检查活动状态
|
||||
Activity activityInDb = baseMapper.selectById(activity.getId());
|
||||
if (ObjectUtils.isEmpty(activityInDb)) {
|
||||
throw new ForbiddenOperationException("活动不存在无法修改");
|
||||
} else if (activityInDb.getStatus() != ActivityStatusEnum.NO_DISTRIBUTE) {
|
||||
throw new ForbiddenOperationException("活动状态错误无法修改");
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.saveOrUpdate(activity)) {
|
||||
throw new DBException("新增/更新活动信息失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void revoke(Long id) {
|
||||
Activity activityInDb = baseMapper.selectById(id);
|
||||
if (ObjectUtils.isEmpty(activityInDb)) {
|
||||
throw new ForbiddenOperationException("活动不存在无法撤销");
|
||||
} else if (activityInDb.getStatus() != ActivityStatusEnum.NO_DISTRIBUTE
|
||||
&& activityInDb.getStatus() != ActivityStatusEnum.DISTRIBUTING) {
|
||||
throw new ForbiddenOperationException("活动状态错误无法撤销");
|
||||
}
|
||||
|
||||
if (!lambdaUpdate()
|
||||
.eq(Activity::getId, id)
|
||||
.set(Activity::getStatus, ActivityStatusEnum.VOIDED)
|
||||
.update()) {
|
||||
throw new DBException("更新活动表失败");
|
||||
}
|
||||
|
||||
// 优惠卷可能没有发放 -> 更新0行
|
||||
ChainWrappers.lambdaUpdateChain(couponService.getBaseMapper())
|
||||
.eq(Coupon::getActivityId, id)
|
||||
.set(Coupon::getStatus, CouponStatusEnum.VOIDED)
|
||||
.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateActivityStatus() {
|
||||
LocalDateTime nowTime = LocalDateTime.now();
|
||||
List<Activity> activityList = lambdaQuery()
|
||||
.in(Activity::getStatus, ActivityStatusEnum.NO_DISTRIBUTE, ActivityStatusEnum.DISTRIBUTING)
|
||||
// 不处理还没有发生的活动
|
||||
.le(Activity::getDistributeStartTime, nowTime)
|
||||
.list();
|
||||
|
||||
if (CollUtils.isEmpty(activityList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Activity activity : activityList) {
|
||||
ActivityStatusEnum status;
|
||||
if (activity.getDistributeEndTime().isBefore(nowTime)) {
|
||||
// 活动结束
|
||||
status = ActivityStatusEnum.LOSE_EFFICACY;
|
||||
} else if (activity.getDistributeStartTime().isBefore(nowTime)) {
|
||||
// 活动开始
|
||||
status = ActivityStatusEnum.DISTRIBUTING;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!lambdaUpdate()
|
||||
.eq(Activity::getId, activity.getId())
|
||||
.set(Activity::getStatus, status)
|
||||
.update()) {
|
||||
throw new DBException("更新活动状态失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cacheComingActivity() {
|
||||
LocalDateTime nowTime = LocalDateTime.now();
|
||||
|
||||
// 获取近一个月未开始/已开始的活动
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Activity> activityList = lambdaQuery()
|
||||
.le(Activity::getDistributeStartTime, nowTime.plusDays(30))
|
||||
.in(Activity::getStatus, Arrays.asList(ActivityStatusEnum.NO_DISTRIBUTE, ActivityStatusEnum.DISTRIBUTING))
|
||||
.orderByAsc(Activity::getDistributeStartTime)
|
||||
.list();
|
||||
|
||||
if (CollUtils.isEmpty(activityList)) {
|
||||
activityList = new ArrayList<>();
|
||||
}
|
||||
|
||||
List<SeizeCouponInfoResDTO> couponInfoList = activityList.stream()
|
||||
.map(activity -> BeanUtils
|
||||
.toBean(activity, SeizeCouponInfoResDTO.class)
|
||||
.setRemainNum(activity.getStockNum())
|
||||
.setType(activity.getType().getType()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 缓存活动列表
|
||||
redisTemplate.opsForValue().set(RedisConstants.RedisKey.ACTIVITY_CACHE_LIST, JsonUtils.toJsonStr(couponInfoList));
|
||||
|
||||
// 缓存优惠卷库存
|
||||
HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
|
||||
couponInfoList.forEach(coupon -> {
|
||||
String key = String.format(RedisConstants.RedisKey.COUPON_RESOURCE_STOCK, coupon.getId() % redisSyncProperties.getQueueNum());
|
||||
// 防止进行中的活动的库存被覆盖
|
||||
if (coupon.getStatus() == ActivityStatusEnum.NO_DISTRIBUTE) {
|
||||
hashOperations.put(key, coupon.getId(), coupon.getRemainNum());
|
||||
} else {
|
||||
hashOperations.putIfAbsent(key, coupon.getId(), coupon.getRemainNum());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seizeCoupon(SeizeCouponReqDTO seizeCouponReqDTO) {
|
||||
Long activityId = seizeCouponReqDTO.getId();
|
||||
Activity activity = lambdaQuery()
|
||||
.eq(Activity::getId, activityId)
|
||||
.select(Activity::getDistributeStartTime, Activity::getDistributeEndTime)
|
||||
.one();
|
||||
|
||||
int seizeCouponFailedCode = ErrorInfo.Code.SEIZE_COUPON_FAILD;
|
||||
if (ObjectUtils.isEmpty(activity)) {
|
||||
throw new CommonException(seizeCouponFailedCode, "活动不存在无法抢卷");
|
||||
} else if (activity.getDistributeStartTime().isAfter(LocalDateTime.now())) {
|
||||
throw new CommonException(seizeCouponFailedCode, "活动未开始无法抢卷");
|
||||
} else if (activity.getDistributeEndTime().isBefore(LocalDateTime.now())) {
|
||||
throw new CommonException(seizeCouponFailedCode, "活动已结束无法抢卷");
|
||||
}
|
||||
|
||||
Long userId = UserContext.currentUserId();
|
||||
if (ObjectUtils.isEmpty(userId)) {
|
||||
throw new CommonException(seizeCouponFailedCode, "用户信息不存在无法抢卷");
|
||||
}
|
||||
|
||||
int activityTag = (int) (activityId % redisSyncProperties.getQueueNum());
|
||||
String syncQueueKey = RedisSyncQueueUtils.getQueueRedisKey(COUPON_SEIZE_SYNC_QUEUE_NAME, activityTag);
|
||||
String stockTable = String.format(COUPON_RESOURCE_STOCK, activityTag);
|
||||
String seizeSuccessList = String.format(COUPON_SEIZE_LIST, activityId, activityTag);
|
||||
List<String> keys = Arrays.asList(syncQueueKey, stockTable, seizeSuccessList);
|
||||
|
||||
// 调用抢卷脚本
|
||||
int state = Optional
|
||||
.ofNullable(redisTemplate.execute(seizeCouponScript, keys, activityId, userId))
|
||||
.orElseThrow(() -> new CommonException(seizeCouponFailedCode, "抢卷失败"));
|
||||
|
||||
if (state < 0) {
|
||||
String failMag = state == -1 ? "请勿重复抢卷" :
|
||||
(state == -2 || state == -4 ? "库存不足抢卷失败" : "内部错误抢卷失败");
|
||||
throw new CommonException(seizeCouponFailedCode, failMag);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,197 @@
|
||||
package com.jzo2o.market.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.jzo2o.api.customer.CommonUserApi;
|
||||
import com.jzo2o.api.customer.dto.response.CommonUserResDTO;
|
||||
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
|
||||
import com.jzo2o.common.expcetions.BadRequestException;
|
||||
import com.jzo2o.common.expcetions.CommonException;
|
||||
import com.jzo2o.common.expcetions.DBException;
|
||||
import com.jzo2o.common.model.PageResult;
|
||||
import com.jzo2o.common.utils.BeanUtils;
|
||||
import com.jzo2o.common.utils.CollUtils;
|
||||
import com.jzo2o.common.utils.ObjectUtils;
|
||||
import com.jzo2o.market.enums.CouponStatusEnum;
|
||||
import com.jzo2o.market.enums.CouponTypeEnum;
|
||||
import com.jzo2o.market.mapper.CouponMapper;
|
||||
import com.jzo2o.market.model.domain.Activity;
|
||||
import com.jzo2o.market.model.domain.Coupon;
|
||||
import com.jzo2o.market.model.dto.request.CouponPageQueryDTO;
|
||||
import com.jzo2o.market.model.dto.response.CouponPageInfoResDTO;
|
||||
import com.jzo2o.market.model.dto.response.CouponSimpleInfoResDTO;
|
||||
import com.jzo2o.market.service.IActivityService;
|
||||
import com.jzo2o.market.service.ICouponService;
|
||||
import com.jzo2o.mvc.utils.UserContext;
|
||||
import com.jzo2o.mysql.utils.PageUtils;
|
||||
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.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 服务实现类
|
||||
* </p>
|
||||
* @author itcast
|
||||
* @since 2023-09-16
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {
|
||||
@Resource
|
||||
private CommonUserApi commonUserApi;
|
||||
@Resource
|
||||
private IActivityService activityService;
|
||||
|
||||
@Override
|
||||
public PageResult<CouponPageInfoResDTO> page(CouponPageQueryDTO couponPageQueryDTO) {
|
||||
if (ObjectUtils.isEmpty(couponPageQueryDTO.getActivityId())) {
|
||||
return new PageResult<>(0L, 0L, new ArrayList<>());
|
||||
}
|
||||
|
||||
Page<Coupon> couponPage = PageUtils.parsePageQuery(couponPageQueryDTO, Coupon.class);
|
||||
LambdaQueryWrapper<Coupon> queryWrapper = Wrappers.<Coupon>lambdaQuery()
|
||||
.eq(Coupon::getActivityId, couponPageQueryDTO.getActivityId());
|
||||
|
||||
return PageUtils.toPage(baseMapper.selectPage(couponPage, queryWrapper), CouponPageInfoResDTO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<CouponSimpleInfoResDTO> getCurrentUserCoupon(CouponStatusEnum status, Integer lastId) {
|
||||
Long userId = UserContext.currentUserId();
|
||||
if (ObjectUtils.isEmpty(userId)) {
|
||||
throw new BadRequestException("用户未授权");
|
||||
}
|
||||
|
||||
List<Coupon> coupons = lambdaQuery()
|
||||
.eq(Coupon::getUserId, userId)
|
||||
.eq(Coupon::getStatus, status)
|
||||
.lt(ObjectUtils.isNotEmpty(lastId), Coupon::getId, lastId)
|
||||
.orderByDesc(Coupon::getCreateTime)
|
||||
.last("LIMIT 10")
|
||||
.list();
|
||||
|
||||
return CollUtils.isEmpty(coupons) ? new ArrayList<>() :
|
||||
coupons.stream()
|
||||
.map(coupon -> BeanUtils.toBean(coupon, CouponSimpleInfoResDTO.class))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void invalidExpiredCoupon() {
|
||||
List<Coupon> couponList = lambdaQuery()
|
||||
.eq(Coupon::getStatus, CouponStatusEnum.NO_USE)
|
||||
// 不处理还没到有效期的优惠卷
|
||||
.le(Coupon::getValidityTime, LocalDateTime.now())
|
||||
.select(Coupon::getId)
|
||||
.list();
|
||||
|
||||
if (CollUtils.isEmpty(couponList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置状态已失效
|
||||
couponList = couponList.stream()
|
||||
.map(coupon -> coupon.setStatus(CouponStatusEnum.INVALID))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!this.updateBatchById(couponList)) {
|
||||
throw new DBException("更新优惠卷状态失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void syncCouponRecord(long activityId, long userId) {
|
||||
Activity activity = activityService.getById(activityId);
|
||||
CommonUserResDTO user = commonUserApi.findById(userId);
|
||||
|
||||
if (!this.save(Coupon.builder()
|
||||
.userId(userId)
|
||||
.userName(user.getNickname())
|
||||
.userPhone(user.getPhone())
|
||||
.activityId(activityId)
|
||||
.name(activity.getName())
|
||||
.type(activity.getType())
|
||||
.amountCondition(activity.getAmountCondition())
|
||||
.discountRate(activity.getDiscountRate())
|
||||
.discountAmount(activity.getDiscountAmount())
|
||||
.validityTime(LocalDateTime.now().plusDays(activity.getValidityDays()))
|
||||
.status(CouponStatusEnum.NO_USE)
|
||||
.build())) {
|
||||
throw new DBException("插入优惠卷表失败");
|
||||
}
|
||||
|
||||
if (!activityService.lambdaUpdate()
|
||||
.setSql("stock_num = stock_num - 1")
|
||||
.eq(Activity::getId, activityId)
|
||||
.gt(Activity::getStockNum, 0)
|
||||
.update()) {
|
||||
throw new DBException("更新活动库存失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AvailableCouponsResDTO> getAvailableCoupon(BigDecimal totalAmount) {
|
||||
Long userId = Optional
|
||||
.ofNullable(UserContext.currentUserId())
|
||||
.orElseThrow(() -> new CommonException("用户信息不存在"));
|
||||
|
||||
List<Coupon> couponList = lambdaQuery()
|
||||
.eq(Coupon::getUserId, userId)
|
||||
.le(Coupon::getAmountCondition, totalAmount)
|
||||
.eq(Coupon::getStatus, CouponStatusEnum.NO_USE)
|
||||
.ge(Coupon::getValidityTime, LocalDateTime.now())
|
||||
.and(and -> and
|
||||
.isNull(Coupon::getDiscountAmount)
|
||||
.or(or -> or.le(Coupon::getDiscountAmount, totalAmount)))
|
||||
.list();
|
||||
|
||||
if (CollUtils.isEmpty(couponList)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
return couponList.stream()
|
||||
.map(coupon -> BeanUtils
|
||||
.toBean(coupon, AvailableCouponsResDTO.class)
|
||||
.setType(coupon.getType().getType())
|
||||
.setDiscountAmount(this.calcDiscountAmount(coupon, totalAmount)))
|
||||
// 默认BigDecimal的比较器排序从小到大 需要按优惠金额从大到小排序
|
||||
.sorted(Comparator.comparing(AvailableCouponsResDTO::getDiscountAmount).reversed())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算优惠卷的优惠价格
|
||||
* @param coupon 优惠卷信息
|
||||
* @param totalAmount 订单总金额
|
||||
*/
|
||||
@Override
|
||||
public BigDecimal calcDiscountAmount(Coupon coupon, BigDecimal totalAmount) {
|
||||
CouponTypeEnum type = coupon.getType();
|
||||
BigDecimal discountAmount = BigDecimal.ZERO;
|
||||
|
||||
if (type == CouponTypeEnum.AMOUNT_DISCOUNT) {
|
||||
discountAmount = coupon.getDiscountAmount();
|
||||
} else if (type == CouponTypeEnum.RATE_DISCOUNT) {
|
||||
// 1 <= coupon.getDiscountRate() <= 99
|
||||
BigDecimal discountRate = new BigDecimal(String.format("0.%02d", 100 - coupon.getDiscountRate()));
|
||||
discountAmount = totalAmount.multiply(discountRate);
|
||||
}
|
||||
|
||||
return discountAmount;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
package com.jzo2o.market.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.jzo2o.api.market.dto.request.CouponUseBackReqDTO;
|
||||
import com.jzo2o.common.expcetions.BadRequestException;
|
||||
import com.jzo2o.common.expcetions.DBException;
|
||||
import com.jzo2o.common.utils.ObjectUtils;
|
||||
import com.jzo2o.market.enums.CouponStatusEnum;
|
||||
import com.jzo2o.market.mapper.CouponUseBackMapper;
|
||||
import com.jzo2o.market.model.domain.Coupon;
|
||||
import com.jzo2o.market.model.domain.CouponUseBack;
|
||||
import com.jzo2o.market.service.ICouponService;
|
||||
import com.jzo2o.market.service.ICouponUseBackService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 优惠券使用回退记录 服务实现类
|
||||
* </p>
|
||||
* @author itcast
|
||||
* @since 2023-09-18
|
||||
*/
|
||||
@Service
|
||||
public class CouponUseBackServiceImpl extends ServiceImpl<CouponUseBackMapper, CouponUseBack> implements ICouponUseBackService {
|
||||
@Resource
|
||||
private ICouponService couponService;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void useBack(CouponUseBackReqDTO couponUseBackReqDTO) {
|
||||
Long couponId = couponUseBackReqDTO.getId();
|
||||
Long userId = couponUseBackReqDTO.getUserId();
|
||||
Long ordersId = couponUseBackReqDTO.getOrdersId();
|
||||
|
||||
Coupon coupon = couponService.getById(couponId);
|
||||
if (ObjectUtils.isEmpty(coupon) || ObjectUtils.notEqual(coupon.getUserId(), userId)) {
|
||||
throw new BadRequestException("优惠卷不存在");
|
||||
} else if (coupon.getStatus() != CouponStatusEnum.USED) {
|
||||
throw new BadRequestException("优惠卷未使用");
|
||||
} else if (ObjectUtils.notEqual(coupon.getOrdersId(), ordersId)) {
|
||||
throw new BadRequestException("优惠券对应订单错误");
|
||||
}
|
||||
|
||||
LocalDateTime nowTime = LocalDateTime.now();
|
||||
CouponStatusEnum status = coupon.getValidityTime().isAfter(nowTime) ? CouponStatusEnum.NO_USE : CouponStatusEnum.INVALID;
|
||||
|
||||
if (!couponService.lambdaUpdate()
|
||||
.eq(Coupon::getId, couponId)
|
||||
.set(Coupon::getUseTime, null)
|
||||
.set(Coupon::getStatus, status)
|
||||
.set(Coupon::getOrdersId, null)
|
||||
.update()) {
|
||||
throw new DBException("更新优惠卷表失败");
|
||||
}
|
||||
|
||||
if (!this.save(CouponUseBack.builder()
|
||||
.couponId(couponId)
|
||||
.userId(userId)
|
||||
.useBackTime(nowTime)
|
||||
.writeOffTime(coupon.getUseTime())
|
||||
.build())) {
|
||||
throw new DBException("更新退回表失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package com.jzo2o.market.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
|
||||
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
|
||||
import com.jzo2o.common.expcetions.BadRequestException;
|
||||
import com.jzo2o.common.expcetions.DBException;
|
||||
import com.jzo2o.common.utils.ObjectUtils;
|
||||
import com.jzo2o.market.enums.CouponStatusEnum;
|
||||
import com.jzo2o.market.mapper.CouponWriteOffMapper;
|
||||
import com.jzo2o.market.model.domain.Coupon;
|
||||
import com.jzo2o.market.model.domain.CouponWriteOff;
|
||||
import com.jzo2o.market.service.ICouponService;
|
||||
import com.jzo2o.market.service.ICouponWriteOffService;
|
||||
import com.jzo2o.mvc.utils.UserContext;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 优惠券核销表 服务实现类
|
||||
* </p>
|
||||
* @author itcast
|
||||
* @since 2023-09-22
|
||||
*/
|
||||
@Service
|
||||
public class CouponWriteOffServiceImpl extends ServiceImpl<CouponWriteOffMapper, CouponWriteOff> implements ICouponWriteOffService {
|
||||
@Resource
|
||||
private ICouponService couponService;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CouponUseResDTO use(CouponUseReqDTO couponUseReqDTO) {
|
||||
Long couponId = couponUseReqDTO.getId();
|
||||
BigDecimal totalAmount = couponUseReqDTO.getTotalAmount();
|
||||
Long userId = UserContext.currentUserId();
|
||||
LocalDateTime nowTime = LocalDateTime.now();
|
||||
|
||||
Coupon coupon = couponService.getById(couponId);
|
||||
if (ObjectUtils.isEmpty(coupon) || ObjectUtils.notEqual(coupon.getUserId(), userId)) {
|
||||
throw new BadRequestException("优惠卷不存在");
|
||||
} else if (coupon.getStatus() != CouponStatusEnum.NO_USE
|
||||
|| coupon.getValidityTime().isBefore(nowTime)) {
|
||||
throw new BadRequestException("优惠卷已使用/已失效");
|
||||
} else if (coupon.getAmountCondition().compareTo(totalAmount) > 0) {
|
||||
throw new BadRequestException("订单金额未达到满减金额");
|
||||
}
|
||||
|
||||
Long ordersId = couponUseReqDTO.getOrdersId();
|
||||
if (!couponService.lambdaUpdate()
|
||||
.eq(Coupon::getId, couponId)
|
||||
.set(Coupon::getUseTime, nowTime)
|
||||
.set(Coupon::getStatus, CouponStatusEnum.USED)
|
||||
.set(Coupon::getOrdersId, ordersId)
|
||||
.update()) {
|
||||
throw new DBException("更新优惠卷表失败");
|
||||
}
|
||||
|
||||
if (!this.save(CouponWriteOff.builder()
|
||||
.couponId(couponId)
|
||||
.userId(userId)
|
||||
.ordersId(ordersId)
|
||||
.activityId(coupon.getActivityId())
|
||||
.writeOffTime(nowTime)
|
||||
.writeOffManName(coupon.getUserName())
|
||||
.writeOffManPhone(coupon.getUserPhone())
|
||||
.build())) {
|
||||
throw new DBException("更新核销表失败");
|
||||
}
|
||||
|
||||
return new CouponUseResDTO(couponService.calcDiscountAmount(coupon, totalAmount));
|
||||
}
|
||||
}
|
||||
18
jzo2o-market/src/main/resources/bootstrap-dev.yml
Normal file
18
jzo2o-market/src/main/resources/bootstrap-dev.yml
Normal file
@ -0,0 +1,18 @@
|
||||
spring:
|
||||
cloud:
|
||||
nacos:
|
||||
username: nacos
|
||||
password: nacos
|
||||
server-addr: 192.168.122.135:8848
|
||||
config:
|
||||
namespace: 75a593f5-33e6-4c65-b2a0-18c403d20f63
|
||||
file-extension: yaml
|
||||
discovery:
|
||||
namespace: 75a593f5-33e6-4c65-b2a0-18c403d20f63
|
||||
ip: ${ACCESS_IP:}
|
||||
|
||||
################# 日志配置 #################
|
||||
logging:
|
||||
level:
|
||||
com.jzo2o: debug
|
||||
org.mongodb.driver: info
|
||||
14
jzo2o-market/src/main/resources/bootstrap-prod.yml
Normal file
14
jzo2o-market/src/main/resources/bootstrap-prod.yml
Normal file
@ -0,0 +1,14 @@
|
||||
spring:
|
||||
cloud:
|
||||
nacos:
|
||||
username: ${NACOS_USERNAME}
|
||||
password: ${NACOS_PASSWORD}
|
||||
server-addr: ${NACOS_ADDR}
|
||||
config:
|
||||
namespace: ${NACOS_NAMESPACE}
|
||||
file-extension: yaml
|
||||
discovery:
|
||||
namespace: ${NACOS_NAMESPACE}
|
||||
logging:
|
||||
level:
|
||||
com.jzo2o: debug
|
||||
14
jzo2o-market/src/main/resources/bootstrap-test.yml
Normal file
14
jzo2o-market/src/main/resources/bootstrap-test.yml
Normal file
@ -0,0 +1,14 @@
|
||||
spring:
|
||||
cloud:
|
||||
nacos:
|
||||
username: ${NACOS_USERNAME}
|
||||
password: ${NACOS_PASSWORD}
|
||||
server-addr: ${NACOS_ADDR}
|
||||
config:
|
||||
namespace: ${NACOS_NAMESPACE}
|
||||
file-extension: yaml
|
||||
discovery:
|
||||
namespace: ${NACOS_NAMESPACE}
|
||||
logging:
|
||||
level:
|
||||
com.jzo2o: debug
|
||||
76
jzo2o-market/src/main/resources/bootstrap.yml
Normal file
76
jzo2o-market/src/main/resources/bootstrap.yml
Normal file
@ -0,0 +1,76 @@
|
||||
################# 服务器配置 #################
|
||||
server:
|
||||
port: 11510
|
||||
undertow:
|
||||
accesslog:
|
||||
enabled: true
|
||||
pattern: "%t %a "%r" %s (%D ms)"
|
||||
dir: /data/logs/undertow/${spring.application.name}/access-logs/
|
||||
servlet:
|
||||
context-path: /market
|
||||
|
||||
################# spring公共配置 #################
|
||||
|
||||
spring:
|
||||
mvc:
|
||||
path-match:
|
||||
matching-strategy: ant_path_matcher
|
||||
format:
|
||||
date: yyyy-MM-dd HH:mm:ss
|
||||
jackson:
|
||||
time-zone: GMT+8
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
profiles:
|
||||
active: dev
|
||||
application:
|
||||
name: jzo2o-market
|
||||
main:
|
||||
# 支持循环依赖注入
|
||||
allow-circular-references: true
|
||||
# bean名相同覆盖
|
||||
allow-bean-definition-overriding: true
|
||||
cloud:
|
||||
nacos:
|
||||
username: ${NACOS_USERNAME}
|
||||
password: ${NACOS_PASSWORD}
|
||||
server-addr: ${NACOS_ADDR}
|
||||
discovery:
|
||||
namespace: ${NACOS_NAMESPACE}
|
||||
config:
|
||||
namespace: ${NACOS_NAMESPACE}
|
||||
file-extension: yaml
|
||||
shared-configs: # 共享配置
|
||||
- data-id: shared-redis-cluster.yaml # 共享redis集群配置
|
||||
refresh: false
|
||||
- data-id: shared-xxl-job.yaml # xxl-job配置
|
||||
refresh: false
|
||||
- data-id: shared-rabbitmq.yaml # rabbitmq配置
|
||||
refresh: false
|
||||
- data-id: shared-mysql.yaml # mysql配置
|
||||
refresh: false
|
||||
# - data-id: shared-spring-seata.yaml # seata
|
||||
# refresh: false
|
||||
|
||||
################# 项目独有配置 #################
|
||||
mysql:
|
||||
db-name: jzo2o-market
|
||||
mybatis:
|
||||
mapper-locations: mapper/*.xml
|
||||
type-aliases-package: com.jzo2o.market.mapper
|
||||
swagger:
|
||||
enable: true
|
||||
package-path: com.jzo2o.market.controller
|
||||
title: 家政服务-促销中心接口文档
|
||||
description: 用于活动优惠券的管理和使用
|
||||
contact-name: 传智教育·研究院
|
||||
contact-url: http://www.itcast.cn/
|
||||
contact-email: yjy@itcast.cn
|
||||
version: v1.0
|
||||
|
||||
################# 日志配置 #################
|
||||
logging:
|
||||
level:
|
||||
com.jzo2o: debug
|
||||
org.mongodb.driver: info
|
||||
feign:
|
||||
enable: true
|
||||
@ -0,0 +1,5 @@
|
||||
<?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.jzo2o.market.mapper.ActivityMapper">
|
||||
|
||||
</mapper>
|
||||
5
jzo2o-market/src/main/resources/mapper/CouponMapper.xml
Normal file
5
jzo2o-market/src/main/resources/mapper/CouponMapper.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?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.jzo2o.market.mapper.CouponMapper">
|
||||
|
||||
</mapper>
|
||||
@ -0,0 +1,5 @@
|
||||
<?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.jzo2o.market.mapper.CouponUseBackMapper">
|
||||
|
||||
</mapper>
|
||||
@ -0,0 +1,5 @@
|
||||
<?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.jzo2o.market.mapper.CouponWriteOffMapper">
|
||||
|
||||
</mapper>
|
||||
@ -0,0 +1,36 @@
|
||||
-- 抢券lua实现
|
||||
-- key: 抢券同步队列, 资源库存, 抢券成功列表
|
||||
-- argv:活动id, 用户id
|
||||
|
||||
-- 优惠券是否已经抢过
|
||||
local couponNum = redis.call("HGET", KEYS[3], ARGV[2])
|
||||
-- hget 获取不到数据返回false而不是nil
|
||||
if couponNum ~= false and tonumber(couponNum) >= 1
|
||||
then
|
||||
return "-1"; -- 已抢卷
|
||||
end
|
||||
-- 库存是否充足校验
|
||||
local stockNum = redis.call("HGET", KEYS[2], ARGV[1])
|
||||
if stockNum == false or tonumber(stockNum) < 1
|
||||
then
|
||||
return "-2"; -- 库存不足抢卷失败
|
||||
end
|
||||
-- 抢券成功列表
|
||||
local listNum = redis.call("HSET", KEYS[3], ARGV[2], 1)
|
||||
if listNum == false or tonumber(listNum) < 1
|
||||
then
|
||||
return "-3"; -- 写入抢卷成功列表失败
|
||||
end
|
||||
-- 减少库存
|
||||
stockNum = redis.call("HINCRBY", KEYS[2], ARGV[1], -1)
|
||||
if tonumber(stockNum) < 0
|
||||
then
|
||||
return "-4" -- 库存不足抢卷失败
|
||||
end
|
||||
-- 抢卷结果写入同步队列
|
||||
local result = redis.call("HSETNX", KEYS[1], ARGV[2], ARGV[1])
|
||||
if result > 0
|
||||
then
|
||||
return "100" -- 抢卷成功返回活动id
|
||||
end
|
||||
return "-5" -- 写入同步队列失败
|
||||
@ -1,5 +1,6 @@
|
||||
package com.jzo2o.orders.manager.controller.consumer;
|
||||
|
||||
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
|
||||
import com.jzo2o.api.orders.dto.request.OrderCancelReqDTO;
|
||||
import com.jzo2o.api.orders.dto.response.OrderResDTO;
|
||||
import com.jzo2o.api.orders.dto.response.OrderSimpleResDTO;
|
||||
@ -104,4 +105,15 @@ public class ConsumerOrdersController {
|
||||
.currentUserType(currentUser.getUserType())
|
||||
.build());
|
||||
}
|
||||
|
||||
@GetMapping("/getAvailableCoupons")
|
||||
@ApiOperation("获取可用优惠券")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "serveId", value = "服务id", required = true, dataTypeClass = Integer.class),
|
||||
@ApiImplicitParam(name = "purNum", value = "购买数量,默认1", dataTypeClass = Long.class)
|
||||
})
|
||||
public List<AvailableCouponsResDTO> getAvailableCoupons(@RequestParam Long serveId,
|
||||
@RequestParam(required = false, defaultValue = "1") Integer purNum) {
|
||||
return ordersCreateService.getAvailableCoupons(serveId, purNum);
|
||||
}
|
||||
}
|
||||
@ -41,7 +41,7 @@ public class OrderCancelJob {
|
||||
|
||||
// 取消所有超时未支付订单
|
||||
transactionTemplate.executeWithoutResult(status ->
|
||||
orderList.forEach(order -> ordersCanceledService.cancelPayOverTimeOrder(order.getId())));
|
||||
orderList.forEach(order -> ordersCanceledService.cancelPayOverTimeOrder(order)));
|
||||
}
|
||||
|
||||
@XxlJob("handlerRefundOrder")
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.jzo2o.orders.manager.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.jzo2o.orders.base.model.domain.Orders;
|
||||
import com.jzo2o.orders.base.model.domain.OrdersCanceled;
|
||||
import com.jzo2o.orders.manager.model.dto.OrderCancelDTO;
|
||||
|
||||
@ -22,5 +23,5 @@ public interface IOrdersCanceledService extends IService<OrdersCanceled> {
|
||||
* 系统取消超时订单(无前置判断)
|
||||
* <br><b>仅内部使用!!!</b>
|
||||
*/
|
||||
void cancelPayOverTimeOrder(Long id);
|
||||
void cancelPayOverTimeOrder(Orders order);
|
||||
}
|
||||
@ -1,12 +1,15 @@
|
||||
package com.jzo2o.orders.manager.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
|
||||
import com.jzo2o.orders.base.model.domain.Orders;
|
||||
import com.jzo2o.orders.manager.model.dto.request.OrdersPayReqDTO;
|
||||
import com.jzo2o.orders.manager.model.dto.request.PlaceOrderReqDTO;
|
||||
import com.jzo2o.orders.manager.model.dto.response.OrdersPayResDTO;
|
||||
import com.jzo2o.orders.manager.model.dto.response.PlaceOrderResDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 下单服务类
|
||||
@ -35,4 +38,9 @@ public interface IOrdersCreateService extends IService<Orders> {
|
||||
* 客户端获取支付状态
|
||||
*/
|
||||
OrdersPayResDTO getPayResult(Long id);
|
||||
|
||||
/**
|
||||
* 客户端获取可用优惠卷
|
||||
*/
|
||||
List<AvailableCouponsResDTO> getAvailableCoupons(Long serveId, Integer purNum);
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.jzo2o.orders.manager.service.client;
|
||||
|
||||
import com.alibaba.csp.sentinel.annotation.SentinelResource;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.jzo2o.api.foundations.ServeApi;
|
||||
import com.jzo2o.api.foundations.dto.response.ServeAggregationResDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 自定义Feign客户端用于熔断降级
|
||||
* @author JIAN
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@SuppressWarnings("unused")
|
||||
public class FoundationClient {
|
||||
@Resource
|
||||
private ServeApi serveApi;
|
||||
|
||||
@SentinelResource(value = "serveById",
|
||||
fallback = "getServeByIdFallback", blockHandler = "getServeByIdBlockHandler")
|
||||
public ServeAggregationResDTO getServeById(Long id) {
|
||||
return serveApi.findById(id);
|
||||
}
|
||||
|
||||
public ServeAggregationResDTO getServeByIdFallback(Long id, Throwable throwable) {
|
||||
log.warn("服务信息接口异常(未触发熔断), 服务id: {}", id, throwable);
|
||||
return null;
|
||||
}
|
||||
|
||||
public ServeAggregationResDTO getServeByIdBlockHandler(Long id, BlockException blockException) {
|
||||
log.warn("服务信息接口异常(触发熔断降级), 服务id: {}", id, blockException);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package com.jzo2o.orders.manager.service.client;
|
||||
|
||||
import com.alibaba.csp.sentinel.annotation.SentinelResource;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.jzo2o.api.market.CouponApi;
|
||||
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
|
||||
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
|
||||
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 自定义Feign客户端用于熔断降级
|
||||
* @author JIAN
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@SuppressWarnings("unused")
|
||||
public class MarketClient {
|
||||
@Resource
|
||||
private CouponApi couponApi;
|
||||
|
||||
@SentinelResource(value = "availableCoupon",
|
||||
fallback = "getAvailableCouponFallback", blockHandler = "getAvailableCouponBlockHandler")
|
||||
public List<AvailableCouponsResDTO> getAvailableCoupon(BigDecimal totalAmount) {
|
||||
return couponApi.getAvailableCoupon(totalAmount);
|
||||
}
|
||||
|
||||
public List<AvailableCouponsResDTO> getAvailableCouponFallback(BigDecimal totalAmount, Throwable throwable) {
|
||||
log.warn("优惠卷接口异常(未触发熔断), 总金额: {}", totalAmount, throwable);
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<AvailableCouponsResDTO> getAvailableCouponBlockHandler(BigDecimal totalAmount, BlockException blockException) {
|
||||
log.warn("优惠卷接口异常(触发熔断降级), 总金额: {}", totalAmount, blockException);
|
||||
return null;
|
||||
}
|
||||
|
||||
@SentinelResource(value = "useCoupon",
|
||||
fallback = "useCouponFallback", blockHandler = "useCouponBlockHandler")
|
||||
public CouponUseResDTO useCoupon(CouponUseReqDTO couponUseReqDTO) {
|
||||
return couponApi.useCoupon(couponUseReqDTO);
|
||||
}
|
||||
|
||||
public CouponUseResDTO useCouponFallback(CouponUseReqDTO couponUseReqDTO, Throwable throwable) {
|
||||
log.warn("优惠卷接口异常(未触发熔断), 相关信息: {}", couponUseReqDTO, throwable);
|
||||
return null;
|
||||
}
|
||||
|
||||
public CouponUseResDTO useCouponBlockHandler(CouponUseReqDTO couponUseReqDTO, BlockException blockException) {
|
||||
log.warn("优惠卷接口异常(触发熔断降级), 相关信息: {}", couponUseReqDTO, blockException);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -74,18 +74,24 @@ public class OrdersCanceledServiceImpl extends ServiceImpl<OrdersCanceledMapper,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelPayOverTimeOrder(Long id) {
|
||||
public void cancelPayOverTimeOrder(Orders order) {
|
||||
// 二次确认防止在此期间支付
|
||||
TradingResDTO tradingResDTO = tradingApi.findTradResultByTradingOrderNo(id);
|
||||
if (ObjectUtils.isEmpty(tradingResDTO) || tradingResDTO.getTradingState() != TradingStateEnum.YJS) {
|
||||
cancelNoPayOrder(OrderCancelDTO.builder()
|
||||
.id(id)
|
||||
.cancelReason("订单超时未支付自动取消")
|
||||
.currentUserId(-1L)
|
||||
.currentUserName("SYSTEM")
|
||||
.currentUserType(UserType.SYSTEM)
|
||||
.build());
|
||||
Long tradingOrderNo = order.getTradingOrderNo();
|
||||
if (ObjectUtils.isNotEmpty(tradingOrderNo)) {
|
||||
// 再次请求防止已支付
|
||||
TradingResDTO tradingResDTO = tradingApi.findTradResultByTradingOrderNo(tradingOrderNo);
|
||||
if (ObjectUtils.isNotEmpty(tradingResDTO) && tradingResDTO.getTradingState() == TradingStateEnum.YJS) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cancelNoPayOrder(OrderCancelDTO.builder()
|
||||
.id(order.getId())
|
||||
.cancelReason("订单超时未支付自动取消")
|
||||
.currentUserId(-1L)
|
||||
.currentUserName("SYSTEM")
|
||||
.currentUserType(UserType.SYSTEM)
|
||||
.build());
|
||||
}
|
||||
|
||||
private void cancelNoPayOrder(OrderCancelDTO orderCancelDTO) {
|
||||
|
||||
@ -3,6 +3,9 @@ package com.jzo2o.orders.manager.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.jzo2o.api.customer.dto.response.AddressBookResDTO;
|
||||
import com.jzo2o.api.foundations.dto.response.ServeAggregationResDTO;
|
||||
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
|
||||
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
|
||||
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
|
||||
import com.jzo2o.api.trade.NativePayApi;
|
||||
import com.jzo2o.api.trade.TradingApi;
|
||||
import com.jzo2o.api.trade.dto.request.NativePayReqDTO;
|
||||
@ -10,6 +13,7 @@ import com.jzo2o.api.trade.dto.response.NativePayResDTO;
|
||||
import com.jzo2o.api.trade.dto.response.TradingResDTO;
|
||||
import com.jzo2o.api.trade.enums.PayChannelEnum;
|
||||
import com.jzo2o.api.trade.enums.TradingStateEnum;
|
||||
import com.jzo2o.common.expcetions.BadRequestException;
|
||||
import com.jzo2o.common.expcetions.CommonException;
|
||||
import com.jzo2o.common.expcetions.ForbiddenOperationException;
|
||||
import com.jzo2o.common.utils.BeanUtils;
|
||||
@ -30,6 +34,8 @@ import com.jzo2o.orders.manager.model.dto.response.PlaceOrderResDTO;
|
||||
import com.jzo2o.orders.manager.porperties.TradeProperties;
|
||||
import com.jzo2o.orders.manager.service.IOrdersCreateService;
|
||||
import com.jzo2o.orders.manager.service.client.CustomerClient;
|
||||
import com.jzo2o.orders.manager.service.client.FoundationClient;
|
||||
import com.jzo2o.orders.manager.service.client.MarketClient;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -38,6 +44,9 @@ import org.springframework.transaction.support.TransactionTemplate;
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -49,6 +58,10 @@ import java.time.LocalDateTime;
|
||||
@Slf4j
|
||||
@Service
|
||||
public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersCreateService {
|
||||
@Resource
|
||||
private MarketClient marketClient;
|
||||
@Resource
|
||||
private FoundationClient foundationClient;
|
||||
@Resource
|
||||
private CustomerClient customerClient;
|
||||
@Resource
|
||||
@ -87,10 +100,22 @@ public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> i
|
||||
}
|
||||
// 获取订单id
|
||||
Long orderId = generateOrderId();
|
||||
// TODO 获取优惠卷相关信息
|
||||
BigDecimal discountAmount = BigDecimal.ZERO;
|
||||
// 计算价格
|
||||
// 计算总金额
|
||||
BigDecimal totalAmount = serve.getPrice().multiply(BigDecimal.valueOf(placeOrderReqDTO.getPurNum()));
|
||||
// 计算优惠卷相关金额
|
||||
BigDecimal discountAmount = BigDecimal.ZERO;
|
||||
Long couponId = placeOrderReqDTO.getCouponId();
|
||||
if (ObjectUtils.isNotEmpty(couponId)) {
|
||||
CouponUseResDTO couponUseResDTO = Optional
|
||||
.ofNullable(marketClient.useCoupon(CouponUseReqDTO.builder()
|
||||
.ordersId(orderId)
|
||||
.id(couponId)
|
||||
.totalAmount(totalAmount)
|
||||
.build()))
|
||||
.orElseThrow(() -> new ForbiddenOperationException("优惠卷核销失败下单失败"));
|
||||
discountAmount = couponUseResDTO.getDiscountAmount();
|
||||
}
|
||||
// 计算实际金额
|
||||
BigDecimal realPayAmount = totalAmount.subtract(discountAmount);
|
||||
|
||||
// 组装订单信息插入数据库完成下单
|
||||
@ -224,4 +249,17 @@ public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> i
|
||||
|
||||
return ordersPayResDTO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AvailableCouponsResDTO> getAvailableCoupons(Long serveId, Integer purNum) {
|
||||
ServeAggregationResDTO serve = foundationClient.getServeById(serveId);
|
||||
if (ObjectUtils.isEmpty(serve) || serve.getSaleStatus() != 2) {
|
||||
throw new BadRequestException("服务不可用");
|
||||
}
|
||||
|
||||
BigDecimal totalAmount = serve.getPrice().multiply(BigDecimal.valueOf(purNum));
|
||||
return Optional
|
||||
.ofNullable(marketClient.getAvailableCoupon(totalAmount))
|
||||
.orElseGet(ArrayList::new);
|
||||
}
|
||||
}
|
||||
@ -93,7 +93,7 @@ public class OrdersManagerServiceImpl extends ServiceImpl<OrdersMapper, Orders>
|
||||
// 订单超过15分钟未支付则自动取消
|
||||
if (OrderStatusEnum.NO_PAY.getStatus().equals(orders.getOrdersStatus())
|
||||
&& orders.getCreateTime().isBefore(LocalDateTime.now().minusMinutes(PAY_OVERTIME_MINUTE))) {
|
||||
ordersCanceledService.cancelPayOverTimeOrder(id);
|
||||
ordersCanceledService.cancelPayOverTimeOrder(orders);
|
||||
orderResDTO.setOrdersStatus(OrderStatusEnum.CANCELED.getStatus());
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user