games-development-ai/docs/agent-specs/2026-06-11-Wave4-community-biz-execution.md
zizi 64a390e640 docs(wave4): Round1双lane收口检查点——链路②闭合harness + Wave4(community+biz)双spec定稿
- 链路②: admin审核台真UI走查闭合(CDP-on-mini harness: admin_walk.py/run_walk.sh)——
  创作者提交→admin点9137「通过」→项目PUBLISHED(4)+feed_rank写入实证(队列3→2+DB);附带修admin构建腐化(vite8→钉5.1.4清装)
- Wave4评审版 HJ-WAVE4-001 + 评审纪要(两轮对抗评审R1 23+R2 6全接受) + 创始人拍板D-A/D-B/D-C回填+ERRATA回流
- Wave4 execution版 HJ-WAVE4-EXEC-001(4路深度recon→起草→三镜头评审逮aigc回调blocker+PII口径订正;主agent抽查file:line属实)
- 总账§3链路②行+§6波次史、作战清单⑩ 同步收口

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 10:09:14 +00:00

83 KiB
Raw Blame History

Wave4community + biz· execution 版

  • 文档编号HJ-WAVE4-EXEC-001
  • 日期2026-06-11
  • 状态:待实现 agent 认领 + 主 agent 验收(评审版三项战略决策 D-A/D-B/D-C 已创始人拍板,见 §0
  • 唯一设计依据docs/agent-specs/2026-06-11-Wave4-community-biz-review.md(编号 HJ-WAVE4-001已两轮对抗评审定稿+ docs/agent-specs/2026-06-11-Wave4-community-biz-review-评审纪要.mdR1 24 条 + R2 6 条逐条处置)。本文不得与评审版矛盾;执行中发现矛盾,停工上报,以评审版为准。
  • 读者:实现 agent 分工认领(建议 community 先、biz 后,单序列资源由 §0 单 agent 收口);主 agent 验收。
  • 环境铁律(继承项目记忆 internal-build-infra-servers / m1-runtime-bringup-state / frontend-spine-built
    • 重型构建/测试一律上 mini-desktopssh mini-desktop15G 内存,有 java17/mvn/docker本机严禁 mvn/npm build(小内存会 OOM源码同步走 git push(阿里云 Gitea ssh://git@101.200.34.71:2222)→ mini-desktop git pullscp 源码树被分类器拦)。
    • staging app 在 mini-desktop 隔离容器 http://100.64.0.7:48080(对内 http://localhost:48080)。
    • 含中文 SQL 在 mini-desktop 执行务必 --default-character-set=utf8mb4(防乱码,记忆铁律)。
    • 「骨架编译过 ≠ 真实可验收」(评审版 R10 铁律 + 记忆 golden-loop-b1-done / frontend-link-ui-walk-publish-fix):完成条件强制要求真实写链路 staging 实测§9.3 两条系统身份写链路)+ biz/community UI 入口真人走查§9.4),不以批跑/API 全绿替代。

0. 拍板前提(评审版 §0.1 / §7本文全按拍板结果起草

决策 拍板结果(本文落点)
D-A biz 形态 轻量 lead form 先行——只交付「询单→报价→进度→demo 确认」信息流闭环,资金流(收款/分账/在线签章)留 M4§7 biz 建设 + §10 M4 留债)
D-B community 功能优先级 通知底座最小集——T-CMU-10 编排 + T-CMU-06 站内信 + T-CMU-11 等级引擎(+可选短信 T-CMU-09一次覆盖 5/5 P0SOC/GRW 域仅留接口桩、不实现§6 community 建设)
D-C 是否本波接 ip seam 不接、维持 D5 推迟——本波 P-BIZ-02 直接复用 aigc 4 模板 + runtime 沙箱(绕开 T-IP-09 模板市场),无需 ip seam
建设次序execution 单 agent 定) 先 community 后 bizcommunity 触达机制最独立、复用面集中、风险最低biz 外部对接多、受 pay 桩制约,适合后段)
P-INC-01 计数权威源execution §0 单 agent 拍) (a) project 发布成功后同进程 notify community 计数(与三上游同范式;实查 ProjectApi countPublishedByCreator 方法,方案 b 需 project 新增端点,故取 a 上游单点改动)。详见 §6.4

1. 目标与范围边界

1.1 目标

交付 Wave4 总 11 P0 = community 5 + biz 6 可验证,不破坏已建 11 模块(错误码段/Flyway 序号/前缀/契约文件名零冲突)。

  • community5 P0 = P-NTF-01/02/03/05 + P-INC-01:通知底座(编排 + 站内信 + 等级引擎)。触达机制 = 同进程 -api notify-push(评审版 §3.3 定型aigc 生成完成 / compliance·project 审核出结果 / trade 收益变动后,由各自 service 在本地事务提交后同进程调用 community 暴露的 CommunityNotifyApi(复用 @Primary 本地实现),写站内信。不新建 MQ、不动 events.schema.json
  • biz6 P0 = P-BIZ-01/02/03/04/08/12:轻量 lead form 信息流闭环询单→报价→进度看板→demo 试玩确认),复用 aigc 4 模板 / runtime 取包 / admin 队列 / project 轻量状态机范式。资金流(收款/分账/在线签章)留 M4

1.2 明确不做(继承评审版 §1 非目标,实现 agent 不得擅自扩界)

  • biz 不收款结算T-BIZ-08 收费、T-BIZ-03 在线电子签章)→ 本波只到「状态机走通 + 线下签署后台标记兜底」,资金流并入 M4。
  • community 不全量铺开 T-CMU 全家桶——SOC/GRW 域(关系图谱/排行/成就/扇出/邮件,全 P1/P2只留接口桩、不实现。
  • 不引入 Flowable / yudao-bpm(当前注释未装配)——复用 project 已落地的轻量状态机范式(ProjectStatusEnum + ProjectServiceImpl 写前流转校验,无独立 ProjectStateService.java)。
  • community「记入钱包/流量包」本波不跨模块写——实查 feed.yamlboost/pinned(无流量包发放端点)、trade.yaml 无 grant/incentive 端点,两个跨模块写 seam 当前均不存在;本波只写 community 自有 reward 触发记录表,真实记账/到账留 M4。
  • P-BIZ-11 学生防沉迷不在 biz——经 Doc C 已划归 complianceT-CMP-36/37),由 compliance 承接(不计入 biz
  • 本波两类留债分开记账(评审版 R1勿混①资金流债biz 签署/收款、community reward 真实记账)→ 统一并入 M4②前端 UI 入口债biz 运营代客发起=WS4 / 客户进度看板=WS3→ 跨工位协调项,可与本波后端并行推进,不与 M4 资金流债捆绑延期§10
  • events.schema.json 本波零改community 走 notify-push、不新增遥测事件——主动规避「只改契约不改 TelemetryEventEnum = 事件静默 rejected」双处同步坑评审版 §3.3 保留口径)。

2. 涉及模块与文件路径(克隆 + 接线全清单)

路径前缀统一 /root/games-development-ai/;包名固定 cn.wanxiang.game.module.{community|biz}.{层}(克隆黄金模块 game-module-project 范式, cn.iocoder 业务包名)。

2.1 新建模块(克隆黄金模块)

