refactor(market):导入项目优惠卷模块初始工程

This commit is contained in:
JIAN 2024-09-20 11:18:53 +08:00
parent 554aa38576
commit d4c244701b
44 changed files with 1541 additions and 0 deletions

9
jzo2o-market/Dockerfile Normal file
View 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
View 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>

View File

@ -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("家政服务-营销中心启动");
}
}

View File

@ -0,0 +1,39 @@
package com.jzo2o.market.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scripting.support.ResourceScriptSource;
import java.util.Objects;
@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;
}
@Bean("lua_test01")
public DefaultRedisScript<Integer> getLuaTest01() {
DefaultRedisScript<Integer> redisScript = new DefaultRedisScript<>();
//resource目录下的scripts文件下的lua_test01.lua文件
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("scripts/lua_test01.lua")));
redisScript.setResultType(Integer.class);
return redisScript;
}
}

View File

@ -0,0 +1,8 @@
package com.jzo2o.market.constants;
public class ErrorInfo {
public static class Msg {
public static final String SEIZE_COUPON_FAILD = "单子已经被抢走了";
}
}

View File

@ -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";
}
}

View File

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

View File

@ -0,0 +1,16 @@
package com.jzo2o.market.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum ActivityStatusEnum {
NO_DISTRIBUTE(1, "待生效"), DISTRIBUTING(2, "进行中"), LOSE_EFFICACY(3, "已失效"),VOIDED(4, "作废");
private int status;
private String name;
public boolean equals(Integer status) {
return status != null && this.status == status;
}
}

View File

@ -0,0 +1,17 @@
package com.jzo2o.market.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum ActivityTypeEnum {
AMOUNT_DISCOUNT(1, "满减"), RATE_DISCOUNT(2, "打折");
private int type;
private String name;
public boolean equals(Integer type) {
return type != null && type.equals(this.type);
}
}

View File

@ -0,0 +1,12 @@
package com.jzo2o.market.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum CouponStatusEnum {
NO_USE(1, "未使用"), USED(2, "已使用"), INVALID(3, "已失效"),VOIDED(4,"已作废");
private int status;
private String name;
}

View File

@ -0,0 +1,48 @@
package com.jzo2o.market.handler;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.redis.annotations.Lock;
import com.jzo2o.redis.constants.RedisSyncQueueConstants;
import com.jzo2o.redis.sync.SyncManager;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import static com.jzo2o.market.constants.RedisConstants.Formatter.*;
import static com.jzo2o.market.constants.RedisConstants.RedisKey.COUPON_SEIZE_SYNC_QUEUE_NAME;
@Component
public class XxlJobHandler {
@Resource
private SyncManager syncManager;
@Resource
private IActivityService activityService;
@Resource
private ICouponService couponService;
/**
* 活动状态修改
* 1.活动进行中状态修改
* 2.活动已失效状态修改
* 1分钟一次
*/
@XxlJob("updateActivityStatus")
public void updateActivitySatus(){
}
/**
* 已领取优惠券自动过期任务
*/
@XxlJob("processExpireCoupon")
public void processExpireCoupon() {
}
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -0,0 +1,119 @@
package com.jzo2o.market.model.domain;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <p>
*
* </p>
*
* @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.NONE)
private Long id;
/**
* 优惠券名称可以和活动名称保持一致
*/
private String name;
/**
* 使用类型1满减2折扣
*/
private Integer 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 Integer 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;
}

View File

@ -0,0 +1,116 @@
package com.jzo2o.market.model.domain;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.IdType;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <p>
*
* </p>
*
* @author itcast
* @since 2023-09-16
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class Coupon implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 优惠券id
*/
@TableId(value = "id", type = IdType.NONE)
private Long id;
/**
* 优惠券名称
*/
private String name;
/**
* 优惠券的拥有者
*/
private Long userId;
/**
* 用户姓名
*/
private String userName;
/**
* 用户手机号
*/
private String userPhone;
/**
* 活动id
*/
private Long activityId;
/**
* 使用类型1满减2折扣
*/
private Integer type;
/**
* 折扣
*/
private Integer discountRate;
/**
* 优惠金额
*/
private BigDecimal discountAmount;
/**
* 满减金额
*/
private BigDecimal amountCondition;
/**
* 有效期
*/
private LocalDateTime validityTime;
/**
* 使用时间
*/
private LocalDateTime useTime;
/**
* 优惠券状态1:未使用2:已使用3:已过期
*/
private Integer status;
/**
* 订单id
*/
private String ordersId;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 逻辑删除
*/
private Integer isDeleted;
}

View File

@ -0,0 +1,53 @@
package com.jzo2o.market.model.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <p>
* 优惠券使用回退记录
* </p>
*
* @author itcast
* @since 2023-09-18
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class CouponUseBack implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 回退记录id
*/
@TableId(value = "id", type = IdType.NONE)
private Long id;
/**
* 优惠券id
*/
private Long couponId;
/**
* 用户id
*/
private Long userId;
/**
* 回退时间
*/
private LocalDateTime useBackTime;
/**
* 核销时间
*/
private LocalDateTime writeOffTime;
}

