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

前言

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

简单描述一下流程

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. 这里模拟一个两张实体类,两张表
  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<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 ;
    }
}

结语:

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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容