Java [工作小總結(jié)] 線上活動獎品配置出錯,導(dǎo)致一系列...

前言

描述:
目的主要是在工作上遇到的一個問題,是關(guān)于定時自動發(fā)放券因為配置獎品的時候配置錯誤導(dǎo)致出現(xiàn)異常,出現(xiàn)這種的狀況的時候,內(nèi)心無疑是慌得一批,好在原來代碼比較健碩,要不然...

簡單描述一下流程

image.png

簡單實現(xiàn)演示

一. 搭建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數(shù)據(jù)庫驅(qū)動-->
        <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. 一個是抽獎獎品表,另一個是用戶領(lǐng)券表

(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.已贈送未領(lǐng)取
    */
    private Integer status;
    /**
    * 贈送類型;1-系統(tǒng)贈送,2-買點贈送
    */
    private Integer presentType;
    /**
    * 獎品類型
    */
    private Integer type;
    /**
    * 當(dāng)次抽獎最新的充值時間
    */
    private Date rechargeTime;
    /**
    * 領(lǐng)取時間
    */
    private Date gainDate;
    /**
    * 贈送數(shù)量
    */
    private Integer value;
    /**
    * 備注
    */
    private String remark;
    /**
    * 過期時間
    */
    private Date expiredDate;
    /**
    * 創(chuàng)建時間
    */
    private Date createDate;
    /**
    * 更新日期
    */
    private Date updateDate;
    /**
    * 刪除標記
    */
    private Integer delFlag;
    /**
    * 領(lǐng)取方式(0:自動到賬,1:手動領(lǐng)取)
    */
    private Integer gainWay;
    
}

(2). 會員領(lǐng)取表

@Data
public class AmCoupon  implements Serializable {
    private static final long serialVersionUID = -52415980477070452L;
    /**
    * 優(yōu)惠券id
    */
    private Integer id;
    /**
    * 優(yōu)惠券名稱
    */
    private String name;
    /**
    * 優(yōu)惠券編碼
    */
    private String code;
    /**
    * 開始時間
    */
    private Date startDate;
    /**
    * 結(jié)束時間
    */
    private Date endDate;
    /**
    * 時長(分)
    */
    private Integer timeLength;
    /**
    * 有效期(天),活動送出的,根據(jù)活動截至?xí)r間設(shè)置有效期
    */
    private Integer dayOut;
    /**
    * 優(yōu)惠券場景id
    */
    private Integer couponSceneId;
    /**
    * 分成給發(fā)布商(0表示不分,1表示分)
    */
    private String dividedIntoPublisher;
    /**
    * 狀態(tài)(0表示無效,1表示有效)
    */
    private String status;
    /**
    * 邏輯刪除標志(0表示正常,1表示刪除)
    */
    private String delFlag;
    /**
    * 創(chuàng)建人
    */
    private String createBy;
    /**
    * 創(chuàng)建時間
    */
    private Date createDate;
    /**
    * 更新人
    */
    private String updateBy;
    /**
    * 更新時間
    */
    private Date updateDate;
    /**
    * 優(yōu)惠券前端顯示名稱
    */
    private String showTitile;
    /**
    * 優(yōu)惠券簡介
    */
    private String introduction;
    /**
    * 券類型(1-聊天券,2-優(yōu)惠券,3-代金券)
    */
    private Boolean couponType;
    /**
    * 失效類型(1-領(lǐng)取生效,2-固定日期,3-永久,4-當(dāng)天有效)
    */
    private Boolean expireType;
    /**
    * 備注
    */
    private String remark;
    
}

三.Mapper

(1). 抽獎獎品Mapper

public interface ActivityResultDao {

    
    /**
     * @Description: 通過實體作為篩選條件查詢
     * @param activityResult 實例對象
     * @return 對象列表
     */
    List<ActivityResult> findAmEventResultList (@Param("activityResult") ActivityResult activityResult);

   /**
     * @Description: 修改數(shù)據(jù)
     * @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>
    <!--通過主鍵修改數(shù)據(jù)-->
    <update id="update">
        update activity_award
        <set>

                status = #{status}

        </set>
        where id = #{id}
    </update>

</mapper>

(2). 會員領(lǐng)取券Mapper

public interface AmCouponDao {

    /**
     * @Description: 新增數(shù)據(jù)
     * @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: 查詢多條數(shù)據(jù)
     * @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: 查詢多條數(shù)據(jù)
     * @param activityResult 實例對象
     * @return 對象列表
     */
    @Override
    public List<ActivityResult> findAmEventResultList (ActivityResult activityResult) {

        activityResult.setStatus(ResultEnum.GIVE_Y.label());
        return this.activityResultDao.findAmEventResultList(activityResult);
    }

    /**
     * @Description: 修改數(shù)據(jù)
     * @param activityResult 實例對象
     */
    @Override
    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public void update(ActivityResult activityResult) {
        this.activityResultDao.update(activityResult);
    }
    
}

(2). 會員領(lǐng)取券表


public interface AmCouponService {


    /**
     * @Description: 新增數(shù)據(jù)
     * @param amCoupon 實例對象
     */
    void save(AmCoupon amCoupon);


}


@Service("amCouponService")
@Transactional(readOnly = true, propagation = Propagation.REQUIRED)
public class AmCouponServiceImpl implements AmCouponService {
    @Autowired
    private AmCouponDao amCouponDao;
    