View File

@ -0,0 +1,68 @@
package com.jzo2o.market.model.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import lombok.*;
import lombok.experimental.Accessors;
/**
* <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.NONE)
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;
}

View File

@ -0,0 +1,19 @@
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;
@Data
@ApiModel("活动分页查询模型")
public class ActivityQueryForPageReqDTO extends PageQueryDTO {
@ApiModelProperty("活动id")
private Long id;
@ApiModelProperty("活动名称")
private String name;
@ApiModelProperty("类型1满减2折扣")
private Integer type;
@ApiModelProperty("优惠券配置状态1待生效2进行中3已失效")
private Integer status;
}

View File

@ -0,0 +1,88 @@
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.ActivityTypeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.checkerframework.checker.units.qual.Length;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Max;
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,新增时不填,修改时必填",required = false)
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时必填",required = false)
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表示无限量其他正数表示最大发放量",required = false)
private Integer totalNum = 0;
@ApiModelProperty(value = "有效期天数",required = true)
@Null(message = "使用期限请输入大于0的整数")
@Min(value = 0, message = "使用期限请输入大于0的整数")
private Integer validityDays;
public void check() {
if(ActivityTypeEnum.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(ActivityTypeEnum.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("发放时间已过期");
}
}
}

View File

@ -0,0 +1,16 @@
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 javax.validation.constraints.Null;
@Data
@ApiModel("运营端优惠券查询模型")
public class CouponOperationPageQueryReqDTO extends PageQueryDTO {
@ApiModelProperty(value = "活动id",required = true)
@Null(message = "请先选择活动")
private Long activityId;
}

View File

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

View File

@ -0,0 +1,43 @@
package com.jzo2o.market.model.dto.response;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@ApiModel("活动分页字段模型")
public class ActivityInfoResDTO {
@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 Integer 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;
}

View File

@ -0,0 +1,99 @@
package com.jzo2o.market.model.dto.response;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModelProperty;
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
public class CouponInfoResDTO implements Serializable {
@ApiModelProperty(value = "优惠券id",required = true)
private Long id;
/**
* 优惠券名称
*/
@ApiModelProperty(value = "活动名称",required = true)
private String name;
@ApiModelProperty("用户姓名")
private String userName;
@ApiModelProperty("用户手机号")
private String userPhone;
/**
* 活动id
*/
@ApiModelProperty(value = "活动id",required = true)
private Long activityId;
@ApiModelProperty(value = "使用类型1满减2折扣",required = true)
private Integer type;
/**
* 折扣
*/
@ApiModelProperty(value = "折扣",required = false)
private Integer discountRate;
/**
* 优惠金额
*/
@ApiModelProperty(value = "优惠金额",required = false)
private BigDecimal discountAmount;
/**
* 满减金额
*/
@ApiModelProperty(value = "满减条件,0:表示无门槛",required = true)
private BigDecimal amountCondition;
/**
* 有效期
*/
@ApiModelProperty("优惠券过期时间")
private LocalDateTime validityTime;
@ApiModelProperty("使用时间")
private LocalDateTime useTime;
/**
* 优惠券状态1:未使用2:已使用3:已过期
*/
@ApiModelProperty("优惠券状态1:未使用2:已使用3:已过期")
private Integer status;
/**
* 订单id
*/
private String ordersId;
/**
* 创建时间
*/
@ApiModelProperty(value = "创建时间",required = true)
private LocalDateTime createTime;
/**
* 更新时间
*/
@ApiModelProperty(value = "更新时间",required = true)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,39 @@
package com.jzo2o.market.model.dto.response;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@ApiModel("抢券列表信息")
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 Integer status;
@ApiModelProperty("优惠券剩余数量")
private Integer remainNum;
@ApiModelProperty("发放数量")
private Integer totalNum;
@ApiModelProperty("库存数量")
private Integer stockNum;
}

View File

@ -0,0 +1,24 @@
package com.jzo2o.market.service;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.market.model.domain.Activity;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jzo2o.market.model.dto.request.ActivityQueryForPageReqDTO;
import com.jzo2o.market.model.dto.request.ActivitySaveReqDTO;
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> {
}

View File

@ -0,0 +1,28 @@
package com.jzo2o.market.service;
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.common.model.PageResult;
import com.jzo2o.market.model.domain.Coupon;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jzo2o.market.model.dto.request.CouponOperationPageQueryReqDTO;
import com.jzo2o.market.model.dto.request.SeizeCouponReqDTO;
import com.jzo2o.market.model.dto.response.CouponInfoResDTO;
import java.math.BigDecimal;
import java.util.List;
/**
* <p>
* 服务类
* </p>
*
* @author itcast
* @since 2023-09-16
*/
public interface ICouponService extends IService<Coupon> {
}

View File