模块 子模块 关键文件(举黄金模块 project 实例为样板file:line
community game-module-community-api pom.xmlartifactId→game-module-community-api/ enums/ApiConstants.javaNAME→community-server、PREFIX→RPC_API_PREFIX+"/community"/ enums/ErrorCodeConstants.java(段 1_107_000_000 起)/ api/CommunityNotifyApi.javaFeign 接口,照 ProjectApi.java:23 @FeignClient(name=ApiConstants.NAME)/ dto/(跨模块 DTO
game-module-community-server pom.xmlartifactId→game-module-community-server/ controller/app/message/AppCommunityMessageController.java@RequestMapping("/community"),前缀 /app-api 框架自动加,照 AppProjectController.java:33-37/ controller/admin/notification/AdminCommunityNotificationController.javamock-triggerAdminProjectController.java:31-35/ service/{message,level,notify}/(业务规则 + @Transactional 只加此层)/ dal/dataobject/community/{MessageDO,LevelDO,RewardDO}.javaextends TenantBaseDO,照 ProjectDO.java:18-83/ dal/mysql/community/@Mapper extends BaseMapperX,显式列禁裸 select*,照 ProjectMapper.java:16-87/ convert/BeanUtils+手写 static非 MapStruct,照 ProjectConvert.java/ api/CommunityNotifyApiImpl.java@RestController @Validated @Primary implements CommunityNotifyApi,照 ProjectApiImpl.java:22-25/ src/test/java/.../service/.../*ServiceImplTest.javaextends BaseMockitoUnitTest
biz game-module-biz-api 同 community-api 结构artifactId→-biz-api、NAME→biz-server、段 1_110_000_000 起)+ enums/BizLeadStatusEnum.java(照 ProjectStatusEnum.java:23-68
game-module-biz-server 同 community-server 结构artifactId→-biz-servercontroller 偏 controller/admin/biz/(运营为主)+ 少量 controller/app/biz/(客户提单 + 我的订单);service/lead/BizLeadServiceImpl.java(流转校验内联,照 ProjectServiceImpl.java:111-116/350-370

2.2 公共装配点(⚠️ 两处,缺任一处克隆即卡 compile实现 agent 不得以旧 skill 为唯一依据

评审版 §2.4 + R2-2 硬提示add-business-module.md 步骤表当前仅列「步骤5=yudao-server/pom.xml 加依赖」,未把 root game-cloud/pom.xml<modules> 注册列为独立步(开头还明写「无需改 Application/yaml只需在 yudao-server/pom.xml 加依赖」)。该 root pom 注册步计划本波收口后才回写进 skill。故本波克隆以本节「装配点=两处」为准——只读 skill 会漏掉 root pom 的 <module> 注册,触发「缺此步父 pom 找不到聚合定义 → mvn -pl … compile 失败」。

# 文件(亲核 2026-06-11 dev/2.0.0 改动 缺此步的后果
game-cloud/pom.xmlroot 聚合 pom<modules> 块(实查 :10 <modules>:34 末为 game-module-studio:35 </modules> line34 之后 / line35 </modules> 之前追加 <module>game-module-community</module> + <module>game-module-biz</module> mvn -pl game-module-community compile 找不到父 pom 聚合定义 → compile 失败
game-cloud/yudao-server/pom.xml <dependencies> 块(实查 :23 <dependencies>:80 末 game-module 依赖为 game-module-studio-server studio-server <dependency> 块之后追加两个 <dependency>groupId=cn.iocoder.cloud / artifactId=game-module-community-servergame-module-biz-server / version=${revision} 单体不把模块编进 JAR → 启动后无此模块、Swagger 无分组
YudaoServerApplication.java / application.yaml 零改YudaoServerApplication 组件扫描 + @MapperScan + application.yaml type-aliases 已通配 cn.wanxiang.game.module;端前缀按 controller.app/controller.admin 包名自动加) —(明确写入本文降认知负荷)

2.3 契约文件(新建,独占无冲突)

  • contracts/db-schemas/V12.0.0__create_game_community.sql + game-cloud/yudao-server/src/main/resources/db/migration/V12.0.0__create_game_community.sql(两处字节一致,见 §0/§5
  • contracts/db-schemas/V13.0.0__create_game_biz.sql + game-cloud/yudao-server/src/main/resources/db/migration/V13.0.0__create_game_biz.sql(两处字节一致)
  • contracts/api-schemas/community.yamlcontracts/api-schemas/biz.yaml(独占新文件,实查均不存在)
  • contracts/README.md §四community=107 / biz=110 已在册:79,只读确认,不改

2.4 上游 notify 接线点(本波在上游各新增 1 处同进程调用 + 上游 -server/pom.xmlgame-module-community-api 依赖)

挂点 文件:line亲核 2026-06-11 接线说明(详见 §6.5
挂点1 P-NTF-02 生成完成 game-module-aigc/.../service/callback/DifyCallbackServiceImpl.java:~52return txService.handleCallbackTx(reqVO); 内层 @Transactional 三表写入链于此提交) 改为先接 Boolean result提交后经 aigcTaskMapper.selectByTraceId(reqVO.getTraceId()) 回查任务,仅 task.status==SUCCEEDED 且 versionId!=null(真实成功终态,排除 config_invalid/failed/幂等无新版本)才调 notifyGenerateDone(task.getCreatorUserId(), task.getGameId(), task.getVersionId()),再 return实参三字段均取自回查任务不走 projectApi.getCreatorUserId
挂点2 P-NTF-03 审核结果 + P-INC-01 发布计数 外层 caller game-module-project/.../controller/admin/project/AdminProjectController.java:55-57projectService.reviewProject(reviewReqVO, reviewerUserId)service 本身 @TransactionalProjectServiceImpl.java:207 service 返回后调 notifyReviewResult(...)isApprove 时多调一次 notifyProjectPublished(...)(合并在同一 caller
挂点3 P-NTF-05 收益变动 game-module-trade/.../service/settle/SettlementServiceImpl.java:~78settle 循环内 firstRecord 分支 / newlyRecorded++recordIncome@Transactional 单条已提交,循环体非事务) if (firstRecord) {...} 分支内调 notifyIncomeChanged(...)(仅首次真实入账才通知,幂等复跑不重发)

⚠️ 行号漂移声明:以上 file:line 基于 dev/2.0.0 工作区快照(亲核日 2026-06-11。落地时若上游模块有改动以语义定位为准aigc=handleCallbackTx 返回处、project=AdminProjectController.reviewProject 调用返回后、trade=settle 循环 firstRecord 分支)。


3. 数据流与依赖(一图看清边界)

graph TB
  subgraph UP["上游(本波各加 1 处提交后同进程 notify"]
    AGC["aigc DifyCallbackServiceImpl<br/>handleCallbackTx 提交后"]
    PRJ["project AdminProjectController<br/>reviewProject 返回后"]
    TRD["trade SettlementServiceImpl<br/>settle firstRecord 分支"]
  end
  subgraph CMU["communityV12 / 段 1-107 / CommunityNotifyApi @Primary"]
    NAPI["CommunityNotifyApi 入口<br/>(系统身份注入 LoginUser(id=0)+finally clearContext)"]
    MSG["game_community_message 站内信"]
    LVL["game_community_level 等级<br/>(published_count 原子自增)"]
    RWD["game_community_reward 触发记录<br/>(uk 幂等;发放留 M4)"]
  end
  subgraph BIZ["bizV13 / 段 1-110"]
    LEAD["game_biz_lead 询单(状态机)"]
    QUOTE["game_biz_quote 报价"]
    PROG["game_biz_progress 进度工单"]
    ACC["game_biz_acceptance 验收"]
  end
  subgraph REUSE["只读复用biz 不反写)"]
    RTPKG["runtime 取包双路由<br/>/app-api/runtime/package/{versionId}(+/manifest)"]
    TPL["aigc 4 模板 clicker/merge/idle/tycoon"]
  end
  subgraph M4["M4 留债(明确推迟)"]
    PAY["pay 收款 / 在线签章 / trade IncentiveCreditApi / feed 流量包-api / 现金到账"]
  end

  AGC -.notifyGenerateDone.-> NAPI
  PRJ -.notifyReviewResult + notifyProjectPublished.-> NAPI
  TRD -.notifyIncomeChanged.-> NAPI
  NAPI --> MSG
  NAPI --> LVL --> RWD
  LEAD --> QUOTE
  LEAD --> PROG
  LEAD --> ACC
  RTPKG -.demo 可预览判定(只读).-> BIZ
  TPL -.模板复用(只读).-> BIZ
  RWD -.真实记账/到账.-> M4
  ACC -.签署/收款.-> M4

  classDef deb fill:#fee,stroke:#c33;
  class PAY deb;
  classDef new fill:#eef,stroke:#36c;
  class AGC,PRJ,TRD new;

事务边界铁律(评审版 §3.3/§4 红线):三上游写方法本身均 @Transactionalnotify 必须在本地事务提交后调用(=外层 caller 或循环内已提交分支),避免「通知失败回滚已提交业务」。三个 notify 调用点统一**try-catch 吞异常 + 仅记 error log**,不得让通知失败中断业务/结算循环(详见 §6.5、§8.3)。


4. 接口与数据契约DDL + 端点 + CommunityNotifyApi

DDL 套黄金模块公共列模板recon errorcode-flyway-ddlconv 实证范式InnoDB + utf8mb4显式列 + 列级中文 COMMENT状态机用 TINYINT非法流转由 Service 校验、DO 层不承载;每表强制 6 审计列(creator/create_time/updater/update_time/deleted BIT(1) DEFAULT b'0'/tenant_id BIGINT DEFAULT 0,全 NOT NULL唯一键含 (业务键, deleted, tenant_id);业务归属列 creator_user_id/user_id归属隔离边界区别于 Yudao 审计列 creatorVARCHAR64

⚠️ 归属隔离口径钉实R-medium PII 红线,实查黄金模块未用 @DataPermission:实查 game-module-project 的 DAL/Mapper @DataPermission 注解(全 game-module grep 无命中),其归属隔离实为 Mapper 查询显式以 creator_user_id/user_id 作 WHERE 谓词、且该 userId 取自 SecurityFrameworkUtils.getLoginUserId()token 解析、非前端入参)——范本=ProjectMapper.selectMyPage「强制按当前用户过滤」、TradeController 各 app 端点 getLoginUserId()故 community/biz 客户侧端点(/app-api/community/messages/app-api/biz/leads/my 等)必须在 Mapper 查询里强制以登录态 user_id 作谓词 enforce 隔离,不得依赖「黄金模块并未启用的 @DataPermission 框架机制」(照搬「靠 DataPermission」会既不加注解、又误以为框架已隔离 → 用户可读他人站内信/收益数额/审核拒绝原因等 PII。隐私隔离必须在后端查询层 enforce可信边界前端不够。

4.1 community DDL — V12.0.0__create_game_community.sql(三表)

文件头注释块须含:契约#2 DB 迁移标识 + 模块名 community + owner WS5 / 文件名 + Flyway 纪律(只新增、已合入禁改、回滚写补偿迁移 V12.0.1/ 表清单 + 职责(引架构 Doc B community 章节)/ 错误码段 community = 1-107-***-*** / 约定InnoDB+utf8mb4、显式列、Yudao 6 审计列creator/create_time/updater/update_time/deleted/tenant_id,与 §4 表头及黄金模块 V1/V9 头注对齐,勿误作「七」去找幽灵第 7 列)、状态机 tinyint、中文注释、禁裸 select*)。

-- 表1game_community_message 站内消息中心P-NTF-01/02/03/05 投递落点)
CREATE TABLE `game_community_message` (
  `id`              BIGINT       NOT NULL AUTO_INCREMENT COMMENT '消息 ID',
  `user_id`         BIGINT       NOT NULL                COMMENT '收件人用户 ID归属隔离Mapper 强制以 getLoginUserId() 作 WHERE 谓词,用户只见自己消息)',
  `type`            TINYINT      NOT NULL                COMMENT '消息类型1系统 2公告 3审核 4收益',
  `biz_ref`         VARCHAR(64)  NOT NULL DEFAULT ''     COMMENT '业务关联键(如 gameId/taskId/settleId供前端跳转',
  `title`           VARCHAR(128) NOT NULL DEFAULT ''     COMMENT '消息标题',
  `content`         VARCHAR(1024) NOT NULL DEFAULT ''    COMMENT '消息正文(审核拒绝含原因)',
  `read_status`     TINYINT      NOT NULL DEFAULT 0      COMMENT '已读状态0未读 1已读',
  `read_time`       DATETIME     NULL                    COMMENT '已读时间',
  `creator`         VARCHAR(64)  NOT NULL DEFAULT ''     COMMENT '创建者Yudao 审计列;系统身份投递=0',
  `create_time`     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater`         VARCHAR(64)  NOT NULL DEFAULT ''     COMMENT '更新者(系统身份写=0防 NOT NULL 拒绝)',
  `update_time`     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted`         BIT(1)       NOT NULL DEFAULT b'0'   COMMENT '逻辑删除0未删 1已删',
  `tenant_id`       BIGINT       NOT NULL DEFAULT 0      COMMENT '租户 IDMVP 单租户=0',
  PRIMARY KEY (`id`),
  KEY `idx_user_read` (`user_id`, `read_status`, `create_time`) COMMENT '我的消息中心列表(按已读态+时间倒序)',
  KEY `idx_user_type` (`user_id`, `type`, `create_time`)        COMMENT '按四类聚合查询'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '站内消息中心四类聚合T-CMU-06';

-- 表2game_community_level 创作者等级P-INC-01 计数权威落点 published_count
CREATE TABLE `game_community_level` (
  `id`              BIGINT       NOT NULL AUTO_INCREMENT COMMENT '等级记录 ID',
  `creator_user_id` BIGINT       NOT NULL                COMMENT '创作者用户 ID',
  `level`           TINYINT      NOT NULL DEFAULT 1      COMMENT '创作者等级1小白MVP 仅维护计数+新人里程碑,分层留 P1',
  `published_count` INT          NOT NULL DEFAULT 0      COMMENT '已发布作品数(计数权威源=project 发布成功 notify 原子累加)',
  `last_milestone`  VARCHAR(32)  NOT NULL DEFAULT ''     COMMENT '末次已触发里程碑键(如 publish_3幂等防重复触发',
  `last_milestone_time` DATETIME NULL                    COMMENT '末次里程碑触发时间',
  `creator`         VARCHAR(64)  NOT NULL DEFAULT ''     COMMENT '创建者',
  `create_time`     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater`         VARCHAR(64)  NOT NULL DEFAULT ''     COMMENT '更新者',
  `update_time`     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted`         BIT(1)       NOT NULL DEFAULT b'0'   COMMENT '逻辑删除',
  `tenant_id`       BIGINT       NOT NULL DEFAULT 0      COMMENT '租户 ID',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_creator` (`creator_user_id`, `deleted`, `tenant_id`) COMMENT '一创作者一条等级记录(幂等初始化)'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '创作者等级T-CMU-11 等级引擎)';

-- 表3game_community_reward 新人奖励触发记录P-INC-01本波只写触发记录发放留 M4
CREATE TABLE `game_community_reward` (
  `id`              BIGINT       NOT NULL AUTO_INCREMENT COMMENT '奖励触发记录 ID',
  `creator_user_id` BIGINT       NOT NULL                COMMENT '创作者用户 ID',
  `milestone`       VARCHAR(32)  NOT NULL                COMMENT '里程碑键(如 publish_3 发满3作品',
  `reward_type`     TINYINT      NOT NULL                COMMENT '奖励类型1流量包 2现金',
  `reward_amount`   BIGINT       NOT NULL DEFAULT 0      COMMENT '奖励额度(流量包=曝光量;现金=分BIGINT 防浮点;与 trade 同口径)',
  `status`          TINYINT      NOT NULL DEFAULT 0      COMMENT '发放状态0已触发待发放本波终态 1已发放M4 回写)',
  `granted_ref`     VARCHAR(64)  NOT NULL DEFAULT ''     COMMENT 'M4 发放回执trade 流水 id / feed 流量包 id本波空',
  `creator`         VARCHAR(64)  NOT NULL DEFAULT ''     COMMENT '创建者(系统身份触发=0',
  `create_time`     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater`         VARCHAR(64)  NOT NULL DEFAULT ''     COMMENT '更新者(系统身份写=0',
  `update_time`     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted`         BIT(1)       NOT NULL DEFAULT b'0'   COMMENT '逻辑删除',
  `tenant_id`       BIGINT       NOT NULL DEFAULT 0      COMMENT '租户 ID',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_creator_milestone` (`creator_user_id`, `milestone`, `deleted`, `tenant_id`) COMMENT '同创作者同里程碑只触发一次(幂等防重复发钱)',
  KEY `idx_status` (`status`) COMMENT 'M4 待发放扫描'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '新人奖励触发记录P-INC-01发放留 M4';

4.2 biz DDL — V13.0.0__create_game_biz.sql(四表)

文件头注释块同 §4.1 范式(模块名 biz / owner WS5 / 错误码段 biz = 1-110-***-*** / 资金流边界:本波只写自有表,不写 pay/trade签署/收款/分账留 M4

-- 表1game_biz_lead B/G 端定制询单主单含轻量状态机CRM=工单)
-- 状态机 status0待跟进 →1已报价 →2制作中 →3待验收 →4已交付单线性五态BizLeadService 写前校验)
CREATE TABLE `game_biz_lead` (
  `id`                BIGINT       NOT NULL AUTO_INCREMENT COMMENT '定制询单 ID',
  `customer_user_id`  BIGINT       NULL                     COMMENT '提单客户用户 IDB 端自助提单时落;运营代客发起可空)',
  `owner_user_id`     BIGINT       NULL                     COMMENT '跟进负责人用户 ID运营admin 队列认领)',
  `biz_type`          TINYINT      NOT NULL DEFAULT 3       COMMENT '定制场景1品牌营销 2文旅 3通用定制',
  `contact_name`      VARCHAR(64)  NOT NULL DEFAULT ''      COMMENT '联系人',
  `contact_phone`     VARCHAR(32)  NOT NULL DEFAULT ''      COMMENT '联系电话',
  `company`           VARCHAR(128) NOT NULL DEFAULT ''      COMMENT '企业/机构名称',
  `title`             VARCHAR(120) NOT NULL DEFAULT ''      COMMENT '需求标题',
  `scene_desc`        VARCHAR(2000) NOT NULL DEFAULT ''     COMMENT '场景需求描述B/G 端在线提交的定制需求正文)',
  `template_id`       VARCHAR(64)  NOT NULL DEFAULT ''      COMMENT '选定的玩法模板 ID复用 aigc 4 模板 clicker/merge/idle/tycoon',
  `demo_version_id`   BIGINT       NULL                     COMMENT 'demo 预览版本 IDruntime game_version.id取包走 /app-api/runtime/package/{versionId}',
  `status`            TINYINT      NOT NULL DEFAULT 0       COMMENT '状态机0待跟进 1已报价 2制作中 3待验收 4已交付',
  `creator`           VARCHAR(64)  NOT NULL DEFAULT ''      COMMENT '创建者Yudao 审计列)',
  `create_time`       DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater`           VARCHAR(64)  NOT NULL DEFAULT ''      COMMENT '更新者(系统/异步写须注入 LoginUser(id=0),否则 NOT NULL 拒绝)',
  `update_time`       DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted`           BIT(1)       NOT NULL DEFAULT b'0'    COMMENT '逻辑删除0未删 1已删',
  `tenant_id`         BIGINT       NOT NULL DEFAULT 0       COMMENT '租户 IDMVP 单租户=0',
  PRIMARY KEY (`id`),
  KEY `idx_owner_status`    (`owner_user_id`, `status`)     COMMENT 'admin 处理队列:按负责人+状态',
  KEY `idx_customer`        (`customer_user_id`, `create_time`) COMMENT '客户侧我的订单看板(/app-api/biz/leads/my',
  KEY `idx_status_update`   (`status`, `update_time`)        COMMENT '运营队列按状态+时间'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = 'B端定制询单主单轻量状态机';

-- 表2game_biz_quote 报价表(报价≠可支付订单,参数化估价输出;收款留 M4
CREATE TABLE `game_biz_quote` (
  `id`                BIGINT       NOT NULL AUTO_INCREMENT COMMENT '报价 ID',
  `lead_id`           BIGINT       NOT NULL                COMMENT '所属询单 IDgame_biz_lead.id',
  `quote_no`          INT          NOT NULL DEFAULT 1       COMMENT '报价版本号(同询单内可多轮报价,递增;唯一性由 service 以 max(quote_no)+1 串行生成保证、DB 不强约束——与黄金模块 game_version.version_no 同范式,非唯一键。若后续需 DB 级唯一可加 uk_lead_quote(lead_id,quote_no,deleted,tenant_id)',
  `amount_fen`        BIGINT       NOT NULL DEFAULT 0       COMMENT '报价金额(单位:分,与 trade 同口径;本波仅展示不收款)',
  `service_items`     VARCHAR(1000) NOT NULL DEFAULT ''     COMMENT '服务项明细(编排内容,参数化估价表单产出)',
  `valid_days`        INT          NOT NULL DEFAULT 30      COMMENT '报价有效天数',
  `remark`            VARCHAR(500) NOT NULL DEFAULT ''      COMMENT '备注',
  `creator`           VARCHAR(64)  NOT NULL DEFAULT ''      COMMENT '创建者',
  `create_time`       DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater`           VARCHAR(64)  NOT NULL DEFAULT ''      COMMENT '更新者',
  `update_time`       DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted`           BIT(1)       NOT NULL DEFAULT b'0'    COMMENT '逻辑删除',
  `tenant_id`         BIGINT       NOT NULL DEFAULT 0       COMMENT '租户 ID',
  PRIMARY KEY (`id`),
  KEY `idx_lead`            (`lead_id`, `quote_no`)         COMMENT '按询单查报价历史'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = 'B端定制报价表报价≠下单收款留 M4';

-- 表3game_biz_progress 进度工单表(状态机各节点流转记录,驱动定制进度看板)
CREATE TABLE `game_biz_progress` (
  `id`                BIGINT       NOT NULL AUTO_INCREMENT COMMENT '进度工单 ID',
  `lead_id`           BIGINT       NOT NULL                COMMENT '所属询单 ID',
  `from_status`       TINYINT      NULL                     COMMENT '流转前状态(首条为 null',
  `to_status`         TINYINT      NOT NULL                COMMENT '流转后状态0-4',
  `operator_user_id`  BIGINT       NULL                     COMMENT '操作人用户 ID运营推进系统推进可空',
  `note`              VARCHAR(500) NOT NULL DEFAULT ''      COMMENT '进度说明(看板时间线展示)',
  `creator`           VARCHAR(64)  NOT NULL DEFAULT ''      COMMENT '创建者',
  `create_time`       DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater`           VARCHAR(64)  NOT NULL DEFAULT ''      COMMENT '更新者',
  `update_time`       DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted`           BIT(1)       NOT NULL DEFAULT b'0'    COMMENT '逻辑删除',
  `tenant_id`         BIGINT       NOT NULL DEFAULT 0       COMMENT '租户 ID',
  PRIMARY KEY (`id`),
  KEY `idx_lead_time`      (`lead_id`, `create_time`)      COMMENT '按询单取进度时间线(看板)'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = 'B端定制进度工单表看板时间线';

-- 表4game_biz_acceptance 交付验收表(试玩→反馈→确认三态;签署/收款留 M4
CREATE TABLE `game_biz_acceptance` (
  `id`                BIGINT       NOT NULL AUTO_INCREMENT COMMENT '验收记录 ID',
  `lead_id`           BIGINT       NOT NULL                COMMENT '所属询单 ID',
  `demo_version_id`   BIGINT       NOT NULL                COMMENT '交付 demo 版本 IDruntime game_version.id试玩取包用',
  `feedback`          VARCHAR(1000) NOT NULL DEFAULT ''     COMMENT '客户试玩反馈',
  `confirm_status`    TINYINT      NOT NULL DEFAULT 0       COMMENT '确认态0待确认 1已确认',
  `confirmed_time`    DATETIME     NULL                     COMMENT '客户确认时间',
  `signed_offline`    BIT(1)       NOT NULL DEFAULT b'0'    COMMENT '线下签署后台标记兜底M4 接在线签章前过渡0未签 1已线下签',
  `creator`           VARCHAR(64)  NOT NULL DEFAULT ''      COMMENT '创建者',
  `create_time`       DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater`           VARCHAR(64)  NOT NULL DEFAULT ''      COMMENT '更新者',
  `update_time`       DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted`           BIT(1)       NOT NULL DEFAULT b'0'    COMMENT '逻辑删除',
  `tenant_id`         BIGINT       NOT NULL DEFAULT 0       COMMENT '租户 ID',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_lead_version` (`lead_id`, `demo_version_id`, `deleted`, `tenant_id`) COMMENT '同询单同 demo 版本一条验收(含 deleted/tenant 维度)',
  KEY `idx_lead`           (`lead_id`)                     COMMENT '按询单查验收'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = 'B端定制交付验收表试玩/反馈/确认;签署收款留 M4';

4.3 CommunityNotifyApi 方法签名(@Primary 本地实现)

路径:game-module-community-api/.../api/CommunityNotifyApi.java(接口,@FeignClient(name=ApiConstants.NAME)ApiConstants.NAME="community-server"PREFIX=RpcConstants.RPC_API_PREFIX+"/community"+ community-server/.../api/CommunityNotifyApiImpl.java@RestController @Validated @Primary implements CommunityNotifyApi,照 ProjectApiImpl.java:22-25)。实现入口注入系统身份 LoginUser(id=0)+finally clearContext§8.2)。统一 CommonResult 信封;幂等以 (userId,type,bizRef) 去重(重复投递不重复落库)。

public interface CommunityNotifyApi {
    // P-NTF-02 生成完成通知aigc 在 DifyCallbackServiceImpl.handleCallback 提交后调
    //   触发判定 = 提交后回查任务为 SUCCEEDED 终态且 versionId 已回填(非 result==TRUE见 §6.5 挂点1
    //   三实参均取自回查的 AigcTaskDOcreatorUserId/gameId/versionId
    CommonResult<Boolean> notifyGenerateDone(@RequestParam("creatorUserId") Long creatorUserId,
                                             @RequestParam("gameId") Long gameId,
                                             @RequestParam("versionId") Long versionId);
    // P-NTF-03 审核结果通知project 在 reviewProject 提交后调;
    //   decision 三态1通过/2拒绝/3下架对齐 ReviewDecisionEnum——本波仅对 1/2 发通知,
    //   decision=3 下架含封禁联动本波不发caller 侧以 decision∈{1,2} 守门,见 §6.5 挂点2reject(2) 须带 reason
    CommonResult<Boolean> notifyReviewResult(@RequestParam("creatorUserId") Long creatorUserId,
                                             @RequestParam("gameId") Long gameId,
                                             @RequestParam("decision") Integer decision,
                                             @RequestParam(value = "reason", required = false) String reason);
    // P-NTF-05 收益变动通知trade 在 SettlementServiceImpl.settle 入账成功后调
    //   netAmount=创作者实得净额settle 作用域无现成 netrecordIncome 内部算、不返回),
    //   须在 settle 内现算 BigDecimal.valueOf(gross).multiply(creatorShare).setScale(0,DOWN)(见 §6.5 挂点3
    CommonResult<Boolean> notifyIncomeChanged(@RequestParam("creatorUserId") Long creatorUserId,
                                              @RequestParam("source") Integer source,
                                              @RequestParam("sourceRef") String sourceRef,
                                              @RequestParam("netAmount") Long netAmount);
    // P-INC-01 发布计数方案aproject 发布成功后同进程 notify累加 published_count 并触发里程碑
    CommonResult<Boolean> notifyProjectPublished(@RequestParam("creatorUserId") Long creatorUserId,
                                                 @RequestParam("gameId") Long gameId);
}

4.4 community.yaml 端点清单(contracts/api-schemas/community.yaml,段注释 1-107

方法 路由 P0 说明
GET /app-api/community/messages P-NTF-01 站内信分页列表query: type 1系统/2公告/3审核/4收益 可选, readStatus 可选, cursor/size
POST /app-api/community/messages/{id}/read P-NTF-01 标记单条已读(落 read_status=1+read_time
GET /app-api/community/messages/unread-count P-NTF-01 未读数角标(按 type 分组计数)
GET /app-api/community/level P-INC-01 当前创作者等级 + published_count + 下一里程碑进度(如 2/3 距新人奖励)
GET /app-api/community/rewards P-INC-01 我的奖励触发记录列表status 触发态/发放态可见)
[RPC] /rpc-api/community/* CommunityNotifyApi 四方法,供上游同进程 @Primary 调用,非对外业务路由
[staging only] POST /admin-api/community/notifications/{type}/mock-trigger 上游未接通前独立验收四类投递,仅 dev/staging 生效@Profile({"dev","staging"}) 或网关层关闭,生产关闭)。⚠️ 实查全 game-module @Profile 先例(现有 dev/staging 差异化手段是 @ConditionalOnProperty 灰度开关,如 aigc AIGC_EXECUTOR_ENABLED)——若用 @Profile 须知是本仓首例、在类或方法级标注、并验证 staging profile 下 Bean 注册生效;亦可改用 @ConditionalOnProperty(name="community.mock-trigger.enabled", havingValue="true") 与现有灰度开关风格一致。二选一钉死

4.5 biz.yaml 端点清单(contracts/api-schemas/biz.yaml,段注释 1-110端前缀框架按包名自动加@RequestMapping 只写 /biz

客户侧 /app-api/biz/*game-studio用户 Token + Mapper 强制 getLoginUserId() 归属谓词WS3

方法 路由 P0 说明
POST /app-api/biz/leads P-BIZ-01 提交定制需求询单(落库即开状态机=待跟进0 + 落 progress 首条 to_status=0req=BizLeadCreateReqVO(bizType/title/sceneDesc/contactName/contactPhone/company)resp=CommonResult<Long>
GET /app-api/biz/leads/my P-BIZ-03 我的定制订单列表(customer_user_id 过滤,客户只见自己)
GET /app-api/biz/leads/{id} 询单详情(含当前状态/最新报价/进度时间线/demo 可预览判定)
GET /app-api/biz/leads/{id}/progress P-BIZ-03 进度看板时间线(game_biz_progress 按时间)
GET /app-api/biz/templates P-BIZ-02 按场景检索可选模板(复用 aigc 4 模板query=bizType
POST /app-api/biz/acceptances/{leadId}/feedback P-BIZ-04 客户试玩后提交反馈req=BizFeedbackReqVO(demoVersionId/feedback)
POST /app-api/biz/acceptances/{leadId}/confirm P-BIZ-04 客户确认验收(confirm_status=1,状态机 3→4 已交付)

demo 试玩取包不在 biz.yaml 自建端点——直接复用 runtime 真实双路由:GET /app-api/runtime/package/{versionId}(清单 metaCommonResult<RuntimePackageRespVO>biz 看板「是否可预览」判定走此条)+ GET /app-api/runtime/package/{versionId}/manifest(原始 JSON宿主 sha256 比对后注入)。两条须写全(评审版 R2-3

admin /admin-api/biz/*game-adminRBAC @PreAuthorizeWS4

方法 路由 P0 权限点 说明
GET /admin-api/biz/leads/page biz:lead:query 处理队列(按 owner/status/bizType 筛选)
POST /admin-api/biz/leads P-BIZ-08/12 biz:lead:create 运营代客发起定制单(customer_user_id 可空)
PUT /admin-api/biz/leads/{id}/assign biz:lead:assign 认领/指派负责人(owner_user_id
POST /admin-api/biz/leads/{id}/quotes P-BIZ-08 biz:quote:create 报价输出(参数化估价,报价≠可支付订单;状态机 0→1
GET /admin-api/biz/leads/{id}/quotes biz:quote:query 报价历史
POST /admin-api/biz/leads/{id}/advance P-BIZ-03 biz:lead:advance 推进状态机→2制作中/→3待验收→3 守 demo 可预览req=BizAdvanceReqVO(toStatus/note/demoVersionId)
PUT /admin-api/biz/acceptances/{id}/sign-offline P-BIZ-04 biz:acceptance:sign 线下签署后台标记兜底(signed_offline=1

权限点登记§7.4 必做)biz:lead:query/create/assign/advancebiz:quote:create/querybiz:acceptance:sign 需在 yudao system_menu 登记后 RBAC 才放行(范本=project:review:query 登记方式)。


§0. 全局单序列资源预占登记(⚠️ 开篇第一节,单 agent 一次性收口提交)

目的(评审版 §8 §0 / R1-5 / R6防 V12/V13 双占、文件名碰撞、错误码撞段。这是一个可勾的中央登记动作,由单 agent 收口提交(建 community 的 agent 在动任何代码前先做完本节并提交biz agent 从该提交 pull

§0.1 预占前置校验(落地时实跑,证据入提交说明)

cd /root/games-development-ai
# ① Flyway 文件名占用校验按文件名非内容应全空、exit≠0=未占用)
ls contracts/db-schemas/V12* contracts/db-schemas/V13* \
   game-cloud/yudao-server/src/main/resources/db/migration/V12* \
   game-cloud/yudao-server/src/main/resources/db/migration/V13* 2>/dev/null; echo "exit=$?  # 期望 exit=2(无此文件)"
#   ⚠️ 不要用 `grep -r 'V12\|V13'` 判占用——它是内容匹配,会命中
#   V11.0.0__create_passport_player_invite.sql 的让位声明注释(两处副本各一行,实测返 2 行),命中仅此注释属预期、非真实占用。
# ② 错误码段占用校验(应全返 0
grep -rn '1_107\|1_110' game-cloud/ | wc -l   # 期望 0
# ③ 契约 yaml 占用校验(应不存在)
ls contracts/api-schemas/community.yaml contracts/api-schemas/biz.yaml 2>/dev/null; echo "exit=$?  # 期望 exit=2"

亲核 2026-06-11dev/2.0.0):① exit=2V12/V13 均无);② 返 0③ exit=2。三项均空 = 可安全预占。

§0.2 预占登记表(单 agent 写死锁定)

资源 community biz 一致性要求
Flyway 主版本 V12.0.0__create_game_community.sql V13.0.0__create_game_biz.sql 两处副本(contracts/db-schemas/ + yudao-server/.../db/migration/字节一致md5sum 全等;命名 V{x.y.z}__{desc}.sql只增不改旧——改 V1V11 任一 → flyway validate 阻断 CI
错误码段 1_107_***_***-apiErrorCodeConstants.java 1_110_***_***(同) contracts/README.md §四:79 已在册(复用不改);段内子码按 new ErrorCode(1_107_00X_***, "中文") 分块、文件头列全 13 段防重叠
契约 yaml contracts/api-schemas/community.yaml contracts/api-schemas/biz.yaml 独占新文件
W4 埋点加列占位 V14 预留给 W4 埋点加列(本波不写,仅在登记表占号防后续撞 V12/V13

Flyway 副本数裁决recon errorcode-flyway-ddlconv 必裁项)取「两副本」contracts/db-schemas/ 授权源 + yudao-server/.../db/migration/ 唯一执行副本),不放单模块 -server/db/migration/。依据V10/V11跨模块件已开「两副本」先例单体部署下 yudao-server 聚合全部 migration单模块副本是冗余且多一处漂移风险放进单模块 classpath jar 反而可能触发同版本 Flyway 在多 jar 重复校验失败V11 守门①根因)。community/biz -server 下不放任何 sql。

§0.3 收口动作

单 agent 一次性创建并提交:两个 V12/V13 SQL各两副本字节一致+ 两个空 yaml 骨架(或含完整端点)+ root game-cloud/pom.xml<module> 注册 + yudao-server pom 依赖。先提交 pushcommunity/biz 实现 agent 再从此提交 pull,避免并行同改 root pom/Flyway 冲突。


5. 逐模块克隆关键步骤community 先biz 后)

通用「克隆 5 步骨架」(评审版 §8cp 黄金模块 → 改 3 个 pom artifactId → ① root pom 注册 <module> + ② yudao-server pom 注册 -server 依赖 → -api 写 ErrorCodeConstants+ApiConstants+枚举+DTO+Feign → -server 写 controller/service/dal/convert/ApiImpl@Primary。装配=两处§2.2),克隆开篇先复述,勿以旧 skill 为唯一依据。

5.1 community 建设步骤13 步,逐步可勾)

# 步骤 关键点 / 防雷
C0 完成 §0 预占收口V12 两副本 + community.yaml + root pom <module> + yudao-server pom 依赖),提交 push 单 agent 收口biz 从此提交 pull
C1 cp -r game-module-project game-module-community,包路径 projectcommunity、artifactId 三处改api/server/聚合 pom 包名固定 cn.wanxiang.game.module.community.*⚠️ 克隆后清理 community-server pom 中 project 发布编排专用、community 不依赖的跨模块 -apicompliance-api/runtime-api/feed-api/system PlayerApi 等——community 作为通知收件 + 等级引擎自包含、不调任何跨模块 -api(卫生项,留孤儿依赖与「最小变更」相悖;与黄金模块列集一致由 §0 装配点保障,非 pom 依赖集)
C2 ApiConstants.javaNAME="community-server"PREFIX=RPC_API_PREFIX+"/community" ⚠️ 不改则 Feign 服务名撞 project-serverR8
C3 ErrorCodeConstants.java:段 1_107_000_000 起,文件头列全 13 段防重叠 ⚠️ 顺手复制 project 100 段忘改 → 撞段R8
C4 CommunityNotifyApi.java四方法§4.3 签名)+ DTO @FeignClient(name=ApiConstants.NAME)
C5 CommunityNotifyApiImpl.java@RestController @Validated @Primary),入口注入 LoginUser(id=0)+finally clearContext ⚠️ R3 系统身份写雷§8.2),单测 mock 测不到
C6 写三表 DOextends TenantBaseDO@TableName 改对应新表名+@KeySequence 名随表改如 game_community_message_seq 或删除)+ MapperBaseMapperX,显式列禁裸 select*,客户侧查询强制 getLoginUserId() 归属谓词,published_count 原子自增方法)+ Convert ⚠️ 克隆继承 @KeySequence("game_project_seq")(实查 ProjectDO:19MySQL 忽略但残留语义错乱)须改名或删;level 自增用 UPDATE … SET published_count=published_count+1 WHERE creator_user_id=?§6.4 防并发丢更新)
C7 service/message/(投递/已读/未读数)+ service/notify/(编排:路由四类→去重→写站内信)+ service/level/(计数累加→里程碑判定→插 reward @Transactional 只加 service 层
C8 controller/app/message/5 个 app 端点§4.4Mapper 强制 getLoginUserId() 归属谓词+ controller/admin/notification/mock-trigger@Profile({"dev","staging"}) 本仓首例 或 @ConditionalOnProperty 灰度开关§4.4/§8.4 二选一) mock-trigger 须限定 staging否则成匿名伪造投递入口§8.4);客户侧端点隔离靠显式 user_id 谓词,非 @DataPermission黄金模块未用
C9 接挂点1aigcDifyCallbackServiceImpl succeeded 后调 notifyGenerateDone§6.5aigc -server/pom.xmlgame-module-community-api 依赖 try-catch 吞异常
C10 接挂点2projectAdminProjectController.reviewProject 返回后调 notifyReviewResultisApprove 时多调 notifyProjectPublished§6.5project -server/pom.xml 加 community-api 依赖 reject 须传 reqVO.getReason()
C11 接挂点3tradeSettlementServiceImpl settle 循环 firstRecord 分支调 notifyIncomeChanged§6.5trade -server/pom.xml 加 community-api 依赖 仅首次入账才通知,幂等不重发
C12 Nacos 配置注册§7.3+ 单测(extends BaseMockitoUnitTestmock insert/updateById 用 any(XxxDO.class) 消歧) 单测覆盖:投递/已读/计数累加/里程碑触发幂等
C13 mini-desktop 工程门 + staging 链路①实测§9.2/§9.3 「编译过≠可验收」

5.2 biz 建设步骤11 步)

# 步骤 关键点 / 防雷
B0 从 §0 收口提交 pullV13 两副本 + biz.yaml + root pom + yudao-server pom 已就位)
B1 cp -r game-module-project game-module-biz,包/artifactId 改 biz 包名 cn.wanxiang.game.module.biz.*
B2 ApiConstants.javaNAME="biz-server"PREFIX=…+"/biz"ErrorCodeConstants.java1_110 ⚠️ R8 同 community
B3 BizLeadStatusEnum.java(单线性五态 + of/canTransit§6.6 范本)+ 错误码(BIZ_LEAD_NOT_EXISTS/BIZ_LEAD_STATUS_ILLEGAL_TRANSITION/BIZ_DEMO_NOT_READY 合法流转矩阵:仅 current→current+1 单跳
B4 写四表 DO + Mapper + Convert§4.2 DDL 对应) ⚠️ 客户侧查询 Mapper 强制以 getLoginUserId()=customer_user_id 作 WHERE 谓词(范本 ProjectMapper.selectMyPage,黄金模块 @DataPermission 可抄,勿假定框架已隔离);克隆后清理 biz-server pom 中 project 发布编排专用、biz 不依赖的跨模块 -apicompliance-api/feed-api/system PlayerApi 等),保留 runtime-apidemo 取包用project-server pom :44 已携带、克隆即继承DO 上 @KeySequence 名随表改(如 game_biz_lead_seq)或删除(本工程恒 MySQL该注解被忽略@TableName 改对应新表名
B5 service/lead/BizLeadServiceImpl.java:提单/报价/advanceStatus(写前 canTransit 校验→3 额外 validateDemoPreviewable§6.6 流转校验内联,不引 Flowable
B6 validateDemoPreviewable:同进程注入 RuntimePackageApi@Primary 本地实现)调 getStatus(versionId) 判 demo 可预览§6.7 负路径) ⚠️ demo 取包失败降级,不透传 500
B7 controller/app/biz/7 个客户端点§4.5+ controller/admin/biz/7 个 admin 端点,@PreAuthorize("@ss.hasPermission('biz:...')") 端前缀框架自动加
B8 BizTemplateService:返 aigc 4 可玩模板clicker/merge/idle/tycoondodge/runner/match 仅契约未实现不返 §6.7
B9 权限点 SQL 登记(system_menu§7.4+ Nacos 配置§7.3+ 单测(BizLeadServiceImplTest:流转合法/非法/→3 无 demo 拒) mock RuntimePackageApi.getStatus 测负路径
B10 mini-desktop 工程门 + staging 链路②实测§9.2/§9.3+ 客户/admin UI 入口走查跟进项§9.4 「编译过≠可验收」

6. 关键实现细节(业务逻辑 + 接线)

6.1 community 通知编排T-CMU-10

四类消息type 1系统/2公告/3审核/4收益统一经 notify service路由 type → 组装 title/content审核拒绝含 reason幂等去重 (user_id,type,biz_ref)(重复投递不重复落库)→ mapper.insert(MessageDO)

6.2 community 站内信T-CMU-06

AppCommunityMessageController 5 端点:列表(按 type/readStatus 过滤、cursor 分页)/ 标记已读(read_status=1+read_time=now/ 未读数(按 type 分组 count/ 等级查询 / 奖励记录列表。归属隔离=Mapper 查询强制以 SecurityFrameworkUtils.getLoginUserId()user_id WHERE 谓词(范本 ProjectMapper.selectMyPage「强制按当前用户过滤」,黄金模块 @DataPermission标记已读须先校验该消息 user_id==当前登录 id 再落 read防越权改他人消息已读态

6.3 community 等级引擎T-CMU-11

level servicenotifyProjectPublished 触发 → 原子自增 published_count§6.4)→ 同事务内判 published_count == publish-threshold(默认 3→ 达标且 uk_creator_milestone 不存在则插一条 game_community_rewardmilestone=publish_3status=0 已触发待发放、reward_type/reward_amount 按配置)。本波终态=触发可观测+本地记账,不跨模块发放。

6.4 P-INC-01 计数权威源(方案 a 落地§0 单 agent 已拍)

  • 计数权威源 = project 发布成功(非 feed 互动回流)。实查 ProjectApi countPublishedByCreator 方法(只有 getCreatorUserId/getCurrentVersionId/getStatus/getFeedMeta/createProject/unlistIfPublished故方案 b 需 project 新增端点;取方案 a = project 发布成功后同进程 notify community 计数(上游单点改动、与三上游 notify 同范式)。
  • ⚠️ 计数交付口径 = 「尽力计数」R-high 对齐,承认 at-most-once 边界):方案 a 的 notify 在事务提交后调用且 try-catch 吞异常§6.5 挂点2本质是 at-most-once 投递——notify 因瞬时故障/重启丢一次,published_count 不会再被补发project 侧无重放、ProjectApi 无 countPublishedByCreator 兜底真相源)。因此本波明确把 published_count 定为「尽力计数」:允许极端情况下少计/漏触发里程碑,不宣称「绝对权威强一致」(与 reward 的 uk_creator_milestone 强幂等是两件事——后者只保证「到达阈值后只发一次」,不保证「计数一定到得了阈值」)。M4 真实记账时由 project 补 countPublishedByCreator 只读端点 + community 读时对账重建届时升为可对账权威源§10.2 ①资金流债已含此项。AC-CMU-5 据此订正为「计数到达阈值即触发」§9.1)。
  • 并发安全recon 必落项)notifyProjectPublished 并发到达同一 creator 时,published_count 累加须 DB 原子 UPDATE … SET published_count=published_count+1 WHERE creator_user_id=?Mapper default 方法),避免读改写丢更新导致计数偏少漏触发里程碑;累加后同事务count==threshold 才插 rewarduk_creator_milestone 兜底幂等(重复发布不重触发)。
  • 首次初始化game_community_level 一创作者一条(uk_creator),首次 notify 时 INSERT … ON DUPLICATE KEY 或先查后插(幂等)。

6.5 三上游 notify 挂点接线(事务提交后 + try-catch 吞异常)

通用规则:三上游写方法本身均 @Transactionalnotify 必在本地事务提交后调用(外层 caller 或循环内已提交分支),避免通知失败回滚业务。统一 try-catch 包裹、仅记 error log,不得让通知失败中断业务/结算循环。

  • 挂点1 P-NTF-02aigcDifyCallbackServiceImpl.handleCallback(亲核 :50-71 return txService.handleCallbackTx(reqVO);,内层 @Transactional 三表写入链于此提交;成功回填动作=内层 DifyCallbackTxService.handleCallbackTx 步骤⑦调 aigcTaskService.completeWithVersion(taskId, versionId)不是 DifyCallbackService 的「成功路径」——勿误引)。
    • ⚠️ 三处硬约束R-blocker照字面无法落地必须按下方处方
      1. handleCallbackTx 返回值无法判别真成功:实查内层 succeeded:204/ config_invalid 业务性失败(:166/ failed:155/ 幂等短路(:122全部 return Boolean.TRUE,故 result==TRUE 不能作为 succeeded 判据(照搬会对生成失败/重复回调误发「生成完成」)。
      2. 外层作用域无 gameId/versionId/creatorUserIdDifyCallbackReqVO 字段仅 traceId/status/templateId/gameConfig/assets/qualityScore/failureReason无三者versionId 是内层事务步骤④新建的局部变量、不随 Boolean 返回;gameId/creatorUserId 是内层经 aigcTaskMapper.selectByTraceId 反查的 task 字段。三个 notify 实参在外层均无来源。
      3. 不走 projectApi.getCreatorUserId(gameId):该路径前提 gameId 本身在外层不可达;且 AigcTaskDO(实查 :37/:41/:45已自带 creatorUserId/gameId/versionId 三字段,回查任务一次即全得,更直接。
    • 可落地处方(实查 selectByTraceId 已存在、AigcTaskDO 三字段齐备aigc 自有 AigcTaskMapper 可直接注入):内层事务提交后,在外层 handleCallback 末尾按 traceId 回查任务,仅真实成功终态才发:
      Boolean result = txService.handleCallbackTx(reqVO);   // 内层 @Transactional 已提交
      // 提交后回查任务:仅 SUCCEEDED 且 versionId 已回填 = 真实成功终态,
      // 天然排除 config_invalid/failedstatus=FAILED与幂等短路无新版本场景
      try {
          AigcTaskDO task = aigcTaskMapper.selectByTraceId(reqVO.getTraceId());
          if (task != null
                  && Objects.equals(task.getStatus(), AigcTaskStatusEnum.SUCCEEDED.getStatus())
                  && task.getVersionId() != null) {
              communityNotifyApi.notifyGenerateDone(task.getCreatorUserId(), task.getGameId(), task.getVersionId());
          }
      } catch (Exception e) { log.error("[handleCallback] 生成完成通知失败不回滚业务traceId={}", reqVO.getTraceId(), e); }
      return result;
      

      注:notifyGenerateDone 的触发判定不是 result==TRUE,而是「回查任务为 SUCCEEDED 终态且 versionId 已回填」§4.3 签名注释同步修正。aigc -server 已依赖 project/runtime -api,本处只新增对 community-api 的依赖;AigcTaskMapper/AigcTaskStatusEnum 为 aigc 自有,直接注入/引用。

  • 挂点2 P-NTF-03 + P-INC-01projectProjectServiceImpl.reviewProject(亲核 :206-242 @Transactional,落 review_record + 状态机 +APPROVE发布编排 :240。notify 不可放方法内(会被回滚牵连)。真实位置=外层 caller AdminProjectController.reviewProject(亲核 :55-59projectService.reviewProject(reviewReqVO, reviewerUserId))。
    • ⚠️ 作用域处方R-mediumcaller 侧 creatorUserId/isApprove 均不在原作用域,须显式取得):实查 AdminProjectController.reviewProject 方法体只有 reviewReqVO(字段 gameId/versionId/decision/reason无 creatorUserId)与 reviewerUserIdisApprove 概念仅存在于 ProjectServiceImpl 内部(:213控制器取不到。caller 须自行派生(AdminProjectController 增注入 ProjectApiproject-server 已依赖 project-api自调 @Primary ProjectApiImpl 可达):
      projectService.reviewProject(reviewReqVO, reviewerUserId);   // service @Transactional 已提交
      try {
          Long creatorUserId = projectApi.getCreatorUserId(reviewReqVO.getGameId()).getCheckedData();
          if (creatorUserId != null) { // 游戏不存在/已删时 null跳过 notify
              Integer decision = reviewReqVO.getDecision();
              boolean isApprove = Objects.equals(decision, ReviewDecisionEnum.APPROVE.getDecision());
              // ★ 仅对「通过(1)/拒绝(2)」发审核结果通知;下架(3, 含封禁联动)本波不发(消除未定义文案分支)
              if (isApprove || Objects.equals(decision, ReviewDecisionEnum.REJECT.getDecision())) {
                  communityNotifyApi.notifyReviewResult(creatorUserId, reviewReqVO.getGameId(), decision, reviewReqVO.getReason());
              }
              if (isApprove) { communityNotifyApi.notifyProjectPublished(creatorUserId, reviewReqVO.getGameId()); }
          }
      } catch (Exception e) { log.error("[reviewProject] 审核/发布通知失败不回滚审核gameId={}", reviewReqVO.getGameId(), e); }
      
    • ⚠️ owner 钉准(评审版 §6.2 注 / recon community-design riskDoc C 标 P-NTF-03 辅技术功能=compliance「审核状态机事件源」实查审核结果落库点是 project 侧 ProjectServiceImpl.reviewProject(写 review_record + 状态机compliance ComplianceGateServiceImpl.evaluate 只做 Gate 二态不碰 project 审核态。故 notify 挂 project.reviewProject 外层 caller不挂 compliance(否则 reason 取不到)。该改挂与「唯一设计依据」评审版 §2.1/§2.2/AC-CMU-3 的 compliance 上游源矛盾,已回流评审版勘误#1§11订正——验收以 project 挂点为准。
    • ⚠️ UNLIST 第二调用路径漏通知R-medium已知不通知项ProjectApi.unlistIfPublishedProjectServiceImpl:282-297)以 SYSTEM_REVIEWER_ID 直接调 service 层 reviewProject(reqVO, SYSTEM_REVIEWER_ID):295绕过 AdminProjectController,供 compliance 封禁联动调用。本波 notify 钩子只在 controller经封禁联动/系统下架路径一律不发通知——属本波非 P0 的已知不通知项(下架通知本就不在 community 5 P0 内),不补钩子(补则须下沉 service 用 TransactionSynchronization.afterCommit,超本波边界)。
    • ⚠️ published_count 计数幂等口径R-high承认边界notifyProjectPublished 仅在 isApprove 分支调用,而 reviewProject 受状态机「仅审核中(REVIEWING)可通过」约束(再审被挡死),故正常每款游戏只产生一次 approve→一次计数。但 P-INC-01 计数权威源建立在「事务提交后 notify + try-catch 吞异常」=at-most-once 投递上,存在两类已知风险:① notify 因瞬时故障/重启丢失 → 该次发布永久少计;② framework 重试/前端重复提交致同一 gameId 二次 approve 通知 → 计数偏大。uk_creator_milestone 只防「重复发钱」、不防「count 本身偏差」。本波取「尽力计数」口径(见 §6.4 收紧 + AC-CMU-5 订正),不引入按 gameId 的计数级幂等表(属 M4 真实记账时统一对账范畴)。
  • 挂点3 P-NTF-05tradeSettlementServiceImpl.settle(亲核 :55-89 循环内 if (firstRecord) { newlyRecorded++; } 分支;recordIncome@Transactional 单条已提交,循环体非事务)。
    • ⚠️ net 是幽灵变量R-medium照字面编译不过:实查 settle 循环作用域只有 revenueAdRevenueRespDTO,含 getRevenueAmount()=gross 毛额 / getCreatorUserId() / getId())与 creatorShare@Value("${trade.creator-share:0.80}")netIncomeServiceImpl.recordIncome 内部calcNet(gross, shareRate) 算(BigDecimal.valueOf(gross).multiply(shareRate).setScale(0, DOWN):86-89recordIncome 返回 boolean不返 net。settle 局部无 net 这个名字。
    • 可落地处方(二选一,钉准取 a 现算——口径与 recordIncome 落账完全一致、零跨改)
      • (a) settle 内现算 net(推荐):firstRecord 分支内先 long net = BigDecimal.valueOf(revenue.getRevenueAmount()).multiply(creatorShare).setScale(0, java.math.RoundingMode.DOWN).longValue();(与 IncomeServiceImpl.calcNet 同公式同舍入,保证通知净额=落账净额),再 try { communityNotifyApi.notifyIncomeChanged(revenue.getCreatorUserId(), TradeIncomeSourceEnum.AD.getSource(), String.valueOf(revenue.getId()), net); } catch (Exception e) { log.error("[settle] 收益变动通知失败不中断结算循环revenueId={}", revenue.getId(), e); }(仅首次真实入账才通知,幂等复跑不重发)。
      • (b) 改 recordIncome 返回入账净额(或入账 DO 含 netsettle 据返回值传——但 recordIncome 返回值语义(首次入账 boolean被结算回标逻辑依赖改签名需回归 trade 现有调用方,故不取。
    • 访问器核实:TradeIncomeSourceEnum.AD(实查 :22 AD(1,"广告")@Getter 于字段 source)访问器为 getSource()(文档用法正确)。
    • ⚠️ 事务边界 nuancerecon community-design risksettle 是定时任务、循环体非事务、内层 recordIncome 单条已提交;firstRecord 分支调 notify 满足「入账已提交」,但若后续条目或回标失败,已发 notify 无法回滚——评审版接受此口径(通知失败不回滚业务、反之亦然),故 notify 异常须 try-catch 吞掉 + 告警日志,不得中断结算循环。

6.6 biz 轻量状态机(范本 project ProjectStatusEnum/ProjectServiceImpl

// game-module-biz-api/.../enums/BizLeadStatusEnum.java
// 合法流转0待跟进→1已报价→2制作中→3待验收→4已交付仅相邻正向单跳不可跳级/回退)
// 非法流转由 BizLeadService 写前拒(抛 BIZ_LEAD_STATUS_ILLEGAL_TRANSITION
// 硬规则→3待验收 必须已有可玩 demodemo_version_id 非空且 runtime 取包可预览),否则抛 BIZ_DEMO_NOT_READY
@Getter @AllArgsConstructor
public enum BizLeadStatusEnum {
    PENDING(0, "待跟进"), QUOTED(1, "已报价"), PRODUCING(2, "制作中"), ACCEPTING(3, "待验收"), DELIVERED(4, "已交付");
    private final Integer status; private final String name;
    public static BizLeadStatusEnum of(Integer status){ return Arrays.stream(values()).filter(e->Objects.equals(e.status,status)).findFirst().orElse(null); }
    /** 合法流转矩阵:仅允许 current→current+1 单跳单线性5 态 4 条边) */
    public static boolean canTransit(Integer from, Integer to){ return from!=null && to!=null && to == from + 1 && of(from)!=null && of(to)!=null; }
}
// game-module-biz-server/.../service/lead/BizLeadServiceImpl.java流转校验骨架范本 ProjectServiceImpl:207 同款事务边界)
// ★ 必须 @Transactional两写改 lead.status + insert progress 进度行)须原子,否则崩溃会留下
//   「状态已翻但时间线缺该节点行」或反之的不一致,看板与状态机错位(事务边界铁律 §3 红线)
@Transactional(rollbackFor = Exception.class)
public void advanceStatus(Long leadId, Integer toStatus, Long operatorUserId){
    BizLeadDO lead = bizLeadMapper.selectById(leadId);
    if (lead == null) throw exception(BIZ_LEAD_NOT_EXISTS);
    Integer from = lead.getStatus();
    if (!BizLeadStatusEnum.canTransit(from, toStatus)) throw exception(BIZ_LEAD_STATUS_ILLEGAL_TRANSITION); // 写前拒非法流转
    // ★ →3待验收 守门:必须有可玩 demo复用 runtime 取包判定,无产物/编译失败/versionId 缺失即拒)
    if (Objects.equals(toStatus, BizLeadStatusEnum.ACCEPTING.getStatus())) { validateDemoPreviewable(lead.getDemoVersionId()); }
    BizLeadDO update = new BizLeadDO(); update.setId(leadId); update.setStatus(toStatus); bizLeadMapper.updateById(update);
    // 落进度工单(看板时间线);若由非 Web 线程/系统身份触发,入口须 LoginUser(id=0)+finally clearContext否则 updater NOT NULL 拒绝 500
    BizProgressDO p = new BizProgressDO(); p.setLeadId(leadId); p.setFromStatus(from); p.setToStatus(toStatus); p.setOperatorUserId(operatorUserId); bizProgressMapper.insert(p);
}

confirm 端点跨表原子性R-medium必规约:客户 POST /app-api/biz/acceptances/{leadId}/confirm§4.5)涉及两表写 + 状态机 3→4——置 game_biz_acceptance.confirm_status=1+confirmed_time advanceStatus(leadId, 4, ...)(推进 lead 3→4 + 落 progress 行)必须在同一 @Transactional(rollbackFor=Exception.class),任一失败整体回滚,杜绝「已确认验收但 lead 未推进到已交付」的跨表不一致。前置约束:canTransit 仅允许相邻正向单跳confirm 走 3→4 合法,但前置态须确为 3待验收,否则 canTransit(非3, 4)BIZ_LEAD_STATUS_ILLEGAL_TRANSITION——端点文案须说明「仅待验收态可确认」。

