我是靠谱客的博主 现实毛衣,最近开发中收集的这篇文章主要介绍编码技巧——权益发放(策略模式+抽象模板),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

日常开发中,遇到这样一个场景:做一个用户会员权益发放模块,权益的类型可能有:平台积分、礼包、优惠券、支付卷、红包、兑换码、爱奇艺会员兑换码等;这些权益来自不同的部门内外的系统,甚至是跨公司合作。当然可以为每种不同的权益单独设计对应的模块提供服务,但是更合适的做法是:抽象出权益模型,使用权益类型区分,这样权益可以统一存储,打包权益的发放可以由DN事务天然的支持,并且可通过用户表示UID对用户权益分表;

此外,发放权益和查询用户权益这2个功能是每个权益类型服务都应该具备的,因此可以抽象成权益服务模板,让各个权益类型实现这个模板;当需要对接新权益或移除旧权益时,只需要调整实现类和权益类型枚举,而不需要修改主流程的代码逻辑。下面给一个代码示例:

1. 权益发放抽象模板

/**
* 权益模板:[模板]在于封装共同的处理流程如参数拼装、DB更新/回滚、异常捕获等、先查本地再调用RPCcheck等...其中的细节由子类实现
*/
@Slf4j
public abstract class BenefitBaseTemplate {
/**
* 同步发放权益
*/
public ReceiveBenefitResp syncSendBenefit(ReceiveBenefitReqDTO receiveBenefitReqDTO){
// 构造请求参数
SendBenefitReq sendBenefitReq = buildSendBenefitReq(receiveBenefitReqDTO);
// 请求数据先入库,失败直接返回
reqDataIntoDB(sendBenefitReq);
SendBenefitResp sendResult;
try {
// 入库成功再调用RPC发放权益
sendResult = this.sendBenefit(sendBenefitReq);
// 更新用户权益信息
updateUserBenefitAfterSendSuc(sendResult);
} catch (Exception e) {
// 这里捕获所有Exception,领取链路异常,直接回滚,返回领取失败
// fixme 注意异常情况
//
(1)case1: 权益发放成功、接口返回失败,可以尝试按流水号查询;未做强一致性回收权益,因为用户再次领取时会提示领取成功(权益发放是幂等的)
//
(2)case2: 此时权益可能发放成功、本地更新DB失败,这里也未做权益回收也是认为没有强一致性需求,因为用户再次领取时会提示领取成功(权益发放是幂等的)
log.error("[benefit receive]syncSendBenefitByType_error_rollback! [receiveBenefitReqDTO={}]", JSON.toJSONString(receiveBenefitReqDTO));
deleteReqDataInDB(sendBenefitReq);
throw e;
}
return new ReceiveBenefitResp(sendResult);
}
/**
* 异步领取权益
*/
@Override
public void asyncSendBenefit(ReceiveBenefitReqDTO receiveBenefitReqDTO) {
// 构造请求参数
SendBenefitReq sendBenefitReq = buildSendBenefitReq(receiveBenefitReqDTO);
// 请求数据先入库,失败直接返回
reqDataIntoDB(sendBenefitReq);
// 入库成功再发放权益,权益发放走异步方式,带重试补偿机制
this.asynSendBenefit(sendBenefitReq);
}
/**
* 查询用户权益
*/
@Override
public List<UserBenefitDTO> queryUserBenefits(ReceiveBenefitReqDTO receiveBenefitReqDTO) {
// 1.先查本地
...
// 2.未查到则再RPC
List<UserBenefitDTO>
userBenefits = this.queryUserBenefits(openid, benefitType, instanceIds);
// 3.异步刷新到本地
...
return userBenefits;
}
/**
* 异步发放权益
*/
private void asynSendBenefit(SendBenefitReq sendBenefitReq) {
try {
Integer benefitType = sendBenefitReq.getBenefitType();
ExecutorService executorService = ExecutorManager.getExecutorService(benefitType);
executorService.submit(
() -> {
// 默认重试3次,失败后走定时任务补偿
RetryTemplate.getInstance().execute(
// 重试方法的实现
context -> {
// 发放权益后更新DB
this.sendBenefit(sendBenefitReq);
updateUserBenefitAfterSendSuc(sendResult);
return null;
},
// 重试方法在重试策略结束后依旧没成功(抛出异常) 则执行下面方法
context -> {
Throwable lastThrowable = context.getLastThrowable();
ResultCodeEnum facadeResultEnum = ResultCodeEnum.SERVER_ERROR;
if (lastThrowable != null) {
if (lastThrowable instanceof BusinessException) {
BusinessException businessException = (BusinessException) lastThrowable;
ResultCodeEnum resultCodeEnum = businessException.getCode();
log.warn("The_reason_for_reissuing_the_voucher_is_BuException,code={},message={}", resultCodeEnum.getCode(),
resultCodeEnum.getDesc());
} else {
log.error("The_reason_for_reissuing_the_voucher_is_other_Exception", lastThrowable);
}
}
// 发失败超过3次后创建补偿定时任务
log.warn("async_sendBenefit_fail_after_3_times_try! intoRetryTask.[sendBenefitReq={}]", sendBenefitReq);
this.intoRetryTask(Collections.singletonList(sendBenefitReq));
throw new BusinessException(facadeResultEnum);
});
}
);
} catch (Exception e) {
log.error("[benefit receive]retry_task_error! e:{}", e);
}
}
/* ... */
/* abstract method below */
/**
* 发放权益
*
* @param sendBenefitReq
* @return
*/
public abstract SendBenefitResp sendBenefit(SendBenefitReq sendBenefitReq);
/**
* 查询用户权益
*
* @param openid
* @param benefitType
* @param instanceIds
* @return
*/
public abstract List<UserBenefitDTO> queryUserBenefits(@NotNull String openid, @NotNull Integer benefitType, List<String> instanceIds);
}

