Files
zhiyin/AGENTS.md
T
yuzhiran 8ee27fdd32 feat: refactor member to pay-per-use gravity purchase; mv webview to clipboard+browser
- member.vue: rewrite from subscription plans (free/growth/sprint) to
  H5-only pay-per-use gravity purchase with quantity selector + QR code
- user.vue: gravity card replacing quota card, add share/contribute/H5-buy
  entry points, plus gravity acquisition modal (share/contribute/buy)
- share.vue: layout fix (flex column), smarter copyLink with cached URL,
  WeChat timeline hint instead of open-type
- share.controller.ts: add GET /:shareCode redirect route (IP record + 302)
- interview.vue: guest mode fix, H5 buy modal, clipboard copy instead of
  webview for mini-program
- App.vue: handleH5UrlParams for ?token=&buy=gravity auto-login
- composables/useGravityPurchase.ts: reusable gravity purchase composable
- remove webview.vue (no longer used), replace with clipboard+browser flow
- AGENTS.md: sync all above changes, fix duplicate numbering
2026-06-20 20:49:15 +08:00

16 KiB
Raw Blame History

职引 (ZhiYin) — AGENTS.md

AI 模拟面试教练,专注校招。NestJS + uni-app(Vue3) + MongoDB。


一、项目结构

zhiyin/
├── backend/                     # NestJS 10.x 后端 (端口 3006, 前缀 /api)
│   └── src/
│       ├── main.ts              # 入口:DOMMatrix polyfill → NestFactory → CORS → ValidationPipe
│       ├── app.module.ts        # 根模块:导入全部子模块 + JWT/Throttler/Mongoose
│       ├── common/
│       │   ├── guards/          # JwtAuthGuard (全局), admin.guard.ts
│       │   ├── strategies/      # JwtStrategy
│       │   ├── decorators/      # @CurrentUser, @Public()
│       │   └── filters/         # AllExceptionsFilter
│       └── modules/             # 20 个模块(详见下文)
├── zhiyin-app/                  # uni-app 3.x 前端 (H5 + 微信小程序)
│   └── src/
│       ├── pages/               # 20 个页面 (pages.json 路由)
│       ├── composables/         # 可复用组合式函数(如 useGravityPurchase
│       ├── services/api.ts      # API 调用封装 (uni.request)
│       ├── config.ts            # 端点定义 + api() 辅助函数
│       └── App.vue              # 设计 Token + 全局样式 + H5 URL 参数处理
└── docs/                        # 产品/架构/部署/路线图文档

后端模块清单

模块 职责
user 手机/邮箱/密码/微信登录, JWT, 配额
interview AI 面试核心(多轮对话 + 评分 + 报告 + 进度)
ai AI 调用封装(deepseek-v4-flash 主 + step-3.5-flash 备)
analyze 简历诊断 / 优化 / 技能缺口分析
resume 简历 CRUD
member 会员套餐 / 权益扣减
payment 微信支付 v3Native + JSAPI + 回调)
progress 进步轨迹雷达图 / 打卡日历 / 行业基准 / 岗位匹配
contribution 面经贡献 + 公司题库(数据飞轮核心)
schedule 定时任务:VIP 过期降级、每日一题推送、微信 token 刷新、月度引力值补给
share 分享链接生成 / 访问追踪 / 积分奖励
tts 语音合成(TTS
admin 管理后台 API
positions 热门岗位维护
interview-review 面试复盘(音频上传 -> whisper.cpp ASR -> AI 评析 -> 口语分析)
career-advice AI 择业顾问:专业分析 + 岗位匹配 + 推荐对话
upload 文件上传(PDF/图片)
email 邮件发送
daily-question 每日一题 API
schemas/ 共享 Schemapricing 定价、site-config、company-bank 等)

前端页面(3 Tab + 18 子页)

  • Tab1 面试: pages/index/index → interview → report → career
  • Tab2 面经: pages/history/history → contribute → company-bank
  • Tab3 我的: pages/user/user → login/member/progress/resume/review/career/about/agreement/privacy/admin/share
  • 其他: internship, result

二、架构约定(必须遵守)

模块模式

每个业务模块遵循 NestJS 标准结构:

模块名/
├── 模块名.module.ts    # @Module({ imports: [MongooseModule.forFeature(...)], controllers, providers, exports })
├── 模块名.controller.ts # @Controller('prefix'),注入 service
├── 模块名.service.ts    # @Injectable(),注入 Model
└── 模块名.schema.ts     # @Schema({ timestamps: true })class + SchemaFactory

