营销专家 API · 优缺点分析与优化方案

对象:现网契约 2026-06-10-aigent-AI对接说明(2).md营销专家部分(§2 会员 8 端点 + §3 活动 9 端点 = 17 端点)。 范围:只针对营销专家场景(活动设计/生命周期/选品 + 会员筛选/标签/增删/导入),不含数据查询。 结论:营销专家与数据专家有同样的共性病根(失败恒 200、错误码无 retriable、无版本、金额裸值),但多一层写安全风险——生命周期 pause/resume/terminate 缺状态冲突码 + 幂等 + 乐观并发。本文先评优缺点,再给一套资源化 + 状态机 + 统一规范的优化方案 + 迁移对照。


一、现状概览(营销专家 17 端点)

活动域(§3,9 端点)

端点作用危险写
§3.0 GET campaign/activity-types允许创建的 4 类活动
§3.1 POST campaign/list活动列表(消歧用)
§3.2 GET campaign/detail活动详情(含复盘 + availableActions)
§3.3 POST campaign/preview活动预演(建草稿 + 校验)preview
§3.4 POST campaign/commit发布/存草稿(凭 previewToken)commit
§3.5 POST campaign/pause · resume暂停 / 恢复直接写
§3.6 POST campaign/terminate终止(不可逆,confirmed=true)直接写
§3.7 POST campaign/product-picker选品(秒杀用)

会员域(§2,8 端点)