    /**
     * @Description: 新增數(shù)據(jù)
     * @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 {
    /**
     * 已贈送未領(lǐng)取
     */
    GIVE_UNCOLLECTED(3),

    /**
     * 已贈送
     */
    GIVE_Y(1),

    /**
     * 未贈送
     */
    GIVE_N(0),

    /**
     * 贈送失敗
     */
    GIVE_FAIL(2),

    /**
     * 已過期
     */
    GIVE_EXPIRE(4),

    /**
     * 已領(lǐng)取
     */
    GIVE_GET(5);

    private Integer label;

    private ResultEnum(Integer label) {
        this.label = label;
    }

    public Integer label() {
        return label;
    }
}

六、定時器

@Component
@Configuration      //1.主要用于標記配置類,兼?zhèn)銫omponent的效果。
@EnableScheduling   // 2.開啟定時任務(wù)
@Slf4j
public class QuartzConfig {


    @Autowired
    private MethodOneService methodOneService;

 /**
     * 定時每秒處理發(fā)放抽獎獎品
     */
    @Scheduled(cron = "0 0/1 * * * ?")
    public void RouletteRafflePrize(){
        log.info("=====RouletteRafflePrizeScheduleJobImpl=====>send award start...");
        methodOneService.luckDrawGivePrize();
        log.info("=====RouletteRafflePrizeScheduleJobImpl=====>send award start...");
    }

}

七. 模擬生產(chǎn)環(huán)境處理自動領(lǐng)取卷的方法

(1). 自動領(lǐng)取方法入口第一個方法

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() {

        // 遍歷查詢會員獲獎所有結(jié)果,準備自動發(fā)放
        ActivityResult activityResult = new ActivityResult();
        List<ActivityResult> resultList =
                activityResultService.findAmEventResultList(activityResult);
        if (CollectionUtils.isNotEmpty(resultList)){
            for (ActivityResult result : resultList) {
                // 調(diào)用方法2
                try {
                    methodTwoService.dropgivePrize(result);
                }catch (Exception e){
                    // 異常就更新領(lǐng)取狀態(tài)失敗,即便第三個方法沒有被捕獲,被第一個方法捕獲到,或者第三個方法被捕獲異常導(dǎo)致第一個方法也知道,也會事務(wù)回滾
                    log.error(
                            "===luckDrawGivePrize===> memberId:{}, activityId:{}, rewardType:{}, error:{}",result.getMemberId(),result.getOfferId(),result.getAwardId(),e);
                    // 更新領(lǐng)取狀態(tài)失敗
                  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: 也是聲明默認的事務(wù)傳播,加入外圍事務(wù)
 */
@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 發(fā)放自動領(lǐng)取獎勵的券

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 {
            // 處理業(yè)務(wù)保存
            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");
            // 模擬 異常, 讓事務(wù)回滾,接著,定時器就會不停的循環(huán)執(zhí)行,導(dǎo)致所有的券都發(fā)放不了,一直回滾,以及領(lǐng)取狀態(tài)是失敗
            // 這里就沒有模擬一條之前配錯的數(shù)據(jù),就是這條數(shù)據(jù)校驗對后就不報異常了,就正常發(fā)放,
            // 這里就模擬異常部分,
            // 解決方案,就是在這個send方法上面加一個new 事務(wù)(不過這種并沒有采納,),即便券配置錯了,也不會影響其它券的正常發(fā)放,就只會把這條有異常的設(shè)置成發(fā)送失敗,下次手動改回來正確的就可以自動發(fā)放了
            // 沒有采納的原因,就是因為怕影響之前的數(shù)據(jù),考慮還要測試的時間,
            // 最后解決方法:就是配置獎品的時候,保證數(shù)據(jù)沒有出錯就行了
            amCouponService.save(amCoupon);
            // 更新狀態(tài)
            activityResult.setStatus(ResultEnum.GIVE_Y.label());
        }catch (Exception e){
            activityResult.setStatus(ResultEnum.GIVE_FAIL.label());
            log.error(" send  = {}   ,  awardId = {} ",activityResult.getMemberId(),activityResult.getAwardId());
        }
        // 更改贈送狀態(tài)
        activityResultService.update(activityResult);
    }
}

這里模擬中斷的效果

@Service("amCouponService")
public class AmCouponServiceImpl implements AmCouponService {
    @Autowired
    private AmCouponDao amCouponDao;

    /**
     * @Description: 新增數(shù)據(jù)
     * @param amCoupon 實例對象
     */
    @Override
    public void save(AmCoupon amCoupon) {
        this.amCouponDao.save(amCoupon);
       // 模擬異常  回滾,直接被捕獲,加到外圍的事務(wù)直接回滾,會員的券記錄沒有發(fā)送
        int i = 1 / 0 ;
    }
}

結(jié)語:

此次就是描述在工作中遇到一個抽獎問題沒有發(fā)放,就是獎品配置的時候配置錯了,定時任務(wù)在掃的時候報錯了,因為一條獎品配置錯了,導(dǎo)致事務(wù)回滾,一直循環(huán)遍歷更新領(lǐng)取的狀態(tài)為失敗。最后用戶抽到獎品遲遲沒有發(fā)出來,讓用戶反饋這個問題才知到,為此總結(jié)記錄下。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容