前言
描述:
目的主要是在工作上遇到的一個問題,是關(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);
}
}
二. 實體類
- 這里模擬一個兩張實體類,兩張表
- 一個是抽獎獎品表,另一個是用戶領(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é)記錄下。