6.7 biz demo 预览取包接线(复用 runtime 双路由 + 负路径降级)

  • 模板列表 P-BIZ-02BizTemplateService 返 aigc 4 可玩模板clicker/merge/idle/tycooncontracts/templates/ 已有 runtime 真实实现dodge/runner/match 仅契约落盘未实现,不返
  • demo 取包:复用 runtime 双路由 GET /app-api/runtime/package/{versionId}(清单 metabiz 看板「是否可预览」走此条)+ /manifest(原始 JSON
  • validateDemoPreviewable(demoVersionId)→3 守门 + 负路径):同进程注入 RuntimePackageApi@Primary 本地实现)调 getStatus(versionId);判定口径=status∈{0预览就绪,1已发布} 即可预览,null=无就绪包=不可预览。负路径R1 必验)demoVersionId 缺失/非法、无编译产物runtime 抛 RUNTIME_PACKAGE_NOT_READY 1-102-001-001、编译失败时BIZ_DEMO_NOT_READY(看板降级显示「该模板暂不可预览」),不透传 500;状态机不允许在无可玩 demo 时推进到「待验收」
  • ⚠️ 预览鉴权口径缺口R-low 拆分表述,勿合并为单一警告):两条路径不同,须分别处理:
    • 「是否可预览」判定(validateDemoPreviewableRuntimePackageApi.getStatus= owner-agnostic、不受 owner 校验影响。实查 RuntimePackageApi.getStatus:60-63委托 RuntimePackageService 纯只查 status 单列、不做 owner/scene 校验,故此判定路径不会RUNTIME_PACKAGE_PREVIEW_NOT_OWNER——实现 agent 勿误以为 getStatus 会被 owner 拦。
    • 客户真实试玩取包(走控制器 GET /app-api/runtime/package/{versionId})才有 owner+scene 门禁preview 场景 userId==null 即拒biz 客户(customer_user_id)≠版本创作者(运营代建)时可能触发 RUNTIME_PACKAGE_PREVIEW_NOT_OWNER(1-102-001-003)。建议biz demo 走 play 场景status=1 已发布) 而非 preview 场景,或 biz demo 先发布到 status=1 后再供客户试玩(避免 owner 校验拦截)。此 owner 风险仅存在于真实试玩取包路径,与上一条 getStatus 判定无关。落地前与 runtime owner 确认此口径。

