领券基本开发完成--遗留问题

This commit is contained in:
jieyuu 2024-07-27 16:24:19 +08:00
parent aa85ef8670
commit 78470e4279
22 changed files with 882 additions and 11 deletions

View File

@ -0,0 +1,29 @@
package net.jieyuu.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusPageConfig {
/* 旧版本配置
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}*/
/**
* 新的分页插件,一缓和二缓遵循mybatis的规则,
* 需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

View File

@ -0,0 +1,17 @@
package net.jieyuu.enums;
public enum CouponCategoryEnum {
/**
* 新人注册
*/
NEW_USER,
/**
* 活动任务
*/
TASK,
/**
* 促销
*/
PROMOTION
}

View File

@ -0,0 +1,17 @@
package net.jieyuu.enums;
public enum CouponPublishEnum {
/**
* 线上
*/
PUBLISH,
/**
* 草稿
*/
DRAFT,
/**
* 下线
*/
OFFLINE
}

View File

@ -0,0 +1,19 @@
package net.jieyuu.enums;
public enum CouponStateEnum {
/**
* 未使用
*/
NEW ,
/**
* 已经使用
*/
USED,
/**
* 过期
*/
EXPIRED;
}

View File

@ -2,10 +2,15 @@ package net.jieyuu.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LoginUser {
/**

View File

@ -35,20 +35,16 @@ public class JWTUtil {
/**
* 根据用户信息生成令牌
*
* @param loginUser
* @param user
* @return
*/
public static String geneJsonWebToken(LoginUser loginUser) {
if (loginUser == null) {
throw new NullPointerException("loginUser对象为空");
}
long userid = loginUser.getId();
public static String geneJsonWebToken(LoginUser user) {
Long userId = user.getId();
String token = Jwts.builder().setSubject(SUBJECT)
.claim("head_img", loginUser.getHeadImg())
.claim("id", userid)
.claim("name", loginUser.getName())
.claim("mail", loginUser.getMail())
.claim("head_img", user.getHeadImg())
.claim("id", userId)
.claim("name", user.getName())
.claim("mail", user.getMail())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.signWith(SignatureAlgorithm.HS256, SECRET).compact();

View File

@ -11,6 +11,14 @@
<artifactId>xdclass-coupon-service</artifactId>
<dependencies>
<dependency>
<groupId>net.jieyuu</groupId>
<artifactId>xdclass-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>

View File

@ -0,0 +1,13 @@
package net.jieyuu;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("net.jieyuu.mapper")
public class CouponApplication {
public static void main(String[] args) {
SpringApplication.run(CouponApplication.class, args);
}
}

View File

@ -0,0 +1,25 @@
package net.jieyuu.config;
import lombok.extern.slf4j.Slf4j;
import net.jieyuu.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@Slf4j
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry
.addInterceptor(new LoginInterceptor())
//拦截的路径
.addPathPatterns("/api/coupon/*/**","/api/coupon_record/v1/*/**")
//放行的路径
.excludePathPatterns("/api/coupon/*/page_coupon");
WebMvcConfigurer.super.addInterceptors(registry);
}
}

View File

@ -0,0 +1,54 @@
package net.jieyuu.controller;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import net.jieyuu.enums.CouponCategoryEnum;
import net.jieyuu.service.CouponService;
import net.jieyuu.utils.JsonData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* <p>
* 前端控制器
* </p>
*
* @author jieyuu
* @since 2024-07-15
*/
@RestController
@RequestMapping("/api/coupon/v1")
public class CouponController {
@Autowired
private CouponService couponService;
@ApiOperation("分页查询优惠券")
@GetMapping("page_coupon")
public JsonData pageCouponList(
@ApiParam("当前页")
@RequestParam(name = "page", defaultValue = "1") int page,
@ApiParam("显示多少条")
@RequestParam(name = "size", defaultValue = "10") int size) {
Map<String, Object> stringObjectMap = couponService.pageCouponActivity(page, size);
return JsonData.buildSuccess(stringObjectMap);
}
@ApiOperation("领取优惠券")
@GetMapping("add/promotion/{coupon_id}")
public JsonData addPromotionCoupon(
@ApiParam(value = "优惠券id",required = true)
@PathVariable("coupon_id")long couponId) {
JsonData jsonData = couponService.addCoupon(couponId, CouponCategoryEnum.PROMOTION);
return jsonData;
}
}

View File

@ -0,0 +1,21 @@
package net.jieyuu.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 前端控制器
* </p>
*
* @author jieyuu
* @since 2024-07-15
*/
@RestController
@RequestMapping("/couponRecordDO")
public class CouponRecordController {
}

View File

@ -0,0 +1,22 @@
package net.jieyuu.mapper;
import net.jieyuu.model.CouponDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author jieyuu
* @since 2024-07-15
*/
public interface CouponMapper extends BaseMapper<CouponDO> {
/**
* 扣减库存
* @param couponId
* @return
*/
int reduceStock(long couponId);
}

View File

@ -0,0 +1,16 @@
package net.jieyuu.mapper;
import net.jieyuu.model.CouponRecordDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author jieyuu
* @since 2024-07-15
*/
public interface CouponRecordMapper extends BaseMapper<CouponRecordDO> {
}

View File

@ -0,0 +1,91 @@
package net.jieyuu.model;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
*
* </p>
*
* @author jieyuu
* @since 2024-07-15
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("coupon")
public class CouponDO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 优惠卷类型[NEW_USER注册赠券TASK任务卷PROMOTION促销劵]
*/
private String category;
/**
* 发布状态, PUBLISH发布DRAFT草稿OFFLINE下线
*/
private String publish;
/**
* 优惠券图片
*/
private String couponImg;
/**
* 优惠券标题
*/
private String couponTitle;
/**
* 抵扣价格
*/
private BigDecimal price;
/**
* 每人限制张数
*/
private Integer userLimit;
/**
* 优惠券开始有效时间
*/
private Date startTime;
/**
* 优惠券失效时间
*/
private Date endTime;
/**
* 优惠券总量
*/
private Integer publishCount;
/**
* 库存
*/
private Integer stock;
private Date createTime;
/**
* 满多少才可以使用
*/
private BigDecimal conditionPrice;
}