认证体系

  • 全局守卫: JwtAuthGuard 默认拦截所有路由(在 app.module.tsAPP_GUARD 注册)
  • 白名单: 公开接口加 @Public() 装饰器(登录、注册、支付回调、分享访问等)
  • 管理员: 管理接口加 @UseGuards(AdminGuard)admin controller 内部)
  • 当前用户: @CurrentUser('userId') 从 JWT payload 提取用户 ID
  • JWT 过期: 7 天,在 app.module.ts 和每个模块的 JwtModule.register 中配置

安全硬性要求

  1. JWT_SECRET 必须来自环境变量,不允许任何硬编码 fallback(已有历史漏洞修复)
  2. 所有外部输入经过 class-validator ValidationPipe({ whitelist: true, forbidNonWhitelisted: true })
  3. 修改用户额度/积分使用 findOneAndUpdate + $inc 原子操作,禁止 read-modify-write
  4. 支付订单查询校验 userId 归属,防止 IDOR
  5. CORS 生产环境必须配置白名单(当前在 main.ts 中从 CORS_ORIGINS 环境变量读取)
  6. 用户内容输出到响应时避免泄漏敏感信息(验证码、密钥等)
  7. MongoDB 查询中对外部输入的字符串做特殊字符转义(尤其在 admin 模块)

AI 调用

  • 主模型: opencode-go (deepseek-v4-flash)
  • 备用模型: NVIDIA (stepfun-ai/step-3.5-flash)
  • 主用不可用时自动切换(在 ai 模块处理)
  • 环境变量: AI_PRIMARY_KEY, AI_BACKUP_KEY

