前言
描述:
目的主要是在工作上遇到的一个问题,是关于定时自动发放券因为配置奖品的时候配置错误导致出现异常,出现这种的状况的时候,内心无疑是慌得一批,好在原来代码比较健硕,要不然...
简单描述一下流程
image.png
简单实现演示
一. 搭建Springboot 项目
(1). 引入pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- rabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.4</version>
</dependency>
<!-- 引入Lombock依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!--添加fastjson依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.7</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-commons</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
<version>0.0.20131108.vaadin1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
</dependencies>
(2).yml 配置
server:
port: 8083
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&useSSL=false
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.mi.entity
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
logging:
level:
com: debug
(3). Application
@SpringBootApplication
@MapperScan("com.mi.dao")//使用MapperScan批量扫描所有的Mapper接口;
public class SpringbootDemo1Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemo1Application.class, args);
}
}
二. 实体类
- 这里模拟一个两张实体类,两张表
- 一个是抽奖奖品表,另一个是用户领券表
(1). 抽奖奖品
@Data
public class ActivityResult implements Serializable {
private static final long serialVersionUID = 361674379353487721L;
private int id;
/**
* 会员id
*/
private String memberId;
private String offerId;
/**
* 奖励id
*/
private String awardId;
/**
* 0:未赠送 1.已赠送 2.赠送失败 3.已赠送未领取
*/
private Integer status;
/**
* 赠送类型;1-系统赠送,2-买点赠送
*/
private Integer presentType;
/**
* 奖品类型
*/
private Integer type;
/**
* 当次抽奖最新的充值时间
*/
private Date rechargeTime;
/**
* 领取时间
*/
private Date gainDate;
/**
* 赠送数量
*/
private Integer value;
/**
* 备注
*/
private String remark;
/**
* 过期时间
*/
private Date expiredDate;
/**
* 创建时间
*/
private Date createDate;
/**
* 更新日期
*/
private Date updateDate;
/**
* 删除标记
*/
private Integer delFlag;
/**
* 领取方式(0:自动到账,1:手动领取)
*/
private Integer gainWay;
}
(2). 会员领取表
@Data
public class AmCoupon implements Serializable {
private static final long serialVersionUID = -52415980477070452L;
/**
* 优惠券id
*/
private Integer id;
/**
* 优惠券名称
*/
private String name;
/**
* 优惠券编码
*/
private String code;
/**
* 开始时间
*/
private Date startDate;
/**
* 结束时间
*/
private Date endDate;
/**
* 时长(分)
*/
private Integer timeLength;
/**
* 有效期(天),活动送出的,根据活动截至时间设置有效期
*/
private Integer dayOut;
/**
* 优惠券场景id
*/
private Integer couponSceneId;
/**
* 分成给发布商(0表示不分,1表示分)
*/
private String dividedIntoPublisher;
/**
* 状态(0表示无效,1表示有效)
*/
private String status;
/**
* 逻辑删除标志(0表示正常,1表示删除)
*/
private String delFlag;
/**
* 创建人
*/
private String createBy;
/**
* 创建时间
*/
private Date createDate;
/**
* 更新人
*/
private String updateBy;
/**
* 更新时间
*/
private Date updateDate;
/**
* 优惠券前端显示名称
*/
private String showTitile;
/**
* 优惠券简介
*/
private String introduction;
/**
* 券类型(1-聊天券,2-优惠券,3-代金券)
*/
private Boolean couponType;
/**
* 失效类型(1-领取生效,2-固定日期,3-永久,4-当天有效)
*/
private Boolean expireType;
/**
* 备注
*/
private String remark;
}
三.Mapper
(1). 抽奖奖品Mapper
public interface ActivityResultDao {
/**
* @Description: 通过实体作为筛选条件查询
* @param activityResult 实例对象
* @return 对象列表
*/
List<ActivityResult> findAmEventResultList (@Param("activityResult") ActivityResult activityResult);
/**
* @Description: 修改数据
* @param activityResult 实例对象
*/
void update(ActivityResult activityResult);
}
xml
<?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.mi.dao.ActivityResultDao">
<!--通过实体作为筛选条件查询-->
<select id="findAmEventResultList" resultType="com.mi.entity.ActivityResult">
select
id, member_id, offer_id, award_id, status, present_type, type, recharge_time, value, remark
from activity_award
<where>
<if test="activityResult.status != null">
and status != #{activityResult.status}
</if>
</where>
</select>
<!--通过主键修改数据-->
<update id="update">
update activity_award
<set>
status = #{status}
</set>
where id = #{id}
</update>
</mapper>
(2). 会员领取券Mapper
public interface AmCouponDao {
/**
* @Description: 新增数据
* @param amCoupon 实例对象
*/
void save(AmCoupon amCoupon);
}
xml
<?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.mi.dao.AmCouponDao">
<!--新增所有列-->
<insert id="save" keyProperty="id" useGeneratedKeys="true">
insert into am_coupon(name, code, start_date, end_date, time_length, day_out, coupon_scene_id, divided_into_publisher, status, del_flag, create_by, create_date, update_by, update_date, show_titile, introduction, coupon_type, expire_type, remark, use_scope_type)
values (#{name}, #{code}, #{startDate}, #{endDate}, #{timeLength}, #{dayOut}, #{couponSceneId}, #{dividedIntoPublisher}, #{status}, #{delFlag}, #{createBy}, #{createDate}, #{updateBy}, #{updateDate}, #{showTitile}, #{introduction}, #{couponType}, #{expireType}, #{remark}, #{useScopeType})
</insert>
</mapper>
四、service 、serviceImpl
(1). 抽奖奖品
public interface ActivityResultService {
/**
* @Description: 查询多条数据
* @param activityResult 实例对象
* @return 对象列表
*/
List<ActivityResult> findAmEventResultList (ActivityResult activityResult);
}
@Service("amEventResultService")
@Transactional(readOnly = true, propagation = Propagation.REQUIRED)
public class ActivityResultServiceImpl implements ActivityResultService {
@Autowired
private ActivityResultDao activityResultDao;
/**
* @Description: 查询多条数据
* @param activityResult 实例对象
* @return 对象列表
*/
@Override
public List<ActivityResult> findAmEventResultList (ActivityResult activityResult) {
activityResult.setStatus(ResultEnum.GIVE_Y.label());
return this.activityResultDao.findAmEventResultList(activityResult);
}
/**
* @Description: 修改数据
* @param activityResult 实例对象
*/
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void update(ActivityResult activityResult) {
this.activityResultDao.update(activityResult);
}
}
(2). 会员领取券表
public interface AmCouponService {
/**
* @Description: 新增数据
* @param amCoupon 实例对象
*/
void save(AmCoupon amCoupon);
}
@Service("amCouponService")
@Transactional(readOnly = true, propagation = Propagation.REQUIRED)
public class AmCouponServiceImpl implements AmCouponService {
@Autowired
private AmCouponDao amCouponDao;
/**
* @Description: 新增数据
* @param amCoupon 实例对象
*/
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void save(AmCoupon amCoupon) {
this.amCouponDao.save(amCoupon);
}
}
五、枚举
(1). 枚举
package com.mi.enn;
/**
* @Author Leon
* @Date 2022/3/17 10:47
* @Describe:
*/
public enum ResultEnum {
/**
* 已赠送未领取
*/
GIVE_UNCOLLECTED(3),
/**
* 已赠送
*/
GIVE_Y(1),
/**
* 未赠送
*/
GIVE_N(0),
/**
* 赠送失败
*/
GIVE_FAIL(2),
/**
* 已过期
*/
GIVE_EXPIRE(4),
/**
* 已领取
*/
GIVE_GET(5);
private Integer label;
private ResultEnum(Integer label) {
this.label = label;
}
public Integer label() {
return label;
}
}
六、定时器
@Component
@Configuration //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling // 2.开启定时任务
@Slf4j
public class QuartzConfig {
@Autowired
private MethodOneService methodOneService;
/**
* 定时每秒处理发放抽奖奖品
*/
@Scheduled(cron = "0 0/1 * * * ?")
public void RouletteRafflePrize(){
log.info("=====RouletteRafflePrizeScheduleJobImpl=====>send award start...");
methodOneService.luckDrawGivePrize();
log.info("=====RouletteRafflePrizeScheduleJobImpl=====>send award start...");
}
}
七. 模拟生产环境处理自动领取卷的方法
(1). 自动领取方法入口第一个方法
public interface MethodOneService {
/**
* 方法1
*/
public void luckDrawGivePrize();
}
@Service
@Slf4j
@Transactional(readOnly = true, propagation = Propagation.REQUIRED)
public class MethodOneServiceImpl implements MethodOneService {
@Autowired
private MethodTwoService methodTwoService;
@Autowired
private ActivityResultService activityResultService;
/**
* 方法1
*/
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void luckDrawGivePrize() {
// 遍历查询会员获奖所有结果,准备自动发放
ActivityResult activityResult = new ActivityResult();
List<ActivityResult> resultList =
activityResultService.findAmEventResultList(activityResult);
if (CollectionUtils.isNotEmpty(resultList)){
for (ActivityResult result : resultList) {
// 调用方法2
try {
methodTwoService.dropgivePrize(result);
}catch (Exception e){
// 异常就更新领取状态失败,即便第三个方法没有被捕获,被第一个方法捕获到,或者第三个方法被捕获异常导致第一个方法也知道,也会事务回滚
log.error(
"===luckDrawGivePrize===> memberId:{}, activityId:{}, rewardType:{}, error:{}",result.getMemberId(),result.getOfferId(),result.getAwardId(),e);
// 更新领取状态失败
result.setStatus(ResultEnum.GIVE_FAIL.label());
activityResultService.update(result);
continue;
}
}
}
}
}
(2). 方法2,
public interface MethodTwoService {
/**
* 方法2
*/
public void dropgivePrize(ActivityResult resultList);
}
/**
* @Author Leon
* @Date 2022/3/16 19:10
* @Describe: 也是声明默认的事务传播,加入外围事务
*/
@Service
@Slf4j
@Transactional(readOnly = true, propagation = Propagation.REQUIRED)
public class MethodTwoServiceImpl implements MethodTwoService {
@Autowired
private MethodThreeService methodThreeService;
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void dropgivePrize(ActivityResult activityResult) {
methodThreeService.send(activityResult);
}
}
(3). 方法3 发放自动领取奖励的券
public interface MethodThreeService {
/**
* 方法3
*/
public void send(ActivityResult activityResult);
}
@Service
@Slf4j
@Transactional(readOnly = true, propagation = Propagation.REQUIRED)
public class MethodThreeServiceImpl implements MethodThreeService {
@Autowired
private ActivityResultService activityResultService;
@Autowired
private AmCouponService amCouponService;
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void send(ActivityResult activityResult) {
try {
// 处理业务保存
AmCoupon amCoupon = new AmCoupon();
amCoupon.setCouponSceneId(1);
amCoupon.setExpireType(true);
amCoupon.setShowTitile("xx");
amCoupon.setDayOut(7);
amCoupon.setName("xxxxx");
amCoupon.setStartDate(new Date());
amCoupon.setEndDate(new Date());
amCoupon.setCouponType(true);
amCoupon.setCode("YYYY");
// 模拟 异常, 让事务回滚,接着,定时器就会不停的循环执行,导致所有的券都发放不了,一直回滚,以及领取状态是失败
// 这里就没有模拟一条之前配错的数据,就是这条数据校验对后就不报异常了,就正常发放,
// 这里就模拟异常部分,
// 解决方案,就是在这个send方法上面加一个new 事务(不过这种并没有采纳,),即便券配置错了,也不会影响其它券的正常发放,就只会把这条有异常的设置成发送失败,下次手动改回来正确的就可以自动发放了
// 没有采纳的原因,就是因为怕影响之前的数据,考虑还要测试的时间,
// 最后解决方法:就是配置奖品的时候,保证数据没有出错就行了
amCouponService.save(amCoupon);
// 更新状态
activityResult.setStatus(ResultEnum.GIVE_Y.label());
}catch (Exception e){
activityResult.setStatus(ResultEnum.GIVE_FAIL.label());
log.error(" send = {} , awardId = {} ",activityResult.getMemberId(),activityResult.getAwardId());
}
// 更改赠送状态
activityResultService.update(activityResult);
}
}
这里模拟中断的效果
@Service("amCouponService")
public class AmCouponServiceImpl implements AmCouponService {
@Autowired
private AmCouponDao amCouponDao;
/**
* @Description: 新增数据
* @param amCoupon 实例对象
*/
@Override
public void save(AmCoupon amCoupon) {
this.amCouponDao.save(amCoupon);
// 模拟异常 回滚,直接被捕获,加到外围的事务直接回滚,会员的券记录没有发送
int i = 1 / 0 ;
}
}
结语:
此次就是描述在工作中遇到一个抽奖问题没有发放,就是奖品配置的时候配置错了,定时任务在扫的时候报错了,因为一条奖品配置错了,导致事务回滚,一直循环遍历更新领取的状态为失败。最后用户抽到奖品迟迟没有发出来,让用户反馈这个问题才知到,为此总结记录下。