Java [工作小总结] 线上活动奖品配置出错,导致一系列...

前言

描述:
目的主要是在工作上遇到的一个问题,是关于定时自动发放券因为配置奖品的时候配置错误导致出现异常,出现这种的状况的时候,内心无疑是慌得一批,好在原来代码比较健硕,要不然...

简单描述一下流程

image.png

简单实现演示

一. 搭建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. 这里模拟一个两张实体类,两张表
  2. 一个是抽奖奖品表,另一个是用户领券表

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

结语:

此次就是描述在工作中遇到一个抽奖问题没有发放,就是奖品配置的时候配置错了,定时任务在扫的时候报错了,因为一条奖品配置错了,导致事务回滚,一直循环遍历更新领取的状态为失败。最后用户抽到奖品迟迟没有发出来,让用户反馈这个问题才知到,为此总结记录下。

你可能感兴趣的:(Java [工作小总结] 线上活动奖品配置出错,导致一系列...)