2. 权益发放实现类

/**
* 礼券服务
*/
@Slf4j
@Service("ticketService")
public class TicketServiceImpl extends BenefitBaseTemplate implements TicketService {
/*...*/
@Override
public List<BenefitDTO> queryBenefitDTOs(List<String> benefitIds, Integer benefitType) {
/*...*/ }
@Override
public List<UserBenefitDTO> queryUserBenefits(@NotNull String openid, @NotNull Integer benefitType, List<String> instanceIds) {
/*...*/
List<MemberBenefitDO> memberBenefitDOs = memberBenefitDAO.selectBenefitByTypeAndInstanceIds(openid, benefitType, instanceIds);
/*...*/
}
@HystrixCommand(groupKey = "TicketServiceGroupKey", commandKey = "sendTicketCommandKey", fallbackMethod = "sendTicketFallback", ignoreExceptions = BusinessException.class)
@Override
public SendBenefitResp sendBenefit(SendBenefitReq sendTicketReq) {
/*...*/
ResponseInfo<List<SaveTicketVO>> facadeResult;
try {
facadeResult = platformUserTicketFacade.sendUserTickets(param);
} catch (Throwable e) {
log.error("[SERIOUS_DUBBO]UserTicketFacade.sendUserTicket_error! [openid={} ticketId={} sendNo={}] e:{}",
sendTicketReq.getOpenid(), sendTicketReq.getBenefitId(), sendTicketReq.getSendNo(), e);
throw new RuntimeException();
}
/*...*/
}
}

3. 权益发放Service工厂

/**
* 权益工厂
*/
@Service
public class BenefitServiceFactory {
@Resource
private BenefitBaseTemplate ticketService;
@Resource
private BenefitBaseTemplate gameWelfareService;
@Resource
private BenefitBaseTemplate couponService;
@Resource
private BenefitBaseTemplate pointService;
@Resource
private BenefitBaseTemplate exchangeCodeService;
@Resource
private BenefitBaseTemplate payCouponService;
private static Map<Integer, BenefitBaseTemplate> typeServiceMap = Maps.newConcurrentMap();
@PostConstruct
public void init() {
typeServiceMap.put(BenefitTypeEnum.NONTHRESHHOLD_TICKET.getBenefitType(), ticketService);
typeServiceMap.put(BenefitTypeEnum.DISCOUNT_TICKET.getBenefitType(), ticketService);
typeServiceMap.put(BenefitTypeEnum.GAME_BENEFIT_PKG.getBenefitType(), gameWelfareService);
typeServiceMap.put(BenefitTypeEnum.GAME_BENEFIT_TICKET.getBenefitType(), ticketService);
typeServiceMap.put(BenefitTypeEnum.MEMBER_POINT.getBenefitType(), pointService);
typeServiceMap.put(BenefitTypeEnum.MALL_TICKET.getBenefitType(), couponService);
typeServiceMap.put(BenefitTypeEnum.THEME_TICKET.getBenefitType(), payCouponService);
typeServiceMap.put(BenefitTypeEnum.EBOOK_EXCHANGECODE.getBenefitType(), exchangeCodeService);
typeServiceMap.put(BenefitTypeEnum.ONDEMAND_TICKET.getBenefitType(), ticketService);
typeServiceMap.put(BenefitTypeEnum.PAY_COUPON.getBenefitType(), payCouponService);
typeServiceMap.put(BenefitTypeEnum.GAME_VOUCHER.getBenefitType(), ticketService);
}
/**
* 根据权益类型获取对应服务
* @param benefitType
* @return
*/
public BenefitBaseTemplate getService(Integer benefitType) {
return typeServiceMap.get(benefitType);
}
}

策略执行,通过权益类型type取出权益发放Service,执行对应的权益发放

	// 取出service
BenefitBaseTemplate benefitService = benefitServiceFactory.getService(receiveBenefitReqDTO.getBenefitType());
// 同步
benefitService.syncSendBenefit(receiveBenefitReqDTO);
// 异步
benefitService.asyncSendBenefit(receiveBenefitReqDTO);

最后

以上就是现实毛衣为你收集整理的编码技巧——权益发放(策略模式+抽象模板)的全部内容,希望文章能够帮你解决编码技巧——权益发放(策略模式+抽象模板)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(54)

评论列表共有 0 条评论

立即
投稿
返回
顶部