From 8ba754daad3edd8ca537c2452018697692231503 Mon Sep 17 00:00:00 2001 From: WangDL Date: Sat, 9 May 2026 21:05:03 +0800 Subject: [PATCH] =?UTF-8?q?ci:=20=E6=94=B9=E7=94=A8=20host=20=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=20runner=20+=20=E7=BA=AF=20shell=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/deploy.yml | 22 +- docs/AI回答.md | 885 ++++++++++++++++++++++++++++++++++++ docs/credentials.md | 108 +++++ 3 files changed, 1007 insertions(+), 8 deletions(-) create mode 100644 docs/AI回答.md create mode 100644 docs/credentials.md diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 6dfd0d7..4aec2de 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -8,11 +8,16 @@ jobs: build-and-deploy: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 + - name: Checkout latest code + run: | + if [ -d /tmp/api-server ]; then + cd /tmp/api-server && git pull + else + git clone http://localhost:3000/suche-Hermes/api-server.git /tmp/api-server + fi - name: Build Docker image - run: docker build -t zhixi-api:latest . + run: cd /tmp/api-server && docker build -t zhixi-api:latest . - name: Stop old container run: docker stop zhixi-api 2>/dev/null || true @@ -29,17 +34,18 @@ jobs: -p 3001:3000 \ -e NODE_ENV=production \ -e PORT=3000 \ - -e DATABASE_URL="mysql://zhixi_user:Zhixi@2026!App@mysql-zhixi:3306/zhixi" \ + -e DATABASE_URL='mysql://zhixi_user:Zhixi@2026!App@mysql-zhixi:3306/zhixi' \ -e REDIS_HOST=redis-zhixi \ -e REDIS_PORT=6379 \ + -e REDIS_PASSWORD='Rds@nTsgKrcqAkbuf6PwJIFMZQzF' \ -e JWT_SECRET=98b1e7e377a40021ad7c46c55e467d2a218a89db7afc7c912780152ad64bdc45 \ -e AI_PROVIDER=mock \ - -e ENABLE_SWAGGER=false \ + -e ENABLE_SWAGGER=true \ + -e SWAGGER_USER=admin \ + -e SWAGGER_PASSWORD='Swgr@fmDentAYVXQUpG6oZDpJ' \ zhixi-api:latest - - name: Wait for startup - run: sleep 5 - - name: Health check run: | + sleep 8 curl -f http://localhost:3001/health || (docker logs zhixi-api --tail 30 && exit 1) diff --git a/docs/AI回答.md b/docs/AI回答.md new file mode 100644 index 0000000..81fcfc6 --- /dev/null +++ b/docs/AI回答.md @@ -0,0 +1,885 @@ +([Files][1])([Files][2])([Files][3])([Files][4])([Files][5]) + +可以。下面我把 **知习后端 Redis 设计** 单独整理一版。 + +你可以把它理解成: + +```text +MySQL:长期真实数据,必须可靠保存 +Redis:短期状态、缓存、队列、限流、锁、任务进度 +``` + +Redis **不需要建表**,需要设计的是: + +```text +1. Redis 用在哪些场景 +2. Key 怎么命名 +3. Value 存什么 +4. 过期时间多久 +5. 哪些数据绝对不能只放 Redis +``` + +--- + +# 一、Redis 在知习里的定位 + +Redis 不作为主数据库。 + +它主要负责: + +```text +1. 缓存 +2. 限流 +3. 队列 +4. 临时任务状态 +5. 分布式锁 +6. 防重复提交 +7. AI 调用次数统计 +8. 短期 token / 黑名单 +9. 通知任务调度 +``` + +核心业务数据仍然必须写入 MySQL: + +```text +用户 +知识库 +知识点 +学习记录 +主动回忆回答 +AI 分析结果 +待巩固项 +复习计划 +学习活跃记录 +通知记录 +用户设置 +``` + +--- + +# 二、Redis Key 命名规范 + +建议统一用这种格式: + +```text +业务域:对象类型:对象ID:字段 +``` + +例如: + +```text +cache:user:123:profile +rate:user:123:ai:daily:2026-05-09 +lock:ai-analysis:session:987 +job:ai-analysis:abc123:status +``` + +统一规则: + +```text +1. 全部小写 +2. 用冒号 : 分隔 +3. 从大范围到小范围 +4. userId、jobId、sessionId 明确写在 key 里 +5. 带日期的 key 用 YYYY-MM-DD +6. 所有临时 key 必须设置 TTL +``` + +--- + +# 三、Redis 主要使用场景 + +## 1. 缓存 Cache + +用于缓存高频读取的数据,减少 MySQL 压力。 + +### 用户资料缓存 + +```text +key: +cache:user:{userId}:profile + +value: +{ + "id": 123, + "nickname": "李明", + "avatarUrl": null, + "learningIdentity": "系统学习者", + "learningDirection": "认知科学" +} + +ttl: +5-10 分钟 +``` + +### 用户知识库列表缓存 + +```text +key: +cache:user:{userId}:knowledge-bases + +value: +[ + { + "id": 1, + "title": "认知心理学", + "itemCount": 42 + } +] + +ttl: +3-5 分钟 +``` + +### 当前用户设置缓存 + +```text +key: +cache:user:{userId}:preferences + +ttl: +10 分钟 +``` + +注意: + +```text +缓存可以丢 +MySQL 不能丢 +缓存更新不及时也没关系,但关键业务查询必须以 MySQL 为准 +``` + +--- + +## 2. 限流 Rate Limit + +用于控制请求频率和 AI 成本。 + +### 用户每日 AI 调用次数 + +```text +key: +rate:user:{userId}:ai:daily:{date} + +example: +rate:user:123:ai:daily:2026-05-09 + +value: +8 + +ttl: +到当天结束,或者 24 小时 +``` + +用途: + +```text +限制每天 AI 分析次数 +限制生成回忆测试次数 +限制找薄弱知识点次数 +``` + +--- + +### IP 请求频率限制 + +```text +key: +rate:ip:{ip}:request:{minute} + +example: +rate:ip:1.2.3.4:request:2026-05-09T10:35 + +value: +42 + +ttl: +60-120 秒 +``` + +用途: + +```text +防刷接口 +防反馈表单乱提交 +防登录接口暴力请求 +``` + +--- + +### 登录尝试次数 + +```text +key: +rate:login:{ip}:{date} + +ttl: +10-30 分钟 +``` + +Apple 登录一般问题不大,但后面如果有邮箱登录、验证码登录,这个会用到。 + +--- + +## 3. 分布式锁 Lock + +用于防止重复提交。 + +### 防止重复 AI 分析 + +```text +key: +lock:ai-analysis:session:{sessionId} + +example: +lock:ai-analysis:session:987 + +value: +randomToken + +ttl: +60-300 秒 +``` + +场景: + +```text +用户连续点两次“提交 AI 分析” +后端只允许创建一个分析任务 +``` + +--- + +### 防止重复导入资料 + +```text +key: +lock:document-import:{importId} + +ttl: +5-30 分钟 +``` + +--- + +### 防止重复生成复习计划 + +```text +key: +lock:review-plan:user:{userId}:item:{itemId} + +ttl: +60-300 秒 +``` + +注意: + +```text +锁一定要设置 TTL +解锁时要校验 value,不能误删别人的锁 +``` + +--- + +## 4. AI 分析任务状态 + +AI 分析建议用异步任务。 + +流程: + +```text +App 提交回答 +→ 后端创建 ai_analysis_jobs 记录 +→ 加入 Redis 队列 +→ 返回 jobId +→ Worker 调用 AI +→ 结果写入 MySQL +→ Redis 状态改为 completed +``` + +### 任务状态 key + +```text +key: +job:ai-analysis:{jobId}:status + +value: +pending / processing / completed / failed + +ttl: +24 小时 +``` + +### 任务进度 key + +```text +key: +job:ai-analysis:{jobId}:progress + +value: +0-100 + +ttl: +24 小时 +``` + +### 任务错误信息 + +```text +key: +job:ai-analysis:{jobId}:error + +value: +"AI provider timeout" + +ttl: +24 小时 +``` + +注意: + +```text +Redis 只存临时状态 +最终任务记录和分析结果必须写入 MySQL: +ai_analysis_jobs +ai_analysis_results +focus_items +review_cards +``` + +--- + +## 5. 资料导入任务状态 + +导入资料也建议异步。 + +场景: + +```text +上传 PDF +粘贴长文本 +导入链接 +AI 解析生成知识点 +``` + +### 导入任务状态 + +```text +key: +job:document-import:{importId}:status + +value: +pending / parsing / chunking / generating / completed / failed + +ttl: +24 小时 +``` + +### 导入进度 + +```text +key: +job:document-import:{importId}:progress + +value: +0-100 + +ttl: +24 小时 +``` + +### 导入处理中提示 + +```text +key: +job:document-import:{importId}:message + +value: +"正在提取关键知识点" + +ttl: +24 小时 +``` + +最终结果写入 MySQL: + +```text +document_imports +uploaded_files +knowledge_items +knowledge_item_relations +tags +``` + +--- + +## 6. BullMQ 队列 + +如果后端是 Node.js / NestJS,建议用 BullMQ。 + +队列由 BullMQ 自动管理 Redis key,不需要你自己建。 + +建议预留 3 个队列: + +```text +ai-analysis +document-import +notification +``` + +--- + +### ai-analysis 队列 + +任务数据: + +```json +{ + "jobId": 123, + "userId": 456, + "sessionId": 789, + "answerId": 1001, + "jobType": "active_recall_analysis" +} +``` + +处理逻辑: + +```text +读取用户回答 +调用 AI +生成分析结果 +写入 ai_analysis_results +生成 focus_items +生成 review_cards +发送通知 +``` + +--- + +### document-import 队列 + +任务数据: + +```json +{ + "importId": 123, + "userId": 456, + "knowledgeBaseId": 789, + "sourceType": "file" +} +``` + +处理逻辑: + +```text +解析文件 +提取文本 +分段 +生成知识点 +写入 knowledge_items +更新 document_imports 状态 +``` + +--- + +### notification 队列 + +任务数据: + +```json +{ + "userId": 456, + "type": "review_due", + "title": "8 张卡片今日到期复习", + "data": { + "reviewCount": 8 + } +} +``` + +处理逻辑: + +```text +写入 notifications 表 +后面可扩展推送 APNs +``` + +--- + +# 四、Redis Key 总表 + +## 缓存类 + +```text +cache:user:{userId}:profile +cache:user:{userId}:preferences +cache:user:{userId}:knowledge-bases +cache:knowledge-base:{knowledgeBaseId}:summary +cache:review:user:{userId}:due-count +``` + +--- + +## 限流类 + +```text +rate:user:{userId}:ai:daily:{date} +rate:user:{userId}:feedback:hourly +rate:ip:{ip}:request:{minute} +rate:ip:{ip}:login:{date} +``` + +--- + +## 锁类 + +```text +lock:ai-analysis:session:{sessionId} +lock:ai-analysis:answer:{answerId} +lock:document-import:{importId} +lock:review-plan:user:{userId}:item:{itemId} +lock:feedback:ip:{ip} +``` + +--- + +## 任务状态类 + +```text +job:ai-analysis:{jobId}:status +job:ai-analysis:{jobId}:progress +job:ai-analysis:{jobId}:error + +job:document-import:{importId}:status +job:document-import:{importId}:progress +job:document-import:{importId}:message +job:document-import:{importId}:error +``` + +--- + +## 会话临时状态 + +```text +session:learning:{sessionId}:heartbeat +session:learning:{sessionId}:current-step +session:active-recall:{sessionId}:draft +``` + +例如用户正在写回答,可以短期保存草稿: + +```text +key: +session:active-recall:{sessionId}:draft + +ttl: +1-24 小时 +``` + +最终提交后还是写 MySQL。 + +--- + +## Token / 黑名单 + +```text +auth:refresh-token:blacklist:{tokenId} +auth:access-token:blacklist:{jwtId} +``` + +退出登录或注销账号时可用。 + +--- + +# 五、TTL 建议 + +```text +用户资料缓存:5-10 分钟 +知识库列表缓存:3-5 分钟 +AI 每日限流:到当天结束或 24 小时 +IP 请求限流:60-120 秒 +登录尝试限流:10-30 分钟 +AI 分析任务状态:24 小时 +资料导入任务状态:24 小时 +分布式锁:60 秒到 30 分钟,按任务类型决定 +学习草稿:1-24 小时 +Token 黑名单:到 token 原本过期时间 +``` + +--- + +# 六、Redis 数据类型建议 + +## String + +最常用。 + +用于: + +```text +缓存 JSON +计数器 +状态 +锁 +``` + +示例: + +```text +rate:user:123:ai:daily:2026-05-09 = 8 +job:ai-analysis:1001:status = processing +``` + +--- + +## Hash + +可选,用于对象字段更新频繁的场景。 + +例如: + +```text +job:ai-analysis:1001 + status = processing + progress = 40 + message = 正在分析薄弱点 +``` + +v0.1 也可以先用 String 多 key,简单直接。 + +--- + +## List / Stream + +队列相关,但如果使用 BullMQ,不需要自己操作。 + +--- + +## Set + +可选。 + +比如记录某天处理过的学习项: + +```text +set:user:{userId}:reviewed-items:{date} +``` + +--- + +## Sorted Set + +后期可用于按时间排序的任务。 + +例如: + +```text +zset:review:due +``` + +但 v0.1 可以先不做,复习到期直接查 MySQL。 + +--- + +# 七、哪些不要放 Redis + +不要把这些只放 Redis: + +```text +用户资料 +知识库内容 +知识点内容 +学习记录 +主动回忆回答 +AI 分析结果 +待巩固项 +复习卡片 +复习记录 +学习活跃记录 +通知记录 +用户设置 +协议同意记录 +``` + +这些全部要写 MySQL。 + +Redis 可以缓存它们,但不是唯一来源。 + +--- + +# 八、Redis 和 MySQL 的典型组合流程 + +## AI 分析流程 + +```text +1. MySQL 创建 ai_analysis_jobs +2. Redis 加入 ai-analysis 队列 +3. Redis 存 job 状态 processing +4. Worker 调用 AI +5. MySQL 写 ai_analysis_results +6. MySQL 写 focus_items +7. MySQL 写 review_cards +8. Redis 存 job 状态 completed +9. MySQL 写 notifications +``` + +--- + +## 资料导入流程 + +```text +1. MySQL 创建 document_imports +2. Redis 加入 document-import 队列 +3. Redis 存导入进度 +4. Worker 解析文件 +5. MySQL 写 knowledge_items +6. MySQL 更新 document_imports 为 success +7. Redis 状态 completed +``` + +--- + +## 学习活跃图流程 + +```text +1. 用户完成学习动作 +2. MySQL 写 learning_records +3. MySQL 更新 daily_learning_activities +4. Redis 可短期缓存今日活跃统计 +5. App 查询活跃图时优先查 MySQL,必要时加缓存 +``` + +--- + +# 九、v0.1 Redis 最小落地范围 + +第一版不用把所有 Redis 场景都做完。 + +建议先做: + +```text +1. Redis 连接 +2. /health 检查 Redis +3. RedisService +4. AI 每日调用限流 +5. AI 分析队列 +6. AI 分析任务状态 +7. 防重复提交锁 +8. document-import 队列预留 +9. notification 队列预留 +``` + +暂时不做: + +```text +复杂缓存策略 +Sorted Set 复习调度 +复杂分布式任务调度 +全量通知推送 +复杂排行榜 +``` + +--- + +# 十、给 Trae 的提示词 + +```text +请为「知习」api-server 设计并接入 Redis。 + +注意: +Redis 不需要像 MySQL 一样建表。 +Redis 只用于缓存、限流、队列、临时状态、分布式锁、防重复提交和任务进度。 +核心业务数据必须写入 MySQL,不能只存在 Redis。 + +请完成以下内容: + +1. 基础接入 +- docker-compose 增加 redis:7-alpine +- 增加 RedisModule +- 增加 RedisService +- 支持 REDIS_HOST、REDIS_PORT、REDIS_PASSWORD、REDIS_DB、REDIS_URL +- /health 检查 Redis 是否可用 + +2. RedisService 方法 +- get +- set +- del +- exists +- expire +- ttl +- incr +- setNx +- lock +- unlock + +3. Key 命名规范 +请创建文档 docs/redis-key-design.md,包含以下 key 规范: + +缓存: +cache:user:{userId}:profile +cache:user:{userId}:preferences +cache:user:{userId}:knowledge-bases + +限流: +rate:user:{userId}:ai:daily:{date} +rate:ip:{ip}:request:{minute} +rate:user:{userId}:feedback:hourly + +锁: +lock:ai-analysis:session:{sessionId} +lock:ai-analysis:answer:{answerId} +lock:document-import:{importId} +lock:review-plan:user:{userId}:item:{itemId} + +任务状态: +job:ai-analysis:{jobId}:status +job:ai-analysis:{jobId}:progress +job:ai-analysis:{jobId}:error +job:document-import:{importId}:status +job:document-import:{importId}:progress +job:document-import:{importId}:message +job:document-import:{importId}:error + +4. BullMQ 队列 +请预留以下队列: +- ai-analysis +- document-import +- notification + +5. AI 分析任务 +请实现基础队列结构: +- 创建 AI 分析任务时写入 MySQL ai_analysis_jobs +- 添加任务到 Redis BullMQ 队列 +- Redis 记录任务 status/progress +- Worker 处理完成后更新 MySQL +- Redis 更新状态为 completed + +6. 限流 +请实现基础 AI 调用限流: +rate:user:{userId}:ai:daily:{date} +用于限制用户每日 AI 分析调用次数。 + +7. 分布式锁 +请实现 lock/unlock 工具,防止重复提交 AI 分析任务。 +锁必须有 TTL。 +解锁时要校验 token,不能误删其他请求的锁。 + +8. 注意事项 +- 不要把用户、知识库、学习记录、AI 分析结果、待巩固项、复习计划只存 Redis +- Redis 中所有临时状态必须设置 TTL +- BullMQ 自动创建的 Redis key 不需要手动管理 +- 代码要模块化,方便后续扩展 +``` + +--- + +一句话总结: + +**Redis 在知习里不是“另一个 MySQL”,它是系统的加速器和调度器。MySQL 存结果,Redis 管过程。** + +[1]: file://my_files/file_00000000e760722fbb391fe031a23b24 "personal_developer_startup_plan.md" +[2]: file://my_files/file_00000000cbe0722fb8f20caa41630738 "Pasted text.txt" +[3]: file://my_files/file_0000000013c071fdbc2b510f32da8546 "Pasted text.txt" +[4]: file://my_files/file_00000000ffd071fd9492b93fd9ebf5ba "Pasted text.txt" +[5]: file://my_files/file_0000000061e471f590475885fd76860c "REAL_MIGRATION_STATUS.md" diff --git a/docs/credentials.md b/docs/credentials.md new file mode 100644 index 0000000..9f735bc --- /dev/null +++ b/docs/credentials.md @@ -0,0 +1,108 @@ +# 知习 (zhixi) 凭据与配置 + +> ⚠️ 本文件包含敏感信息,**不要提交到公开仓库**。 + +*** + +## 服务器 + +| 项目 | 值 | +| ------ | ------------------ | +| IP | 81.70.187.179 | +| SSH 用户 | ubuntu | +| SSH 密钥 | `服务器密钥/WangDL.pem` | + +*** + +## MySQL + +| 项目 | 值 | +| ------- | ---------------------------------------------------------- | +| Host | 127.0.0.1:3306(服务器内部)/ 通过 SSH 隧道 localhost:3306 | +| 数据库名 | zhixi | +| root 密码 | `Zhixi@2026!Root` | +| 业务账号 | `zhixi_user` | +| 业务密码 | `Zhixi@2026!App` | +| 连接字符串 | `mysql://zhixi_user:Zhixi@2026!App@mysql-zhixi:3306/zhixi` | + +*** + +## Redis + +| 项目 | 值 | +| ---- | ----------------------------------------------- | +| Host | 127.0.0.1:6379(服务器内部)/ 通过 SSH 隧道 localhost:6379 | +| 密码 | `Rds@nTsgKrcqAkbuf6PwJIFMZQzF` | +| 最大内存 | 256MB | +| 淘汰策略 | allkeys-lru | + +*** + +## JWT + +| 项目 | 值 | +| --------------- | ------------------------------------------------------------------ | +| Secret | `98b1e7e377a40021ad7c46c55e467d2a218a89db7afc7c912780152ad64bdc45` | +| accessToken 过期 | 1h | +| refreshToken 过期 | 7d | + +*** + +## Swagger API 文档 + +| 项目 | 值 | +| ---- | ------------------------------------ | +| URL | | +| 用户名 | `admin` | +| 密码 | `Swgr@fmDentAYVXQUpG6oZDpJ` | +| 认证方式 | Basic Auth | + +*** + +## Gitea + +| 项目 | 值 | +| ------------ | ------------------------------------------ | +| URL | | +| SSH | ssh://git\@81.70.187.179:2222 | +| Runner Token | `9ypD3K9SWXR17CUNbWsvBxMIfz0MBqLiBLcRkGRZ` | + +*** + +## 端口映射 + +| 端口 | 服务 | +| ---- | ------------------------------ | +| 22 | SSH | +| 3000 | Gitea | +| 3001 | zhixi-api | +| 3306 | MySQL(仅 127.0.0.1,通过 SSH 隧道访问) | +| 6379 | Redis(仅 127.0.0.1,通过 SSH 隧道访问) | + +*** + +## SSH 隧道(本地开发用) + +```bash +ssh -f -N -L 3306:127.0.0.1:3306 -L 6379:127.0.0.1:6379 \ + -i api-server/服务器密钥/WangDL.pem ubuntu@81.70.187.179 +``` + +*** + +## 本地环境变量(.env) + +```env +PORT=3000 +NODE_ENV=development +DATABASE_URL="mysql://zhixi_user:Zhixi@2026!App@localhost:3306/zhixi" +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD=Rds@nTsgKrcqAkbuf6PwJIFMZQzF +JWT_SECRET=98b1e7e377a40021ad7c46c55e467d2a218a89db7afc7c912780152ad64bdc45 +AI_PROVIDER=mock +ENABLE_SWAGGER=true +SWAGGER_USER=admin +SWAGGER_PASSWORD=Swgr@fmDentAYVXQUpG6oZDpJ +``` +