05 管理活动与选品

运营端位于 seckill-admin, 功能围绕活动的配置与管理展开, 入口分三类: 登录、主域视图 (选品数据源)、活动管理与选品。

登录 (UserController)

入口为 POST /user/login, 仅一个登录方法: 调用 support 提供的 UserDubboService 完成凭证校验与 JWT 签发。网关侧虽有统一的鉴权/授权体系,但登录接口本身在白名单中 (未登录也要能访问), 所以 admin 侧要再做一次角色校验------只允许管理员账号登录管理后台:

java 复制代码
@PostMapping ("/login")
public Result<LoginResponse> login (@Valid @RequestBody LoginRequest request) {
    LoginResponse response = userService.login (request);
    Integer roleCode = response.getUser () == null ? null : response.getUser (). getRole ();
    if (Role.fromCode (roleCode) != Role. ADMIN) {
        throw new BizException (ResultEnum.FORBIDDEN.getCode (), "仅管理员可登录管理后台");
    }
    return Result.success (response);
}

主域视图 (SupportProductController)

主域只以 Dubbo 对外暴露能力, 浏览器无法直接访问, 因此需要为选品在 admin 中提供一个主域商品与 SKU 的只读转发入口。它持有 support 的 ProductDubboService 与 SkuDubboService, 提供四个查询:

端点 用途
GET /support/product/list 主域商品分页(选品列表页)
GET /support/product/{spuNo} 单个主域商品
GET /support/product/{spuNo}/skus 商品(SPU)下全部 SKU(勾选数据源)
GET /support/product/sku/{skuNo} 单个主域 SKU

活动管理与配置 (ActivityController)

其余功能全部收在 ActivityController 中 (活动管理、配置、选品统一入口)。要点: 在 seckill-base 里活动与 SKU 是严格分开的两个子域, admin 侧需要聚合两者来处理。

生命周期: 管理端只提供暂停、继续、关闭 (激活由定时任务自动完成)------直接复用 base 活动侧接口;

创建、更新、删除: 只与秒杀域活动有关, 同样直接复用 base 接口;

查询:列表 (全量/按状态) 与单个查询直接复用 base; 另多一个"详情", 由 admin 聚合"活动 + 该活动下的 SKU 列表"返回 (ActivityDetailVO), 供详情页一次拉取。

活动选品 : 跨域编排

选品流程: 前端先经 SupportProductController 展示主域商品与 SKU, 运营勾选并设置秒杀价、划拨库存等, 再提交给 ActivityController 完成添加。除库存外的字段都是静态配置,唯独库存必须保证"总量守恒" (主域库存与秒杀域配额此消彼长), 因此这一步是跨域写操作, 由 admin 做两段事务编排。

添加 (主域划拨 → 秒杀域落库)

java 复制代码
// 阶段一:批量划拨 (support 域内单事务: 整批扣 / 零扣)
try {
    skuService.batchDeductStock (stockItems);           // 原子扣减 + 按 skuNo 排序防死锁
} catch (Exception e) {
    BizException biz = findBizException (e);
    if (biz != null) throw biz;                        // 确认失败: 已回滚, 零副作用, 直接上抛
    // 未知结果: 不动作、转人工
    throw new BizException (SYSTEM_ERROR, "库存划拨结果未知,请人工核对主域库存后再决定是否重试");
}

// 阶段二:秒杀域批量落库 (base 域内单事务: 整批插 / 零插)
try {
    seckillProductSkuService.batchAdd (request);
} catch (Exception e) {
    BizException biz = findBizException (e);
    if (biz != null) {
        restoreStock (activityNo, stockItems, "添加失败补偿归还");   // 确认失败 → 整批补偿归还
        throw biz;
    }
    // 未知结果: 查证已落库则按成功返回, 否则转人工
    if (! verifyAdded (activityNo, stockItems)) {
        throw new BizException (SYSTEM_ERROR, "落库结果未知,请人工核对活动商品后再决定是否重试");
    }
}

主域扣减的两道防护: CAS 语义的原子扣减 (WHERE stock >= quantity, 数量不足则影响 0 行)+ 批量按 skuNo 排序执行 (多活动同时划拨同一批 SKU 时, 统一加锁顺序、不会死锁)。

删除 (秒杀域删除 → 主域归还)

秒杀域删除是整体删行, 删完之后行就消失、配额不可再查------所以归还数量必须先预读快照, 它是"超时后恢复归还"的唯一依据:

java 复制代码
List<StockChangeItem> preReadItems = preReadQuota (activityNo, skuNos);   // 预读配额快照
try {
    restoreItems = seckillProductSkuService.batchRemove (request)...;     // 阶段一: 秒杀域批量删
} catch (Exception e) {
    if (确认失败) throw biz;                                             // 零删除, 无需归还
    restoreItems = verifyRemoved (activityNo, skuNos, preReadItems);      // 未知: 查证是否已删
    if (restoreItems == null) { /* 未删/读取失败 → 转人工 */ }
}
restoreStock (activityNo, restoreItems, "删除归还");                      // 阶段二: 主域归还

失败裁决原则

结果形态 判定方式 处理
确认失败 异常链含 BizException(事务已回滚=零副作用) 补偿归还可安全重试(≤3 次);或直接上抛
未知结果(超时等) 无 BizException 不重试写操作;能查证的先查证(base 侧有查询接口),查无结论转人工
查证手段 只做读(verifyAdded / verifyRemoved) 秒杀域可查证;主域库存无查询能力 → 归还超时只能转人工

涉及库存的写操作,未知状态下绝不重试写 (防止双重效果); 重试只用于"确认失败后的补偿"与"读查证"------宁可留给人工核对, 不可重复动库存。

添加与删除跨两域执行, 但没有任何"横跨两域的锁": 每个域各自"单事务 + 失败补偿", 跨域层面不存在持锁等待; 域内层面的等待 (活动行闸门、排序后的行锁) 只排队、不成环。

相关推荐
天远数科1 小时前
Java数据工程:利用天远人车核验加强版优化智慧高速ETC发行合规体验
java·人工智能
.Bryce.1 小时前
在智能体中使用A2A工具:让大模型自己决定“该找谁帮忙“
android·java·服务器·redis·后端·agent·a2a
xiaoqiMikko2 小时前
fastjson 1.2.84 的补丁关掉了一个缓存:addAccept 包前缀白名单下,反序列化慢了 13~28 倍
java·后端·性能优化
XIAEE2 小时前
转生到儿童编程异世界:我用 Vue 和 Scratch 建了一座 DREAM 学院
java·spring
XIAEE2 小时前
初次使用 Node + Koa + MongoDB 搭建服务器:从零到第一个 API
java·spring
m0_587383002 小时前
无人自助拼豆系统核心功能开发与部署落地全流程实战指南
java·经验分享·后端·struts·架构
雷一言说2 小时前
Spring AI 2.0:RAG 文档分块大小策略选择
java·spring
XIAEE2 小时前
Java 世界历险记:从一行 System.out.println 到整个 Spring 宇宙
java·spring
XIAEE2 小时前
微前端狂想曲:我试图让三个不同门派的前端住进同一栋楼
java·spring