大营销平台 —— 第二阶段应用接口实现

一、前言

前面我们实现了用户返利,这一节主要是为了对接前端,但是就目前的接口而言,是不够的,上一节实现用户返利也只是实现了签到接口。事实上前端还需要以下信息:用户的额度、用户是否已经签到、用户的权重。

二、应用接口实现

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音乐在后台没关。。。。。。

相关推荐
一水1 小时前
AI 时代审查思维:审查第一篇
java·jvm·数据库·spring
RainCityLucky1 小时前
Java Swing 自定义组件库分享(十六)
java·笔记·后端
爱跳舞的烤冷面2 小时前
自学嵌入式第N天(Linux篇——文件编程)
java·开发语言·前端
莫逸风2 小时前
【AgentScope 2.0】10-总结回顾详解
java·ai·agent·springai·agentscope
怪奇云呼军2 小时前
闪电智能VoiceAgent 如何管理呼入、接听、桥接和挂断状态?
java·前端·网络·数据库·人工智能
Escalating_xu2 小时前
【Linux】进程间通信:从匿名管道、FIFO 到共享内存、消息队列与信号量
java·linux·面试
RuoyiOffice2 小时前
Springboot+WebSocket×场景×渠道:企业统一消息中心编排实战
spring boot·websocket·短信·消息中心·spring boot 3·站内信·场景编排
云烟成雨TD2 小时前
Micrometer 系列【67】统一观测:基于 Spring Boot 的生产级演示案例 | 跨线程场景
spring boot·云原生·链路追踪
IT机器猫2 小时前
RabbitMQ基础二
java·spring boot·分布式·spring cloud·log4j·rabbitmq·springamqp