From 83fbf61e8af2af7038a8dbfdde8c888132702bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Thu, 13 Aug 2026 17:04:01 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BB=88=E5=B1=80=E8=8A=82=E7=82=B9=20n?= =?UTF-8?q?odeOf=20=E5=85=88=E4=BA=8E=E7=BB=93=E7=AE=97=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=EF=BC=88nodeOf=20=E5=A4=B1=E8=B4=A5=E5=88=99=E6=9C=AA=E7=BB=93?= =?UTF-8?q?=E7=AE=97=EF=BC=8C=E9=98=B2=E5=B7=B2=E7=BB=93=E7=AE=97=E5=8D=B4?= =?UTF-8?q?=E6=8A=A5=E9=94=99=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- biz/service/level_play.go | 10 +++++----- .../plans/2026-08-13-story-theater-v2.md | 18 +++++++++++++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/biz/service/level_play.go b/biz/service/level_play.go index b0254d8..c8ef7da 100644 --- a/biz/service/level_play.go +++ b/biz/service/level_play.go @@ -156,6 +156,11 @@ func (s *levelPlay) Choose(ctx context.Context, parentUid, childId, levelId, nod return node, nil, nil } + // v2.1:终局节点剧本先于结算构建(只读查询失败则未结算,无副作用) + finalNode, err := nodeOf(ctx, levelRec, nextRec) + if err != nil { + return nil, nil, err + } // 终局:路径流水在锁内结算后记录,避免状态读到本次到达导致重复结算误判 settle, err := common.WithLock(ctx, fmt.Sprintf("child:%d:level:%d", childId, levelId), 10*time.Second, 3, 200*time.Millisecond, func() (*FinalSettle, error) { return s.settle(ctx, child, levelRec, nextRec, nodeId, optionId) @@ -163,11 +168,6 @@ func (s *levelPlay) Choose(ctx context.Context, parentUid, childId, levelId, nod if err != nil { return nil, nil, err } - // v2.1:终局节点剧本随结算返回(前端先演绎结局台词再跳结算页) - finalNode, err := nodeOf(ctx, levelRec, nextRec) - if err != nil { - return nil, nil, err - } settle.FinalNode = finalNode return nil, settle, nil } diff --git a/docs/superpowers/plans/2026-08-13-story-theater-v2.md b/docs/superpowers/plans/2026-08-13-story-theater-v2.md index 5bd86f4..05a9602 100644 --- a/docs/superpowers/plans/2026-08-13-story-theater-v2.md +++ b/docs/superpowers/plans/2026-08-13-story-theater-v2.md @@ -1146,18 +1146,30 @@ git commit -m "docs: 剧情演绎文档补充 + 全量剧本生成导入" - [ ] **Step 2: Choose 终局分支返回终局 Node** -`biz/service/level_play.go` Choose 中 `WithLock` 调用之后、`return nil, settle, nil` 之前插入: +`biz/service/level_play.go` Choose 终局分支改为**先 nodeOf 再结算**(内容表只读查询失败则未结算,无副作用;与决策分支同顺序): ```go - // v2.1:终局节点剧本随结算返回(前端先演绎结局台词再跳结算页) + // v2.1:终局节点剧本先于结算构建(只读查询失败则未结算,无副作用) finalNode, err := nodeOf(ctx, levelRec, nextRec) if err != nil { return nil, nil, err } + // 终局:路径流水在锁内结算后记录,避免状态读到本次到达导致重复结算误判 + settle, err := common.WithLock(ctx, fmt.Sprintf("child:%d:level:%d", childId, levelId), 10*time.Second, 3, 200*time.Millisecond, func() (*FinalSettle, error) { + return s.settle(ctx, child, levelRec, nextRec, nodeId, optionId) + }) + if err != nil { + return nil, nil, err + } settle.FinalNode = finalNode + return nil, settle, nil ``` -(`nodeOf` 与决策分支同函数、同签名,构建选项 + 元素 + buildNode;只读查询,锁外执行安全。) +(`nodeOf` 与决策分支同函数、同签名,构建选项 + 元素 + buildNode;只读查询,锁外执行安全。审查补充:nodeOf 与结算无数据依赖,先构建可避免「已结算但返回 err」的异常路径。) + +> 审查记录(aea79d8 双审查 APPROVE): +> - SHOULD-FIX(已修):原实现「锁内结算 → 锁外 nodeOf」,nodeOf 失败时已结算却返回 err(logRoute 多写一条终局流水 → FailStreak 误算)。改为 nodeOf 先于 WithLock,失败则未结算。 +> - NIT(不修):终局节点无选项,nodeOf 内 NodeOption 查询返回空集,成本极低;FinalNode 透出链路无新增单测,Step 5 既有测试通过即可 - [ ] **Step 3: dto FinalSettle 加 FinalNode**