一、前言
前面我们实现了用户返利,这一节主要是为了对接前端,但是就目前的接口而言,是不够的,上一节实现用户返利也只是实现了签到接口。事实上前端还需要以下信息:用户的额度、用户是否已经签到、用户的权重。
二、应用接口实现
1.获取用户是否已经签到
这是前端需要知道的信息,这样才可以对签到按钮展示不同动画,比如如果已经签到就应该灰度显示。
这个接口实现很简单,前面我们利用日期作为返利订单的唯一标识,现在我们也可以利用日期去查询返利订单,如果查到了,就说明已经签过到了,反之就是没签到,返回一个布尔值即可,
java
@RequestMapping(value = "is_calendar_sign_rebate", method = RequestMethod.POST)
@Override
public Response<Boolean> isCalendarSignRebate(String userId) {
try {
log.info("查询用户是否完成日历签到返利开始 userId:{}", userId);
String outBusinessNo = dateFormat.format(new Date());
List<BehaviorRebateOrderEntity> behaviorRebateOrderEntities = behaviorRebateService.queryOrderByOutBusinessNo(userId, outBusinessNo);
log.info("查询用户是否完成日历签到返利完成 userId:{} orders.size:{}", userId, behaviorRebateOrderEntities.size());
return Response.<Boolean>builder()
.code(ResponseCode.SUCCESS.getCode())
.data(!behaviorRebateOrderEntities.isEmpty())
.info(ResponseCode.SUCCESS.getInfo())
.build();
} catch (Exception e) {
log.error("查询用户是否完成日历签到返利失败 userId:{}", userId, e);
return Response.<Boolean>builder()
.code(ResponseCode.UN_ERROR.getCode())
.data(false)
.info(ResponseCode.UN_ERROR.getInfo())
.build();
}
}
从仓储一路透传上来,仓储中仍然采用自己查自己的方式,只填充想查询的字段即可:
java
@Override
public List<BehaviorRebateOrderEntity> queryOrderByOutBusinessNo(String userId, String outBusinessNo) {
//1.请求对象
UserBehaviorRebateOrder userBehaviorRebateOrderReq = new UserBehaviorRebateOrder();
userBehaviorRebateOrderReq.setUserId(userId);
userBehaviorRebateOrderReq.setOutBusinessNo(outBusinessNo);
List<UserBehaviorRebateOrder> userBehaviorRebateOrders = userBehaviorRebateOrderDao.queryOrderByOutBusinessNo(userBehaviorRebateOrderReq);
List<BehaviorRebateOrderEntity> behaviorRebateOrderEntities = new ArrayList<>(userBehaviorRebateOrders.size());
for (UserBehaviorRebateOrder userBehaviorRebateOrder : userBehaviorRebateOrders) {
BehaviorRebateOrderEntity behaviorRebateOrderEntity = BehaviorRebateOrderEntity.builder()
.userId(userBehaviorRebateOrder.getUserId())
.orderId(userBehaviorRebateOrder.getOrderId())
.behaviorType(userBehaviorRebateOrder.getBehaviorType())
.rebateDesc(userBehaviorRebateOrder.getRebateDesc())
.rebateType(userBehaviorRebateOrder.getRebateType())
.rebateConfig(userBehaviorRebateOrder.getRebateConfig())
.outBusinessNo(userBehaviorRebateOrder.getOutBusinessNo())
.bizId(userBehaviorRebateOrder.getBizId())
.build();
}
return behaviorRebateOrderEntities;
}
2.用户额度查询
这个接口也不难,就是要把所有用户的总/日/月的次数和剩余次数传回前端:
java
@RequestMapping(value = "query_user_activity_account", method = RequestMethod.POST)
@Override
public Response<UserActivityAccountResponseDTO> queryUserActivityAccount(UserActivityAccountRequestDTO request) {
try {
log.info("查询用户活动账户开始 userId:{} activityId:{}", request.getUserId(), request.getActivityId());
if (StrUtil.isBlank(request.getUserId()) || null == request.getActivityId()) {
throw new AppException(ResponseCode.ILLEGAL_PARAMETER.getCode(), ResponseCode.ILLEGAL_PARAMETER.getInfo());
}
ActivityAccountEntity activityAccountEntity = raffleActivityAccountQuotaService.queryActivityAccountEntity(request.getActivityId(), request.getUserId());
UserActivityAccountResponseDTO userActivityAccountResponseDTO = UserActivityAccountResponseDTO.builder()
.totalCount(activityAccountEntity.getTotalCount())
.totalCountSurplus(activityAccountEntity.getTotalCountSurplus())
.dayCount(activityAccountEntity.getDayCount())
.dayCountSurplus(activityAccountEntity.getDayCountSurplus())
.monthCount(activityAccountEntity.getMonthCount())
.monthCountSurplus(activityAccountEntity.getMonthCountSurplus())
.build();
log.info("查询用户活动账户完成 userId:{} activityId:{} dto:{}", request.getUserId(), request.getActivityId(), JSON.toJSONString(userActivityAccountResponseDTO));
return Response.<UserActivityAccountResponseDTO>builder()
.code(ResponseCode.SUCCESS.getCode())
.data(userActivityAccountResponseDTO)
.info(ResponseCode.SUCCESS.getInfo())
.build();
} catch (Exception e) {
log.error("查询用户活动账户失败 userId:{} activityId:{}", request.getUserId(), request.getActivityId(), e);
return Response.<UserActivityAccountResponseDTO>builder()
.code(ResponseCode.UN_ERROR.getCode())
.info(ResponseCode.UN_ERROR.getInfo())
.build();
}
}
3.抽奖权重查询
回顾一下权重的概念,权重是按照用户的积分来指定奖池,而我们的系统中暂时没有增加积分系统,所以暂时利用已使用的额度来表示积分,后续会用额度映射积分。
java
//3.找出最小符合的值
Integer userScore = repository.queryActivityAccountTotalUserCount(userId, strategyId);
java
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RuleWeightVO {
private String ruleValue;
private Integer weight;
private List<Integer> awardIds;
private List<Award> awardList;
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class Award {
private Integer awardId;
private String awardTitle;
}
}
java
@RequestMapping(value = "query_raffle_strategy_rule_weight", method = RequestMethod.POST)
@Override
public Response<List<RaffleStrategyRuleWeightResponseDTO>> queryRaffleStrategyRuleWeight(RaffleStrategyRuleWeightRequestDTO requestDTO) {
try {
log.info("查询抽奖策略权重规则配置开始 userId:{} activity:{}", requestDTO.getUserId(), requestDTO.getActivityId());
//1.参数校验
if (StringUtils.isBlank(requestDTO.getUserId()) || null == requestDTO.getActivityId()) {
throw new AppException(ResponseCode.ILLEGAL_PARAMETER.getCode(), ResponseCode.ILLEGAL_PARAMETER.getInfo());
}
//2.查询用户抽奖总次数
Integer userActivityAccountTotalUseCount = raffleActivityAccountQuotaService.queryRaffleActivityAccountPartakeCount(requestDTO.getActivityId(), requestDTO.getUserId());
// 3. 查询规则
List<RaffleStrategyRuleWeightResponseDTO> raffleStrategyRuleWeightList = new ArrayList<>();
List<RuleWeightVO> ruleWeightVOList = raffleRule.queryAwardRuleWeightByActivityId(requestDTO.getActivityId());
for (RuleWeightVO ruleWeightVO : ruleWeightVOList) {
//转换对象
List<RaffleStrategyRuleWeightResponseDTO.StrategyAward> strategyAwards = new ArrayList<>();
List<RuleWeightVO.Award> awardList = ruleWeightVO.getAwardList();
for (RuleWeightVO.Award award : awardList) {
RaffleStrategyRuleWeightResponseDTO.StrategyAward strategyAward = new RaffleStrategyRuleWeightResponseDTO.StrategyAward();
strategyAward.setAwardId(award.getAwardId());
strategyAward.setAwardTitle(award.getAwardTitle());
strategyAwards.add(strategyAward);
}
//封装对象
RaffleStrategyRuleWeightResponseDTO raffleStrategyRuleWeightResponseDTO = new RaffleStrategyRuleWeightResponseDTO();
raffleStrategyRuleWeightResponseDTO.setRuleWeightCount(ruleWeightVO.getWeight());
raffleStrategyRuleWeightResponseDTO.setStrategyAwards(strategyAwards);
raffleStrategyRuleWeightResponseDTO.setUserActivityAccountTotalUseCount(userActivityAccountTotalUseCount);
raffleStrategyRuleWeightList.add(raffleStrategyRuleWeightResponseDTO);
}
Response<List<RaffleStrategyRuleWeightResponseDTO>> response = Response.<List<RaffleStrategyRuleWeightResponseDTO>>builder()
.code(ResponseCode.SUCCESS.getCode())
.info(ResponseCode.SUCCESS.getInfo())
.data(raffleStrategyRuleWeightList)
.build();
log.info("查询抽奖策略权重规则配置完成 userId:{} activity:{}", requestDTO.getUserId(), requestDTO.getActivityId());
return response;
} catch (Exception e) {
log.info("查询抽奖策略权重规则配置失败 userId:{} activity:{}", requestDTO.getUserId(), requestDTO.getActivityId(), e);
return Response.<List<RaffleStrategyRuleWeightResponseDTO>>builder()
.code(ResponseCode.UN_ERROR.getCode())
.info(ResponseCode.UN_ERROR.getInfo())
.build();
}
}
三、前后端联调
注意调一下前端的方法名,有个名字写错了。具体修改点可以直接问AI。

