签收激励抽奖完成 — 处理链路流程图

sip-mac-activity · ActivitySignIncentiveConsumer → LotteryStrategyService · 点击节点查看详情

消息队列
数据库操作
第三方服务调用
业务逻辑
分布式锁
日志输出
并发执行
PHASE 1 · 消息接收与记录查找
1
📨
RocketMQ 消息到达
消费者 ActivitySignIncentiveConsumer 接收码服务发来的 traceCode 消息
topic: sign-incentive
入口类:ActivitySignIncentiveConsumer.onMessage(MessageExt)
消息体:traceCode(UTF-8 字符串)
注解:@RocketMQMessageListener(topic="${activity.rocketmq.topic.sign-incentive}")
日志:签收激励消费消息 traceCode={}, reconsumeTimes={}
2
🔍
参数校验 — 空 traceCode 检查
校验消息体是否为空,空则忽略
StringUtils.hasText(traceCode) 为 false 时记录 warn 日志并 return,不消费。
3
🗄️
查询待消费记录
按 traceCode 查询 activity_sign_incentive_msg 表中最新的未消费记录
PostgreSQL
方法:activitySignIncentiveMsgService.getLatestPending(traceCode)
SQL 逻辑:WHERE trace_code=? AND consume_status=NO ORDER BY id DESC LIMIT 1
注意:落库为异步线程池提交,消息可能先于落库到达。
record == null(未找到)
reconsumeTimes < 5 → 抛异常触发 MQ 退避重投(秒级到分钟级递增)
reconsumeTimes ≥ 5 → 放弃重试,交由扫表补偿任务兜底
record != null(找到记录)
继续后续抽奖流程 ↓
PHASE 2 · 参数反序列化
4
📦
反序列化抽奖参数
从落库记录中还原 BatchLotteryParam、TokenInfo,构造 LotteryParam
BatchLotteryParam ← JSON.parse(record.signParam)
TokenInfo ← JSON.parse(record.headerParam)
LotteryParam ← LotteryStrategyService.getLotteryParam(batchParam, traceCode)
LotteryParam 包含 traceCode、scanTagType、tagLevel、scanScene、taskId 等字段。
PHASE 3 · 分布式锁获取
5
🔒
获取 Redisson 公平锁
按 traceCode 粒度加锁,防止同一码并发抽奖
waitTime=3s · leaseTime=10s
锁 Key:LOTTERY_LOCK:{traceCode}
锁类型:Redisson Fair Lock(公平锁,按请求顺序获取)
参数:tryLock(waitTime=3s, leaseTime=10s)
获取失败:直接返回"操作频繁"结果,不进入抽奖逻辑。
finally 块:无论成功失败均释放锁(lock.unlock())。
PHASE 4 · 上下文构建(含第三方服务调用)
6
🏷️
查询码信息(串行优先)
调用码服务获取 traceCode 对应的码/标签信息,为后续并发查询提供基础数据
第三方 · CodeApiClient
调用:codeApiClient.queryCodeInfo(traceCode, tagLevel, scanTagType)
目标服务:sip-mac-code(码服务)
返回:TagInfo(码标签信息,包含 productCode、batchCode 等)
超时:commonTimeoutMs(默认 3000ms)
关键:此调用必须先于其他并发调用完成,后续查询依赖其返回的 productCode 等字段。
码信息获取后并发执行
⚡ Virtual Thread 并发调用(PartialResultScope · 超时 commonTimeoutMs=3s)
👤
查询人员信息
获取扫码人角色类型与身份
第三方 · DataApiClient
dataApiClient.getPersonRoleType(userId, operatorRole)
目标:sip-mac-data(数据服务)
返回:UserInfoVO(含角色映射)
🏢
查询经销商信息
获取码关联的经销商
第三方 · DataApiClient
dataApiClient.getDealerInfoByCode(dealerCode)
目标:sip-mac-data(数据服务)
返回:DealerInfo(经销商名称、区域等)
🏪
查询门店信息
获取扫码门店详情
第三方 · DataApiClient
dataApiClient.shopByCode(shopCode)
目标:sip-mac-data(数据服务)
返回:StoreInfo(门店名称、地址、区域等)
🏭
查询仓库信息
获取码关联的仓库
第三方 · DataApiClient
dataApiClient.warehouseByCode(warehouseCode)
目标:sip-mac-data(数据服务)
返回:WarehouseInfo(仓库名称、区域等)
📦
查询产品信息
批量获取产品详情
第三方 · DataApiClient
dataApiClient.queryProductInfoByCodes(productCodes)
目标:sip-mac-data(数据服务)
返回:List<ProductInfoVO>(产品名称、品类等)
🗺️
解析地理位置
经纬度→行政区划
第三方 · 高德地图 API
geoUtils.getScanDivision(longitude, latitude)
目标:高德地图 REST API(外部互联网服务)
返回:DivisionDTO(省/市/区行政区划编码)
注意:此为唯一的外部互联网调用,其余均为内部微服务调用。
并发结果汇总(允许部分失败)
PHASE 5 · 重复抽奖校验
7
🔄
检查是否重复抽奖
查询 activity_win_record 表,判断该 traceCode 是否已中过奖
PostgreSQL
逻辑:按 traceCode + 场景查询中奖记录,已存在则直接返回"已参与"结果。
目的:防止 MQ 重投导致同一码重复发奖。
PHASE 6 · 活动匹配与策略执行
8
📋
查询候选活动
从 DB + Redis 缓存获取当前有效的活动列表,按优先级排序
PostgreSQL + Redis
缓存 Key:LOTTERY_QUERY_ACTIVITIES:CANDIDATES:{scene}
缓存时间:candidateCacheSeconds(默认 60s)
过滤条件:活动状态=启用、时间范围有效、场景匹配
排序:按优先级 priority 降序。
🔁 遍历候选活动列表(命中即停)
⚙️
构建活动上下文 + 执行策略检查
构建 LotteryUserContext,调用 lotteryStrategyManager.checkLottery() 执行策略规则链
策略模式
上下文包含:扫码人信息、门店/经销商/仓库/产品信息、地理位置、活动配置、奖品策略等。
策略链:区域限制 → 时间限制 → 频次限制 → 人群限制 → 库存检查 → 概率计算
叠加活动:命中主活动后,检查是否可叠加额外奖励(stackActivity 逻辑)。
命中(isHit = true)
记录中奖结果,跳出循环,进入结果保存阶段
未命中
继续下一个候选活动;全部未命中则返回"未中奖"结果
PHASE 7 · 结果保存与状态更新
9
💾
保存抽奖结果
中奖记录写入 activity_win_record 表,扣减奖品库存
PostgreSQL · 事务
写入表:activity_win_record(中奖记录表)
库存扣减:调用 prizeService 扣减对应奖品库存
失败回滚:保存失败时触发库存回滚(prizeService.rollback)
10
🔓
释放分布式锁
finally 块中释放 Redisson Fair Lock
if (lock.isHeldByCurrentThread()) lock.unlock();
确保锁一定被释放,避免死锁。
11
✅
标记消息已消费
更新 activity_sign_incentive_msg 表的 consume_status 为 YES
PostgreSQL
方法:activitySignIncentiveMsgService.markConsumed(record.getId())
SQL:UPDATE activity_sign_incentive_msg SET consume_status=YES WHERE id=?
12
📝
输出日志:签收激励抽奖完成
记录 traceCode 和抽奖结果 JSON
目标日志
log.info("签收激励抽奖完成, traceCode={}, result={}", traceCode, JsonUtil.objectToJson(result))
result 包含:isHit(是否命中)、message(结果描述)、activityId、prizeId 等。

第三方服务调用汇总

序号 调用方式 目标服务 方法 用途 执行方式
1 Feign HTTP sip-mac-code(码服务) queryCodeInfo() 查询码/标签信息 串行(优先)
2 Feign HTTP sip-mac-data(数据服务) getPersonRoleType() 查询人员角色 并发
3 Feign HTTP sip-mac-data(数据服务) getDealerInfoByCode() 查询经销商 并发
4 Feign HTTP sip-mac-data(数据服务) shopByCode() 查询门店 并发
5 Feign HTTP sip-mac-data(数据服务) warehouseByCode() 查询仓库 并发
6 Feign HTTP sip-mac-data(数据服务) queryProductInfoByCodes() 查询产品信息 并发
7 REST HTTP 高德地图 API(外部互联网) getScanDivision() 经纬度→行政区划 并发