commit 75cb62b283d91a4eb32f13000a80de68737dd297 Author: 张斌 <259278618@qq.com> Date: Thu Aug 13 11:45:45 2026 +0800 1 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..499f74d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git +.idea +workspace +ui-src/node_modules +ui-src/dist \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02b60bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# 运行时数据与本地环境 +workspace/ +.idea/ +.DS_Store + +# 前端 +ui-src/node_modules/ +ui-src/dist/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..50544da --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,85 @@ +# CLAUDE.md + +## 目录结构与职责(硬性约束) + +> **`biz/` 是泛化占位名,不是固定目录命名**。表格中 `biz/` 代表「业务模块目录」,各项目必须按自身业务命名替换(本项目为 `kb/`),禁止新项目照抄 `biz/`;`ui-src/`、`data/`、`workspace/` 亦为本项目目录名,各项目按自身命名。 + +| 目录 | 职责 | 强约束 | +|---|---|---| +| common/ | 通用层:HTTP 服务与鉴权中间件、文件解析(parser + pdf/docx/html/text)、中文分词、向量 JSON、DAO 基类、查询缓存、协程池封装(pool.go) | 不得依赖业务模块包(仅 pool.go 依赖 `biz/consts` 取池默认值,既成事实);新增跨模块通用能力放这里 | +| biz/consts/ | 常量集中地:表名(table_name.go)、状态(status.go)、内容类型、默认参数与各协程池默认大小(consts.go) | 业务常量一律在此集中,禁止散落 magic number;新增池默认大小在此定义 | +| biz/model/ | entity(表结构,与 DAO 一一对应)、dto(请求/响应结构,`g.Meta` 内嵌定义路由)、domain(领域模型,如 RiskSummary / Citation) | entity 只做表映射,不带业务逻辑;dto 是 controller 与 HTTP 的唯一出入口 | +| biz/dao/ | 单表数据访问,每表一个文件 | 无业务逻辑;查询经 base_dao 缓存 | +| biz/service/ | 业务逻辑:规则校验、文件读写、事务、跨表组装、调用 dao、LLM 编排 | 不直接写 HTTP 响应(例外见下);并行任务走 common 协程池 | +| biz/controller/ | 接口层:接收参数、调用 service、组装返回值 | 见「分层职责规范」;禁止调用 dao | +| 前端目录 | 前台 `ui-src/` 为 uni-app (Vue 3) 多端工程(Android/iOS/平板/微信小程序/H5);后台 `admin-src/` 为 Vue 3 + Element Plus Web 工程 | 前台/后台开发用 vite 代理,生产构建产物 `dist` 由后端托管;多端工程差异见技术设计.md | +| 运行时数据目录 | SQLite 库、上传/解析文件(本项目 `data/` `workspace/`) | 不提交 git;删除即丢失数据,改动前先确认 | + +## 分层职责规范(硬性要求) + +严格分层 `controller → service → dao`,禁止跨层调用(controller 禁止直接调 dao)。 + +| 层 | 目录 | 职责 | 禁止 | +|---|---|---|---| +| controller | biz/controller | 接收参数(依赖 DTO `v` tag 自动校验)、调用 service、组装返回值 | 直接调用 dao;手写业务规则校验(库表依赖/跨字段,应下沉 service);文件 IO;状态流转;跨表数据组装 | +| service | biz/service | 业务逻辑:规则校验、文件读写、事务、跨表组装、调用 dao | 直接写 HTTP 响应(例外见下) | +| dao | biz/dao | 单表数据访问,每表一个文件 | 业务逻辑 | + +**例外**:SSE 流式响应、HTML/文件导出等"直接写响应体"的场景由 controller 完成——这是"值返回"的流式形式,事件序列化、心跳属 HTTP 协议职责,保留在 controller。 + +## 分层文件对齐与代码模式(硬性要求) + +- 每张业务表对应一组 `entity / dao / service / controller / dto` 文件,数量严格对齐;虚拟表(向量 vec0 / FTS5)不建独立分层文件,由主表 dao 统一管理 +- **不建 parser/rag 等技术目录**:纯技术能力(文档解析、中文分词、向量序列化)平铺在 `common/`;业务编排(分块、检索、工作流)归入对应 service 文件 +- entity:每文件一张表,`orm` 标签与列名一致,时间字段用 `*gtime.Time`,只做表映射 +- dao:单例 `var Xxx = &xxxDao{}`,`init()` 内 `CREATE TABLE IF NOT EXISTS` + 索引 + 迁移;通用 CRUD 复用 `common/base_dao.go`(InsertAndReturnId / GetOneByPk / UpdateByPk / DeleteByPk) +- controller:结构体名决定路由前缀(如 `dataset` → `/dataset`),接口定义在 dto(`g.Meta` 携带 path/method/summary) +- **接口只允许 GET / POST**:写操作传 JSON body(或 multipart),读操作走 query params;无 PUT/DELETE +- dao 查询缓存:查询用 `gdb.CacheOption`(TTL 来自配置),**写操作后必须清对应缓存**,否则出现"库里已改、查询还是旧值" + +## 并发规范(grpool 协程池) + +- **可并行的场景**:纯 IO 任务——读查询、LLM/Embedding 调用、文件读取。SQLite 写一律回主 goroutine 串行(无 WAL 时并发写会 `database is locked`,锁定风险归零,并发只赢在 IO 等待上) +- **新增并行点的固定三处**:`common/pool.go` 加池变量(grpool 封装)→ `biz/consts` 加默认大小 → `config.yml` 的 `pool` 段加 `key: 并发度`(缺失或非法时回退默认值) +- 禁止直接用裸 `go` 启动并行工作负载,一律走 `common` 的池(池清单与默认值见 README 配置说明) +- **防死锁**:等待链单向「主 → A池 → B池」,被等待池的任务内不得再等待任何池(会饿死 worker);池无 Wait 方法,等待用调用方 `sync.WaitGroup`,任务结果经 buffered channel 回主 goroutine +- **共享状态安全(内存)**:池内任务并发执行,共享实例(service 单例、model 句柄等)只允许**只读**访问;可变字段必须在**提交池之前**由主 goroutine 一次性预置,任务内禁止写共享字段——Go map 并发写直接 `fatal error: concurrent map writes`,无锁、无降级、不可恢复,只能崩溃重启。需要可变共享状态时按优先级:① 无共享(任务内新建、buffered channel 传递结果)② 锁(`sync.Mutex`/`RWMutex`,锁内只做内存操作,LLM/DB 等 IO 放锁外)③ `sync/atomic`(仅限 int 类标量计数/标志,如 `atomic.AddInt64`,并发计数禁用普通 `++`;复合结构不要用 atomic,指针 CAS 属例外) +- **锁的使用**:互斥场景唯一入口是 `common.WithLock[T any](ctx, key, expire, retries, retryInterval, fn func() (T, error)) (T, error)`——泛型回调,业务返回值经 T 原样透出给下游;内部按 config.yml 自动选择锁实现(配置了 `redis` 节点 → redis 锁,跨实例互斥,SET NX EX + token 对比删除防误删他人锁;未配置 → gcache 内存锁,单实例互斥),禁止直接用 gcache/gredis 自己实现加锁。拿不到锁(被占用,`ErrLockHeld`)最多重试 `retries` 次、每次间隔 `retryInterval`(`retries=0` 立即失败;ctx 取消/超时同样终止等待);中间件故障不重试直接返回。锁自动释放:无论 fn 成功、失败还是 panic,defer 释放。`expire` 必须 > 0(进程崩溃兜底不死锁),fn 耗时必须在 expire 前完成,fn 内禁止长耗时 IO(LLM/DB 调用);锁粒度按业务唯一键尽量小 +- **裸 `go` 允许的例外**:`go func(){ wg.Wait(); close(ch) }()` 收尾惯用法、SSE 心跳、流式管道(Stream 读写)等长生命周期/非工作负载协程 + +## 文档职责(三文档体系) + +| 文档 | 职责 | 何时补充/更新 | +|---|---|---| +| CLAUDE.md(本文件) | 公司通用开发规范:分层职责、代码模式、并发/事务/缓存约束、流程 | 规范变化时 | +| README.md | 项目功能介绍:架构、数据流、表清单、功能模块、API 清单、使用说明 | 功能增减时 | +| 技术设计.md | 实现细节与技术决策:DDL、检索参数、风险与备选方案 | 关键技术决策/参数变化时 | + +## 开发流程(文档驱动,硬性要求) + +永远以文档驱动开发:用户提出开发需求 → 先给出实现方案(技术选型、影响面、改动清单) → **用户确认后先补充文档再动手写代码**。补充哪个文档取决于内容性质:规范 → 本文件,功能 → README,实现细节/技术决策 → 技术设计.md。禁止未经确认直接开发,禁止先写代码后补文档。 + +## 数据访问规范(硬性要求) + +- **事务**:涉及多张表的增删改操作必须包数据库事务,禁止逐表裸调用。事务放 dao 层方法内,service 层负责编排;`tx.Begin` 后必须用 `defer` 防护已提交后的二次 Rollback +- **SQL 单表约束**:每个 SQL 只允许访问一张表,禁止 JOIN 与跨表子查询(`IN (SELECT ...)` / `EXISTS`);跨表数据一律拆为多条单表 SQL + 应用层内存组装——先取外键 id 列表,再对目标表 `IN` 查询;`IN` 参数须按 ≤100 分批(SQLite 变量数上限 999) +- **禁止 N+1 查询**:禁止在循环中逐条查库。循环场景一律改为批处理——一次 `ListByXxx` 取回后按外键在内存分组 +- **缓存一致性**:DAO 查询走缓存(TTL 来自 `database.cache.ttl`),写操作后必须清对应缓存 +- **批处理 SQL**:批量写入用 `InsertAll` 类方法,批量删除用 `IN` 子句,禁止循环单条 INSERT/DELETE +- **配置即使用**:config.yml 中出现 redis / mq 等中间件配置时,代码必须实际接入使用,禁止"配置了但代码不用"或"代码写死但配置缺失" +- **消息/回调幂等**:接入 MQ / Webhook 时,消费与回调处理必须幂等——MQ 至少一次语义、webhook 失败重试都可能重复投递同一事件,禁止依赖"只投一次"假设。幂等手段:以业务唯一键(如 `任务ID + 事件类型`)先查重或建唯一约束再落库,重复事件直接忽略;重试与补偿逻辑同样要防重复执行 + +## 运维部署规范(硬性要求) + +- **部署形态**:Docker Compose 单机部署,前后端一体单端口;运行时数据目录挂载持久化,容器重建不丢数据。部署文件在项目根目录(`Dockerfile` / `docker-compose.yml`),启动与使用见 README +- **数据即文件**:文件型存储,备份 = 打包「数据库目录(小而关键)+ 上传文件目录(大而可重建)」两个目录;迁移 = 拷贝到新机器即可 +- **运行时数据与代码分离**:数据目录不提交 git;**删除即丢数据,改动前先确认** +- **配置即文件**:项目配置文件为唯一配置入口(监听端口、并发度等),环境变量覆盖无效 + +## 约定 + +- controller 方法签名固定为 `(ctx, *dto.XxxReq) (*dto.XxxRes, error)`,实例注册模式 `var Xxx = &xxx{}` +- **参数校验优先用 GoFrame DTO 校验**:请求结构体用 `v` tag(required / regex / in 等)声明,框架自动校验并返回错误,controller 不手写校验;仅 DTO 表达不了的业务规则(跨字段依赖、查库校验如重名、取值范围依赖配置)放 service。JSON 格式解析可留在 controller 或下沉 service,但须保持与调用点一致 +- 响应组装(实体 → DTO 字段映射)在 controller 进行 +- service 方法签名 ctx 开头,错误统一用 `gerror` +- 编译验证:`go build ./...` diff --git a/README.md b/README.md new file mode 100644 index 0000000..9f5add1 --- /dev/null +++ b/README.md @@ -0,0 +1,217 @@ +# 三十六计小课堂 + +一款面向低龄儿童(4-8 岁)的闯关式学习《三十六计》应用。不教历史故事,而是把每一条计策翻译成**现代生活场景**,让孩子在校园、家庭、公园等熟悉的情境中学会"遇到问题怎么想、怎么做"。 + +## 功能特性 + +- **分支情境闯关**:36 计 × 每计 1-3 个现代情境,每个情境是一棵决策树——环境场景、人物、道具构成每个决策的要素,不同选择走向不同分支、产生不同结果,多条路线都能拿到最佳结果;每步即时反馈"智慧点评";**通关 = 到达最佳终局,完美 = 走完所有分支,完美才解锁下一关**;未通关走错路线扣教训分 +- **多样互动**:闯关不止选项点击——道具选择、步骤排序、动作过关(跳跃/攀爬/躲避/奔跑,一套组件配置驱动)、拖拽放置、接取收集、找线索、连线配对共 8 类互动形态按年龄适配;操作型互动成功/失败同样走向对应决策分支,教育逻辑不受互动形式影响 +- **计策学堂**:每章开头先认识计策——卡通图文讲解名称、拼音、释义与"什么时候用它",再进情境践行,构成认知 → 践行 → 反思完整学习闭环 +- **智慧总结**:每章闯关完成后一道反思题"哪个说法总结了这条计策",把直觉选择转成语言认知,答对奖励积分 +- **生活践行**:家长中心下发生活小任务(把游戏里的计谋用到真实生活),孩子完成、家长确认后奖励积分,知行合一 +- **成长守护**:单次会话一关(5-10 分钟)、每日学习限额与休息提醒由家长端设置;等级称号(小学徒→小军师→大将军)见证成长 +- **年龄分级**:4-6 岁启蒙版(全量拼音 + 朗读)与 6-8 岁进阶版(拼音辅助可关),按注册年龄段自动过滤内容 +- **闯关地图**:三十六计按六套分组(胜战计/敌战计/攻战计/混战计/并战计/败战计),依次解锁 +- **游戏化激励**:星星评分、完美标记(钻石)、计策卡收集(集齐 36 张成大成就)、徽章成就、每日签到、积分 +- **奖品兑换**:积分兑换虚拟奖品(即时到账)与实物奖品(家长确认 + 兑换码) +- **家长陪伴**:家长账号注册登录,下挂多个孩子档案(年龄/进度独立);家长中心查看学习报告、确认实物奖品 +- **再玩一次**:已通关情境随时可重玩,补完剩余分支看不同结果(不计分无压力),多路线价值由"完美"机制天然覆盖 +- **章末温故**:每章完美通关后自动回顾之前 2-3 个已学计谋的关卡(选项打乱),完成解锁下一章并奖励积分,防遗忘 +- **多端覆盖**:一套 uni-app 代码发布 Android / iOS / 平板 / 微信小程序 / H5 +- **管理后台**:计策、情境关卡、决策节点、奖品、徽章内容运营,家长/孩子与兑换管理 + +## 技术架构 + +``` +┌─────────────┐ ┌──────────────┐ +│ ui-src/ │ │ admin-src/ │ +│ uni-app │ │ Vue3 + EP │ +│ 五端前台 │ │ 管理后台 │ +└──────┬──────┘ └──────┬───────┘ + │ HTTP / JSON │ + └────────┬────────┘ + ┌────────▼────────┐ + │ Go + GoFrame │ + │ controller → service → dao │ + │ 鉴权中间件 · 文件解析 · 缓存 │ + └────────┬────────┘ + ┌────────▼────────┐ + │ SQLite (data/) │ ← 种子数据启动初始化,后台可迭代 + └─────────────────┘ +``` + +- **后端**:Go + GoFrame,分层 `controller → service → dao`,详见 CLAUDE.md +- **前端前台**:uni-app (Vue 3),一套代码五端,生产产物由后端托管(H5 端)或独立打包发布 +- **前端后台**:Vue 3 + Element Plus 独立 Web 工程,产物由后端托管 +- **数据库**:SQLite 单文件,运行时数据在 `data/`,上传文件在 `workspace/`,均不提交 git +- **部署**:Docker Compose 单机,前后端一体单端口,数据目录挂载持久化 + +## 目录结构 + +| 目录 | 职责 | +|---|---| +| common/ | 通用层:HTTP 服务与鉴权、文件解析、中文分词、向量 JSON、DAO 基类、查询缓存、协程池 | +| biz/consts/ | 常量集中地:表名、状态、内容类型、协程池默认大小 | +| biz/model/ | entity(表结构)、dto(请求/响应 + 路由)、domain(领域模型) | +| biz/dao/ | 单表数据访问,每表一个文件 | +| biz/service/ | 业务逻辑:规则校验、事务、跨表组装、积分/兑换、闯关判分 | +| biz/controller/ | 接口层:接收参数、调用 service、组装返回值 | +| ui-src/ | uni-app 前台工程(五端) | +| admin-src/ | 管理后台 Web 工程 | +| data/ | SQLite 数据库(运行时数据,不提交 git) | +| workspace/ | 上传文件(图片/音频,不提交 git) | + +## 数据流 + +1. 前端调用 HTTP API(JSON),经鉴权中间件 → controller +2. controller 接收参数(DTO `v` tag 自动校验)→ 调用 service +3. service 执行业务规则(判分、积分计算、兑换校验)→ 调用 dao +4. dao 单表访问 SQLite,读查询走缓存,写操作后清对应缓存 +5. 返回值经 controller 组装 DTO 回前端 + +## 功能模块 + +### 前台(多端) + +| 模块 | 说明 | +|---|---| +| 家长中心 | 家长注册/登录(微信 openid 或手机号)、孩子档案管理(含每日学习限额)、学习报告、实物奖品确认、生活任务确认 | +| 计策学堂 | 每章开头的认知环节:图文/动画讲解计策名称、拼音、释义、使用时机 | +| 关卡地图 | 36 计按六套分组展示,星星进度、解锁状态、计策卡图标 | +| 情境闯关 | 每关一棵决策树:环境场景 + 人物冲突 + 道具行为构成决策要素,入口 → 多样互动(选择/排序/动作过关-跳跃攀爬躲避奔跑/拖拽/找线索,按年龄适配)→ 多路线通往不同终局;通关=到达最佳终局,完美=走完全部分支解锁下一关;未通关走错扣教训分 | +| 章末温故 | 每章完美后自动出现:随机抽 2-3 个已学计谋关卡重温(选项打乱),完成解锁下一章 + 奖励积分 | +| 智慧总结 | 每章完成后的反思题("哪个说法总结了这条计策"),答对 +5 分 | +| 生活践行 | 章完美后下发的真实生活小任务(家长引导完成并确认,得积分) | +| 成长等级 | 按完美关卡数升级的称号体系(小学徒→小军师→大将军) | +| 计策卡 | 通关计策单元解锁对应卡片,集卡图鉴 | +| 积分 | 闯关得分、每日签到,积分流水明细 | +| 徽章 | 成就徽章(集卡、连续学习、通关数、复习、探索等),自动解锁 | +| 奖品 | 积分兑换虚拟/实物奖品,兑换记录与状态 | +| 朗读拼音 | 拼音随内容一次性标注入库、朗读音频 TTS 生成(写时一次性,读取零计算),低龄档全量开启 | + +### 后台(Web) + +| 模块 | 说明 | +|---|---| +| 内容管理 | 计策、情境关卡、决策节点(含互动形态配置)与分支选项 CRUD(含音频/图片资源上传) | +| 元素库管理 | 场景/人物/道具元素 CRUD 与素材上传 | +| 奖品管理 | 奖品定义、积分价格、库存 | +| 徽章管理 | 徽章定义与解锁条件 | +| 用户管理 | 家长/孩子列表、孩子进度与积分查看 | +| 兑换管理 | 兑换记录、实物发货/确认、兑换码生成 | +| 生活任务管理 | 生活践行任务配置(关联计策、任务文案、奖励积分) | +| 数据统计 | 节点卡点分析:各节点/选项选择分布、失败终局到达率,按关卡聚合 | + +## 数据表清单 + +| 表 | 职责 | +|---|---| +| parent | 家长账号:微信 openid、手机号、昵称 | +| child | 孩子档案:所属家长、昵称、年龄段、积分余额、每日学习限额(学习主体,进度/积分/兑换均按孩子) | +| strategy | 计策:名称、拼音、释义、分组、图标、解锁前置、学堂内容、总结问题 | +| level | 情境关卡:所属计策、情境主题、年龄段、排序、内容版本号 | +| element | 元素库:场景(环境)/ 人物 / 道具,决策树的结构性骨架(关卡关联场景、节点关联人物、选项关联道具) | +| scene_node | 情境节点:决策节点(含互动形态)/ 终局节点(含最佳/良好/失败评级)、关联人物 | +| node_option | 分支选项:行为文本、使用道具、指向的下一节点、即时点评 | +| user_progress | 闯关进度:孩子 × 关卡、星星、完美标记、得分、通关时内容版本 | +| chapter_review | 章末温故记录:孩子 × 章节、复习关卡快照、完成状态 | +| user_route_log | 决策路径流水:孩子每步选择记录,支撑学习报告与内容优化 | +| user_collection | 计策卡收集:孩子 × 计策 | +| badge | 徽章定义与解锁条件 | +| user_badge | 孩子已获徽章 | +| point_log | 积分流水:变动、原因类型、余额快照 | +| life_task | 生活践行任务:关联计策、任务描述、家长引导语、确认奖励积分 | +| life_task_log | 生活任务完成记录:孩子 × 任务、家长确认状态 | +| prize | 奖品:积分价格、库存、类型(虚拟/实物) | +| redemption | 兑换记录:孩子 × 奖品、状态流转、兑换码 | +| admin_user | 后台管理员账号 | + +## API 清单 + +### 前台(`/api/` 前缀,家长账号体系) + +| 接口 | 方法 | 说明 | +|---|---|---| +| POST /api/parent/register | POST | 家长注册(微信 openid 或手机号) | +| POST /api/parent/login | POST | 家长登录 | +| POST /api/parent/child/create | POST | 创建孩子档案(昵称、年龄段) | +| POST /api/parent/child/update | POST | 更新孩子资料(昵称、头像、年龄段、每日学习限额) | +| GET /api/parent/child/list | GET | 孩子档案列表(含各孩子积分余额) | +| GET /api/parent/report | GET | 学习报告(按孩子:通关数、星星、近期活动) | + +### 内容与闯关 + +| 接口 | 方法 | 说明 | +|---|---|---| +| GET /api/strategy/list | GET | 关卡地图:计策列表 + 进度/解锁状态 | +| GET /api/strategy/detail | GET | 计策详情(含学堂内容、总结问题)+ 关卡列表 | +| GET /api/level/detail | GET | 关卡入口节点、情境信息与元素(环境/人物/道具,含素材地址) | +| POST /api/level/choose | POST | 提交当前节点 + 所选选项(操作型互动提交成功/失败出口),返回下一节点;到达终局时结算(未通关结算积分/教训分,已通关不计分只记完成度) | +| POST /api/review/start | POST | 开始章末温故:随机抽 2-3 个已学计谋关卡返回 | +| POST /api/review/complete | POST | 温故完成:校验后解锁下一章 + 发放奖励积分 | +| POST /api/summary/submit | POST | 智慧总结答题(strategy_id + 选项),答对 +5 分(每章一次) | +| GET /api/task/list | GET | 生活任务列表(含状态:已下发/待确认/已确认) | +| POST /api/task/confirm | POST | 家长确认生活任务完成(child_id + task_id + 可选照片),发放积分 | +| GET /api/collection/list | GET | 已收集计策卡 | + +### 积分与奖励 + +| 接口 | 方法 | 说明 | +|---|---|---| +| GET /api/points/info | GET | 积分余额 | +| GET /api/points/log | GET | 积分流水 | +| POST /api/signin | POST | 每日签到 | +| GET /api/badge/list | GET | 徽章列表与已获状态 | +| GET /api/prize/list | GET | 可兑换奖品 | +| POST /api/prize/redeem | POST | 积分兑换奖品 | +| GET /api/redemption/list | GET | 我的兑换记录 | + +### 后台(`/api/admin/` 前缀,管理员鉴权) + +| 接口 | 方法 | 说明 | +|---|---|---| +| POST /api/admin/login | POST | 管理员登录 | +| GET/POST /api/admin/strategy/… | GET/POST | 计策 CRUD | +| GET/POST /api/admin/level/… | GET/POST | 关卡 CRUD | +| GET/POST /api/admin/node/… | GET/POST | 情境节点(决策/终局)CRUD | +| GET/POST /api/admin/option/… | GET/POST | 分支选项 CRUD | +| GET/POST /api/admin/element/… | GET/POST | 元素库(场景/人物/道具)CRUD | +| GET/POST /api/admin/task/… | GET/POST | 生活任务 CRUD | +| GET /api/admin/stats/level | GET | 节点卡点统计(选择分布、失败率) | +| GET/POST /api/admin/prize/… | GET/POST | 奖品 CRUD | +| GET/POST /api/admin/badge/… | GET/POST | 徽章 CRUD | +| GET /api/admin/user/list | GET | 用户列表 | +| GET /api/admin/redemption/list | GET | 兑换记录 | +| POST /api/admin/redemption/ship | POST | 实物发货/确认 | + +## 使用说明 + +### 本地开发 + +```bash +# 后端(Go 1.22+) +go run main.go + +# 前台(ui-src/,uni-app) +cd ui-src && npm install && npm run dev:h5 # H5 调试 +npm run dev:mp-weixin # 微信小程序调试 + +# 后台(admin-src/) +cd admin-src && npm install && npm run dev # vite 代理到后端 +``` + +后端启动时自动初始化数据库与种子数据(36 计内容、题目、奖品、徽章)。 + +### Docker 部署 + +```bash +docker compose up -d +``` + +- 单端口对外,前端产物(H5 + 后台)由后端托管 +- `data/`、`workspace/` 挂载持久化,容器重建不丢数据 +- App / 小程序端独立打包发布(见技术设计.md) + +### 数据备份 + +文件型存储:备份 = 打包 `data/`(小而关键)+ `workspace/`(大而可重建)两个目录。 diff --git a/技术设计.md b/技术设计.md new file mode 100644 index 0000000..adea0f0 --- /dev/null +++ b/技术设计.md @@ -0,0 +1,459 @@ +# 技术设计 + +《三十六计小课堂》实现细节与技术决策。规范见 CLAUDE.md,功能总览见 README.md。 + +## 1. 技术选型 + +| 层 | 选型 | 理由 | +|---|---|---| +| 前端前台 | uni-app (Vue 3 + Vite) | 一套代码发布 Android / iOS / 平板 / 微信小程序 / H5;Vue 3 语法与团队技术栈一致;小程序支持生态最成熟 | +| 前端后台 | Vue 3 + Element Plus | 后台为桌面 Web 场景,EP 组件成熟;独立工程 `admin-src/`,与 uni-app 前台互不影响 | +| 后端 | Go + GoFrame v2 | 团队既有规范(CLAUDE.md 分层),性能与部署简单 | +| 数据库 | SQLite(单文件) | 单机部署、文件型备份、零运维;写操作串行化规避并发锁 | +| 缓存 | gcache 内存 / 可选 redis | 读查询缓存(TTL 来自 `database.cache.ttl`),写后清缓存 | +| 部署 | Docker Compose 单机 | 前后端一体单端口,数据目录挂载持久化 | +| 文件存储 | 本地磁盘 `workspace/` | 图片/音频资源,HTTP 静态托管 | +| 朗读语音 | TTS 生成音频文件(预留) | 低龄档朗读;首期可先接入在线 TTS 或人工录制,音频文件落 workspace | + +不采用:Flutter(小程序支持不成熟)、Taro(需转 React)、原生双端(维护成本翻倍)。 + +## 2. 总体架构 + +``` +uni-app 前台(5端) ─┐ +Vue3+EP 后台 ──────┼── HTTP/JSON ──> Go 服务 ──> SQLite + │ │ + └── 静态资源(图片/音频) <── workspace/ +``` + +- 路由前缀:前台 `/api/`,后台 `/api/admin/`(管理员鉴权) +- 种子数据:启动时检测 `strategy` 表为空则从 Go embed 的 JSON 导入,后台运营可直接改库迭代 +- 资源访问:上传文件存 `workspace/uploads/`,数据库存相对路径,静态路由映射 + +## 3. 数据库设计(DDL) + +表名集中在 `biz/consts/table_name.go`,状态常量在 `biz/consts/status.go`。 + +### parent(家长)与 child(孩子档案) + +家长注册登录(微信 openid 或手机号),下挂多个孩子档案。**学习主体是孩子**:进度、积分、计策卡、兑换均按 child 维度,家长负责注册、查看报告与确认实物奖品。 + +```sql +CREATE TABLE parent ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + openid TEXT UNIQUE, -- 微信小程序 openid + phone TEXT UNIQUE, -- 手机号(H5/App 注册) + password TEXT, -- bcrypt 哈希(手机号注册时) + nickname TEXT, + avatar TEXT, -- 头像文件相对路径 + status INTEGER NOT NULL DEFAULT 1, + created_at DATETIME, + updated_at DATETIME +); +CREATE INDEX idx_parent_openid ON parent(openid); + +CREATE TABLE child ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + parent_id INTEGER NOT NULL, + nickname TEXT, + avatar TEXT, + age_group TEXT NOT NULL DEFAULT '4-6', -- 年龄段档位:4-6 / 6-8 + points INTEGER NOT NULL DEFAULT 0, -- 积分余额(冗余,以 point_log 为准,写时同事务) + daily_limit_minutes INTEGER NOT NULL DEFAULT 0, -- 每日学习限额(0=不限,家长端设置,前端本地计时) + status INTEGER NOT NULL DEFAULT 1, + created_at DATETIME, + updated_at DATETIME +); +CREATE INDEX idx_child_parent ON child(parent_id); +``` + +后续各表中 `child_id` 均指孩子(原 user 维度表统一改为 child 语义)。 + +### strategy(计策) + +```sql +CREATE TABLE strategy ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, -- 计策名,如「声东击西」 + pinyin TEXT NOT NULL, -- 拼音 zhu sheng dong ji xi + group_no INTEGER NOT NULL, -- 六套分组:1胜战 2敌战 3攻战 4混战 5并战 6败战 + group_name TEXT NOT NULL, + meaning TEXT NOT NULL, -- 儿童语言释义 + teach_content TEXT, -- 计策学堂:儿童化讲解(名称/释义/使用时机) + teach_image TEXT, + teach_audio TEXT, + summary_q TEXT, -- 智慧总结反思题 + summary_options TEXT, -- 反思题选项 JSON(含正确答案) + summary_audio TEXT, + icon TEXT, -- 计策卡图标 + sort_order INTEGER NOT NULL, -- 组内排序 + unlock_before INTEGER, -- 解锁前置:NULL=首计或按进度解锁 + status INTEGER NOT NULL DEFAULT 1 +); +CREATE INDEX idx_strategy_group ON strategy(group_no, sort_order); +``` + +解锁规则:`unlock_before` 为空或前置计策已通关(服务端校验)。 + +### level(关卡,每计 3-5 关) + +```sql +CREATE TABLE level ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + strategy_id INTEGER NOT NULL, + title TEXT NOT NULL, -- 场景标题,如「足球场上的假动作」 + scene_id INTEGER, -- 环境场景元素(element.id,type=1),整关发生场所 + scene_content TEXT NOT NULL, -- 情境描述(含拼音标注格式) + scene_image TEXT, + scene_audio TEXT, -- 情境朗读音频 + age_group TEXT NOT NULL DEFAULT '4-6', -- 内容年龄段标签,按孩子档位过滤 + content_version INTEGER NOT NULL DEFAULT 1, -- 内容版本:节点/选项改动时 +1 + sort_order INTEGER NOT NULL, + status INTEGER NOT NULL DEFAULT 1 +); +CREATE INDEX idx_level_strategy ON level(strategy_id, sort_order); +``` + +### scene_node(情境节点)与 node_option(分支选项) + +闯关以**决策树**组织:每关(level)一个情境,一棵树。`scene_node` 为节点(决策节点或终局节点),`node_option` 为决策节点的分支选项,选项通过 `next_node_id` 指向下一节点;多条不同分支可汇聚到同一终局节点(正确路线不唯一)。后台保存时校验有向无环(防死循环)。 + +```sql +CREATE TABLE scene_node ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + level_id INTEGER NOT NULL, + title TEXT, -- 节点标题(可选) + character_id INTEGER, -- 当前人物元素(element.id,type=2),冲突主体 + content TEXT NOT NULL, -- 情境描述(含拼音标注格式) + image TEXT, + audio TEXT, -- 情境朗读音频 + node_type INTEGER NOT NULL DEFAULT 1, -- 1 决策节点 2 终局节点 + interaction_type INTEGER NOT NULL DEFAULT 1, -- 互动形态:1选项选择 2道具选择 3步骤排序 4动作过关 5拖拽放置 6接取收集 7找线索 8连线配对 + config TEXT, -- 互动配置 JSON:动作过关子模式(jump/climb/dodge/run)+难度(速度/障碍密度)等 + result_type INTEGER NOT NULL DEFAULT 0, -- 终局评级:0 非终局 1 失败 2 良好 3 最佳 + is_entry INTEGER NOT NULL DEFAULT 0, -- 是否入口节点(每关一个) + sort_order INTEGER NOT NULL DEFAULT 1, + status INTEGER NOT NULL DEFAULT 1 +); +CREATE INDEX idx_node_level ON scene_node(level_id); + +CREATE TABLE node_option ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + node_id INTEGER NOT NULL, -- 所属决策节点 + text TEXT NOT NULL, -- 选项文本(行为描述) + prop_id INTEGER, -- 使用道具元素(element.id,type=3,可空) + audio TEXT, + next_node_id INTEGER, -- 指向下一节点(同 level 内) + feedback TEXT NOT NULL, -- 选择后的即时点评(儿童语言) + feedback_audio TEXT, + sort_order INTEGER NOT NULL DEFAULT 1, + status INTEGER NOT NULL DEFAULT 1 +); +CREATE INDEX idx_option_node ON node_option(node_id); +``` + +### element(元素库:场景 / 人物 / 道具) + +元素是决策树的结构性骨架(见 4.3 内容建模规范),统一一张表按类型复用,后台一个入口管理。 + +```sql +CREATE TABLE element ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + e_type INTEGER NOT NULL, -- 1 场景(环境) 2 人物 3 道具 + name TEXT NOT NULL, + image TEXT, + audio TEXT, -- 元素名称朗读 + description TEXT, -- 儿童语言介绍(道具点击提示等) + status INTEGER NOT NULL DEFAULT 1, + sort_order INTEGER NOT NULL DEFAULT 1 +); +CREATE INDEX idx_element_type ON element(e_type); +``` + +约束:决策节点(node_type=1)至少 2 个选项;终局节点(node_type=2)无选项;每关恰好 1 个入口节点;`next_node_id` 必须指向同 level 节点;保存/加载时拓扑校验无环;选项 `prop_id`、节点 `character_id`、关卡 `scene_id` 须指向存在的元素。 + +### 进度与成就 + +```sql +CREATE TABLE user_progress ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + child_id INTEGER NOT NULL, + level_id INTEGER NOT NULL, + stars INTEGER NOT NULL DEFAULT 0, -- 0-3 + score INTEGER NOT NULL DEFAULT 0, -- 本次得分 + perfect INTEGER NOT NULL DEFAULT 0, -- 1=本关所有终局都到达过(完美,解锁下一关) + content_version INTEGER NOT NULL DEFAULT 1, -- 通关时关卡版本(后台改内容后版本不一致,标记"可重新挑战") + completed_at DATETIME, + UNIQUE(child_id, level_id) +); + +CREATE TABLE chapter_review ( -- 章末温故:每章完美后随机抽旧关复习,完成解锁下一章 + id INTEGER PRIMARY KEY AUTOINCREMENT, + child_id INTEGER NOT NULL, + strategy_id INTEGER NOT NULL, -- 温故发生在哪一章 + level_ids TEXT NOT NULL, -- 抽中的复习关卡 JSON 快照,如 [1,3,5] + status INTEGER NOT NULL DEFAULT 1, -- 1 进行中 2 已完成 + created_at DATETIME, + UNIQUE(child_id, strategy_id) +); + +CREATE TABLE user_route_log ( -- 决策路径流水:每步选择一条,终局一条 + id INTEGER PRIMARY KEY AUTOINCREMENT, + child_id INTEGER NOT NULL, + level_id INTEGER NOT NULL, + node_id INTEGER NOT NULL, + option_id INTEGER NOT NULL DEFAULT 0, -- 0=终局记录 + result_type INTEGER NOT NULL DEFAULT 0, -- 终局评级(仅终局记录行) + created_at DATETIME +); +CREATE INDEX idx_route_log_child ON user_route_log(child_id, level_id, created_at); + +CREATE TABLE user_collection ( -- 计策卡:计策单元全关卡通关后解锁 + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + strategy_id INTEGER NOT NULL, + unlocked_at DATETIME, + UNIQUE(user_id, strategy_id) +); + +CREATE TABLE badge ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + icon TEXT, + cond_type INTEGER NOT NULL, -- 1集卡数 2连续签到 3通关数 4积分 5完美关卡数 6复习完成数 + cond_value INTEGER NOT NULL, -- 达成阈值 + status INTEGER NOT NULL DEFAULT 1 +); + +CREATE TABLE user_badge ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + badge_id INTEGER NOT NULL, + earned_at DATETIME, + UNIQUE(user_id, badge_id) +); +``` + +### life_task / life_task_log(生活践行任务) + +游戏内践行(情境闯关)之外的真实生活践行:每章完美后任务下发,家长引导孩子实践,家长确认为权威入口(天然防刷)。 + +```sql +CREATE TABLE life_task ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + strategy_id INTEGER NOT NULL, -- 关联计策(该章完美后下发) + title TEXT NOT NULL, -- 任务名,如「引开小猫」 + description TEXT NOT NULL, -- 给孩子看的任务描述 + guide TEXT NOT NULL, -- 给家长的引导语 + reward_points INTEGER NOT NULL DEFAULT 20, -- 家长确认后奖励积分 + status INTEGER NOT NULL DEFAULT 1 +); +CREATE INDEX idx_life_task_strategy ON life_task(strategy_id); + +CREATE TABLE life_task_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + child_id INTEGER NOT NULL, + task_id INTEGER NOT NULL, + status INTEGER NOT NULL DEFAULT 1, -- 1 已下发 2 待家长确认 3 已确认 + confirm_photo TEXT, -- 家长确认时上传的照片(可选) + confirmed_at DATETIME, + created_at DATETIME, + UNIQUE(child_id, task_id) +); +``` + +### 积分与奖品 + +```sql +CREATE TABLE point_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + change INTEGER NOT NULL, -- 正负变动 + reason_type INTEGER NOT NULL, -- 1闯关 2签到 3兑换扣减 4管理调整 + ref_id INTEGER, -- 关联业务 id(level/prize) + balance_after INTEGER NOT NULL, + created_at DATETIME +); +CREATE INDEX idx_point_log_user ON point_log(user_id, created_at); + +CREATE TABLE prize ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + description TEXT, + icon TEXT, + p_type INTEGER NOT NULL DEFAULT 1, -- 1虚拟(即时到账) 2实物(家长确认) + points_cost INTEGER NOT NULL, + stock INTEGER NOT NULL DEFAULT 0, -- -1 不限量 + status INTEGER NOT NULL DEFAULT 1, + sort_order INTEGER NOT NULL DEFAULT 1 +); + +CREATE TABLE redemption ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + prize_id INTEGER NOT NULL, + points_cost INTEGER NOT NULL, + status INTEGER NOT NULL DEFAULT 1, -- 1待领取(虚拟) 2待发货(实物) 3已发货 4已取消 5已领取(家长确认) + code TEXT, -- 兑换码(实物发货后生成) + created_at DATETIME, + updated_at DATETIME +); +CREATE INDEX idx_redemption_user ON redemption(user_id, created_at); +``` + +### admin_user(后台管理员) + +```sql +CREATE TABLE admin_user ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password TEXT NOT NULL, -- bcrypt 哈希 + status INTEGER NOT NULL DEFAULT 1, + created_at DATETIME +); +``` + +## 4. 关键设计 + +### 4.1 种子数据初始化 + +- 种子 JSON 用 `go:embed` 打进二进制(`biz/service/seed/`),含:元素库(场景约 15 / 人物约 10 / 道具约 20)、36 计全部内容、每计 1-3 个现代情境关卡(决策树:节点 + 分支选项 + 元素关联)、初始奖品、初始徽章、默认管理员账号 +- 启动时在 dao 层(各表 `init()` 内已有 `CREATE TABLE IF NOT EXISTS`)之后,service 层执行 `seed.EnsureSeeded()`:查 `strategy` 空表则按序插入,插入包事务;随后对全部内容调用标注服务生成拼音(见 4.6),TTS 音频异步生成,均只执行一次 +- 后台运营迭代 = 后台改内容 + 可选导出种子;不覆盖用户数据表 + +### 4.2 年龄段分级 + +- 两档:`4-6`(启蒙)/ `6-8`(进阶) +- 内容层:`level.age_group` 标签,列表接口按孩子档位过滤(采用"关卡带最低档位标签"策略:`4-6` 孩子只看 `4-6` 关卡,`6-8` 孩子两档都看,后续可加独立进阶关卡) +- 表现层:前端按档位开关拼音与朗读(低龄档默认全量开启)与互动形态(4-6 档仅选择类 + 低速操作类,6-8 档全开放) + +### 4.3 分支决策闯关(POST /api/level/choose) + +**决策树骨架(元素结构)**:元素是决定决策走向与分支的核心要素,内容按统一骨架构建: + +| 结构 | 元素 | 说明 | +|---|---|---| +| 关卡 | 环境场景(level.scene_id) | 冲突发生的场所,整关背景 | +| 决策节点 | 人物(scene_node.character_id) | 节点中冲突的主体("谁"遇到问题) | +| 选项 | 道具(node_option.prop_id) | 该行为使用的道具("用什么"),可空 | +| 分支 | next_node_id | 该行为在环境中的后果 | + +内容写作范式:**决策节点 = 环境中人物遇到的冲突;选项 = 对谁、用什么道具、做什么行为;分支 = 行为的后果**。LLM 生成初稿与人工审核均按此骨架;道具应与环境匹配(后台保存时校验元素存在,环境匹配为内容软规范)。元素不参与判分(分支由决策树显式定义),只决定决策的表达与走向解释。 + +**互动形态(interaction_type)**:决策节点的交互方式不限于选项点击,按 8 个大类组织:1 选项选择 / 2 道具选择 / 3 步骤排序 / 4 动作过关 / 5 拖拽放置 / 6 接取收集 / 7 找线索 / 8 连线配对。其中**动作过关类(4)是一套平台动作组件**,子模式由节点 `config` 配置:跳跃(jump,跳过陷阱坑/河沟抄近路)、攀爬(climb,翻墙越障)、障碍躲避(dodge,躲避巡逻)、操作奔跑(run,追击/逃回)——同一组件,地形与目标配置不同,实现成本一份、玩法多样;难度(速度/障碍密度)同样走 config 并按年龄段过滤(4-6 档低速,6-8 档全速)。**操作类节点(4-6)复用选项表预置"成功/失败"两个出口**(text=成功了/没成功,next 指向对应分支,feedback 写互动点评),choose 接口与决策树模型零改动;前端按 interaction_type + config 渲染对应互动组件(uni-app 触摸/CSS 动画实现,无需游戏引擎),互动结束后按结果提交对应选项。枚举可扩展。 + +**学习闭环**:每章按「计策学堂(认知)→ 情境闯关(践行)→ 完美 → 章末温故 → 智慧总结(反思)」推进:进入章节先看学堂(strategy.teach_*,图文 + 音频约 1 分钟)→ 逐关闯关;完美且温故完成后出现智慧总结一题(strategy.summary_*,答对 +5 分每章一次,答错展示正确解释);随后解锁下一章。单次会话 = 一关(5-10 分钟),匹配低龄注意力,地图随时进出。 + +**流程**(儿童逐步决策,每步即时反馈): +1. `GET /api/level/detail` 返回关卡入口节点与情境信息 +2. `POST /api/level/choose`(`level_id + node_id + option_id`): + - 服务端校验:node 属于 level、option 属于 node、关卡已解锁;校验通过返回下一节点内容 + - 若下一节点是**决策节点**:继续选择 + - 若下一节点是**终局节点**:返回终局评级与结算结果(星星、积分),本次闯关结束 +3. 每步选择后前端展示该选项的即时点评(feedback) + +**判分规则**(答案以服务端为准,防篡改): +- 终局评级 → 星数:最佳=3 星、良好=2 星、其他结果=0 星 +- 多条路线均可到达最佳终局(决策树分支汇聚),任何路线拿到最佳都算"正确领悟" +- 结算时机:首次到达某终局时结算,重复路线不重复结算(防刷分) + - 该关**未通关**时:最佳 +30 / 良好 +10 / 其他结果 -10(教训分,余额扣至 0 不为负;**同一关卡连续 2 次到达失败终局后不再扣分**,防重复挫败;扣分事件前端可爱化表达"没找到妙计,-10 军粮") + - 该关**已通关**后补分支:不结算积分,只记完成度(探索无压力,避免"强制走错路还扣分"的矛盾) +- **通关** = 到达任一最佳终局(3 星、发积分);**完美** = 本关所有终局都到达过(解锁下一关、+20 分、钻石标记) +- 单关完美后检查:该计策全部关卡是否完美 → 是则解锁计策卡(写 user_collection) + +**结算写库**(同事务):user_progress(取最高星 + perfect 标记 + 记录通关时 content_version)、point_log + `child.points` 同步、user_collection 解锁。并发防重入:同一孩子同一关结算用 `common.WithLock`(key=`child:{id}:level:{id}`)。 + +**路径记录**:每次 choose(含终局)写一条 user_route_log——支撑家长学习报告与运营内容分析(哪个节点卡点、哪些路线无人走);写失败仅记日志不阻断主流程(学习数据非关键路径)。 + +**再玩一次**:已通关关卡解锁校验放行,可随时重玩换路线(复用同一 choose 流程);已通关后不结算积分/星级,纯探索其他分支;完美判定由 user_route_log 统计(该关 distinct 终局节点数 = 终局总数)。 + +**章末温故(章节间复习)**:每章(计)完美后、解锁下一章前出现温故环节——`POST /api/review/start` 服务端从**之前已完美章节**随机抽 2-3 个计谋、每计抽 1 个已通关关卡(选项顺序打乱,防位置记忆)返回;孩子逐关走同一 choose 流程(已通关不计分);全部到达终局后 `POST /api/review/complete` 校验:写 chapter_review(UNIQUE(child_id, strategy_id) 防重复)、解锁下一章、发放复习奖励积分(+10)。末章无下一章不触发。后续可运营"复习专用新场景关卡",将温故升级为真迁移测试。 + +**内容版本**:后台保存节点/选项改动时 `level.content_version + 1`;地图接口对 `progress.content_version < level.content_version` 的关卡标记"内容已更新,可重新挑战",星级保留、重玩可刷新。 + +### 4.4 积分规则 + +| 来源 | 分值 | +| --- | --- | +| 未通关首次到达最佳终局 | +30 分(3 星) | +| 未通关首次到达良好终局 | +10 分(2 星) | +| 未通关首次到达其他终局 | -10 分(教训分,余额扣至 0 不为负) | +| 完美(全终局到达) | +20 分 | +| 章末温故完成 | +10 分(每章一次) | +| 智慧总结答对 | +5 分(每章一次) | +| 生活践行任务(家长确认) | +20 分(每任务一次) | +| 每日签到 | +5 分(按日期去重,同日重复请求直接忽略) | +| 兑换 | 扣减奖品积分(校验余额与库存) | +| 管理调整 | 后台手动 ± | + +签到去重:`point_log` 按 `child_id + reason_type=2 + 当天日期` 查重,重复直接返回"已签到"。 + +### 4.5 奖品兑换 + +- 虚拟奖品:余额校验 → 库存校验 → 扣积分 → 写 redemption(状态=待领取)→ 发放入库(如徽章/皮肤字段),同事务 +- 实物奖品:扣积分 → 写 redemption(状态=待发货)→ 兑换成功即弹庆祝动画"请爸爸妈妈帮你领取";后台发货后生成兑换码(`code`)→ 家长在家长中心"确认领取"(状态=5 已领取)→ 孩子端下次打开提示"奖品到啦" +- 库存扣减:事务内 `UPDATE prize SET stock=stock-1 WHERE id=? AND stock>0`,行级原子扣减防超卖(SQLite 单写者天然串行,`database is locked` 风险由重试兜底) + +### 4.6 拼音与朗读(写时一次性标注,读时零成本) + +拼音与音频都遵循"写时转换"原则:内容量小(约 250 节点)但读请求量大(所有端所有用户),标注做一次入库,前端读取零计算、五端一致。 + +- **拼音入库**:种子 JSON 只存原文 → 初始化时由 `common` 标注服务生成拼音(优先查多音字/专名词表,兜底拼音库,必要时 LLM 初稿 + 人工审核)→ 随原文一并入库(各内容表存 `*_pinyin` 字段);后台改文案保存时调用同一标注函数自动重标,仍是写时一次性 +- **多音字词表**:`biz/consts` 维护成语整体拼音(如「声东击西 shēng dōng jī xī」)与专名 override,标注时优先查表,保证教育内容准确性 +- **朗读音频**:与拼音同一"写时生成"流水线——TTS(如火山/微软)在初始化与后台保存时异步生成音频落 `workspace/uploads/audio/`,表存相对路径;文件名 `{表}_{内容id}_{文本hash}.mp3` 保证**幂等**(文本未变重复保存不重生成,后台改文案只重生成变更节点);生成失败不阻塞内容落库,后台提供"重新生成音频"按钮兜底,音频缺失时前端降级不播 +- **静态资源**:Go 静态路由托管 `workspace/uploads/`,URL 直接进前端;元素素材(场景/人物/道具图片与名称音频)同样按"写时一次性"生成与上传,随内容接口返回 + +### 4.7 缓存与并发 + +- 读接口(strategy 列表、level 详情、prize 列表、badge 列表)走 `gdb.CacheOption`,TTL 取 `database.cache.ttl` +- 写操作后清对应缓存:内容修改(后台)→ 清内容缓存;判分/兑换 → 清用户相关缓存 +- 内容型缓存为全量列表,可后台修改时全局失效;用户数据(进度/积分)不加缓存或短 TTL,避免一致性负担 +- 并行点:种子初始化无并行需求;批量节点/选项加载(≤100 分批 IN)主 goroutine 串行即可,无需 grpool +- 锁:兑换/签到等防重入场景用 `common.WithLock`(内存锁即可,单实例部署),key 用 `child:{id}:{业务}` + +### 4.8 鉴权与安全 + +- 前台家长账号体系:微信小程序用 `openid` 静默登录(code2session 换 openid);H5/App 手机号注册 + token(`gf_token` 或自签 JWT,config 配置)。登录后持 parent token +- 孩子维度接口(闯关/积分/兑换/报告)请求带 `child_id`,中间件校验归属(`child.parent_id == token.parent_id`),防越权访问他人孩子数据 +- 后台:管理员账号 + 独立 token,中间件校验 `admin_user` 身份 +- 判分、兑换等写接口一律服务端校验,前端只做展示 +- 上传接口(后台)限制文件类型与大小(图片/音频白名单) + +### 4.9 家长中心与学习报告 + +- 孩子档案:家长创建/修改多个孩子(年龄段、昵称),接口见 README API 清单 +- 学习报告(GET /api/parent/report):按孩子聚合——已通关计策数、星星总数、最近学习时间、各计策完成状态、复习/探索徽章获得情况 +- 路径摘要:按 user_route_log 统计孩子最近 N 关的终局分布(家长可看到"孩子在哪条路线上卡住/选择了失败分支") +- 实物奖品确认:redemption 列表按孩子展示,家长凭兑换码领取 +- 数据全部单表 SQL + 应用层内存组装(遵循无 JOIN 约束),报告接口低频调用可短缓存 + +### 4.10 践行与成长体系 + +**生活践行任务**(知行合一):章完美后任务下发(life_task 按 strategy 关联)→ 孩子端显示"和爸爸妈妈一起做"卡片 → 家长查看引导语、带孩子实践 → 家长确认(POST /api/task/confirm,可选照片)→ 积分到账 + 状态流转(1 已下发 → 2 待家长确认 → 3 已确认)。防刷:UNIQUE(child_id, task_id) + 家长确认为唯一权威入口。 + +**成长等级**:按完美关卡数映射称号(规则表在 biz/consts,如小学徒 → 小书童 → 小军师 → 军师 → 大将军),**派生值不落库**——结算接口计算并返回新等级,前端播升级动画;家长报告展示当前称号。 + +**兑换仪式感**:虚拟奖品兑换成功即庆祝动画;实物奖品兑换成功即弹"庆祝 + 请爸爸妈妈帮你领取"(预期管理),后台发货生成兑换码后家长在家长中心"确认领取"(状态 5 已领取),孩子端下次打开提示"奖品到啦"。 + +**使用时长管理**:家长端设置每日限额(child.daily_limit_minutes,0=不限);前端本地计时,达限额弹休息提醒(护眼);单次会话 = 一关(5-10 分钟)天然切分学习时段。服务端仅存设置与展示(报告"今日学习时长"按当日活动估算),不强制中断——低龄应用以家长监督为主。 + +## 5. 开发计划 + +| 里程碑 | 内容 | 验收标准 | +| --- | --- | --- | +| M1 骨架与闯关核心 | 前后端工程初始化、数据模型(家长/孩子/决策树/元素库/路径表/内容版本)+ 种子数据、家长注册登录与孩子档案、计策学堂、关卡地图、关卡详情(含元素)、分支决策与终局结算、进度与路径记录 | H5 端可完整走通「家长注册 → 建孩子档案 → 学堂 → 分支闯关(环境/人物/道具)→ 多路线拿最佳终局 → 完美解锁下一计」 | +| M2 游戏化与成长 | 积分流水、签到、徽章、计策卡、完美与解锁流转、章末温故、智慧总结、成长等级、生活践行任务、时长管理、道具选择/步骤排序/接取收集互动、拼音/朗读资源接入 | 全激励闭环可用,完美解锁与教训分生效,温故/总结/生活任务闭环生效,成长等级与时长守护可用,多样互动(选择/排序/接取)可用,低龄档朗读生效 | +| M3 兑换与后台 | 奖品兑换、兑换记录与发货(含家长确认领取与庆祝动画)、后台内容/奖品/徽章/生活任务/家长与孩子管理、学习报告、节点卡点统计、动作过关组件(跳跃/攀爬/躲避/奔跑)+ 拖拽/找线索 | 后台改内容前台即时生效(内容版本标记可重新挑战),实物兑换流程走通(确认领取闭环),家长报告与卡点统计可看,动作过关互动多端可用 | +| M4 多端发布 | H5 内测 → 微信小程序提审 → Android/iOS/平板打包、平板布局适配 | 五端全部可运行 | + +## 6. 风险与备选方案 + +| 风险 | 影响 | 应对 | +| --- | --- | --- | +| 36 计决策树内容策划量大 | M1 依赖内容质量 | 首期每计先 1 个情境(每关 5-8 节点,内容量 36×7≈250 节点),种子 JSON 单独文件便于内容团队迭代;可引入 LLM 生成决策树初稿 + 人工审核(common 已有 LLM 编排能力) | +| SQLite 并发写锁 | 兑换/判分高峰期 `database is locked` | 写操作串行化 + 重试兜底;数据量小(单家庭应用),单实例部署下风险可控;必要时切 WAL 模式 | +| uni-app 多端差异 | 小程序/App 渲染差异、音频自动播放限制、操作型互动组件(触摸/CSS 动画)端差异 | 组件层封装平台差异;互动组件先 H5/小程序验证再 App 适配;音频走用户手势触发播放 | +| 小程序审核与儿童合规 | 儿童类目需备案/资质、按《儿童个人信息网络保护规定》做家长授权与最小化收集 | 家长账号注册即家长授权入口,注册协议写明;提前准备合规材料;H5 端先行上线验证 | +| 朗读音频版权/生成成本 | TTS 语音质量影响体验 | 首期接入商用 TTS,文件落库可随时人工替换 | +| 积分防刷 | 重复挑战刷分 | 首次到达终局才结算 + 服务端判分 + 兑换校验余额,日志留痕 | +| 低龄使用健康 | 单次/每日使用超时伤眼 | 前端本地计时 + 家长端限额设置 + 单次会话=一关(5-10 分钟) |