四、bug修复
1.日期static
实体类的充血方法和格式化属性都要加上static,因为所有对象应该共享同一个当前时间。
Month同理。
java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RaffleActivityAccountDay {
private final static SimpleDateFormat dateFormatDay = new SimpleDateFormat("yyyy-MM-dd");
/** 自增ID */
private String id;
/** 用户ID */
private String userId;
/** 活动ID */
private Long activityId;
/** 日期(yyyy-mm-dd) */
private String day;
/** 日次数 */
private Integer dayCount;
/** 日次数-剩余 */
private Integer dayCountSurplus;
/** 创建时间 */
private Date createTime;
/** 更新时间 */
private Date updateTime;
public static String currentDay() {
return dateFormatDay.format(new Date());
}
}
这里也要改,否则返利的额度无法入账。

2.MQ死循环
对于唯一索引冲突不再报错,直接return跳过
java
@Slf4j
@Component
public class RebateMessageCustomer {
@Value("${spring.rabbitmq.topic.send_rebate}")
private String topic;
@Resource
private IRaffleActivityAccountQuotaService raffleActivityAccountQuotaService;
@RabbitListener(queuesToDeclare = @Queue(value = "${spring.rabbitmq.topic.send_rebate}"))
public void listener(String message) {
try {
log.info("监听用户行为返利消息 topic:{} message:{}", topic, message);
//1.转换消息
BaseEvent.EventMessage<SendRebateMessageEvent.SendRebateMessage> eventMessage = JSON.parseObject(message, new TypeReference<BaseEvent.EventMessage<SendRebateMessageEvent.SendRebateMessage>>() {
}.getType());
SendRebateMessageEvent.SendRebateMessage rebateMessage = eventMessage.getData();
if (!RebateTypeVO.SKU.getCode().equals(rebateMessage.getRebateType())) {
log.info("监听用户行为返利消息 - 非SKU奖励暂时不处理 topic:{} message:{}", topic, message);
return;
}
//2.入账奖励
SkuRechargeEntity skuRechargeEntity = new SkuRechargeEntity();
skuRechargeEntity.setUserId(rebateMessage.getUserId());
skuRechargeEntity.setSku(Long.valueOf(rebateMessage.getRebateConfig()));
skuRechargeEntity.setOutBusinessNo(rebateMessage.getBizId());
raffleActivityAccountQuotaService.createOrder(skuRechargeEntity);
}catch (AppException e) {
if (ResponseCode.INDEX_DUP.getCode().equals(e.getCode())) {
// 幂等命中:该 bizId 已入账,静默确认,不重投
log.warn("返利入账幂等命中,跳过");
return;
}
log.error("监听用户行为返利消息,消费失败 topic:{} message:{}", topic, message, e);
throw e; // 真失败才重投
} catch (Exception e) {
log.error("监听用户行为返利消息,消费失败 topic:{} message:{}", topic, message, e);
throw e;
}
}
}
3.无法判断用户当日是否签到
前面的代码少加了一行:

4.权重查不到用户额度
我发现只能查到user=xiaofuge2的账户权重总额度。
检查后发现是因为分库分表导致的,其他不在这个表中的用户是查不到的,根因在于Dao的分库分表方法的参数用了基本类型Long activityId和String userId。
修改后如下:
java
@DBRouter
Integer queryRaffleActivityAccountPartakeCount(RaffleActivityAccount raffleActivityAccountReq);
注意:@DBRouter的参数必须是对象,不能是Long activityId和String userId。
5.ctrl+alt+v失灵
原因是开了QQ音乐在后台没关。。。。。。