View File

@ -0,0 +1,86 @@
package net.jieyuu.model;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
*
* </p>
*
* @author jieyuu
* @since 2024-07-15
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("coupon_record")
public class CouponRecordDO implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 优惠券id
*/
private Long couponId;
/**
* 创建时间获得时间
*/
private Date createTime;
/**
* 使用状态 可用 NEW,已使用USED,过期 EXPIRED;
*/
private String useState;
/**
* 用户id
*/
private Long userId;
/**
* 用户昵称
*/
private String userName;
/**
* 优惠券标题
*/
private String couponTitle;
/**
* 开始时间
*/
private Date startTime;
/**
* 结束时间
*/
private Date endTime;
/**
* 订单id
*/
private Long orderId;
/**
* 抵扣价格
*/
private BigDecimal price;
/**
* 满多少才可以使用
*/
private BigDecimal conditionPrice;
}

View File

@ -0,0 +1,34 @@
package net.jieyuu.service;
import net.jieyuu.enums.CouponCategoryEnum;
import net.jieyuu.utils.JsonData;
import java.util.Map;
/**
* <p>
* 服务类
* </p>
*
* @author jieyuu
* @since 2024-07-15
*/
public interface CouponService {
/**
* 分页查询优惠券
* @param page
* @param size
* @return
*/
Map<String, Object> pageCouponActivity(int page, int size);
/**
* 领取优惠券
* @param couponId
* @param couponCategoryEnum
* @return
*/
JsonData addCoupon(long couponId, CouponCategoryEnum couponCategoryEnum);
}

View File

