# learn-words **Repository Path**: jetydu/learn-words ## Basic Information - **Project Name**: learn-words - **Description**: 背单词 - **Primary Language**: C# - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-08 - **Last Updated**: 2026-08-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 单词记忆系统 · 开发文档 > 本文档面向开发者,介绍项目的架构、环境搭建、目录结构、数据库设计、认证机制、API 接口、前端结构、核心业务逻辑与部署方式。 --- ## 一、项目概览 单词记忆系统是一款轻量化英语学习工具,覆盖「单词录入 → 背诵 → 默写 → 选词测试 → 阅读理解 → 学习统计」全流程。系统支持**多用户登录**,所有学习数据按用户隔离。 ### 核心特性 - 自定义单词批量录入,支持按**年级**(三上 ~ 九下)分类管理。 - 背诵 / 默写 / 选词测试三种学习模式,各自独立统计错误次数。 - 学习数据统计:完成率、正确率、各模式错误数、薄弱单词 TOP、近 7 天趋势。 - 阅读理解:内置示例文章,随机出题 + 答案解析。 - **管理员权限**:管理员可见「单词列表」「系统设置」,普通用户不可见。 - 基于 JWT 的用户认证,数据按 `UserId` 隔离。 --- ## 二、技术栈 | 层 | 技术 | 说明 | | --- | --- | --- | | 前端 | Vue 3(组合式 API) | 视图层框架 | | 前端 | Vite | 构建与开发服务器 | | 前端 | Vue Router 4 | 前端路由 + 导航守卫 | | 前端 | Tailwind CSS | 样式方案 | | 前端 | TypeScript | 类型安全 | | 后端 | .NET 10(ASP.NET Core Web API) | RESTful 接口服务 | | 认证 | JWT Bearer(Microsoft.AspNetCore.Authentication.JwtBearer) | 无状态登录态 | | 数据库 | SQLite | 文件型数据库,免部署 | | ORM | FreeSql(`UseAutoSyncStructure`) | 自动建表/同步结构 | | 文档 | Swashbuckle.AspNetCore(Swagger UI) | 接口调试 | > 端口约定:后端 `http://localhost:5077`,前端开发服务器 `http://localhost:5173`(已配置 `/api` 代理转发到后端)。 --- ## 三、环境搭建 ### 3.1 前置依赖 - [.NET 10 SDK](https://dotnet.microsoft.com/) - [Node.js 18+](https://nodejs.org/)(含 npm 或 pnpm) ### 3.2 后端启动 ```powershell cd backend\WordMemory.Api dotnet restore dotnet run ``` 启动后: - API 基址:`http://localhost:5077/api` - Swagger 文档:`http://localhost:5077/swagger` > FreeSql 会自动创建 `words`、`users`、`settings`、`readings` 表(依据实体 `UseAutoSyncStructure`)。数据库文件为 `wordmemory.db`(位于后端项目运行目录)。 > > 启动时若 `readings` 表为空,会自动写入 6 篇示例阅读理解文章。 ### 3.3 前端启动 ```powershell cd frontend npm install # 或 pnpm install npm run dev # 开发模式,访问 http://localhost:5173 npm run build # 生产构建(含 vue-tsc 类型检查 + vite build) ``` ### 3.4 数据库配置 后端 `appsettings.json`: ```json "ConnectionStrings": { "DefaultConnection": "Data Source=wordmemory.db" } ``` 可直接修改为绝对路径或迁移到其它 SQLite 文件。 --- ## 四、目录结构 ``` 背单词/ ├─ README.md # 本文档(开发文档) ├─ 使用文档.md # 使用文档(含手动 SQL) ├─ backend/ │ └─ WordMemory.Api/ │ ├─ Controllers/ # HTTP 接口层 │ │ ├─ AuthController.cs # 注册 / 登录(公开接口) │ │ ├─ WordsController.cs # 单词增删改查、背诵、默写 │ │ ├─ QuizController.cs # 选词测试 │ │ ├─ StatsController.cs # 学习统计 │ │ ├─ SettingController.cs # 系统设置(每日学习量) │ │ └─ ReadingController.cs # 阅读理解 │ ├─ Services/ # 业务逻辑层 │ │ ├─ WordService.cs # 单词/设置/统计核心逻辑 │ │ ├─ UserService.cs # 注册/登录/JWT 生成 │ │ └─ ReadingService.cs # 阅读理解(随机获取 + 示例数据) │ ├─ Models/ # 数据实体(映射数据库表) │ │ ├─ Word.cs # words 表 │ │ ├─ User.cs # users 表 │ │ ├─ Setting.cs # settings 表 │ │ └─ Reading.cs # readings 表 │ ├─ Dtos/ # 请求/响应对象 │ │ ├─ AddWordRequest.cs # 新增单词(含年级) │ │ ├─ AuthDtos.cs # 注册/登录/响应(含 IsAdmin) │ │ ├─ WordResponse.cs # 单词/统计/每日统计 DTO │ │ ├─ ReadingDtos.cs # 阅读理解响应 DTO │ │ └─ ...(其余请求 DTO) │ ├─ Program.cs # 应用入口 / 服务注册 / 中间件 │ ├─ appsettings.json # 配置(连接串、JWT) │ └─ wordmemory.db # SQLite 数据库文件 └─ frontend/ ├─ src/ │ ├─ App.vue # 根组件(顶部导航,管理员过滤) │ ├─ api/index.ts # API 请求封装 │ ├─ router/index.ts # 路由 + 管理员守卫 │ ├─ types/index.ts # TS 类型 + 年级常量 │ ├─ views/ # 页面组件 │ │ ├─ Login.vue # 登录/注册 │ │ ├─ Memorize.vue # 背诵模式 │ │ ├─ Dictation.vue # 默写模式 │ │ ├─ Quiz.vue # 选词测试 │ │ ├─ Reading.vue # 阅读理解 │ │ ├─ Stats.vue # 学习统计 │ │ ├─ WordList.vue # 单词列表(管理员) │ │ └─ SystemSettings.vue # 系统设置(管理员) │ └─ main.ts # 入口 ├─ index.html ├─ vite.config.ts # 开发代理配置 └─ package.json ``` --- ## 五、数据库设计 ### 5.1 words 表(单词) | 字段 | 类型 | 说明 | | --- | --- | --- | | Id | INTEGER PK | 主键,自增 | | WordText | TEXT | 单词文本 | | Phonetic | TEXT | 音标 | | Meaning | TEXT | 中文释义 | | UnfamiliarCount | INTEGER | 背诵模式不熟悉次数 | | MemorizeCount | INTEGER | 背诵次数 | | DictationErrorCount | INTEGER | 默写模式错误次数 | | DictationCount | INTEGER | 默写次数(每次默写 +1) | | QuizCount | INTEGER | 选词学习次数 | | QuizErrorCount | INTEGER | 选词模式错误次数 | | CreateTime | DATETIME | 添加时间 | | FirstTime | DATETIME | 今日首次学习时间(用于每日新词标记) | | LastMemorizeTime | DATETIME | 最后学习时间 | | UserId | INTEGER | 所属用户 ID | | Grade | INTEGER | 年级(1=三上 2=三下 3=四上 4=四下 …) | ### 5.2 users 表(用户) | 字段 | 类型 | 说明 | | --- | --- | --- | | Id | INTEGER PK | 主键,自增 | | Username | TEXT | 用户名(唯一) | | PasswordHash | TEXT | 密码哈希(SHA256) | | IsAdmin | INTEGER | 是否管理员(0/1) | | CreateTime | DATETIME | 创建时间 | ### 5.3 settings 表(系统设置) | 字段 | 类型 | 说明 | | --- | --- | --- | | Id | INTEGER PK | 主键,自增 | | Key | TEXT | 设置键名(唯一) | | Value | TEXT | 设置值 | | Description | TEXT | 描述 | | UpdateTime | DATETIME | 更新时间 | | UserId | INTEGER | 所属用户 ID | 目前唯一键:`dailyMemorizeCount`(每日学习量,三个模式共享)。 ### 5.4 readings 表(阅读理解) | 字段 | 类型 | 说明 | | --- | --- | --- | | Id | INTEGER PK | 主键,自增 | | Title | TEXT | 标题 | | Content | TEXT | 文章内容 | | Question | TEXT | 问题 | | OptionA / OptionB / OptionC / OptionD | TEXT | 四个选项 | | AnswerIndex | INTEGER | 正确答案索引(0=A 1=B 2=C 3=D) | | Explanation | TEXT | 答案解析 | | CreateTime | DATETIME | 创建时间 | --- ## 六、认证与权限 ### 6.1 JWT 认证 - 注册/登录接口公开,其余接口均需 `Authorization: Bearer `。 - token 中携带 `NameIdentifier`(用户 ID)与 `Name`(用户名)。 - 前端 `request()` 封装自动附带 token;401 时自动清理并跳转登录页。 ### 6.2 管理员权限 - `users.IsAdmin = 1` 表示管理员(注册用户默认为 0)。 - 登录/注册响应 `AuthResponse` 返回 `isAdmin`,前端存入 localStorage。 - **菜单过滤**:`App.vue` 中「单词列表」「系统设置」标记 `adminOnly`,非管理员不显示。 - **路由守卫**:`router/index.ts` 中 `/list`、`/settings` 为非管理员不可访问,直接跳转背诵模式。 ### 6.3 管理员绑定普通用户 - `users.ManagedUserId` 表示管理员绑定的普通用户 ID(可空)。 - 后端 `WordService.GetEffectiveUserIdAsync(userId)`:若当前用户是管理员且 `ManagedUserId` 有值,则返回绑定的普通用户 ID;否则返回自身 ID。 - 所有业务 Controller(单词、背诵、默写、选词、统计、设置)通过 `GetEffectiveUserIdAsync` 获取实际操作的 userId,因此**管理员的所有单词操作(含学习模式、统计、设置)都作用于所绑定普通用户的单词库**。 - 未绑定(`ManagedUserId = NULL`)的管理员操作自己的数据。 > 手动设置绑定:`UPDATE users SET ManagedUserId = (SELECT Id FROM users WHERE Username='普通用户名') WHERE Username='管理员用户名';` --- ## 七、API 接口 > 统一响应格式:`{ "code": 200, "data": ..., "message": ... }`。除登录/注册外均需 JWT。 ### 7.1 认证 Auth | 方法 | 路径 | 说明 | | --- | --- | --- | | POST | /api/auth/register | 注册(返回 token + isAdmin) | | POST | /api/auth/login | 登录(返回 token + isAdmin) | ### 7.2 单词 Words | 方法 | 路径 | 说明 | | --- | --- | --- | | POST | /api/words | 批量新增单词(body: `{rawInput, grade}`) | | GET | /api/words | 分页查询(search / isMemorized / page / pageSize) | | GET | /api/words/{id} | 查询单个单词 | | PUT | /api/words/{id} | 修改单词 | | DELETE | /api/words/{id} | 删除单词 | | DELETE | /api/words/batch | 批量删除(body: `{ids}`) | ### 7.3 背诵模式 | 方法 | 路径 | 说明 | | --- | --- | --- | | GET | /api/words/memorize/list?count=&grade= | 获取背诵列表(grade 可选,0/空=全部) | | GET | /api/words/random?grade= | 随机一个单词 | | PUT | /api/words/{id}/memorize | 标记掌握/不熟悉(body: `{action: "mastered"\|"unfamiliar"}`) | ### 7.4 默写模式 | 方法 | 路径 | 说明 | | --- | --- | --- | | GET | /api/words/dictation/list?count=&grade= | 获取默写列表 | | GET | /api/words/dictation/random?grade= | 随机一个单词 | | POST | /api/words/dictation/check/{id} | 校验默写(body: `{userInput}`) | ### 7.5 选词测试 | 方法 | 路径 | 说明 | | --- | --- | --- | | GET | /api/quiz/generate?count=&grade= | 生成题目(4 选项) | | POST | /api/quiz/submit | 提交答案(含每题的 correctOptionIndex) | ### 7.6 统计 / 设置 / 阅读 | 方法 | 路径 | 说明 | | --- | --- | --- | | GET | /api/stats | 学习统计汇总 | | GET | /api/setting/dailyMemorizeCount | 获取每日学习量 | | PUT | /api/setting/dailyMemorizeCount/{count} | 更新每日学习量 | | GET | /api/reading/random | 随机一篇阅读理解 | --- ## 八、前端结构 ### 8.1 页面与路由 | 路由 | 组件 | 权限 | 说明 | | --- | --- | --- | --- | | /login | Login.vue | 公开 | 登录 / 注册 | | /memorize | Memorize.vue | 登录 | 背诵模式 | | /dictation | Dictation.vue | 登录 | 默写模式 | | /quiz | Quiz.vue | 登录 | 选词测试 | | /reading | Reading.vue | 登录 | 阅读理解 | | /stats | Stats.vue | 登录 | 学习统计 | | /list | WordList.vue | 管理员 | 单词列表 | | /settings | SystemSettings.vue | 管理员 | 系统设置 + 新增单词 | ### 8.2 年级筛选 三个学习模式页面顶部均有「年级」下拉框,默认「全部」;切换后重新请求(`grade` 查询参数)。年级常量 `GRADE_OPTIONS` 与 `getGradeLabel()` 在 `types/index.ts` 中定义。 ### 8.3 请求封装 `api/index.ts` 统一处理 token、401 跳转、响应解包(`code !== 200` 抛错)。 --- ## 九、核心业务逻辑 ### 9.1 每日新词标记(InitTodayWords) 三个模式调用前先执行:若今天还没有任何单词被标记(`FirstTime` 为空),则把最早的一批未标记单词(按 Id 升序,取 `count` 个)设置 `FirstTime = now`,作为「今日新词」。带 `grade` 筛选时仅标记该年级。 ### 9.2 背诵模式选词(GetMemorizeWordsAsync) 条件:`(FirstTime 在今天 且 MemorizeCount == 0)` **或** `UnfamiliarCount > 0`,按 `UnfamiliarCount` 降序。 即:今日新词 + 历史不熟悉单词。 ### 9.3 默写模式选词(GetDictationWordsAsync) 条件:`(FirstTime 在今天 且 DictationCount == 0)` **或** `DictationErrorCount > 0`,按 `DictationErrorCount` 降序。 即:今日新词 + 历史默写错误单词。 ### 9.4 选词测试选词(GenerateQuizAsync) 条件:`(FirstTime 在今天 且 QuizCount == 0)` **或** `QuizErrorCount > 0`,按 `QuizErrorCount` 降序。干扰项从同年级其它单词释义中随机取 3 个。 ### 9.5 错误计数规则 | 模式 | 操作 | 计数变化 | | --- | --- | --- | | 背诵 | 标记「已掌握」 | `UnfamiliarCount--`(>0 时减)、`MemorizeCount++` | | 背诵 | 标记「不熟悉」 | `UnfamiliarCount++`、`MemorizeCount++` | | 默写 | 写对 | `DictationErrorCount--`(>0 时减)、`DictationCount++` | | 默写 | 写错 | `DictationErrorCount++`、`DictationCount++` | | 选词 | 答对 | `QuizErrorCount--`(>0 时减)、`QuizCount++` | | 选词 | 答错 | `QuizErrorCount++`、`QuizCount++` | ### 9.6 单词录入格式 - `单词/音标/中文` → 去首 `*` 号、音标加 `[]` - `单词/中文`(无音标) - 多条按行拆分;重复单词(同用户)跳过。 ### 9.7 数据隔离 `WordService` 每个公开方法均接收 `userId`,所有 `WHERE` 附加 `UserId == userId`。Controller 通过 `User.FindFirstValue(ClaimTypes.NameIdentifier)` 取当前用户 ID。 --- ## 十、构建与部署 ### 10.1 后端发布 ```powershell cd backend\WordMemory.Api dotnet publish -c Release -o ./publish ``` 将 `publish/` 与 `wordmemory.db`(或重新生成)一并部署,配置反向代理(Nginx / IIS)转发到 `http://localhost:5077`。 ### 10.2 前端发布 ```powershell cd frontend npm run build ``` 产物在 `frontend/dist/`,托管于任意静态服务器。需将 `/api` 请求代理到后端地址(生产环境在服务器配置中处理)。 ### 10.3 生产环境检查清单 - [ ] 替换 `appsettings.json` 中 JWT `SecretKey` 为强随机串。 - [ ] 确认 `ConnectionStrings` 指向正确的 SQLite 路径且文件可写。 - [ ] 前端 `/api` 代理指向正确后端域名。 - [ ] 生产环境建议关闭或保护 Swagger。 --- ## 十一、已知限制 / 后续可优化 1. **密码哈希无盐值**:当前仅 SHA256,建议升级为带盐慢哈希(如 PBKDF2 / bcrypt)。 2. **JWT 无服务端吊销**:注销仅清除客户端 token,建议引入刷新令牌或黑名单。 3. **设置项单一**:目前仅 `dailyMemorizeCount`,如需各模式独立可扩展 Setting 键值。 4. **阅读理解无管理界面**:目前靠启动时示例数据 + 手动 SQL 维护,可后续增加 CRUD 管理页。 5. **统计页图表**:当前以表格为主,可按需接入图表库(如 ECharts)。 --- ## 十二、常见问题(FAQ) **Q:启动后数据库表没生成?** A:FreeSql `UseAutoSyncStructure(true)` 在首次 DB 操作时建表,可先访问一次接口或 Swagger 触发。 **Q:前端一直跳登录页?** A:检查后端是否正常返回 token;确认 `vite.config.ts` 的 `/api` 代理目标端口(默认 5077)与后端一致。 **Q:如何把某用户设为管理员?** A:`UPDATE users SET IsAdmin = 1 WHERE Username = '用户名';`(需重启或重新登录)。 **Q:单词列表 / 系统设置打不开?** A:这两个菜单仅管理员可见,确认当前登录账号 `IsAdmin = 1`。