第十三章 电商优惠券微服务业务介绍和模块开发
第1集 互联网公司中优惠券微服务业务介绍和效果体验
简介:介绍常见的优惠券业务和课程案例效果体验
互联网企业最重要的是拉新业务,产品经理 自然诞生了很多玩法
- 砍价
- 拼团
- 裂变
- 优惠券
电商优惠券逻辑,优惠券的玩法很多,主要讲一下比较常见的优惠券
获取方式维度
被动:新人优惠券
- 无门槛现金劵
- …其他
第一次登录注册某平台,登录成功后进入到首个页面弹出新人红包或者某固定位置领取新人红包,前端领取位置及细节不做详细讲解,根据实际业务场景而定
主动:领取优惠券
- 满减劵
- …其他
促进商品订单成交,而设置的优惠券,提高下单支付率
使用门槛维度
- 无门槛现金劵
- 满减劵
- 满减折扣卷
- 运费抵扣券
- 兑换券
- 店铺劵
- 单品劵
- …其他
按照多维度设计,就需要用到规则引擎,这类一般是大厂的营销中台设计的,复杂且容易出问题
优惠券常见属性
- 类型:无门槛、满减、单品、折扣等
- 每人领劵次数
- 发券总量
- 优惠券开始时间和结束时间
天猫超市图
课程案例图
第2集 1024电商平台优惠券微服务业务介绍和数据库建立
简介:优惠券微服务介绍和效果体验
- 大课优惠券业务需求介绍
- 新用户注册-发放后端配置的新人优惠券
- 用户可以主动领取优惠券
- 下单可以选择对应的优惠券抵扣
- 支持满减优惠券-无门槛优惠券两种
- 多种元数据配置
- 类型:无门槛、满减等
- 每人领劵次数限制
- 发券总量控制
- 优惠券开始时间和结束时间
- 优惠券状态配置
- 核心知识:
- 高并发下扣减劵库存
- 超发
- 单人超领取
- 高并发下扣减劵库存
- 原生分布式锁 redisson框架分布锁使用
- 分布式锁 最佳实践
- 数据库表介绍
#优惠券表
CREATE TABLE `coupon` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`category` varchar(11) DEFAULT NULL COMMENT '优惠卷类型[NEW_USER注册赠券,TASK任务卷,PROMOTION促销劵]',
`publish` varchar(11) DEFAULT NULL COMMENT '发布状态, PUBLISH发布,DRAFT草稿,OFFLINE下线',
`coupon_img` varchar(524) DEFAULT NULL COMMENT '优惠券图片',
`coupon_title` varchar(128) DEFAULT NULL COMMENT '优惠券标题',
`price` decimal(16,2) DEFAULT NULL COMMENT '抵扣价格',
`user_limit` int(11) DEFAULT NULL COMMENT '每人限制张数',
`start_time` datetime DEFAULT NULL COMMENT '优惠券开始有效时间',
`end_time` datetime DEFAULT NULL COMMENT '优惠券失效时间',
`publish_count` int(11) DEFAULT NULL COMMENT '优惠券总量',
`stock` int(11) DEFAULT '0' COMMENT '库存',
`create_time` datetime DEFAULT NULL,
`condition_price` decimal(16,2) DEFAULT NULL COMMENT '满多少才可以使用',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4;
#优惠券领劵记录
CREATE TABLE `coupon_record` (
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
`coupon_id` bigint(11) DEFAULT NULL COMMENT '优惠券id',
`create_time` datetime DEFAULT NULL COMMENT '创建时间获得时间',
`use_state` varchar(32) DEFAULT NULL COMMENT '使用状态 可用 NEW,已使用USED,过期 EXPIRED;',
`user_id` bigint(11) DEFAULT NULL COMMENT '用户id',
`user_name` varchar(128) DEFAULT NULL COMMENT '用户昵称',
`coupon_title` varchar(128) DEFAULT NULL COMMENT '优惠券标题',
`start_time` datetime DEFAULT NULL COMMENT '开始时间',
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`order_id` bigint(11) DEFAULT NULL COMMENT '订单id',
`price` decimal(16,2) DEFAULT NULL COMMENT '抵扣价格',
`condition_price` decimal(16,2) DEFAULT NULL COMMENT '满多少才可以使用',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=141 DEFAULT CHARSET=utf8mb4;
- 测试数据准备
INSERT INTO `coupon` (`id`, `category`, `publish`, `coupon_img`, `coupon_title`, `price`, `user_limit`, `start_time`, `end_time`, `publish_count`, `stock`, `create_time`, `condition_price`)
VALUES
(18, 'NEW_USER', 'PUBLISH', 'https://file.xdclass.net/video/2020/alibabacloud/zt-alibabacloud.png', '永久有效-新人注册-0元满减-5元抵扣劵-限领取2张-不可叠加使用', 5.00, 2, '2000-01-01 00:00:00', '2099-01-29 00:00:00', 100000000, 99999991, '2020-12-26 16:33:02', 0.00),
(19, 'PROMOTION', 'PUBLISH', 'https://file.xdclass.net/video/2020/alibabacloud/zt-alibabacloud.png', '有效中-21年1月到25年1月-20元满减-5元抵扣劵-限领取2张-不可叠加使用', 5.00, 2, '2000-01-29 00:00:00', '2025-01-29 00:00:00', 10, 3, '2020-12-26 16:33:03', 20.00),
(22, 'PROMOTION', 'PUBLISH', 'https://file.xdclass.net/video/2020/alibabacloud/zt-alibabacloud.png', '过期-20年8月到20年9月-商品id3-6元抵扣劵-限领取1张-可叠加使用', 6.00, 1, '2020-08-01 00:00:00', '2020-09-29 00:00:00', 100, 100, '2020-12-26 16:33:03', 0.00),
(20, 'PROMOTION', 'PUBLISH', 'https://file.xdclass.net/video/2020/alibabacloud/zt-alibabacloud.png', '有效中-20年8月到21年9月-商品id1-8.8元抵扣劵-限领取2张-不可叠加使用', 8.80, 2, '2020-08-01 00:00:00', '2021-09-29 00:00:00', 100, 96, '2020-12-26 16:33:03', 0.00),
(21, 'PROMOTION', 'PUBLISH', 'https://file.xdclass.net/video/2020/alibabacloud/zt-alibabacloud.png', '有效中-20年8月到21年9月-商品id2-9.9元抵扣劵-限领取2张-可叠加使用', 8.80, 2, '2020-08-01 00:00:00', '2021-09-29 00:00:00', 100, 96, '2020-12-26 16:33:03', 0.00);
第3集 优惠券微服务 MybatisPlusGenerator代码自动生成工具
简介:Mybatis-plus-generator代码自动化生成微服务相关类
- 配置代码生成(标记TODO的记得修改)
public class MyBatisPlusGenerator {
public static void main(String[] args) {
//1. 全局配置
GlobalConfig config = new GlobalConfig();
// 作者
config.setAuthor("二当家小D")
// 生成路径,最好使用绝对路径,window路径是不一样的
//TODO TODO TODO TODO
.setOutputDir("/Users/xdclass/Desktop/demo/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://127.0.0.1:3306/xdclass_user?useSSL=false")
.setUsername("root")
.setPassword("xdclass.net");
//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("user","address");
//4. 包名策略配置
PackageConfig pkConfig = new PackageConfig();
pkConfig.setParent("net.xdclass")
.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 相关代码生成完毕 ========");
}
}
- 导入生成好的代码
- model (为啥不放common项目,如果是确定每个服务都用到的依赖或者类才放到common项目)
- mapper 类接口拷贝
- resource/mapper文件夹 xml脚本拷贝
- controller
- service 不拷贝
第4集 新版MybatisPlus分页插件配置 优惠券分页列表接口
简介:Mybatis-plus-分页插件配置 优惠劵列表开发
- MybatisPlus分页插件配置(Common项目配置)
@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;
}
}
- 优惠券列表分页接口开发
@Override
public Map<String, Object> pageCouponActivity(int page, int size) {
//第1页,每页2条
Page<CouponDO> pageInfo = new Page<>(page, size);
IPage<CouponDO> couponDOPage = couponMapper.selectPage(pageInfo, new QueryWrapper<CouponDO>()
.eq("publish", CouponPublishEnum.PUBLISH)
.eq("category", CouponCategoryEnum.PROMOTION)
.orderByDesc("create_time"));
Map<String, Object> pageMap = new HashMap<>(3);
pageMap.put("total_record", couponDOPage.getTotal());
pageMap.put("total_page", couponDOPage.getPages());
pageMap.put("current_data", couponDOPage.getRecords().stream().map(obj -> beanProcess(obj)).collect(Collectors.toList()));
return pageMap;
}
第5集 优惠券微服务-登录拦截器配置-SwaggerUI配置
简介:登录拦截器配置和SwaggerUI接口文档配置
- 配置登录拦截器
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
LoginInterceptor loginInterceptor() {
return new LoginInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor())
.addPathPatterns("/api/coupon_record/*/**")
.addPathPatterns("/api/coupon/*/**")
//不拦截的路径
.excludePathPatterns("/api/coupon/*/page_coupon");
WebMvcConfigurer.super.addInterceptors(registry);
}
}
- 领劵controller接口开发
- SwaggerUI接口文档
第6集 优惠券微服务-C端领劵接口核心校验业务逻辑开发
简介:C端用户领劵接口核心业务逻辑开发
service层开发
- 业务逻辑
@Override
public JsonData addCoupon(long couponId, String couponCategory) {
LoginUser loginUser = LoginInterceptor.threadLocal.get();
CouponDO couponDO = couponMapper.selectOne(new QueryWrapper<CouponDO>().eq("id", couponId)
.eq("category", couponCategory)
.eq("publish", CouponPublishEnum.PUBLISH));
this.couponCheck(couponDO,loginUser.getId());
CouponRecordDO couponRecordDO = new CouponRecordDO();
BeanUtils.copyProperties(couponDO,couponRecordDO);
couponRecordDO.setCreateTime(new Date());
couponRecordDO.setUseState(CouponStateEnum.NEW.name());
couponRecordDO.setUserId(loginUser.getId());
couponRecordDO.setUserName(loginUser.getName());
couponRecordDO.setCouponId(couponId);
couponRecordDO.setId(null);
//高并发下扣减劵库存,采用乐观锁,当前stock做版本号,延伸多种防止超卖的问题,一次只能领取1张,TODO
int rows = couponMapper.reduceStock(couponId,couponDO.getStock());
if(rows == 1){
//库存扣减成功才保存
couponRecordMapper.insert(couponRecordDO);
}else {
log.warn("发放优惠券失败:{},用户:{}",couponDO,loginUser);
throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
}
return JsonData.buildSuccess();
}
- 优惠券校验逻辑
/**
* 优惠券检查
* @param couponDO
* @param userId
*/
private void couponCheck(CouponDO couponDO,long userId){
//优惠券不存在
if(couponDO == null){
throw new BizException(BizCodeEnum.COUPON_NO_EXITS);
}
//库存不足
if(couponDO.getStock()<=0){
throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
}
//是否在领取时间范围
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",userId));
if(recordNum>=couponDO.getUserLimit()){
throw new BizException(BizCodeEnum.COUPON_OUT_OF_LIMIT);
}
}
- 找出上面的业务可能存的问题,大家自行思考