@ -0,0 +1,164 @@
package net.jieyuu.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import net.jieyuu.enums.BizCodeEnum;
import net.jieyuu.enums.CouponCategoryEnum;
import net.jieyuu.enums.CouponPublishEnum;
import net.jieyuu.enums.CouponStateEnum;
import net.jieyuu.exception.BizException;
import net.jieyuu.interceptor.LoginInterceptor;
import net.jieyuu.mapper.CouponRecordMapper;
import net.jieyuu.model.CouponDO;
import net.jieyuu.mapper.CouponMapper;
import net.jieyuu.model.CouponRecordDO;
import net.jieyuu.model.LoginUser;
import net.jieyuu.service.CouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.jieyuu.utils.CommonUtil;
import net.jieyuu.utils.JsonData;
import net.jieyuu.vo.CouponVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* <p>
* 服务实现类
* </p>
*
* @author jieyuu
* @since 2024-07-15
*/
@Service
@Slf4j
public class CouponServiceImpl implements CouponService {
@Autowired
private CouponMapper couponMapper;
@Autowired
private CouponRecordMapper couponRecordMapper;
@Override
public Map<String, Object> pageCouponActivity(int page, int size) {
Page<CouponDO> pageInfo = new Page<>(page, size);
IPage<CouponDO> couponDOIPage = couponMapper.selectPage(pageInfo, new QueryWrapper<CouponDO>()
.eq("publish", CouponPublishEnum.PUBLISH)
.eq("category", CouponCategoryEnum.PROMOTION)
.orderByAsc("create_time"));
Map<String, Object> pageMap = new HashMap<>(3);
pageMap.put("total_record", couponDOIPage.getTotal());
pageMap.put("total_page", couponDOIPage.getPages());
pageMap.put("current_data", couponDOIPage.getRecords().stream().map(obj -> beanProcess(obj)).collect(Collectors.toList()));
return pageMap;
}
/**
* 领券接口
* 1获取优惠券是否纯在
* 2校验优惠券是否可以领取: 时间 库存 超过限制
* 3扣减库存
* 4保存领券记录
*
* @param couponId
* @param category
* @return
*/
@Override
public JsonData addCoupon(long couponId, CouponCategoryEnum category) {
LoginUser loginUser = LoginInterceptor.threadLocal.get();
CouponDO couponDO = couponMapper.selectOne(new QueryWrapper<CouponDO>()
.eq("id", couponId)
.eq("category", category.name()));
//优惠券是否可以领取
this.couponCheck(couponDO, loginUser.getId());
//构建领券记录
CouponRecordDO couponRecordDO = new CouponRecordDO();
BeanUtils.copyProperties(couponDO, couponRecordDO);
couponRecordDO.setCreateTime(new Date());
couponRecordDO.setUseState(CouponStateEnum.NEW.name());
couponRecordDO.setCouponId(couponId);
couponRecordDO.setUserName(loginUser.getName());
couponRecordDO.setUserId(loginUser.getId());
couponRecordDO.setId(null);
//扣减库存 todo
int rows = 1;//couponMapper.reduceStock(couponId);
if (rows == 1) {
//扣减库存成功才保存记录
couponRecordMapper.insert(couponRecordDO);
} else {
log.warn("发放优惠券错误:{},用户:{}", couponDO, loginUser);
throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
}
return JsonData.buildSuccess();
}
/**
* 校验优惠券是否可以领取
*
* @param couponDO
* @param id
*/
private void couponCheck(CouponDO couponDO, Long id) {
if (couponDO == null) {
throw new BizException(BizCodeEnum.COUPON_NO_EXITS);
}
//库存不足
if (couponDO.getStock() <= 0) {
throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
}
//未经发布优惠券 确认非法
if (!couponDO.getPublish().equals(CouponPublishEnum.PUBLISH.name())) {
throw new BizException(BizCodeEnum.COUPON_GET_FAIL);
}
//判断是否在领取时间
long time = CommonUtil.getCurrentTimestamp();
long start = couponDO.getStartTime().getTime();
long end = couponDO.getEndTime().getTime();
if (time < start || time > end) {
throw new BizException(BizCodeEnum.COUPON_OUT_OF_TIME);
}
//用户是否超过限制
//查询用户已经拥有的优惠券数量
int recordNum = couponRecordMapper.selectCount(new QueryWrapper<CouponRecordDO>()
.eq("coupon_id", couponDO.getId())
.eq("user_id", id));
//判断优惠券是否超过限制
if (recordNum >= couponDO.getUserLimit()) {
throw new BizException(BizCodeEnum.COUPON_OUT_OF_LIMIT);
}
}
private CouponVO beanProcess(CouponDO couponDO) {
CouponVO couponVO = new CouponVO();
BeanUtils.copyProperties(couponDO, couponVO);
return couponVO;
}
}

View File

@ -0,0 +1,78 @@
package net.jieyuu.vo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class CouponVO {
/**
* id
*/
private Long id;
/**
* 优惠卷类型[NEW_USER注册赠券TASK任务卷PROMOTION促销劵]
*/
private String category;
/**
* 优惠券图片
*/
@JsonProperty("coupon_img")
private String couponImg;
/**
* 优惠券标题
*/
@JsonProperty("coupon_title")
private String couponTitle;
/**
* 抵扣价格
*/
private BigDecimal price;
/**
* 每人限制张数
*/
@JsonProperty("user_limit")
private Integer userLimit;
/**
* 优惠券开始有效时间
*/
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss", locale = "zh", timezone = "GMT+8")
@JsonProperty("start_time")
private Date startTime;
/**
* 优惠券失效时间
*/
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss", locale = "zh", timezone = "GMT+8")
@JsonProperty("end_time")
private Date endTime;
/**
* 优惠券总量
*/
@JsonProperty("publish_count")
private Integer publishCount;
/**
* 库存
*/
private Integer stock;
/**
* 满多少才可以使用
*/
@JsonProperty("condition_price")
private BigDecimal conditionPrice;
}

View File