7. 配置与权限登记

7.1 装配点§2.2 复述)

两处:① root game-cloud/pom.xml <modules> 注册 <module>game-module-community</module>+<module>game-module-biz</module>line34 后);② yudao-server/pom.xml <dependencies> 注册两个 -server 依赖studio-server 后)。

7.2 上游 -server pom 加 community-api 依赖

aigc / project / trade 三个 -server/pom.xml 各加 game-module-community-api 依赖(照 project-server/pom.xml:34-53 加 compliance-api/runtime-api/feed-api 的写法)。

7.3 Nacos 模块配置注册(对齐 add-business-module.md 步骤6

配置项 默认值 用途
community.sms.enabled false 短信发送开关(收益高优可选短信;默认关,站内信成功即 AC 达标)
community.level.publish-threshold 3 新人里程碑阈值(发满 3 作品触发 reward
biz.state-machine.timeout-hours (评审版列) 状态机节点超时(如需;硬编码则可跳过)

deploy/nacos/ 加配置项并导入,验证 Nacos 控制台可见。若 community/biz 通知模板/阈值硬编码无模块级配置,本步可精简(评审版未强制要求新增 Nacos 配置)。

7.4 biz admin 权限点登记(⚠️ recon biz-design 必做缺口)

/admin-api/biz/*@PreAuthorize("@ss.hasPermission('biz:...')"),权限点 biz:lead:query/create/assign/advancebiz:quote:create/querybiz:acceptance:sign 需在 yudao system_menu 登记后 RBAC 才放行——否则 admin 端点建好但管理员无权限调用403。本文要求execution 落地时以 V13 或单独菜单初始化 SQL 登记这些权限点(范本=project:review:query 在 yudao 的登记方式)。


8. 边角失败路径(必落)

8.1 事务提交后 notify + try-catch 吞异常

见 §6.5:三上游 notify 均在事务提交后调用 + try-catch 仅记 error log通知失败不回滚业务、不中断结算循环。

8.2 系统身份写防 updater NOT NULL 雷R3单测 mock 测不到)

community 通知投递 / reward 记账 / biz 状态机推进若由非 Web 线程或系统身份触发 → 无登录态 → updater NOT NULL 拒绝 → 链路 500/任务卡死。单测 mock 测不到此雷(项目记忆铁律)。

范本(照 EventIngestServiceImpl.injectSystemIdentityIfAnonymousgame-module-telemetry/.../service/event/EventIngestServiceImpl.java:187-198 + finally :144-147canonical 源头=AigcGenerateExecutor.executeWithSystemIdentity

// CommunityNotifyApiImpl 四方法入口 + BizLeadServiceImpl 系统/异步触发入口
LoginUser loginUser = new LoginUser().setId(0L).setUserType(UserTypeEnum.ADMIN.getValue()).setTenantId(1L);
SecurityContextHolder.setContext(new SecurityContextImpl(...)); // 注入系统身份
try {
    // ... 业务写DefaultDBFieldHandler 自动填 creator/updater='0'
} finally {
    SecurityContextHolder.clearContext(); // 防 Web 线程上下文泄漏
}

8.3 demo 取包失败降级

见 §6.7validateDemoPreviewable 对取包失败/无产物/versionId 缺失抛 BIZ_DEMO_NOT_READY、看板降级「该模板暂不可预览」、状态机拒推进到待验收,不透传 500

8.4 mock-trigger 安全边界

/admin-api/community/notifications/{type}/mock-trigger@Profile({"dev","staging"})本仓首例,全 game-module 无 @Profile 先例,须验证 staging profile 下 Bean 注册生效) @ConditionalOnProperty(与现有 AIGC_EXECUTOR_ENABLED 灰度开关风格一致,推荐)网关层关闭,生产一律关闭——否则成匿名伪造投递入口(安全须在可信边界 enforce前端不够。三者二选一钉死给最小代码范本。

8.5 @Primary Bean 冲突R7

新模块 Feign 接口 + 本地 @Primary 实现可能 Bean 冲突致启动失败M1 实证 4 个 CommonApiApiImpl@RestController @Primary;仍冲突则给 @FeignClientprimary=false(修法 commit d7fac90启动盯 BeanDefinition 冲突日志。


9. 验证方法community 5 + biz 6 P0 AC + 工程门 + staging 实测 + UI 走查)

9.1 AC 逐条口径

community5 P0

# 口径
AC-CMU-1 P-NTF-01 四类消息(系统/公告/审核/收益)可投递、/app-api/community/messages 列表可读、{id}/readread_status=1 且 unread-count 递减
AC-CMU-2 P-NTF-02 aigc 生成完成后 notifyGenerateDone 被调→通知到达消息中心前提aigc 已接挂点1 或经 mock-trigger 验投递)
AC-CMU-3 P-NTF-03 审核出结果后 notifyReviewResult 被调→落站内信。decision=1通过/2拒绝 发通知decision=3下架(含封禁联动经 unlistIfPublished 系统身份路径)本波不发通知(非 P0 已知边界caller 以 decision∈{1,2} 守门§6.5 挂点2decision=2拒绝须带 reason——但实查 ReviewReqVO.reason@Size(max=500)无 @NotNull,全链路不强制 reject 时 reason 非空,故 community 侧 notify service 对 decision=2 且 reason 空须给兜底文案(如「未提供原因」)或记 warn避免落「拒绝原因为空」站内信配合 P-OPN-02
AC-CMU-4 P-NTF-05 trade 结算入账后 notifyIncomeChanged 被调→站内信(+community.sms.enabled=true 时可选短信)送达
AC-CMU-5 P-INC-01 计数到达阈值(默认 3community.level.publish-threshold)即触发:计数权威源=project 发布成功 notifyProjectPublished 累加 published_count尽力计数口径——at-most-once 投递承认丢通知会漏触发§6.4),到达阈值后等级引擎自动写一条 game_community_rewarduk_creator_milestone 幂等,重复发布不重触发);本波终态=触发可观测+本地记账,status=0待发放;记入钱包/流量包+现金真实到账、以及计数可对账重建project 补 countPublishedByCreator 兜底)留 M4

biz6 P0

# 口径
AC-BIZ-1 P-BIZ-01 POST /app-api/biz/leads 提交即落 game_biz_lead 一行 status=0待跟进 + 落 game_biz_progress 首条 to_status=0。Swagger/curl 可验
AC-BIZ-2 P-BIZ-02 GET /app-api/biz/templates 返 aigc 4 可玩模板;选定后经 runtime 双路由预览(/package/{versionId} + /manifest)。★负路径R1 必验)versionId 缺失/非法、无编译产物、编译失败(RUNTIME_PACKAGE_NOT_READY)时看板降级「该模板暂不可预览」而非透传 500advanceStatustoStatus=3validateDemoPreviewable 拒→抛 BIZ_DEMO_NOT_READY
AC-BIZ-3 P-BIZ-03 状态机各节点可经 /admin-api/biz/leads/{id}/advance 推进、/app-api/biz/leads/{id}/progress 取看板时间线(后端 API 层=本波验收。客户侧看板game-studio WS3+admin 队列WS4=完成条件增强项,随对应工位排期走真人 UI 走查
AC-BIZ-4 P-BIZ-04 客户「试玩+反馈+确认」三态走通——feedback 落 game_biz_acceptance、confirm 置 confirm_status=1 且状态机 3→4。签署/收款留 M4本波线下签署+sign-offline 兜底
AC-BIZ-5 P-BIZ-08 /admin-api/biz/leads(代客发起)+/admin-api/biz/leads/{id}/quotes报价Swagger/curl 可发起品牌营销定制单并完成报价(报价≠可支付订单)。运营代客发起 UIWS4=独立跟进项
AC-BIZ-6 P-BIZ-12 文旅场景(bizType=2可发起定制单并走验收。P0 子集=场景需求 CRUD+模板复用+专属看板必交(与 P-BIZ-01/03 同能力栈P1「快速交付增强」列 M4

9.2 工程门精确 CLImini-desktop 执行,逐项可勾)

# 1) 编译communitybiz 换路径)
mvn -DskipTests clean install -pl game-cloud/game-module-community/game-module-community-server -am
# 2) 单测(继承 BaseMockitoUnitTest 绿)
#    community: 投递/已读/计数累加/里程碑触发幂等biz: BizLeadServiceImplTest 流转合法/非法/→3 无 demo 拒
# 3) 启动 yudao-server → 应用层 Flyway 自动 migrate V12/V13两处副本字节一致校验md5sum 全等)
# 4) Swagger 可见(返回非空且含新端点)
curl http://localhost:48080/v3/api-docs?group=community   # biz 换 group=biz
# 5) doc.html 可见 community/biz 分组Spring 启动无 @Primary Bean 冲突日志

9.3 staging 两条系统身份写链路实测(⚠️ R3 必测,单测 mock 测不到,「骨架编译过≠可验收」)

  • 链路①=community 通知投递:上游 notify或 staging mock-trigger)→ 写 game_community_message,验系统身份注入 LoginUser(id=0) 生效、updater'0'、不 500
  • 链路②=biz 状态机推进admin 推进定制单状态(/admin-api/biz/leads/{id}/advance)→ 写 game_biz_lead/game_biz_progress(含 reward 触发记账若由非 Web 线程触发),验非 Web 线程/系统身份写 updater NOT NULL 不 500

9.4 UI 入口真人走查(完成条件增强项,跨工位绑定,不阻塞本波后端收口)

项目记忆铁律「编排器旁路/批跑 API 全绿掩盖 UI 缺陷」(frontend-link-ui-walk-publish-fix——UI 走查是唯一手段。

  • community:消息中心列表/已读/未读角标在 game-studioWS3真人走查6c6g CDP 走查配方:run_in_background 起 chrome + player_cdp.CdpSession,记忆 frontend-link-ui-walk-publish-fix)。
  • biz客户侧进度看板game-studio /biz-orders/my-orders 或邀请链WS3+ 运营代客发起/处理队列game-adminWS4权限点 §7.4 登记后)真人走查。

10. 完成条件 + M4 留债 + 回滚

10.1 完成条件(全勾才算 done「无验证证据不得 claim 完成」)

  1. §0 单序列资源预占收口提交V12/V13 两副本字节一致 + 错误码段 + yaml + 两处装配点),三项前置校验证据入提交说明。
  2. community/biz 各自 mvn -DskipTests clean install -pl … -am 通过mini-desktop+ 单测绿。
  3. 启动 yudao-serverFlyway migrate V12/V13 成功(两副本 md5 全等)+ curl …group=community/group=biz 非空含新端点 + doc.html 可见 + 无 @Primary Bean 冲突。
  4. community 5 P0 + biz 6 P0 AC§9.1)逐条满足(含 AC-BIZ-2 负路径、AC-CMU-5 幂等)。
  5. staging 两条系统身份写链路实测通过§9.3updater 落 '0' 不 500——这是「骨架编译过≠真实可验收」的硬验收
  6. UI 入口真人走查§9.4)作为完成条件增强项跟进(绑 WS3/WS4 排期,不阻塞本波后端收口)。
  7. 收口后按知识沉淀铁律回写 .agents/skills/add-business-module.md107/110 升「已落地」、补 root pom 注册独立步、补「跨模块通知优先同进程 -api notify-push 而非 MQ」范式+ 新踩坑,更新 .agents/README.md;更新 docs/mvp/MVP进度总账.md + 两模块 .agent

10.2 M4 留债(两类债分离,评审版 R1

①资金流债(受 pay 桩 + ICP/支付进件日历闸门制约)

留债 说明
biz T-BIZ-08 收费 报价转可支付订单 → pay 进件后接 PayOrderApi 收款(amount_fen 已按「分」口径预留,与 trade 同,直接复用)
biz T-BIZ-03 在线电子签章 三方签章;本波 game_biz_acceptance.signed_offline 线下兜底过渡
community reward 真实记账/到账 记入钱包/流量包+现金到账=trade 暴露 IncentiveCreditApi + feed 暴露流量包分配 -api当前均不存在)后补 seam本波只写 game_community_reward.status=0M4 回写 granted_ref+status=1

②前端 UI 入口债(跨工位协调,可与本波后端并行,非 M4 资金流债)

留债 工位
客户侧进度看板 game-studio /biz-orders/my-orders 或邀请链 WS3
运营代客发起 + 处理队列 game-admin权限点 §7.4 登记) WS4
community 消息中心 UI WS3

10.3 回滚

  • Flyway:已合入迁移禁止修改,回滚只写新补偿迁移(V12.0.1/V13.0.1 DROP两副本字节一致CI 跑 flyway validate 阻断改旧。
  • 代码:两模块纯新增(克隆),回滚=root pom 撤 <module> + yudao-server pom 撤依赖 + 删两模块目录 + 撤三上游 notify 调用点(三处 try-catch 块)+ 撤 community.yaml/biz.yaml对已建 11 模块零行为变更,回滚面收敛。
  • 上游接线回滚:三个 notify 调用点均 try-catch 独立块,注释/删除即恢复上游原行为(不影响 aigc/project/trade 既有事务)。

11. 与评审版的勘误回流登记(治理铁律:以评审版为准 → 矛盾须回流不得单方改写)

本文 line6 自定铁律「本文不得与评审版矛盾;执行中发现矛盾,停工上报,以评审版为准」。落地实查发现评审版 3 处结论与真实代码不符,已回流评审版勘误段HJ-WAVE4-001 顶部「⚠️ 勘误 ERRATA」+ §2.1/§2.2/AC-CMU-3 inline 标注),本文相应处方与之一致,验收口径唯一。

勘误# 评审版原结论 实查订正 本文落点
勘误#1 P-NTF-03 上游源 = compliance「审核状态机事件源」§2.1/§2.2 Mermaid + P0 表 + AC-CMU-3 审核态权威在 project ProjectServiceImpl.reviewProject(写 review_record + 状态机 + reasoncompliance 不持审核态Gate 二态 + 封禁。notify 挂 project AdminProjectController.reviewProject 外层 caller §6.5 挂点2含作用域处方+ §2.4 挂点表
勘误#2 (评审版未展开 decision 态数approve/reject 两态 ReviewDecisionEnum 三态1通过/2拒绝/3下架本波仅 1/2 发通知3下架unlistIfPublished 系统身份第二路径)不发 §4.3 签名注释 + §6.5 挂点2 + AC-CMU-3
勘误#3 P-INC-01 计数权威源(隐含强一致语义) at-most-once 投递→「尽力计数」,丢通知会漏触发;可对账重建留 M4 §6.4 + AC-CMU-5

未采纳项(实现 agent 已能直接定、无需回流评审版的实现细节)aigc 挂点1 的「succeeded 判别 + ID 取数」走提交后 selectByTraceId 回查(评审版未涉及代码级取数,属 execution 细节trade 挂点3 的 net 现算同上biz advanceStatus/confirm 的 @Transactional 边界(同上)。这些是 execution 版职责内的可落地化,不构成与评审版的结论矛盾,故只在本文钉准、不回流。


证据边界2026-06-11 亲核 dev/2.0.0root pom <modules> :10-35末 game-module-studio :34yudao-server pom <dependencies> :23-…(末 game-module 依赖 studio-server :80Flyway 两副本 ceiling=V11V1V11 各两副本,最高 V11.0.0__create_passport_player_invite.sqlV10 golden-loop + V11 passport 均在两处V12/V13 文件名占用 ls exit=2未占用错误码段 grep '1_107\|1_110' game-cloud/ 返 0community.yaml/biz.yaml 不存在exit=2ProjectApi 现有方法=getCreatorUserId/getCurrentVersionId/getStatus/getFeedMeta/createProject/unlistIfPublished countPublishedByCreator故 P-INC-01 取方案 aaigc DifyCallbackServiceImpl.handleCallbackreturn txService.handleCallbackTx(reqVO)(内层 @Transactional 三表写入链project AdminProjectController.reviewProject:55-57projectService.reviewProjectservice @Transactional :207APPROVE 同事务发布编排 :237-239trade SettlementServiceImpl.settle 循环 firstRecord/newlyRecorded++ 分支recordIncome @Transactional 单条提交、循环体非事务)。设计依据=评审版 HJ-WAVE4-001 + 两轮评审纪要 + 四份深度 recon含可直接入文 DDL/端点/挂点 artifacts。行号漂移以语义定位为准。本文为 execution 设计产出,未实跑 mvn/Flyway/单测——落地后须经 §9 工程门 + staging 两链路实测 + UI 走查验证方可 claim 完成(评审版 R10 铁律)。