支付(微信支付 v3

  • Native 支付(H5 扫码): POST /payment/create-product(按量购买引力值)
  • JSAPI 支付(小程序内): POST /payment/jsapi-product(按量购买引力值)
  • 支付回调: POST /payment/notify@Public,验签 + 解密 + 自动到账)
  • 支付结果轮询: GET /payment/check/:outTradeNo
  • 产品定价: GET /member/plans(含 products 字段,定义引力值单价和赠送量)
  • 需要微信商户证书文件(通过 postbuild 复制到 dist
  • 注意: 当前会员体系已从按月订阅制改为按量购买引力值制(小程序内复制链接到浏览器打开购买,H5 直接扫码支付)

三、开发命令

⚠️ 构建铁律:必须始终使用 npm run build:* 命令,禁止直接调用 npx uni buildnpx nest build 前端 npm run build:mp-weixinnpm run build:h5 脚本包含头像文件(avatar-*.png)复制步骤, 后端 npm run buildnest build 的别名。直接使用 npx 会遗漏这些关键步骤,导致线上数字人头像不显示等问题。

后端

# 路径: backend/
npm run start:dev          # 开发模式(watch
npm run build              # 编译到 dist/
npm test                   # 单元测试(43 个,jest --forceExit --detectOpenHandles
npm run test:watch         # 监听模式
npm run test:cov           # 覆盖率报告
npm run test:e2e           # 集成测试(需 MongoDB 运行)
npm run test:browser       # Playwright API 测试(需后端运行)

前端

# 路径: zhiyin-app/
npm run dev:mp-weixin      # 微信小程序开发(uni -p mp-weixin
npm run build:mp-weixin    # 构建小程序(输出 dist/build/mp-weixin/
npm run dev:h5             # H5 开发(端口 8888,带 /api 代理到 localhost:3006
npm run build:h5           # 构建 H5(输出 dist/build/h5/
npm test                   # 前端单元测试(vitest,7 个)

构建检查

# 后端构建(注意 OOM:需 NODE_OPTIONS="--max-old-space-size=2048"
cd backend && npm run build

部署后端

cd backend && npm run build
cp -rf dist/* /www/wwwroot/server/zhiyin/backend/dist/
cp -r certs /www/wwwroot/server/zhiyin/backend/dist/src/certs
pm2 restart yhl-backend
sleep 3 && curl -s http://localhost:3006/api/user/wx-login -X POST -H "Content-Type: application/json" -d '{"code":"test"}'

部署前端 H5

cd zhiyin-app && npm run build:h5
rm -rf /www/wwwroot/zhiyin.yzrcloud.cn/assets
cp -r dist/build/h5/index.html /www/wwwroot/zhiyin.yzrcloud.cn/
cp -r dist/build/h5/assets /www/wwwroot/zhiyin.yzrcloud.cn/
chown -R www:www /www/wwwroot/zhiyin.yzrcloud.cn/index.html /www/wwwroot/zhiyin.yzrcloud.cn/assets
# 验证无缺失文件
grep -oP '["'"'"']([a-zA-Z0-9_-]+\.[a-z]+(\.js|\.css|\.png|\.svg))["'"'"']' /www/wwwroot/zhiyin.yzrcloud.cn/assets/index-*.js | sort -u

小程序上传(先 build 后 upload,两步分开更安全)

cd zhiyin-app && npm run build:mp-weixin && node scripts/upload-mp.js
# 注意:build:mp-weixin 已自动复制 avatar-*.png 到 dist/build/mp-weixin/static/
# 如遇数字人头像不显示,检查是否漏了 cp 步骤,重新用 npm run build:mp-weixin 构建

四、测试注意事项

  • e2e 测试需要 MongoDB 运行 + jest-setup.ts 设置测试 JWT_SECRET
  • payment.controller.spec.ts 涉及微信支付,需 mock 外部依赖
  • benchmark.service.spec.ts 涉及行业基准计算
  • Playwright 测试需要后端已在运行(测试 api.browser.spec.ts
  • 测试文件位置:backend/src/**/*.spec.ts(单元),backend/test/*.e2e-spec.ts(集成),backend/test/*.browser.spec.ts(浏览器)
  • 前端测试:zhiyin-app/src/**/*.spec.tsvitest + jsdom

五、定时任务(4 个 cron,在 schedule 模块)

服务 周期 职责
VipExpiryService 每日 00:00 扫描过期 VIP 并降级为 free 计划
DailyQuestionPushService 每日 09:00 通过微信订阅消息推送每日一题(需配置模板 ID)
WechatTokenService 每 2 小时 刷新微信 access_token(缓存到 Redis
GravityTopUpService 每日 02:00 给所有未过期的成长版/冲刺版用户补给月度引力值

六、项目状态与开发阶段

当前: Phase 1MVP 上线)进行中 — v1.0.16

阶段 状态 关键交付
Phase 0: 战略升级 完成 定价重构(免费 + ¥19.9/月),三层壁垒设计
Phase 0.5: 壁垒构建 完成 数据飞轮(面经贡献+题库),留存入围(进步轨迹+打卡日历+每日一题)
Phase 1: MVP 上线 🚧 当前 小程序 v1.0.16 已上传、引力值体系统一(订阅制改为按量购买)、管理后台完善、H5 已部署、生产模式已启用
Phase 1.5: 商业化 📋 规划 引力值运营策略、每日一题定时推送、PMF 验证
Phase 2: 增强 + 题库 📋 规划 50+ 校招岗位、技能缺口分析、公司真题库建设
Phase 3: 秋招冲刺 📋 规划 高校合作、B 端服务、KOC 推广

详细产品规划见 docs/PRODUCT-PLAN.md,路线图见 docs/ROADMAP.md


七、环境变量

后端(backend/.env,不提交 git,在 .gitignore 中)

MONGODB_URI=mongodb://localhost:27017/zhiyin
JWT_SECRET=your-strong-secret-at-least-32-chars
NODE_ENV=production
PORT=3006
AI_PRIMARY_KEY=xxx
AI_BACKUP_KEY=xxx
WECHAT_APPID=wxf466b3c3bc411ffc
WECHAT_MCHID=xxx
WECHAT_API_KEY=xxx
WECHAT_SERIAL_NO=xxx
WECHAT_PRIVATE_KEY_PATH=/path/to/apiclient_key.pem
WX_DAILY_QUESTION_TMPL=微信订阅消息模板 ID
CORS_ORIGINS=http://localhost:8888,https://zhiyin.yzrcloud.cn
EMAIL_HOST=smtp.qiye.aliyun.com
EMAIL_PORT=465
EMAIL_SECURE=true
EMAIL_USER=contact@yuzhiran.com
EMAIL_PASSWORD=xxx
EMAIL_FROM=宇之然AI磁场 <contact@yuzhiran.com>
WHISPER_CPP_PATH=/home/wlt/whisper.cpp   # whisper.cpp 路径
WHISPER_MODEL=base                        # ASR 模型:tiny / base / small
WHISPER_LANGUAGE=zh                       # ASR 语言
WHISPER_THREADS=4                         # ASR CPU 线程数

前端(zhiyin-app/.env.production,已提交 git

VITE_API_BASE_URL=https://zhiyinwx.yzrcloud.cn/api
VITE_APP_NAME=AI磁场

八、测试 / 管理员账号

账号 密码 角色 说明
13701190814@139.com Zhiyin2024! admin 管理员,可访问管理后台
test@yzrcloud.cn 123456 user 测试账号
test@test.com 验证码 123456 admin 旧管理员(dev 模式可用)

管理后台路径:/pages/admin/admin,进入后自动验证管理员身份(onMounteddoVerify)。


九、Git

  • 远程仓库: http://127.0.0.1:2999/txai-dev/zhiyin.git(本机 Gitea,带 token 认证)
  • 默认分支: master
  • 最新 tag: v1.0.16(小程序上传版本号源自 git tag)

十、技术细节与坑

  1. DOMMatrix polyfill: main.ts 顶部有 pdf-parse 所需的浏览器 API polyfillDOMMatrix / DOMPoint),新增 PDF 相关功能时注意兼容性
  2. postbuild: backend/package.json 中的 postbuild 脚本自动复制 certs/dist/src/certs/,这是微信支付证书的必要步骤
  3. 微信小程序 appid: zhiyin-app/manifest.jsonmp-weixin.appid = wxf466b3c3bc411ffc;开发模式 appid = __UNI__DEV__
  4. 前端 API 调用: zhiyin-app/src/services/api.ts 封装了 uni.request,自动处理 token 注入(从 uni.getStorageSync('token'))和 401 过期跳转
  5. 前端环境判断: config.ts 中使用 // #ifdef H5 / // #ifdef MP-WEIXIN 条件编译区分 H5 和小程序
  6. API 限流: 100 次/60 秒(在 app.module.ts 中配置),注意避免在定时任务和批量操作中被限
  7. 验证码: 生产模式(NODE_ENV=production)使用真实 SMTP 发邮件验证码;非生产模式手机验证码固定为 123456、邮件验证码在响应中返回 devCode
  8. MongoDB: 8 个核心集合 + 2 个分享集合
  9. 引力值体系: 所有计划统一走引力值消耗(面试 5、优化 3、下载 2)。VIP 不再免额度,成长版每月 250 引力值,冲刺版每月 600 引力值,每日凌晨 2 点定时补给。免费用户注册送 5 引力值。小程序内通过分享得引力值/贡献面经/复制官网链接到浏览器打开购买三种方式获取引力值;H5 直接扫码支付按量购买(¥5/份)。
  10. api.ts 陷阱: 对象字面量必须在 export const apiService = {const apiService = { ... export default apiService 中包裹,否则 uni-app 构建报错 Expected ";" but found ":"。git pull 后经常丢失这行声明,需手动补回
  11. H5 构建 assets 清理: assets/ 中的旧 hash 文件不能随意删除——index-*.js(主 bundle)动态 import 了所有 page chunk,删除仍在引用的文件会导致浏览器 NS_ERROR_CORRUPTED_CONTENT
  12. 管理后台自动验证: admin.vueonMounted 自动调用 doVerify(),进入后台即检测 JWT 中 role 是否为 admin,不再需要手动点击"验证管理员身份"按钮
  13. 分享重定向路由: share.controller.tsGET /api/share/:shareCode 是公开路由(泛匹配,放最后避免拦截其他路由),访问时记录访问者 IP → 302 重定向到 H5 首页
  14. 小程序官网购买走剪贴板: 小程序内"官网购买"不再使用 webview 内嵌 H5,改为 uni.setClipboardData 复制带 JWT token 的 URL 到剪贴板,提示用户在手机浏览器中打开购买(App.vuehandleH5UrlParams 解析 ?token=?buy=gravity 参数自动登录跳转)

十一、交付检查清单(每次实施/修改后执行)

Step 1: 代码评审

  • 是否符合现有模块模式(schema→service→controller→module
  • 是否有多余变量、import、参数
  • 命名是否与项目一致
  • 是否有重复代码或可复用逻辑

Step 2: 安全评审

  • 外部输入是否有注入风险(HTML、Shell、NoSQL
  • 接口是否正确加了 JWT guard 或 @Public()
  • 管理端接口是否有 AdminGuard
  • 修改用户额度/积分是否用 $inc 原子操作
  • 敏感信息是否泄漏到响应或日志中
  • 用户内容输出是否做了转义

Step 3: 性能评审

  • 是否存在 N+1 查询
  • 大表查询是否有索引覆盖
  • 读操作是否该加 .lean()
  • 是否有内存泄漏风险(puppeteer 等资源未释放)

Step 4: 测试验证

cd backend && npm run build
npm test -- --forceExit --detectOpenHandles

Step 5: LSP 诊断

  • Changed files diagnostics clean
  • as any / @ts-ignore / @ts-expect-error