@ -0,0 +1,29 @@
server:
port: 9002
spring:
application:
name: xdclass_coupon-service
#数据库配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://134.175.219.253:3306/xdclass_coupon?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 59ae8683c59fead903132a8d440bd7d9fd4936529d1d6f45f9d41111d7537bdd
redis:
host: 134.175.219.253
password: 123456
port: 8000
#配置plus打印sql日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#设置日志级别,ERROR/WARN/INFO/DEBUG,默认是INFO以上才显示
logging:
level:
root: INFO

View File

@ -0,0 +1,27 @@
<?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="net.jieyuu.mapper.CouponMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="net.jieyuu.model.CouponDO">
<id column="id" property="id" />
<result column="category" property="category" />
<result column="publish" property="publish" />
<result column="coupon_img" property="couponImg" />
<result column="coupon_title" property="couponTitle" />
<result column="price" property="price" />
<result column="user_limit" property="userLimit" />
<result column="start_time" property="startTime" />
<result column="end_time" property="endTime" />
<result column="publish_count" property="publishCount" />
<result column="stock" property="st ock" />
<result column="create_time" property="createTime" />
<result column="condition_price" property="conditionPrice" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, category, publish, coupon_img, coupon_title, price, user_limit, start_time, end_time, publish_count, stock, create_time, condition_price
</sql>
</mapper>

View File

@ -0,0 +1,26 @@
<?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="net.jieyuu.mapper.CouponRecordMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="net.jieyuu.model.CouponRecordDO">
<id column="id" property="id" />
<result column="coupon_id" property="couponId" />
<result column="create_time" property="createTime" />
<result column="use_state" property="useState" />
<result column="user_id" property="userId" />
<result column="user_name" property="userName" />
<result column="coupon_title" property="couponTitle" />
<result column="start_time" property="startTime" />
<result column="end_time" property="endTime" />
<result column="order_id" property="orderId" />
<result column="price" property="price" />
<result column="condition_price" property="conditionPrice" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, coupon_id, create_time, use_state, user_id, user_name, coupon_title, start_time, end_time, order_id, price, condition_price
</sql>
</mapper>

View File

@ -0,0 +1,94 @@
package net.jieyuu.db;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
public class MyBatisPlusGenerator {
public static void main(String[] args) {
//1. 全局配置
GlobalConfig config = new GlobalConfig();
// 是否支持AR模式
config.setActiveRecord(true)
// 作者
.setAuthor("jieyuu")
// 生成路径最好使用绝对路径window路径是不一样的
//TODO TODO TODO TODO
.setOutputDir("D:\\workspace\\project\\xdclass-shop\\xdclass-shop\\xdclass-coupon-service\\src\\main\\java")
// 文件覆盖
.setFileOverride(true)
// 主键策略
.setIdType(IdType.AUTO)
.setDateType(DateType.ONLY_DATE)
// 设置生成的service接口的名字的首字母是否为I默认Service是以I开头的
.setServiceName("%sService")
//实体类结尾名称
.setEntityName("%sDO")
//生成基本的resultMap
.setBaseResultMap(true)
//不使用AR模式
.setActiveRecord(false)
//生成基本的SQL片段
.setBaseColumnList(true);
//2. 数据源配置
DataSourceConfig dsConfig = new DataSourceConfig();
// 设置数据库类型
dsConfig.setDbType(DbType.MYSQL)
.setDriverName("com.mysql.cj.jdbc.Driver")
//TODO TODO TODO TODO
.setUrl("jdbc:mysql://134.175.219.253:3306/xdclass_coupon?useSSL=false")
.setUsername("root")
.setPassword("59ae8683c59fead903132a8d440bd7d9fd4936529d1d6f45f9d41111d7537bdd");
//3. 策略配置globalConfiguration中
StrategyConfig stConfig = new StrategyConfig();
//全局大写命名
stConfig.setCapitalMode(true)
// 数据库表映射到实体的命名策略
.setNaming(NamingStrategy.underline_to_camel)
//使用lombok
.setEntityLombokModel(true)
//使用restcontroller注解
.setRestControllerStyle(true)
// 生成的表, 支持多表一起生成以数组形式填写
//TODO TODO TODO TODO
.setInclude("coupon","coupon_record");
//4. 包名策略配置
PackageConfig pkConfig = new PackageConfig();
pkConfig.setParent("net.jieyuu")
.setMapper("mapper")
.setService("service")
.setController("controller")
.setEntity("model")
.setXml("mapper");
//5. 整合配置
AutoGenerator ag = new AutoGenerator();
ag.setGlobalConfig(config)
.setDataSource(dsConfig)
.setStrategy(stConfig)
.setPackageInfo(pkConfig);
//6. 执行操作
ag.execute();
System.out.println("======= 小滴课堂 Done 相关代码生成完毕 ========");
}
}