7.8 KiB
云端数据正确性修复 Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 修复排行榜快照的本人识别、云端数据全量清除、TTS 缓存读取及排行榜列表 key。
Architecture: 在云函数侧生成仅用于服务端匹配的 SHA-256 memberKey;快照保留该字段,但云函数响应会剥离它。将可独立验证的快照匹配和 TTS 缓存读取抽入纯帮助模块,并用 Node 内置测试验证;客户端云端清除按 openid 分页删除全部文档。
Tech Stack: 微信小程序原生 JavaScript、CloudBase wx-server-sdk、Node.js node:test、Node.js crypto。
文件结构
- 新建
cloudfunctions/shared/cloud-data.js:用户指纹、快照本人项查找、响应脱敏、TTS 单文档缓存读取。 - 新建
utils/cloud-delete.js:可在小程序运行时和 Node 测试中复用的分页删除控制流。 - 新建
tests/cloud-data-correctness.test.js:纯帮助模块的回归测试。 - 修改
cloudfunctions/leaderboard/index.js:构建快照指纹,并在快照和实时响应返回前剥离内部字段。 - 修改
cloudfunctions/tts/index.js:使用单文档缓存读取帮助函数。 - 修改
utils/cloud.js:分页删除当前 openid 的全部文档,失败向调用方传播。 - 修改
pages/settings/settings.js:只在云端删除成功后显示成功提示;失败时给出可理解的反馈。 - 修改
pages/leaderboard/leaderboard.wxml:循环 key 使用rank。
Task 1: 纯数据帮助模块与回归测试
Files:
-
Create:
cloudfunctions/shared/cloud-data.js -
Create:
tests/cloud-data-correctness.test.js -
Step 1: 写失败测试
const test = require('node:test')
const assert = require('node:assert/strict')
const { memberKey, findMyEntry, publicEntry, cachedFileId } = require('../cloudfunctions/shared/cloud-data')
test('findMyEntry matches a hashed snapshot member and strips its internal key', () => {
const key = memberKey('my-openid')
const entry = findMyEntry([{ rank: 2, memberKey: key, duration: 90 }], key)
assert.deepEqual(entry, { rank: 2, duration: 90, isMe: true })
})
test('cachedFileId reads a CloudBase doc object rather than an array', () => {
assert.equal(cachedFileId({ fileID: 'cloud://env.tts-cache/start.mp3' }), 'cloud://env.tts-cache/start.mp3')
})
- Step 2: 运行测试确认失败
Run: node --test tests/cloud-data-correctness.test.js
Expected: FAIL,原因是 cloudfunctions/shared/cloud-data.js 尚不存在。
- Step 3: 实现最小帮助模块
const crypto = require('crypto')
const memberKey = (openid) => crypto.createHash('sha256').update(String(openid || '')).digest('hex')
const publicEntry = (entry) => {
if (!entry) return null
const { memberKey: _memberKey, ...publicData } = entry
return publicData
}
const findMyEntry = (ranked, key) => {
const item = (ranked || []).find(entry => entry.memberKey === key)
return item ? { ...publicEntry(item), isMe: true } : null
}
const cachedFileId = (data) => data && typeof data.fileID === 'string' ? data.fileID : null
module.exports = { memberKey, publicEntry, findMyEntry, cachedFileId }
- Step 4: 运行测试确认通过
Run: node --test tests/cloud-data-correctness.test.js
Expected: PASS,2 个测试均通过。
Task 2: 排行榜快照匹配与脱敏
Files:
-
Modify:
cloudfunctions/leaderboard/index.js:1-348 -
Test:
tests/cloud-data-correctness.test.js -
Step 1: 扩充失败测试
test('publicEntry never returns memberKey', () => {
assert.deepEqual(publicEntry({ rank: 1, memberKey: 'secret', name: '甲' }), { rank: 1, name: '甲' })
})
test('publicRanked strips memberKey from every snapshot entry', () => {
assert.deepEqual(publicRanked([{ rank: 1, memberKey: 'secret' }]), [{ rank: 1 }])
})
- Step 2: 运行测试确认失败
Run: node --test tests/cloud-data-correctness.test.js
Expected: FAIL,原因是 publicRanked 尚未导出。
- Step 3: 接入帮助模块
在帮助模块中增加 publicRanked = (ranked) => (ranked || []).map(publicEntry)。在排行榜云函数中 require('../shared/cloud-data');创建榜单项时加入 memberKey: memberKey(item.openid);构建 myEntry 与返回 ranked 时统一使用 publicEntry,快照读取时调用 findMyEntry(data.ranked, memberKey(myOpenid)),并对 ranked 调用 publicRanked 后返回客户端。
- Step 4: 运行测试与语法检查
Run: node --test tests/cloud-data-correctness.test.js && node --check cloudfunctions/leaderboard/index.js
Expected: PASS,且语法检查退出码为 0。
Task 3: TTS 单文档缓存读取
Files:
-
Modify:
cloudfunctions/tts/index.js:1-123 -
Test:
tests/cloud-data-correctness.test.js -
Step 1: 扩充失败测试
test('cachedFileId rejects missing or non-string file IDs', () => {
assert.equal(cachedFileId({}), null)
assert.equal(cachedFileId({ fileID: 1 }), null)
})
- Step 2: 运行测试确认失败
Run: node --test tests/cloud-data-correctness.test.js
Expected: FAIL,直到帮助函数对无效数据返回 null。
- Step 3: 替换错误的数组读取
将 r.data.length / r.data[0] 访问替换为:
const cached = cachedFileId(r.data)
if (cached) resolvedFileID = cached
- Step 4: 验证
Run: node --test tests/cloud-data-correctness.test.js && node --check cloudfunctions/tts/index.js
Expected: PASS,且语法检查退出码为 0。
Task 4: 云端全量清除与界面反馈
Files:
-
Modify:
utils/cloud.js:215-250 -
Modify:
pages/settings/settings.js:339-382 -
Create:
utils/cloud-delete.js -
Step 1: 写失败测试
在 tests/cloud-data-correctness.test.js 中为独立的 deleteAllPages(fetchPage, remove) 辅助函数添加测试:两页共三条文档时,断言三个 id 都传给 remove;任一 remove 抛错时,断言 Promise reject。
- Step 2: 运行测试确认失败
Run: node --test tests/cloud-data-correctness.test.js
Expected: FAIL,原因是 utils/cloud-delete.js 尚不存在。
- Step 3: 实现与接入分页删除
在 utils/cloud-delete.js 增加 deleteAllPages:循环调用注入的 fetchPage(cursor),逐条 await remove(id),当本页数量小于页大小时结束。utils/cloud.js 使用 _openid、按 _id 游标查询并调用该帮助函数;查询或删除失败直接抛出。设置页捕获异常后显示“云端数据未清除,请重试”,仅成功路径显示“已清除”。
- Step 4: 验证
Run: node --test tests/cloud-data-correctness.test.js && node --check utils/cloud.js && node --check pages/settings/settings.js
Expected: PASS,且三个语法检查退出码为 0。
Task 5: 修复列表 key 与完整验证
Files:
-
Modify:
pages/leaderboard/leaderboard.wxml:81-97 -
Step 1: 修改循环 key
将 wx:key="openid" 改为 wx:key="rank",因为返回给客户端的每个榜单项都包含稳定且唯一的 rank。
- Step 2: 运行完整验证
Run:
node --test tests/cloud-data-correctness.test.js
for f in app.js config.js utils/*.js pages/*/*.js components/*/*.js custom-tab-bar/index.js cloudfunctions/*/index.js cloudfunctions/shared/*.js; do node --check "$f"; done
git diff --check
Expected: 所有测试通过、所有语法检查退出码为 0、git diff --check 无输出。
- Step 3: 人工验收清单
在微信开发者工具中验证:日/月/年榜快照命中时本人高亮;榜外但位于快照前 500 名时显示底部本人条目;设置页清除后重启不恢复训练数据;清空本地 TTS fileID 缓存后第二次调用命中服务端缓存。