端点作用危险写
§2.1 POST member/filter4 维筛选
§2.2 GET member/tag/list标签库(系统标签不可增删)
§2.3/2.4 member/tag/op/preview·commit打/移标签preview→commit
§2.5 POST member/create新增会员直接写
§2.6/2.7 member/delete/preview·commit删除会员(confirmed=true,真删未实现preview→commit
§2.8 POST member/import批量导入(逐行部分成功)直接写

4 类活动(注意枚举与字面相反)

FLASH_SALE=秒杀 · DIRECT_AMOUNT_OFF=订单直减(Save AED 1-999) · ORDER_DISCOUNT=全单折扣(Pay X% 10-99) · FIRST_ORDER_DISCOUNT=首单优惠。


二、优点(值得保留)

#优点出处
✅1preview→commit 统一危险写范式 + previewToken 一次性/10min TTL§0.4
✅2activity-types 防非法类型:传清单外 activityType 被 preview 提前拒 AIGENT_INVALID_ACTIVITY_TYPE§3.0
✅3availableActions 按状态下发:AI 据此判断能否 pause/resume/terminate§3.2
✅4下游 MKT_ 错误码透传*:MKT_DISCOUNT_VALUE_INVALID 等具体原因直接转述§3.3
✅5terminate 二次确认confirmed=true + AIGENT_CONFIRM_REQUIRED§3.6
✅6选品冲突检测:picker 返 conflict/conflictCampaignId/Name 防活动撞车§3.7
✅7复盘统计完整:detail 含 usedCount/totalPayAmount/refundOrderCount 等§3.2

优化方案保留并强化这些:沿用 preview→commit + activity-types 校验 + 冲突检测 + confirmed 二次确认。


三、缺点 / 问题

P0 · 写安全(最该先改)

#问题出处
M1生命周期写端点无状态冲突码 + 无幂等 + 无乐观并发CampaignActionRequest 只有 id。状态前置不满足(pause 非 ACTIVE)只「下游码透传」无结构化 AIGENT_STATE_CONFLICTavailableActions 是 detail 快照存在 TOCTOU 竞态;terminate confirmed=true 可无限重放,重复终止是否二次归还库存未定义——风险最高的操作防护最弱§3.5/3.6/3.2
M2失败恒 HTTP 200 + 错误码无 retriable:MKT_* 透传但无 category/retriable,可重试降级与永久参数错同被当终态§0.2/0.3
M3commit 不校验 token 门店绑定:门店信息来自 previewToken 快照、不传 braId,文档自陈「aigent 不校验越权,调用方负责」——门店 A 的 token 被 B 拿去 commit 会落到 A§0.1/§7.4

P1 · 契约语义

#问题出处
M4discountValue 单字段多态:语义由 discountType(PERCENTAGE/FIXED_AMOUNT) 重载;又另有 directAmount 表达减免——两套字段、易写反§3.1/3.2
M5活动类型枚举与字面相反:文档明确警告「DIRECT_AMOUNT_OFF=直减、ORDER_DISCOUNT=折扣,枚举与字面相反」,字面都含 AMOUNT/DISCOUNT,极易写反§3 开头
M6金额全裸值无币种directAmount/discountValue/flashPrice/minOrderAmount 全裸 AED,多区域静默错算§3.0–3.3
M7list 只下发 display 预格式化串*:机读原始 dateStart/dateEnd/timeStart/timeEnd 只在 detail 有;displayTimeSlot=null 把「全天」压进缺省§3.1 vs §3.2
M8availableActions 承诺超出可调端点:列 EDIT/PUBLISH/WITHDRAW/DELETE/SHARE 但仅 pause/resume/terminate 有端点;补端点时语义从「展示占位」悄变「可调契约」(无版本号的破坏);PENDING 的 WITHDRAW、DRAFT 的 EDIT/PUBLISH 是状态机死角§3.2
M9product-picker 复用 CampaignListQuerykeyword 语义漂移(list 匹配 activityName、picker 匹配 spuName)、忽略 activityType/status;price/originalPrice 与 preview 的 flashPrice 命名不对称§3.7/3.3
M10list 查询能力弱:status 单值、无时间区间过滤、返 endedReason 却不能按它过滤、无排序 → 分页不稳定§3.1
M11会员域affectedCount 是「目标数、实际生效以执行为准」(预估≠实际);delete/commit 真删未实现仍走 confirmed 流;系统标签不可增删但混在 tagCodes§2.3/2.7/2.2

P2 · 一致性

#问题出处
M12秒杀进度字段冗余:flashSkuCount/flashTotalStock/flashRemainingStock/flashSoldCount/flashStockProgress 五个,progress≈sold/total 可能不自洽§3.2
M13时间字段混用:dateStartyyyy-MM-ddcreateTime 是 datetime、display* 是 dd/MM/yyyy§3.1/3.2
M14枚举(status/endedReason/discountType/activityType)未声明开放性,未约定未知值容错§3.x
M15缺批量(detail/pause 单 id)+ 缺字段投影(list 固定返十余复盘字段,消歧只需 id/name/status)§3.1/3.2

四、优化方案

与数据专家优化同一套规范(统一信封/错误/Money/版本),再叠加营销专属的资源化 + 状态机 + 幂等。约定:前缀 /pos/ai/v1;时间 13 位 epoch ms;金额 Money 对象。

4.1 端点(资源化)

优化后替代现网标注
GET /meta/campaign-activity-typesactivity-types(元数据上移)规范化
GET /campaigns(list)campaign/list补排序/过滤/投影
GET /campaigns/{id}campaign/detail补机读原始字段
POST /campaigns:preview:commitcampaign/preview·commit沿用 + token 门店校验
POST /campaigns/{id}:pause · :resume · :terminate · :withdrawcampaign/pause·resume·terminate(+ 补 withdraw)状态机 + 幂等 + CAS
GET /products(选品)campaign/product-picker拆 DTO 重定位
会员域:POST /member:filter/member/tags:preview·commit/member/member/{id}:delete-preview·commit/member:importmember/*规范化

4.2 生命周期写(解 M1,核心)

请求POST /campaigns/{id}:pause body { expected_status? }(CAS 乐观并发)。 响应{ campaign_id, status }关键约束

约束行为
状态前置不满足(pause 非 ACTIVE)AIGENT_STATE_CONFLICT(409, details{current_status, allowed_actions})
目标态已达成(已 PAUSED 再 pause)幂等成功,返当前状态(不报错)
terminate 重复调库存归还恰好一次(幂等键去重)
expected_status 与实际不符CAS 失败 → AIGENT_STATE_CONFLICT,杜绝 TOCTOU

4.3 优惠字段(解 M4/M5/M6)

现网优化后
discountValue(多态,由 discountType 重载)+ directAmountpercent_to_pay(10-99) / amount_off(Money) / flash_price(Money),非适用置 null
类型 DIRECT_AMOUNT_OFF/ORDER_DISCOUNT(与字面相反)重命名 FIXED_AMOUNT_OFF / WHOLE_ORDER_PERCENT,带 legacy_code 过渡
裸 AED 金额全换 Money{amount,currency,minor_unit}

4.4 list / detail(解 M7/M10/M15)

  • list 行补机读原始:date_start/endtime_start/end(epoch) + is_long_term/is_all_day 布尔;display_*presentation-only 勿解析
  • statuses[](多值)、created_from/toended_reason 过滤 + sort:[{field,order}](默 createTime DESC)+ view(SLIM|FULL) 投影。
  • list/detail 收敛为「detail = list ∪ 额外明细」超集,后续纯加法。

4.5 availableActions(解 M8)

升级为机读对象:available_actions: [{ action, endpoint, available, status }] 或拆 enabled_actions/all_actions;补 PENDING/DRAFT 的 :withdraw 消除状态机死角。

4.6 选品(解 M9)

拆独立 ProductPickerQuery{ spu_keyword, page, page_size },重定位为 GET /products;价格命名对齐(picker price/original_price ↔ preview flash_price,给映射示例)。

4.7 统一错误模型 + 版本(解 M2/M3/M14)

  • 失败回真实 HTTP 状态 + error{error_code, category, retriable, details};补全 MKT_* 的 category/retriable。
  • commit 校验「鉴权门店==token 门店」→ AIGENT_TOKEN_SCOPE_MISMATCH(403)。
  • /pos/ai/v1 + api_version;每枚举标 OPEN/CLOSED,OPEN 者要求消费者遇未知值走默认分支。

五、迁移对照(现网 → 优化后)

现网优化后处置
campaign/pause(body id):pause(路径 id + expected_status)加状态冲突码 + 幂等
campaign/terminate(confirmed):terminate(幂等,库存归还一次)解重放
discountValue+directAmountpercent_to_pay/amount_off/flash_price拆多态
DIRECT_AMOUNT_OFF/ORDER_DISCOUNTFIXED_AMOUNT_OFF/WHOLE_ORDER_PERCENT(+legacy_code)重命名
裸金额Money 对象
list 仅 display*+ date/time 原始字段 + is_long_term/is_all_day补机读
availableActions: string[][{action,endpoint,available,status}] + withdraw升级
product-picker(复用 CampaignListQuery)GET /products(ProductPickerQuery)拆 DTO
失败 200 + data.errorCode真实 HTTP + error{category,retriable}

六、待确认 / 受限项

现状处置
member/delete/commit 真删未实现(受理即返回)声明 AIGENT_DOWNSTREAM_UNSUPPORTED 非重试且不消耗 token
触达发送 / 设计专家本期未实现suspended[] 声明,不设计端点
affectedCount 预估 vs 实际目标数≠实际生效字段标 is_estimate:true,commit 后返实际

附:相关文档

  • 现网契约:2026-06-10-aigent-AI对接说明(2).md(§2 会员、§3 活动)
  • 既有建议:营销活动API-优化建议(给数据团队).md(P0×3/P1×14/P2×12 多视角合并版)
  • 对照:数据专家API-优缺点与优化方案(针对2026-06-10契约).md(同一套规范)