前言
描述:
目的主要是在工作上遇到的一个问题,是关于定时自动发放券因为配置奖品的时候配置错误导致出现异常,出现这种的状况的时候,内心无疑是慌得一批,好在原来代码比较健硕,要不然...
简单描述一下流程
简单实现演示
一. 搭建Springboot 项目
(1). 引入pom
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-amqp
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.0
io.springfox
springfox-swagger2
2.9.2
io.springfox
springfox-swagger-ui
2.9.2
org.apache.commons
commons-lang3
3.9
commons-collections
commons-collections
3.2.1
com.alibaba
easyexcel
3.0.5
cn.hutool
hutool-all
5.4.4
org.projectlombok
lombok
true
junit
junit
test
mysql
mysql-connector-java
8.0.16
com.alibaba
fastjson
1.2.7
org.junit.platform
junit-platform-commons
com.vaadin.external.google
android-json
0.0.20131108.vaadin1
compile
com.google.guava
guava
28.0-jre
(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 findAmEventResultList (@Param("activityResult") ActivityResult activityResult);
/**
* @Description: 修改数据
* @param activityResult 实例对象
*/
void update(ActivityResult activityResult);
}
xml
update activity_award
status = #{status}
where id = #{id}
(2). 会员领取券Mapper
public interface AmCouponDao {
/**
* @Description: 新增数据
* @param amCoupon 实例对象
*/
void save(AmCoupon amCoupon);
}
xml
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})
四、service 、serviceImpl
(1). 抽奖奖品
public interface ActivityResultService {
/**
* @Description: 查询多条数据
* @param activityResult 实例对象
* @return 对象列表
*/
List 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 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 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 ;
}
}
结语:
此次就是描述在工作中遇到一个抽奖问题没有发放,就是奖品配置的时候配置错了,定时任务在扫的时候报错了,因为一条奖品配置错了,导致事务回滚,一直循环遍历更新领取的状态为失败。最后用户抽到奖品迟迟没有发出来,让用户反馈这个问题才知到,为此总结记录下。