@ -0,0 +1,18 @@
package com.jzo2o.market.service;
import com.jzo2o.market.model.domain.CouponUseBack;
import com.baomidou.mybatisplus.extension.service.IService;
import java.time.LocalDateTime;
/**
* <p>
* 优惠券使用回退记录 服务类
* </p>
*
* @author itcast
* @since 2023-09-18
*/
public interface ICouponUseBackService extends IService<CouponUseBack> {
}

View File

@ -0,0 +1,17 @@
package com.jzo2o.market.service;
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> {
}

View File

@ -0,0 +1,58 @@
package com.jzo2o.market.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.*;
import com.jzo2o.market.constants.TabTypeConstants;
import com.jzo2o.market.enums.ActivityStatusEnum;
import com.jzo2o.market.mapper.ActivityMapper;
import com.jzo2o.market.model.domain.Activity;
import com.jzo2o.market.model.dto.request.ActivityQueryForPageReqDTO;
import com.jzo2o.market.model.dto.request.ActivitySaveReqDTO;
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.market.service.ICouponWriteOffService;
import com.jzo2o.mysql.utils.PageUtils;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
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.stream.Collectors;
import static com.jzo2o.market.constants.RedisConstants.RedisKey.*;
import static com.jzo2o.market.enums.ActivityStatusEnum.*;
/**
* <p>
* 服务实现类
* </p>
*
* @author itcast
* @since 2023-09-16
*/
@Service
public class ActivityServiceImpl extends ServiceImpl<ActivityMapper, Activity> implements IActivityService {
private static final int MILLION = 1000000;
@Resource
private RedisTemplate redisTemplate;
@Resource
private ICouponService couponService;
@Resource
private ICouponWriteOffService couponWriteOffService;
}

View File

@ -0,0 +1,80 @@
package com.jzo2o.market.service.impl;
import cn.hutool.db.DbRuntimeException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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.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.*;
import com.jzo2o.market.enums.ActivityStatusEnum;
import com.jzo2o.market.enums.CouponStatusEnum;
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.domain.CouponWriteOff;
import com.jzo2o.market.model.dto.request.CouponOperationPageQueryReqDTO;
import com.jzo2o.market.model.dto.request.SeizeCouponReqDTO;
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
import com.jzo2o.market.model.dto.response.CouponInfoResDTO;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponUseBackService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.market.utils.CouponUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.mysql.utils.PageUtils;
import com.jzo2o.redis.utils.RedisSyncQueueUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
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.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import static com.jzo2o.common.constants.ErrorInfo.Code.SEIZE_COUPON_FAILD;
import static com.jzo2o.market.constants.RedisConstants.RedisKey.*;
/**
* <p>
* 服务实现类
* </p>
*
* @author itcast
* @since 2023-09-16
*/
@Service
@Slf4j
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {
@Resource(name = "seizeCouponScript")
private DefaultRedisScript<String> seizeCouponScript;
@Resource
private RedisTemplate redisTemplate;
@Resource
private IActivityService activityService;
@Resource
private ICouponUseBackService couponUseBackService;
@Resource
private ICouponWriteOffService couponWriteOffService;
}

View File

@ -0,0 +1,25 @@
package com.jzo2o.market.service.impl;
import com.jzo2o.common.utils.DateUtils;
import com.jzo2o.common.utils.IdUtils;
import com.jzo2o.market.model.domain.CouponUseBack;
import com.jzo2o.market.mapper.CouponUseBackMapper;
import com.jzo2o.market.service.ICouponUseBackService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
/**
* <p>
* 优惠券使用回退记录 服务实现类
* </p>
*
* @author itcast
* @since 2023-09-18
*/
@Service
public class CouponUseBackServiceImpl extends ServiceImpl<CouponUseBackMapper, CouponUseBack> implements ICouponUseBackService {
}

View File

@ -0,0 +1,20 @@
package com.jzo2o.market.service.impl;
import com.jzo2o.market.model.domain.CouponWriteOff;
import com.jzo2o.market.mapper.CouponWriteOffMapper;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 优惠券核销表 服务实现类
* </p>
*
* @author itcast
* @since 2023-09-22
*/
@Service
public class CouponWriteOffServiceImpl extends ServiceImpl<CouponWriteOffMapper, CouponWriteOff> implements ICouponWriteOffService {
}

View File

@ -0,0 +1,16 @@
package com.jzo2o.market.utils;
import cn.hutool.core.util.ObjectUtil;
import com.jzo2o.market.enums.ActivityTypeEnum;
import com.jzo2o.market.model.domain.Coupon;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 优惠券相关工具
*/
public class CouponUtils {
}

View 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

View 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

View 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

View File

@ -0,0 +1,76 @@
################# 服务器配置 #################
server:
port: 11510
undertow:
accesslog:
enabled: true
pattern: "%t %a &quot;%r&quot; %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

View 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.ActivityMapper">
</mapper>

View 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>

View 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.CouponUseBackMapper">
</mapper>

View 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.CouponWriteOffMapper">
</mapper>

View File

@ -0,0 +1,37 @@
-- 抢券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 ARGV[1] ..""
end
return "-5"