This commit is contained in:
2026-08-13 11:45:45 +08:00
commit 75cb62b283
5 changed files with 774 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
.git
.idea
workspace
ui-src/node_modules
ui-src/dist
+8
View File
@@ -0,0 +1,8 @@
# 运行时数据与本地环境
workspace/
.idea/
.DS_Store
# 前端
ui-src/node_modules/
ui-src/dist/
+85
View File
@@ -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 ./...`
+217
View File
@@ -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 APIJSON),经鉴权中间件 → 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/`(大而可重建)两个目录。
+459
View File
@@ -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.idtype=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.idtype=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.idtype=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, -- 关联业务 idlevel/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_reviewUNIQUE(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_minutes0=不限);前端本地计时,达限额弹休息提醒(护眼);单次会话 = 一关(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 分钟) |