Compare commits

...

7 Commits

Author SHA1 Message Date
pmc
3e8a212e9f fix(admin): 修复 NEXT_PUBLIC_API_BASE_URL 注入时机导致登录 Network Error
All checks were successful
Build and Deploy LTY / build-and-deploy (push) Successful in 8m38s
- qy-lty-admin/Dockerfile: build 阶段加 ARG/ENV,让该变量在 next build 时进客户端 JS 包
- .gitea/workflows/deploy.yaml: admin docker build 加 --build-arg https://${DOMAIN_API}/api;删除已失效的 sed 替换
- k8s/admin-deployment-prod.yaml: 删除运行时无效的 NEXT_PUBLIC_API_BASE_URL env,留注释说明

根因:Next.js NEXT_PUBLIC_* 变量在 next build 时被静态编译进客户端 JS。
原配置在容器运行时才设该变量,对已打包的 fallback 默认值无效,
导致线上前端实际打到 http://localhost:8000/api 触发 ERR_CONNECTION_REFUSED。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 15:53:37 +08:00
pmc
cfd8a4923a docs(qy-lty-admin): 初始化 REQUIREMENTS.md(Validated)
把代码库现状拆成带 REQ-ID 的可追溯需求:47 项已交付(AUTH-01~06、PERM-01~05、DASH-01~02、
AI-01、CONT-01~09、SYS-01~03、UPL-01~02、UI-01~06、DEP-01~04),1 项缺口(PERM-06,
后端权限校验闭环未验证)。Active 段留空,附 12 项候选优先级供下一 milestone 参考。
2026-05-07 14:33:02 +08:00
pmc
5d8f81a4a9 docs(qy-lty-admin): 初始化 PROJECT.md + STATE.md
Brownfield 文档化模式 —— 从 .planning/codebase/ 推断 47 项已交付能力(AUTH/PERM/DASH/AI/CONT/SYS/UPL/UI/DEP),
Active 段留空待 /gsd-new-milestone 启动下一周期。
PROJECT.md 沿用 qy_lty 的章节结构(核心价值 / 已交付 / 进行中 / 范围外 / 背景 / 约束 / 关键决策 / 演进规则)。
STATE.md 记录锚定路径与 git 由父 Lila-Server 仓库管理的关键说明。
2026-05-07 14:32:53 +08:00
pmc
2ac4af8a9a chore(qy-lty-admin): 初始化 GSD config.json
镜像 qy_lty 的工作流配置(YOLO + coarse + 并行 + balanced 模型 + 全部 workflow agent 启用),
保持 qy-lty-admin 与 qy_lty 双仓库的 GSD 行为一致。
2026-05-07 14:32:45 +08:00
pmc
a85b6a79a8 docs: 映射 qy-lty-admin 代码库 2026-05-07 10:58:29 +08:00
pmc
7223c52e9f docs: bootstrap GSD workflow with brownfield documentation pass 2026-05-07 10:43:16 +08:00
pmc
64a8cb8f7b docs: map existing codebase 2026-05-07 10:37:16 +08:00
27 changed files with 4757 additions and 3 deletions

View File

@ -73,6 +73,7 @@ jobs:
for attempt in 1 2 3; do
echo "Build admin attempt $attempt/3..."
DOCKER_BUILDKIT=0 docker build \
--build-arg NEXT_PUBLIC_API_BASE_URL=https://${{ env.DOMAIN_API }}/api \
--tag ${{ env.CR_SERVER_ACTIVE }}/${{ env.CR_ORG }}/lty-admin:${{ env.IMAGE_TAG }} \
--tag ${{ env.CR_SERVER_ACTIVE }}/${{ env.CR_ORG }}/lty-admin:latest \
./qy-lty-admin 2>&1 | tee -a /tmp/build.log && break
@ -118,7 +119,8 @@ jobs:
# Replace domain placeholders by environment
sed -i "s|qy-lty.airlabs.art|${{ env.DOMAIN_API }}|g" k8s/ingress.yaml
sed -i "s|qy-lty-admin.airlabs.art|${{ env.DOMAIN_ADMIN }}|g" k8s/ingress.yaml
sed -i "s|https://qy-lty.airlabs.art|https://${{ env.DOMAIN_API }}|g" k8s/admin-deployment-prod.yaml
# NEXT_PUBLIC_API_BASE_URL 已改为 docker build --build-arg 注入(见 admin 构建步骤),
# admin-deployment-prod.yaml 中不再保留可被 sed 替换的占位
# Replace DB host by environment
sed -i "s|pgm-7xv4811oj11j86htzo.pg.rds.aliyuncs.com|${{ env.DB_HOST }}|g" k8s/backend-deployment-prod.yaml

View File

@ -23,8 +23,8 @@ spec:
env:
- name: NODE_ENV
value: "production"
- name: NEXT_PUBLIC_API_BASE_URL
value: "https://qy-lty.airlabs.art"
# NEXT_PUBLIC_API_BASE_URL 必须在 docker build 时通过 --build-arg 注入Next.js 客户端打包),
# 运行时在此处设置无效。详见 .gitea/workflows/deploy.yaml 与 qy-lty-admin/Dockerfile。
livenessProbe:
httpGet:
path: /

View File

@ -0,0 +1,163 @@
# 洛天依应用管理后台qy-lty-admin
## 项目简介
qy-lty-admin 是「洛天依Luotianyi智能陪伴产品」生态的 **Web 管理后台前端**,基于 **Next.js 15 (App Router) + React 19 + TypeScript** 构建,搭配 Tailwind CSS + Radix UI / shadcn 风格组件。它通过 `/api/v1/admin/` 命名空间消费独立后端 [qy_lty](../qy_lty/)Django的 REST 接口,为运营者提供 AI / 内容(服饰/道具/家居/食物/歌曲/舞蹈/成就/好感度)/ 用户与权限 / 系统设置等模块的管理能力。
## 核心价值
**运营者能基于真实角色权限,安全且无障碍地管理后端各业务模块的数据**——`lib/permissions.ts` 中的 RBAC 矩阵 + `qy_lty` 后端服务端校验必须始终配套生效。一旦权限校验链路断裂(前端伪造角色或后端漏校验),整个管理后台就从"运营工具"退化为"任意操作面板",其余所有 UI / UX 优化都无法弥补这种安全风险。
## 需求清单
### 已交付
<!-- 已交付且在生产链路上跑通的能力,从 .planning/codebase/ 推断 -->
**鉴权与会话AUTH**
- ✓ **AUTH-01** 邮箱 + 密码登录页(`app/login/page.tsx` + `lib/api/auth.ts:emailLogin`)— existing
- ✓ **AUTH-02** 注册 / 找回密码占位页(`app/register/``app/forgot-password/`)— existing
- ✓ **AUTH-03** Bearer token 拦截器自动注入(`lib/api/client.ts` 请求拦截器从 localStorage 读 `auth_token`)— existing
- ✓ **AUTH-04** 401 响应统一处理(响应拦截器清空 token + 重定向 `/login`)— existing
- ✓ **AUTH-05** Cookie 镜像 token`js-cookie`7 天有效期,供 `middleware.ts` 路由保护使用)— existing
- ✓ **AUTH-06** 退出登录调用后端 logout 接口并清空双存储 — existing
**RBAC 权限体系PERM**
- ✓ **PERM-01** 5 角色 × 13 模块的 `PERMISSION_MATRIX``lib/permissions.ts`)— existing
- ✓ **PERM-02** `hasPermission(module)` / `hasPathPermission(pathname)` / `getModuleFromPath()` 工具集 — existing
- ✓ **PERM-03** `DashboardShell` 挂载时按路径校验权限并渲染访问拒绝 UI`components/dashboard-shell.tsx`)— existing
- ✓ **PERM-04** `Sidebar` 按角色过滤可见菜单项(`components/sidebar.tsx`)— existing
- ✓ **PERM-05** `middleware.ts` 在受保护路径前校验 cookie token 并重定向未鉴权访问 — existing
**仪表盘DASH**
- ✓ **DASH-01** 仪表盘首页(`app/page.tsx`)含 KPI 统计卡片、概览图表、最近活动 Feed — existing
- ✓ **DASH-02** Recharts 数据可视化集成 — existing
**AI 管理AI**
- ✓ **AI-01** AI 模型 / Bot 管理(`app/ai-model/page.tsx` + `lib/api/ai-models.ts`)— existing
**内容管理CONT**
- ✓ **CONT-01** 服饰模块 CRUD列表 / 详情 / 编辑 / 创建对话框,`app/outfits/` + `lib/api/outfits.ts`)— existing
- ✓ **CONT-02** 道具模块 CRUD`app/props/`)— existing
- ✓ **CONT-03** 家居装饰模块 CRUD`app/home-decor/`)— existing
- ✓ **CONT-04** 食物模块 CRUD`app/food/`)— existing
- ✓ **CONT-05** 歌曲模块 CRUD`app/songs/` + `lib/api/songs.ts`)— existing
- ✓ **CONT-06** 舞蹈模块 CRUD`app/dances/` + `lib/api/dances.ts`)— existing
- ✓ **CONT-07** 成就模块管理(`app/achievements/` + `lib/api/achievements.ts`)— existing
- ✓ **CONT-08** 好感度系统管理页(`app/affinity/page.tsx`1005 行单文件,规则/等级/互动 三段)— existing
- ✓ **CONT-09** 后端响应到前端类型的适配器层(`lib/api/adapters.ts` + 各模块 `mapBackend*` 函数)— existing
**系统管理SYS**
- ✓ **SYS-01** 用户管理模块(`app/users/` + `lib/api/users.ts`)— existing
- ✓ **SYS-02** 权限/角色管理模块(`app/permissions/` + `lib/api/roles.ts`)— existing
- ✓ **SYS-03** 系统设置页(`app/settings/`)— existing
**文件上传UPL**
- ✓ **UPL-01** 后端代理上传接口封装(`lib/api/upload.ts`,支持 image / avatar / audio / animation 类型 + 大小限制)— existing
- ✓ **UPL-02** 上传进度回调Axios `onUploadProgress`)— existing
**通用 UI 基础设施UI**
- ✓ **UI-01** shadcn 风格原子组件库(`components/ui/`30+ 组件,复制粘贴模式可直接修改)— existing
- ✓ **UI-02** 表单层React Hook Form + Zod + `@hookform/resolvers`)— existing
- ✓ **UI-03** Toast 通知Sonner + Radix Toast封装为 `hooks/use-toast.ts`)— existing
- ✓ **UI-04** 暗黑/明亮主题切换(`next-themes` + Tailwind CSS 变量)— existing
- ✓ **UI-05** 移动端断点检测 hook`hooks/use-mobile.tsx`)— existing
- ✓ **UI-06** 删除 / 发布二次确认对话框(`components/delete-confirmation-dialog.tsx``publish-confirmation-dialog.tsx`)— existing
**部署DEP**
- ✓ **DEP-01** Docker 多阶段构建builder + runnerrunner 镜像 < 200MB)— existing
- ✓ **DEP-02** Next.js standalone 输出(`.next/standalone` + `public/`)— existing
- ✓ **DEP-03** Yarn + 淘宝镜像源(`registry.npmmirror.com`,仅 Dockerfile 内)— existing
- ✓ **DEP-04** 端口 3000 暴露 + `yarn start` 入口 — existing
### 进行中
<!-- 当前正在建设的目标。GSD 通过 phase 推动这一段;移到 Validated 才算完成 -->
(暂无 — 本次 `/gsd-new-project` 仅做 brownfield 文档化。下次新增功能 / 子系统时使用 `/gsd-new-milestone` 启动新 milestone把当时要交付的能力加到这一段然后 `/gsd-plan-phase` 拆 phase。
### 范围外
<!-- 明确排除项 + 理由。防止后续被无意识地拉回来 -->
- **后端实现** — 在独立项目 [qy_lty](../qy_lty/) 中维护Django + DRF + Channels。本仓库**只**作为消费方调用 `/api/v1/admin/` REST 接口,不编写任何服务端代码。
- **Unity 客户端业务逻辑** — 在 `C:\Unity2022project\LTY_App_Project_URP`(手机 App`C:\Unity2022project\LTY_Project`(设备端)独立维护。本管理后台不与 Unity 客户端直接通信。
- **跨项目混合修改记录** — 服务端 / 管理后台改动**各自记录**到对方仓库的 `docs/修改记录.md`,跨项目联动**两端各写一条互相引用**不混在同一条记录里CLAUDE.md 显式规定)。
- **国际化i18n** — UI 文案当前硬编码中文,未引入 i18n 库;管理后台用户群体仅运营人员,不在本周期范畴。
- **移动端原生体验** — 仅响应式 Web移动 App 在 `LTY_App_Project_URP` 独立维护。
- **真正意义的测试套件** — 当前未配置 Jest/Vitest无任何测试文件。CONCERNS.md 标为 MEDIUM 工程债,需独立 milestone 系统性补齐。
- **客户端错误追踪服务(如 Sentry** — 仅依赖 Sonner toast + console未引入第三方监控如需要再开 milestone 评估。
## 背景上下文
**生态位**本项目是「设备—App—管理端」三角中的 **管理端** 节点,是运营者操作 qy_lty 后端业务数据的唯一图形化入口。
**对接服务清单**(以 CLAUDE.md 为准):
| 服务 | 路径 | 通讯方式 |
|------|------|---------|
| qy_lty 后端 Django | `../qy_lty/``C:\Users\admin\Desktop\Lila-Server\qy_lty` | HTTP REST `/api/v1/admin/``/card/category/*``/music/``/dances/``/affinity/``/common/upload/` 等 |
**最近活跃工作**git log + 修改记录推断):
- 2026-04 ~ 至今:好感度系统管理页(`affinity/page.tsx`1005 行)建设完成,与 qy_lty 后端 P1 数据层联动
- 2026-05-07完成 brownfield 代码库映射commit `a85b6a7`
- 持续在内容模块outfits / songs / dances 等)补充 CRUD 表单与详情视图
**已知工程现状**(详见 `.planning/codebase/`
- 整体规模约 27K LOC TypeScript / TSX
- 测试覆盖**零**(无 Jest/Vitest 配置,无任何测试文件)— 是最大的工程债
- `next.config.mjs` 配置 `eslint.ignoreDuringBuilds: true``typescript.ignoreBuildErrors: true` — 构建期不阻断错误
- 多锁文件并存(`package-lock.json` + `pnpm-lock.yaml` + `yarn.lock`)— 本地与 Docker 容易拉到不同版本
- `lib/api/client.ts` 含大量 `console.log/warn/error` 调试残留,会暴露 token 前缀
- 部分依赖钉为 `"latest"``@hookform/resolvers``react-hook-form``recharts``zod`)— 升级不可控
- `app/affinity/page.tsx`1005 行)、`components/ui/sidebar.tsx`763 行)等单文件偏大,需要拆分
- 权限校验**仅在客户端**localStorage.user_role 可篡改)— 后端必须重检每个 admin 接口才能闭环
- Token 存于 localStorageXSS 暴露面)+ 7 天有效期不轮换 — CONCERNS.md 标 HIGH
## 约束
- **技术栈**Next.js 15.2.4App Router+ React 19 + TypeScript 5 — 已锁定,迁移成本高
- **技术栈**Tailwind CSS 3.4 + Radix UI + shadcn 风格组件 — 不替换为其他设计系统
- **运行时**Node.js 22.10.0 AlpineDocker 镜像锁定CI/Local 应保持版本一致)
- **HTTP 客户端**Axios已配置请求/响应拦截器) — 不改用 fetch / SWR / React Query避免重写大量适配器
- **后端契约**:所有请求走 `NEXT_PUBLIC_API_BASE_URL + /api/v1/admin/...`;响应结构 `{ success, code, message, data }`(由 qy_lty 的 `StandardResponseMiddleware` 保证)— 整体壳层不可改
- **Token 协议**`Authorization: Bearer {token}`token 同时存 localStorageAPI 客户端读取)与 cookiemiddleware 读取)— 双存储必须保持同步
- **角色命名**5 个角色(超级管理员 / 内容管理员 / AI模型管理员 / 卡牌管理员 / 查看者 / 管理员),中文名硬编码,与 qy_lty 后端必须一致
- **包管理器**:项目并存三种 lockfile**Dockerfile 用 yarn**,本地任选其一但**不要混用**CLAUDE.md 警告)
- **文档规范**:每次代码改动**必须**追加到 `docs/修改记录.md` 顶部CLAUDE.md 强制规则,结构性文档变更同样适用)
- **沟通语言**与用户沟通使用中文CLAUDE.md「沟通语言」节只能因用户显式要求才切换
- **项目独立**`qy-lty-admin``qy_lty` 独立维护,**修改记录、planning 工件不混合**
- **跨仓库联动**:任何 `/api/v1/admin/` 接口契约改动**两端各写一条 `docs/修改记录.md` 并互相引用**
## 关键决策
| 决策 | 理由 | 结果 |
|------|------|------|
| 权限矩阵硬编码在前端(`lib/permissions.ts` | 5 角色固定、变更频率低,不必引入额外配置中心 | ⚠️ Revisit — 客户端权限**仅是 UI 礼貌**后端必须独立校验。CONCERNS.md 标"信任边界违规"为极高优先级,需明文写进 CLAUDE.md |
| Token 同时写 localStorage + cookie | API 客户端需 JS 可读middleware 需 SSR 可读 | ⚠️ Revisit — 双存储扩大了 XSS 暴露面,理想形态是后端 HttpOnly cookie当前形态是与后端契约的妥协 |
| shadcn 风格复制粘贴组件(不是 npm 包) | 可直接修改源码而不被 npm 升级覆盖 | ✓ Good — 已稳定使用30+ 组件 |
| Axios + 拦截器(不是 fetch / SWR / React Query | 拦截器统一处理 token 注入与 401迁移到 hook 形态会重写大量代码 | ✓ Good — 当前形态稳定 |
| Next.js standalone 输出Docker | 镜像体积约 100MBvs 标准 500MB | ✓ Good — Dockerfile 已正确处理 public + next.config.mjs 复制 |
| 构建期忽略 TS / ESLint 错误(`ignoreBuildErrors: true` | 早期开发不阻断构建 | ⚠️ Revisit — 应在生产配置中关闭并修干净;属技术债 |
| API 适配器层(`mapBackend*` | 后端字段命名与前端类型契约不一致,集中映射避免散落 | ✓ Good — 已成为约定,新模块沿用即可 |
| `.planning/` 锚定在 `qy-lty-admin\`(不是 `Lila-Server\` | `qy_lty``qy-lty-admin` 是独立项目CLAUDE.md 规定各自维护 | ✓ Good — 2026-05-07 通过预创建空目录强制锚定生效 |
## 演进规则
本文档在 phase 切换与 milestone 边界处更新。
**每次 phase 切换后**(通过 `/gsd-transition`
1. 需求被推翻?→ 移到「范围外」并说明理由
2. 需求已交付?→ 移到「已交付」并标注 phase 引用
3. 出现新需求?→ 加到「进行中」
4. 有决策需要记录?→ 加到「关键决策」
5. 「项目简介」是否仍然准确?→ 如有偏移则更新
**每个 milestone 完成后**(通过 `/gsd-complete-milestone`
1. 全面回顾所有章节
2. 复核「核心价值」—— 是否仍是当前最高优先级?
3. 审视「范围外」—— 排除理由是否仍然成立?
4. 用当前状态更新「背景上下文」
---
*最后更新2026-05-07brownfield 文档化初始化完成(已映射现有系统,尚无进行中 milestone — 使用 /gsd-new-milestone 启动下一周期)*

View File

@ -0,0 +1,133 @@
# Requirements — 洛天依应用管理后台qy-lty-admin
**初始化日期**: 2026-05-07
**类型**: Brownfield 文档化(从 `.planning/codebase/` 推断)
**状态**: 已落地能力归档完成Active milestone 待 `/gsd-new-milestone` 启动
---
## Validated已交付能力
以下需求均为 **2026-05-07 之前已上线** 的能力,从代码与 `docs/` 推断而来。任何修改都需要走完整 phase 流程,不要直接动。
### 鉴权与会话AUTH
- [x] **AUTH-01** 邮箱 + 密码登录页(`app/login/page.tsx``lib/api/auth.ts:emailLogin`
- [x] **AUTH-02** 注册 / 找回密码占位页(`app/register/``app/forgot-password/`
- [x] **AUTH-03** Bearer token 拦截器自动注入(`lib/api/client.ts` 请求拦截器)
- [x] **AUTH-04** 401 响应统一处理(清空 token + 重定向 `/login`
- [x] **AUTH-05** Cookie 镜像 token`js-cookie`7 天有效期,供 middleware 读取)
- [x] **AUTH-06** 退出登录调后端 logout 接口并清空双存储
### RBAC 权限体系PERM
- [x] **PERM-01** 5 角色 × 13 模块 `PERMISSION_MATRIX``lib/permissions.ts`
- [x] **PERM-02** `hasPermission()` / `hasPathPermission()` / `getModuleFromPath()` 工具集
- [x] **PERM-03** `DashboardShell` 路径级权限校验 + 访问拒绝 UI
- [x] **PERM-04** `Sidebar` 按角色过滤可见菜单项
- [x] **PERM-05** `middleware.ts` 受保护路径 token 校验 + 重定向
- [ ] **PERM-06** 后端独立权限校验闭环 — ⚠️ **客户端校验仅是 UI 礼貌**CONCERNS.md 标极高严重级;需要审计 qy_lty 后端 `/api/v1/admin/*` 是否对每个接口重新校验角色,并在 CLAUDE.md 中明文文档化
### 仪表盘DASH
- [x] **DASH-01** 仪表盘首页KPI 卡片 + 概览图表 + 最近活动)
- [x] **DASH-02** Recharts 数据可视化集成
### AI 管理AI
- [x] **AI-01** AI 模型 / Bot 管理(`app/ai-model/page.tsx``lib/api/ai-models.ts`
### 内容管理CONT
- [x] **CONT-01** 服饰模块 CRUD`app/outfits/`
- [x] **CONT-02** 道具模块 CRUD`app/props/`
- [x] **CONT-03** 家居装饰模块 CRUD`app/home-decor/`
- [x] **CONT-04** 食物模块 CRUD`app/food/`
- [x] **CONT-05** 歌曲模块 CRUD`app/songs/`
- [x] **CONT-06** 舞蹈模块 CRUD`app/dances/`
- [x] **CONT-07** 成就模块管理(`app/achievements/`
- [x] **CONT-08** 好感度系统管理页(`app/affinity/page.tsx`1005 行)
- [x] **CONT-09** 后端响应到前端类型的适配器层(`lib/api/adapters.ts` + 各模块 `mapBackend*`
### 系统管理SYS
- [x] **SYS-01** 用户管理模块(`app/users/`
- [x] **SYS-02** 权限/角色管理模块(`app/permissions/`
- [x] **SYS-03** 系统设置页(`app/settings/`
### 文件上传UPL
- [x] **UPL-01** 后端代理上传接口封装(`lib/api/upload.ts`image / avatar / audio / animation
- [x] **UPL-02** 上传进度回调Axios `onUploadProgress`
### 通用 UI 基础设施UI
- [x] **UI-01** shadcn 风格原子组件库(`components/ui/`30+ 组件)
- [x] **UI-02** 表单层React Hook Form + Zod + `@hookform/resolvers`
- [x] **UI-03** Toast 通知Sonner + Radix Toast`hooks/use-toast.ts`
- [x] **UI-04** 暗黑/明亮主题切换(`next-themes` + Tailwind CSS 变量)
- [x] **UI-05** 移动端断点检测 hook`hooks/use-mobile.tsx`
- [x] **UI-06** 二次确认对话框(删除 / 发布)
### 部署DEP
- [x] **DEP-01** Docker 多阶段构建builder + runner
- [x] **DEP-02** Next.js standalone 输出
- [x] **DEP-03** Yarn + 淘宝镜像源(仅 Dockerfile
- [x] **DEP-04** 端口 3000 + `yarn start` 入口
---
## Active当前 milestone 目标)
**(暂无)**
本次 `/gsd-new-project` 是 brownfield 文档化,没有指定新 milestone。
下一次启动新功能开发时,请用:
```
/gsd-new-milestone
```
GSD 会引导你确认 milestone 目标、把新需求加到本段(带 REQ-ID然后 `/gsd-plan-phase` 拆 phase。
**候选优先级**(来自 CONCERNS.md 与项目活动信号,按风险/价值排序,仅供参考):
1. **极高** 验证 qy_lty 后端是否对所有 `/api/v1/admin/*` 接口独立校验角色PERM-06— 否则当前 RBAC 仅是 UI 礼貌,是真实安全漏洞
2. **高** 移除 `lib/api/client.ts``lib/api/upload.ts` 等的 console.log/warn/error 调试残留(暴露 token 前缀)
3. **高** 关闭 `next.config.mjs``eslint.ignoreDuringBuilds``typescript.ignoreBuildErrors`,并修干净存量类型/lint 错误
4. **高** 收敛多 lockfile 冲突(保留 yarn.lock删除 package-lock.json + pnpm-lock.yaml + CI 校验)
5. **高** Token 存储方案重构(移到 HttpOnly cookie + 后端登出黑名单 + 短/长 token 刷新机制)
6. **中** 拆分 `app/affinity/page.tsx`1005 行)与 `components/ui/sidebar.tsx`763 行)等大文件
7. **中** 引入测试基础设施Vitest + 关键路径测试,先覆盖 `lib/permissions.ts` + `lib/api/client.ts`
8. **中**`"latest"` 依赖改为具体 caret 范围(`@hookform/resolvers``react-hook-form``recharts``zod`
9. **中** Husky pre-commit hook + lint-staged强制 lint / type-check / 修改记录提醒)
10. **低** 添加 `app/error.tsx` 与各模块 `error.tsx`Next.js 错误边界)
11. **低** 权限校验 hook 化 + `useMemo` 记忆化
12. **低** 客户端登录失败节流30 秒禁用按钮 + 后端账户锁定)
---
## Out of Scope
(理由详见 PROJECT.md此处不重复
- 后端实现 — 在 `../qy_lty/` 独立项目Django
- Unity 客户端业务逻辑 — 在 `LTY_App_Project_URP` / `LTY_Project` 独立项目
- 国际化i18n — 当前中文硬编码,运营群体不需要
- 移动端原生体验 — 仅响应式 Web
- 跨项目混合修改记录 — 各自维护
- Sentry / APM 客户端错误追踪 — 暂不引入
---
## Traceability
<!-- 由 /gsd-plan-phase 在生成 phase 时回填:每个 phase 解决哪些 REQ-ID -->
(暂无 phase`/gsd-new-milestone` 后启动)
---
*Last updated: 2026-05-07 after brownfield documentation pass*

View File

@ -0,0 +1,69 @@
# Project State — 洛天依应用管理后台qy-lty-admin
**最后更新**: 2026-05-07brownfield 文档化初始化)
## 项目引用
参见:`.planning/PROJECT.md`(更新于 2026-05-07
**核心价值**:运营者能基于真实角色权限,安全且无障碍地管理后端各业务模块——`lib/permissions.ts` 的客户端 RBAC + qy_lty 后端服务端校验必须**配套生效**才完整。
**当前重点**:暂无 Active milestone — 待 `/gsd-new-milestone` 启动下一周期
## 状态
| 项目 | 状态 |
|------|------|
| 代码库映射 | ✅ `.planning/codebase/` 7 文档commit `a85b6a7` |
| PROJECT.md | ✅ 已交付段已从 codebase 推断填充,进行中段空 |
| REQUIREMENTS.md | ✅ 已交付段已拆 REQ-ID进行中段空可追溯性待 phase 回填 |
| 路线图 | ⏸️ 暂未生成(无进行中需求 → 无 phase 可分) |
| 当前 phase | — |
| 当前 milestone | — |
## 下一步
**当你准备开始下一个开发周期**
```
/gsd-new-milestone
```
GSD 会:
1. 询问 milestone 目标例如后端权限校验闭环验证、Token 存储重构、测试基础设施……)
2. 把需求加到 `.planning/REQUIREMENTS.md` 的 Active 段
3. 路由到 `/gsd-roadmap` 拆 phase
**候选优先级排序见 `REQUIREMENTS.md → Active → 候选优先级` 段**。
## 工作流配置
详见 `.planning/config.json`
- 模式:**YOLO**(自动通过审批,直接执行)
- 粒度:**Coarse**(每个 milestone 拆 3-5 phase
- 并行化:**已启用**
- 工作流 agentresearch / plan_check / verifier 全部启用
- 模型档位:**balanced**Sonnet 主力)
- `.planning/` 提交到 git**是**(提交至父级 `Lila-Server\` 仓库)
随时可用 `/gsd-settings` 调整。
## 锚定路径重要说明
`.planning/` 必须保持在 `c:\Users\admin\Desktop\Lila-Server\qy-lty-admin\` 这一层(**不是父级 `Lila-Server\`**)。父级 `.git` 容易让 GSD CLI 误把 `Lila-Server` 当作 project_root本目录的存在就是锚定信号不要删。
`qy-lty-admin\` 自身**没有** `.git`——版本控制由父级 `Lila-Server\.git` 统一管理。任何 `.planning/` 工件的提交都通过父仓库进行;**不要**在 `qy-lty-admin\` 内执行 `git init`,否则会形成嵌套仓库与父仓库冲突。
## 项目规则提醒
CLAUDE.md 中两条强制规则,做任何 phase 时必须遵守:
1. **沟通语言**所有面向用户的回复使用中文CLAUDE.md 顶部「语言」要求 + 跨项目约定)
2. **修改记录**:每次代码 / 配置 / `package.json` / Dockerfile / CI / 文档结构性改动 **必须**追加到 `docs/修改记录.md` 顶部CLAUDE.md「项目修改记录规则」节
`qy-lty-admin``qy_lty` 是独立项目,修改记录互不混合,跨项目联动两端各写一条互相引用对方的条目。
---
*由 /gsd-new-projectbrownfield 文档化)生成于 2026-05-07*

View File

@ -0,0 +1,203 @@
# Architecture
**Analysis Date:** 2026-05-07
## Pattern Overview
**Overall:** Next.js 15 App Router with client-centric admin dashboard, role-based access control (RBAC) via localStorage permission matrix, and layered API abstraction.
**Key Characteristics:**
- Client-side rendering-first pattern with permission checks in layout/shell components
- Token-based authentication with Axios interceptors for transparent token injection
- Role-based module access controlled by permission matrix in `lib/permissions.ts`
- Form handling via React Hook Form + Zod for validation
- Shadcn-style UI components copied into `components/ui/` (not npm packages)
## Layers
**Page Layer (Presentation/Routing):**
- Purpose: App Router page components that mount client-side shells and load data
- Location: `app/[module]/page.tsx` (e.g., `app/outfits/page.tsx`, `app/login/page.tsx`)
- Contains: "use client" page entries, form pages, list views
- Depends on: DashboardShell, custom feature components, API clients
- Used by: Next.js App Router, browser navigation
**Component Layer (UI & Business Logic):**
- Purpose: Reusable business components (modals, dialogs, tables, feature-specific UI)
- Location: `components/` (feature folders) and `components/ui/` (primitives)
- Contains: DashboardShell, Sidebar, feature dialogs (AddOutfitDialog, DeleteConfirmationDialog), stat cards
- Depends on: UI primitives, API clients, React Hook Form, icons (lucide-react)
- Used by: Page components
**API Client Layer (HTTP Communication):**
- Purpose: Axios-based API communication with interceptors for auth, error handling
- Location: `lib/api/` (e.g., `lib/api/client.ts`, `lib/api/auth.ts`, `lib/api/outfits.ts`)
- Contains: API singleton instance, interceptors, module-specific request/response adapters
- Depends on: Axios, localStorage (for token retrieval), environment variables
- Used by: Page components and feature components
**Permission/Auth Layer (Access Control):**
- Purpose: Role-based module visibility and path-level permission checks
- Location: `lib/permissions.ts` (RBAC matrix), `middleware.ts` (route protection)
- Contains: PERMISSION_MATRIX (role → module[]), utility functions (hasPermission, getUserRole, getModuleFromPath)
- Depends on: localStorage.user_role, localStorage.is_superuser
- Used by: DashboardShell, Sidebar, middleware
**Utility Layer:**
- Purpose: Type definitions, helpers, environment detection
- Location: `lib/utils.ts`, `lib/api/types.ts`, `lib/api/adapters.ts`
- Contains: Tailwind utilities (cn()), type mappings, mock data
- Depends on: Nothing (standalone)
- Used by: All layers
## Data Flow
**Authentication Flow (Login → Authenticated State):**
1. User fills login form on `app/login/page.tsx`
2. Form submission calls `emailLogin(email, password)` from `lib/api/auth.ts`
3. Axios POSTs to `{NEXT_PUBLIC_API_BASE_URL}/v1/admin/login/`
4. Response contains `token`, `role`, `is_superuser`
5. `saveAuthToken(token, isSuperUser, role)` stores to localStorage and Cookie
6. Page redirects to `/` (dashboard)
7. Middleware checks Cookie `auth_token`; on subsequent page loads, Axios request interceptor injects token from localStorage
**Protected Page Access Flow:**
1. User navigates to protected route (e.g., `/outfits`)
2. Middleware checks cookie; if missing, redirects to `/login`
3. Page mounts, DashboardShell component renders
4. DashboardShell calls `hasPathPermission(pathname)` at mount
5. Function checks localStorage.user_role against PERMISSION_MATRIX
6. If denied, renders access-denied UI; if allowed, renders children
7. Sidebar calls `hasPermission(module)` for each menu item (mounted only) to filter visible items
8. Sidebar also displays current role from localStorage.user_role
**Data Fetching Flow (Page → Component → API → Backend):**
1. Page component (e.g., `app/outfits/page.tsx`) mounts as "use client"
2. Page renders feature components (e.g., OutfitsList, OutfitsTable)
3. Feature component calls `getOutfits(params)` from `lib/api/outfits.ts`
4. API function calls `apiClient.get('/card/category/clothing/?...')`
5. Request interceptor injects token: `Authorization: Bearer {token}`
6. Backend receives request with token in header
7. Response returns `{ success, code, data, message }`
8. API adapter function maps backend response to frontend type (e.g., `mapBackendOutfit`)
9. Component stores result in React state or updates UI
10. Response interceptor handles 401 (token expired) → clears localStorage → redirects to `/login`
**State Management:**
- **Token/Auth state:** Persisted in `localStorage.auth_token`, `localStorage.user_role`, `localStorage.is_superuser`; synced to Cookie `auth_token`
- **Component state:** React useState (shallow, page-scoped)
- **Form state:** React Hook Form (form-scoped)
- **No global state manager** (Redux/Zustand not used)
**Authentication Token Lifecycle:**
1. Stored in localStorage + Cookie (7-day expiry) after login
2. On every API request, request interceptor reads from localStorage
3. If 401 response, token deleted from localStorage, user redirected to `/login`
4. Cookie-based fallback allows middleware to check auth on route entry
## Client/Server Component Split
**Server Components:**
- `app/layout.tsx` — Root layout with metadata only; does NOT render protected content
- Most pages are **"use client"** due to permission checks and hooks
**Client Components ("use client"):**
- All pages under `app/[module]/` — Need React hooks for permission checks, form handling
- `components/dashboard-shell.tsx` — Runs permission checks via `hasPathPermission()`
- `components/sidebar.tsx` — Uses `usePathname()`, `useRouter()`, `useState()` for role-based filtering
- Feature components — Use useState, API calls, form handling
## Key Abstractions
**DashboardShell:**
- Purpose: Layout wrapper that enforces route-level permissions
- Location: `components/dashboard-shell.tsx`
- Pattern: Client wrapper checking `hasPathPermission(pathname)` on mount; renders either access-denied UI or children
- Used by: All page components (wraps page content)
**Sidebar:**
- Purpose: Dynamic navigation menu filtered by user role
- Location: `components/sidebar.tsx`
- Pattern: Client component reading `localStorage.user_role` after mount; filters menu items via `hasPermission(module)`
- Menu structure: Three sections (AI Admin, Content Admin, System Admin) with conditional rendering
- Icon mapping: MenuItem interface + lucide-react icons
**API Client Singleton:**
- Purpose: Centralized HTTP client with reusable interceptors
- Location: `lib/api/client.ts` exports `apiClient` (Axios instance)
- Pattern: Single instance created at module load; request/response interceptors for auth/error handling
- Interceptors: Request injects token; response handles 401 by clearing token + redirecting
**Permission Matrix:**
- Purpose: Role → Module[] mapping
- Location: `lib/permissions.ts` PERMISSION_MATRIX constant
- Pattern: Record<RoleName, PermissionModule[]> with 5 roles × 13 modules
- Functions: getUserRole() → RoleName; hasPermission(module) → boolean; getModuleFromPath(pathname) → module
**API Adapter Pattern:**
- Purpose: Map backend response schema to frontend type
- Example: `mapBackendOutfit()` in `lib/api/outfits.ts`
- Pattern: Backend returns `{ id, name, image_url, rarity_display, ... }`; adapter extracts and renames to `{ id, name, imageUrl, rarity, ... }`
- Why: Frontend type contracts differ from backend schema
## Entry Points
**Browser Entry:**
- Location: `app/layout.tsx` (Root layout)
- Triggers: Initial page load or navigation
- Responsibilities: Renders HTML shell; child routes rendered by App Router
**Auth Entry:**
- Location: `app/login/page.tsx`
- Triggers: Unauthenticated users accessing protected routes (redirected by middleware)
- Responsibilities: Render login form, call `emailLogin()`, save auth token, redirect to dashboard
**Dashboard Entry:**
- Location: `app/page.tsx`
- Triggers: Authenticated users navigating to `/` or post-login redirect
- Responsibilities: Check auth status, render stats/charts, provide links to submodules
**Protected Route Entry (Example):**
- Location: `app/outfits/page.tsx`
- Triggers: User navigates to `/outfits`
- Responsibilities: Wrap content in DashboardShell (permission check), render OutfitsList component, fetch data
## Error Handling
**Strategy:** Try-catch at API level; Axios interceptor handles 401; UI displays error toast or inline messages.
**Patterns:**
- **API Request Errors:** Caught in try-catch, logged, thrown to caller (page/component)
- **401 Unauthorized:** Interceptor clears token, redirects to `/login`
- **Form Validation Errors:** React Hook Form + Zod pre-submission; validation errors displayed inline
- **Permission Denied:** DashboardShell renders access-denied UI (no error thrown)
- **Mock Data Fallback:** `lib/api/client.ts` exports mock users/roles; can be used for offline testing
## Cross-Cutting Concerns
**Logging:**
- Verbose Axios logging in request/response interceptors (console.log with emoji prefixes)
- Error logging in catch blocks
**Validation:**
- React Hook Form + Zod for form fields
- Backend response type-checked against ApiResponse interface
**Authentication:**
- localStorage.auth_token + Axios Bearer header injection
- Middleware checks Cookie auth_token for route protection
- 401 response triggers token cleanup + redirect
**Internationalization:**
- Chinese UI text hardcoded in components
- No i18n library used
---
*Architecture analysis: 2026-05-07*

View File

@ -0,0 +1,655 @@
# Codebase Concerns
**Analysis Date:** 2026-05-07
## Tech Debt
### Multiple Lock Files Conflict Risk
**Issue:** Three conflicting lock files coexist: `package-lock.json`, `pnpm-lock.yaml`, and `yarn.lock`.
**Files:**
- `package-lock.json` (177KB)
- `pnpm-lock.yaml` (2.3KB)
- `yarn.lock` (91KB)
**Impact:**
- Risk of installing different dependency versions across environments
- CI/CD, Docker builds, and local development may pull incompatible versions
- Lock file merge conflicts during multi-developer collaboration
- Dockerfile uses yarn exclusively, but npm/pnpm may be used locally, creating version drift
**Current State:**
- `Dockerfile` (line 12, 34) explicitly uses `yarn.lock*`
- `.gitignore` likely checks in all three files
- CLAUDE.md explicitly warns "不要混用" but provides no automation to enforce it
**Fix Approach:**
1. Choose single package manager (recommend yarn, per Dockerfile)
2. Delete `package-lock.json` and `pnpm-lock.yaml` from git history
3. Add enforcement to `.gitignore` and CI/CD (fail if multiple lock files modified)
4. Document in CLAUDE.md that ONLY yarn should be used
**Priority:** High - impacts build reproducibility and team workflow
---
### Production Build Ignores TypeScript & ESLint Errors
**Issue:** Build succeeds despite type/lint errors; production may contain undetected bugs.
**Files:**
- `next.config.mjs:16-21` — Both `ignoreDuringBuilds: true`
**Current Configuration:**
```javascript
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
```
**Impact:**
- TypeScript errors (type mismatches, missing nullchecks) not caught at build time
- ESLint warnings (unused imports, unreachable code) ignored
- QA burden shifts to runtime discovery
- Breaking changes in dependencies not detected early
**Evidence:** README.md (line 121) acknowledges this: "务必在本地执行 `next lint``tsc --noEmit` 保证质量" — manual step, not enforced
**Fix Approach:**
1. Set both to `false` in production config
2. Fix all current type/lint errors (likely small count given ~27K LOC)
3. Add pre-commit hooks to `husky` to block commits with errors
4. Make lint/type checks mandatory in CI/CD
**Priority:** High - gates correctness
---
### Console Logging in Client Code (Debug Artifact)
**Issue:** Production code contains console.log/warn/error calls, exposing debug info and token prefixes.
**Files & Line Numbers:**
- `lib/api/client.ts:23``console.log('🔍 Token检查:', ...)`
- `lib/api/client.ts:29``console.warn('⚠️ 未找到auth_token...')`
- `lib/api/client.ts:35``console.log('📡 发送请求:', ...)`
- `lib/api/client.ts:36``console.log('📋 请求头:', ...)`
- `lib/api/client.ts:47``console.log('✅ 响应成功:', ...)`
- `lib/api/client.ts:51``console.error('❌ 响应错误:', ...)`
- `lib/api/client.ts:55``console.warn('🚫 认证失败...')`
- `lib/api/client.ts:56``console.log('响应详情:', ...)`
- `lib/api/upload.ts:33``console.log('📤 开始上传文件:', ...)`
- `lib/api/upload.ts:43``console.log(...上传进度...)`
- `lib/api/upload.ts:59``console.log('✅ 文件上传成功:', ...)`
- `lib/api/upload.ts:62``console.error("文件上传失败:", error)`
- Similar issues in upload multifile (lines 83, 93)
- `components/sidebar.tsx:97``console.error("退出登录失败:", error)`
- `app/outfits/page.tsx:72, 117, 135, 154` — console.error calls
- Many other pages: `app/*/page.tsx` contain error logging
**Impact:**
- Token prefixes exposed in browser console → XSS/developer tools leak risk
- Error details (e.g., API paths, status codes) visible to end users
- Production debugging should use structured error handling, not console
- Could be scraped by malicious actors with browser console access
**Evidence:** `lib/api/token-debug.ts` is entire debug utility with `console.log` throughout (lines 7-54), showing token prefix extraction logic
**Fix Approach:**
1. Remove all console.log/warn/error in client code, replace with structured error handling
2. Create error boundary/toast notification pattern for user-facing errors
3. Optional: Create client-side error tracking service (Sentry) for production
4. Add ESLint rule: `no-console` (already available in Next.js config, just not enabled)
**Priority:** High - security and code cleanliness
---
## Security Concerns
### Client-Side Permission Enforcement Only (Trust Boundary Violation)
**Issue:** Permissions are enforced ONLY on client; backend may not re-validate on API calls.
**Files:**
- `lib/permissions.ts:65-87``getUserRole()`, `hasPermission()` read from `localStorage.user_role`
- `components/sidebar.tsx:102-104` — Filter menu items client-side only
- `middleware.ts:17-44` — Token validation in middleware, but NO permission check against module being accessed
**Risk Model:**
```
User (attacker) → Modify localStorage.user_role="超级管理员" →
Sidebar shows all menus (useless) BUT
Direct API call to DELETE /api/v1/admin/users/123 → Backend accepts if no re-check
```
**Current Evidence:**
- `lib/api/users.ts:24-43``getUsers()` makes raw API call; no permission guard in client code
- No evidence of backend role check in Django API (not inspectable from frontend code, but CRITICAL to verify)
**Impact:**
- **CRITICAL:** If backend doesn't re-validate role on each endpoint, attackers can:
- Modify localStorage role → make admin API calls
- Bypass middleware token check (SSR can't access localStorage) → direct API calls with stolen token
- Perform unauthorized operations (delete users, change settings, etc.)
**Trust Boundary Rule:** Backend MUST re-check permission for EVERY admin API call, regardless of client claims.
**Fix Approach:**
1. **URGENT:** Audit Django backend API (`qy_lty/lib/api/v1/admin/`) to confirm all endpoints validate user role/permissions
2. Document in CLAUDE.md: "Client-side permission filters are UI courtesy only; backend MUST validate on all admin/ endpoints"
3. Add explicit checks in frontend error handling for 403 Forbidden responses
4. Consider adding request logging/monitoring to catch unauthorized attempts
**Priority:** CRITICAL - security vulnerability if backend doesn't re-check
---
### Token Storage in localStorage (XSS Vulnerability)
**Issue:** Auth token stored in `localStorage` instead of secure HttpOnly cookie; vulnerable to XSS.
**Files:**
- `lib/api/auth.ts:48-59` — Stores token in localStorage AND sets js-cookie
- `lib/api/client.ts:21-22` — Reads token from localStorage in request interceptor
- `middleware.ts:32` — Token read from Cookie (correct), but localStorage is fallback
**Current Implementation:**
```typescript
// Stored in BOTH localStorage and cookie
localStorage.setItem("auth_token", token); // XSS risk
Cookies.set("auth_token", token, { expires: 7, path: '/' }); // No HttpOnly flag
```
**Impact:**
- XSS attack can steal token from localStorage
- js-cookie (js-cookie, no HttpOnly flag) also vulnerable to XSS
- 7-day expiry without rotation = long XSS window
- `localStorage.user_role` also stored unencrypted (attackable)
**Proper Secure Pattern:**
- HttpOnly, Secure, SameSite cookies (set by backend during login)
- Token never exposed to JavaScript
- Client reads from Bearer token response, never stores in accessible storage
**Fix Approach:**
1. Backend should set HttpOnly cookie during login response
2. Remove localStorage token storage entirely
3. Keep only sealed HTTP-only cookie for auth
4. If SSR requires token access, use secure session storage (not localStorage)
5. Add CSP (Content-Security-Policy) headers to mitigate XSS
**Priority:** High - significant security risk
---
### No Token Rotation / Refresh Strategy
**Issue:** Token expires in 7 days, no refresh token or rotation mechanism visible.
**Files:**
- `lib/api/auth.ts:63``Cookies.set("auth_token", token, { expires: 7, path: '/' })`
- `lib/api/client.ts:54-60` — 401 handler just redirects to /login, doesn't attempt refresh
**Impact:**
- User activity > 7 days → auto-logout (UX degradation)
- No short-lived access token + long-lived refresh token pattern
- Stolen token valid for full 7 days
- No token revocation (logout doesn't invalidate server-side if stateless JWT)
**Fix Approach:**
1. Implement short-lived access token (15-30 min) + refresh token (7-14 days)
2. Refresh interceptor: if 401, attempt token refresh before redirecting to /login
3. Backend session should blacklist tokens on logout
4. Document token lifecycle in CLAUDE.md
**Priority:** Medium - acceptable for internal admin tool, but should improve for production
---
### No Input Validation on File Upload
**Issue:** File uploads accept any mimetype/size without validation.
**Files:**
- `lib/api/upload.ts:25-65``uploadFile()` accepts File object, no size/type checks in client
- `components/ui/file-upload.tsx` — Likely has upload UI, needs validation
**Impact:**
- Malicious files (executables, oversized) may reach backend
- Backend must validate, but client-side check is first defense
- No maximum file size enforced client-side
**Fix Approach:**
1. Add file size limits (e.g., max 50MB)
2. Whitelist MIME types (images: image/jpeg, image/png, image/webp only)
3. Show user error before upload attempt
4. Verify backend enforces same limits
**Priority:** Medium - backend should catch it, but client should be first defense
---
## Fragile Areas
### Large Pages with Mixed Concerns (1000+ line files)
**Issue:** Several pages handle data fetching, UI rendering, and business logic in single component.
**Large Files:**
- `app/affinity/page.tsx` (1005 lines) — Affinity rules, levels, interactions all in one page
- `components/ui/sidebar.tsx` (763 lines) — Navigation logic, permission filtering, logout in one component
- `app/songs/[id]/page.tsx` (651 lines) — Song detail page, editing, deletion mixed
- `app/songs/page.tsx` (644 lines) — Song list, search, pagination, CRUD operations
**Fragility Risks:**
- Hard to test individual business logic
- Changes to one feature (e.g., affinity rules) risk breaking another (levels)
- Difficult to reuse UI logic across pages
- High cognitive load for maintenance
**Safe Modification Pattern:**
1. Extract data-fetching logic to `lib/api/` hooks or services
2. Create smaller sub-components (e.g., `<AffinityRuleSection>`, `<AffinityLevelSection>`)
3. Use custom hooks for state management per feature
4. Keep pages as composition only
**Example Refactor:**
```typescript
// Current: 1005-line page
// After:
export default function AffinityPage() {
return (
<DashboardShell>
<DashboardHeader />
<AffinityRulesSection />
<AffinityLevelsSection />
<InteractionLogsSection />
</DashboardShell>
)
}
```
**Priority:** Medium - refactor as part of feature changes, not urgent
---
### No Error Boundaries (Next.js error.tsx Files Missing)
**Issue:** No error boundary components for graceful error handling in App Router.
**Files:**
- `app/` has NO `error.tsx` files
- Only some leaf routes have `loading.tsx` (e.g., `app/outfits/loading.tsx`)
**Current Error Handling:**
- Pages catch errors in `try/catch`, show toast notifications
- Uncaught errors propagate, may show raw error UI
- No fallback UI for API failures
**Impact:**
- User sees blank page or browser error on network failure
- No recovery mechanism (retry button, fallback content)
- Log data lost if error page crashes
**Next.js Error Boundary Pattern:**
```typescript
// app/error.tsx (catches errors in all children)
'use client'
export default function Error({ error, reset }) {
return (
<div>
<h1>Something went wrong</h1>
<button onClick={() => reset()}>Try again</button>
</div>
)
}
```
**Fix Approach:**
1. Add `app/error.tsx` for app-level errors
2. Add segment-level `error.tsx` for feature-specific handling (e.g., `app/outfits/error.tsx`)
3. Keep try/catch for API calls, render fallback UI
4. Add `app/not-found.tsx` for 404 routes
**Priority:** Low - pages have basic error handling via toasts, but UX could improve
---
### Hardcoded API Base URL Fallback
**Issue:** API client falls back to hardcoded URL if env var not set.
**Files:**
- `lib/api/client.ts:9``API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000/api"`
**Risk:**
- If `NEXT_PUBLIC_API_BASE_URL` not set in production, silently uses localhost
- Requests fail silently in production, confusing to debug
- No warning log when fallback is used
**Fix Approach:**
1. Remove fallback; throw error if env var not set at build time
2. Use `if (!process.env.NEXT_PUBLIC_API_BASE_URL) throw new Error("...")`
3. Document required env vars in deployment guide
**Priority:** Low - environmental issue, unlikely in production if properly configured
---
## Performance Bottlenecks
### No Memoization on Permission Checks
**Issue:** `hasPermission()`, `getUserRole()` called on every render without memoization.
**Files:**
- `components/sidebar.tsx:102-104` — Calls `hasPermission(item.module)` in `.filter()` on every render
- `lib/permissions.ts:65-87` — No caching; reads localStorage every time
**Current Pattern:**
```typescript
const visibleAiItems = mounted ? aiMenuItems.filter((item) => hasPermission(item.module)) : []
```
**Impact:**
- Sidebar re-renders, re-filters items, re-reads localStorage unnecessarily
- Minimal impact on small arrays (3-15 items), but scales poorly
**Fix Approach:**
1. Memoize `getUserRole()` result in sidebar:
```typescript
const userRole = useMemo(() => getUserRole(), [mounted])
const visibleItems = useMemo(() =>
aiMenuItems.filter(item => hasPermission(item.module)),
[userRole]
)
```
2. Or move to custom hook: `const { role, allowedModules } = usePermissions()`
**Priority:** Low - not a visible performance issue, but good practice
---
## Cross-Repo Coupling Risks
### Strong Runtime Dependency on qy_lty Backend
**Issue:** Front-end depends on specific API contract from `qy_lty/` Django backend; contract drift risk.
**Files:**
- `lib/api/users.ts:6-21` — Maps backend `ParadiseUser` to `User` type (field mapping)
- `lib/api/outfits.ts` (not read, but expected) — Maps backend outfit schema
- ALL API modules expect specific response structure from `/api/v1/admin/*`
**Coupling Points:**
- Backend field names: `username`, `email`, `phone_number`, `date_joined`, `last_login`, `is_superuser`, `is_staff`
- Response structure: `{ results: [...], count: number }` (Django REST Framework default)
- Endpoint paths: `/v1/admin/login/`, `/user/`, `/common/upload/`
- Token storage key in Redis: `admin_token:{token}` (per CLAUDE.md)
**Risk Scenario:**
1. Backend dev changes `/v1/admin/users/` response format
2. Frontend still sends old field names or expects old response shape
3. Silent failures or type mismatches
4. Detected late in testing or production
**Mitigation in Place:**
- CLAUDE.md (line 34) requires both repos update `docs/修改记录.md` when contract changes
- But no automated contract testing (OpenAPI, TypeScript codegen)
**Fix Approach:**
1. **Shared types/API contract** (OpenAPI/GraphQL schema) versioned in git
2. Generate TypeScript types from backend OpenAPI spec (e.g., with Swagger-Codegen)
3. Add pre-commit hook to detect schema changes
4. Document in CLAUDE.md: "Any API response structure change requires updating both repos' docs"
**Priority:** Medium - mitigated by manual process, but automatable
---
## Dependencies at Risk
### No Enforcement of Dependency Updates
**Issue:** Many dependencies pinned to `latest`, creating unpredictable updates.
**Files:**
- `package.json:12, 56, 63``"latest"` version specifiers
**Current Versions Using "latest":**
```json
"@hookform/resolvers": "latest",
"react-hook-form": "latest",
"recharts": "latest",
"zod": "latest"
```
**Impact:**
- `npm install` at different times installs different major versions
- Breaking changes in `react-hook-form` or `recharts` not caught by lock file
- Tests pass locally, fail in CI if new major version published
**Fix Approach:**
1. Replace all `"latest"` with specific versions (e.g., `"^7.0.0"` for caret range)
2. Use Dependabot or renovate to automate safe updates
3. Pin devDependencies to exact versions
**Example:**
```json
"@hookform/resolvers": "^3.3.4",
"react-hook-form": "^7.51.0",
"recharts": "^2.12.0",
"zod": "^3.22.0"
```
**Priority:** Medium - not urgent, but good practice
---
### Unused Dependencies or Overlapping Packages
**Issue:** Unclear if all installed packages are actively used.
**Potential Issues:**
- Multiple notification libraries: `sonner` + `@radix-ui/react-toast` (which is used?)
- Figure out which is preferred and remove duplication
**Files:**
- `package.json:36, 59` — Both toast packages listed
**Fix Approach:**
1. Audit each dependency in `package.json`
2. Remove unused packages
3. Consolidate duplicate functionality
**Priority:** Low - cleanup task
---
## Test Coverage Gaps
### No Tests Found
**Issue:** No Jest/Vitest configuration or test files detected in codebase.
**Expected Test Files:**
- `*.test.ts`, `*.spec.ts`, `*.test.tsx`, `*.spec.tsx` not found
- No `jest.config.*`, `vitest.config.*`
**Impact:**
- Complex logic (permission matrix, API adapters) not tested
- Refactoring risky (no regression protection)
- Onboarding new developers without safety net
- No confidence in critical paths (auth, user management)
**Critical Areas Needing Tests:**
- `lib/permissions.ts` — Permission matrix logic (many branches)
- `lib/api/client.ts` — Request/response interceptors (error handling paths)
- `lib/api/users.ts` — User mapping logic (backend schema mismatches)
- Custom hooks in pages (data fetching, state management)
**Fix Approach:**
1. Set up Jest or Vitest (Vitest recommended for ESM + modern tooling)
2. Create basic test for `lib/permissions.ts`:
```typescript
// lib/permissions.test.ts
describe('Permission checks', () => {
it('should allow super admin all modules', () => {
// Mock localStorage with 超级管理员
expect(hasPermission('users')).toBe(true)
expect(hasPermission('ai-model')).toBe(true)
})
it('should restrict content admin to content modules', () => {
// Mock localStorage with 内容管理员
expect(hasPermission('users')).toBe(false)
expect(hasPermission('outfits')).toBe(true)
})
})
```
3. Aim for 80%+ coverage of critical API functions
**Priority:** Medium - important for long-term maintainability
---
## Missing Critical Features
### No Logout Token Revocation
**Issue:** `logout()` clears client-side storage but doesn't invalidate server-side token.
**Files:**
- `lib/api/auth.ts:108-131``logout()` calls API endpoint, then clears localStorage
**Problem:**
- If using stateless JWT, backend has no record of logout
- Token remains valid until expiry (7 days)
- Attacker with stolen token can still use it after target user "logs out"
**Expected Behavior:**
- Server should blacklist token on logout (Redis blacklist or DB flag)
- Verify endpoint `/v1/admin/logout/` actually invalidates token
**Fix Approach:**
1. Audit backend logout endpoint to confirm token is invalidated
2. Add token blacklist to backend (Redis: token → blacklisted, TTL=7 days)
3. Check token against blacklist on every request
4. Document in CLAUDE.md
**Priority:** High - security issue if not handled on backend
---
### No Rate Limiting on Client (Brute Force Risk)
**Issue:** Login page may be vulnerable to brute force attacks.
**Files:**
- `app/login/page.tsx` — Likely has login form
- `lib/api/auth.ts:25-40` — No rate limiting before API call
**Impact:**
- Attacker can spam login attempts to guess password
- Backend must implement rate limiting, but client can't spam if it has throttling
**Fix Approach:**
1. Client-side: Disable login button for 30 seconds after failed attempt
2. Backend: Implement account lockout (3 failed attempts = 10 min lockout)
3. Monitor login failures in logs
**Priority:** Medium - backend responsibility, but client should assist
---
## Build Configuration Issues
### Next.js Standalone Output May Have Deployment Pitfalls
**Issue:** Standalone mode used, but edge case behaviors not fully documented.
**Files:**
- `next.config.mjs:30``output: 'standalone'`
**What Standalone Does:**
- Bundles all dependencies into `.next/standalone` folder
- Reduces Docker image size (~100MB vs ~500MB)
- Requires explicit `COPY .next .next` in Dockerfile (correctly done)
**Potential Issues:**
- Public assets not automatically copied; must COPY separately (done in Dockerfile:44-45)
- `next.config.mjs` must be manually copied (done in Dockerfile:46)
- Environment variables must be passed at runtime, not build time
- Debugging bundle issues harder (bundled deps not in node_modules)
**Current Dockerfile (lines 44-46) handles this correctly:**
```dockerfile
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/next.config.mjs ./
```
**Risk:**
- If dependencies add new output files in future versions, they won't be copied
- Node version mismatch between builder (22.10.0) and runner (22.10.0) is correct but fragile
**Fix Approach:**
1. Document standalone output caveats in deployment guide
2. Test Docker image monthly to ensure no drift in builder/runner
3. Consider base image pinning: `FROM node:22.10.0-alpine@sha256:...`
**Priority:** Low - current setup is correct, just needs documentation
---
### No Pre-commit Hooks or Husky Setup
**Issue:** No automated checks before commits; developers can commit broken code.
**Files:**
- No `.husky/` directory
- No `pre-commit` config
- CLAUDE.md doesn't mention hook setup
**Risk:**
- Developer commits console.log, broken types, or lint errors
- CI catches them later, but feedback loop is slow
- No enforcement of modification record updates (CLAUDE.md requirement)
**Fix Approach:**
1. Install husky: `npm install husky --save-dev`
2. Setup hook: `npx husky install`
3. Create `.husky/pre-commit`:
```bash
#!/bin/sh
npm run lint && npm run type-check
```
4. Create `.husky/prepare-commit-msg` to remind user about docs/修改记录.md
**Priority:** Low - improvement for team workflow
---
## Summary Table
| Category | Issue | Severity | Fix Effort |
|----------|-------|----------|-----------|
| Security | Client-only permissions (backend trust) | CRITICAL | High |
| Security | Token in localStorage (XSS) | High | Medium |
| Security | No token revocation on logout | High | High |
| Tech Debt | Lock file conflicts | High | Low |
| Tech Debt | Build ignores TS/ESLint errors | High | Low |
| Tech Debt | Console logging in production | High | Low |
| Fragility | Large 1000+ line pages | Medium | Medium |
| Performance | No memoization on permissions | Low | Low |
| Testing | Zero test coverage | Medium | High |
| Build | Standalone output not fully documented | Low | Low |
---
*Concerns audit: 2026-05-07*

View File

@ -0,0 +1,210 @@
# 代码规范
**分析日期:** 2026-05-07
## 命名规范
**文件命名:**
- 组件文件PascalCase + `.tsx` 扩展名(如 `DashboardShell.tsx``AddOutfitDialog.tsx`
- 工具函数/库文件kebab-case + `.ts` 扩展名(如 `error-handler.ts``client.ts`
- 特殊情况:对话框类组件使用 kebab-case`add-outfit-dialog.tsx``delete-confirmation-dialog.tsx`
**函数命名:**
- 使用 camelCase`handleLogin``fetchOutfits``mapBackendOutfit`
- API 函数:动词 + 名词camelCase`getOutfits``createOutfit``updateOutfit``deleteOutfit`
- 事件处理函数:`handle` + 事件名(如 `handleSubmit``handleChange``handleSendVerificationCode`
- 工具函数:动词 + 目标(如 `formatDate``toDisplayOutfit`
**变量命名:**
- 状态变量camelCase`email``password``isLoading``selectedOutfit`
- 布尔值:`is``has` 前缀(如 `isLoading``isSubmitting``hasPermission`
- 常量UPPER_SNAKE_CASE`TOAST_LIMIT``TOAST_REMOVE_DELAY``API_BASE_URL`
**类型/接口命名:**
- PascalCase`EmailLoginResponse``DashboardShellProps``ApiResponse<T>`
- Props 接口:`组件名Props` 后缀(如 `ButtonProps``DashboardShellProps`
- 联合类型:`RoleName``PermissionModule`
## 代码样式
**格式化:**
- 使用 TypeScript strict 模式(`tsconfig.json``"strict": true`
- 目标ES6`target: "ES6"`
- 模块化解析:`bundler` 模式(用于 Next.js
- 无 ESLint/Prettier 配置文件(`next.config.mjs``eslint.ignoreDuringBuilds: true`
- 代码风格通过 TypeScript 编译器和 Next.js 内置检查
**缩进和空格:**
- 使用 2 空格缩进(根据代码库一致性)
- 函数声明和块语句间使用一致空格
**类型注解:**
- 所有函数参数必须有类型注解
- 所有变量应有类型注解(特别是导出的接口和公共函数返回值)
- 使用 TypeScript 严格模式防止隐式 `any`
## 导入组织
**顺序:**
1. React 和 Next.js 核心库(`import React from "react"``import { useState } from "react"`
2. Next.js 功能(`import { useRouter } from "next/navigation"``import Link from "next/link"`
3. 第三方库(`import axios from "axios"``import Cookies from "js-cookie"`
4. 本项目组件(`import { Button } from "@/components/ui/button"`
5. 本项目库和工具(`import { cn } from "@/lib/utils"`
6. 本项目 hooks`import { useToast } from "@/components/ui/use-toast"`
7. 类型导入(`import type { ApiResponse } from "./client"`
**路径别名:**
- 项目配置 `@/*` 指向项目根目录
- 组件:`@/components/...`
- 库函数:`@/lib/...`
- Hooks`@/hooks/...`
- 类型和接口:通过 `@/lib/api/types` 集中导入
- UI 组件:`@/components/ui/...`
## 错误处理
**模式:**
- 使用 `try-catch` 处理异步操作和 API 调用(见 `auth.ts``outfits.ts`
- 创建自定义错误类 `ApiError` 扩展 `Error`,包含 `status` 属性(见 `error-handler.ts` 第 46-54 行)
- 统一的错误处理函数 `handleApiException()` 将不同类型的错误转换为用户友好的消息(见 `error-handler.ts` 第 79-90 行)
- 错误信息映射表 `errorMessages` 定义特定错误代码(`USER_NOT_FOUND``ROLE_EXISTS` 等)的消息
**API 错误处理:**
- 在 Axios 响应拦截器中处理 401 未授权错误:清除 token 并重定向到登录页面(见 `client.ts` 第 54-61 行)
- 对 API 失败使用 Radix Toast 组件显示错误提示(见 `error-handler.ts` 第 69-76 行、第 93-100 行)
- 统一的 `handleApiRequest()` 包装器函数处理成功/失败情况并自动显示 toast`error-handler.ts` 第 102-144 行)
**日志:**
- 使用 `console.log()``console.warn()``console.error()` 进行调试和错误追踪
- API 请求/响应在拦截器中记录详细日志,包括 token 检查、请求头、响应状态(见 `client.ts` 第 20-66 行)
- 使用 emoji 表情增强日志可读性(`🔍 Token检查``✅ Token已添加``❌ 响应错误`
## 日志记录
**框架:** `console`(浏览器原生)
**使用规范:**
- API 请求/响应日志在 Axios 拦截器中集中管理
- 所有认证流程加日志token 检查、保存、清除)
- 错误信息带上上下文URL、状态码、方法
- 生产环境应考虑减少日志或使用第三方服务(未配置)
## 注释
**何时写注释:**
- JSDoc 风格注释用于公共 API 函数(见 `auth.ts` 第 5-7 行、第 24-30 行)
- 注释解释"为什么"而不是"是什么"
- 复杂业务逻辑或算法需要行内注释
**JSDoc/TSDoc 风格:**
```typescript
/**
* 邮箱登录接口
* @param email 邮箱
* @param password 密码
* @returns 包含token的响应
*/
export const emailLogin = async (email: string, password: string): Promise<EmailLoginResponse> => {
// ...
}
```
**中文注释:** 所有注释使用中文(符合 CLAUDE.md 的语言偏好)
## 函数设计
**大小:**
- 函数长度通常 20-50 行(见 `handleLogin``login/page.tsx` 第 28-63 行)
- 超过 100 行的函数应考虑拆分(如 `AddOutfitDialog` 组件中的多步表单分离为 Tabs
**参数:**
- 单个参数优于多个参数
- 对象参数用于选项(如 `handleApiRequest()` 的 options 对象,第 104-110 行)
- 类型参数用于泛型函数(如 `handleResponse<T>()``ApiResponse<T>`
**返回值:**
- 异步函数返回 `Promise<T>`
- 布尔查询函数返回 `boolean`(如 `hasPermission()``isAuthenticated()`
- 数据获取返回具体类型或 `null`(见 `getOutfit()` 返回 `Promise<Outfit>`
## 模块设计
**导出:**
- 公共 API 函数从 `lib/api/` 模块导出(如 `emailLogin``getOutfits`
- 类型从 `lib/api/types.ts` 集中导出
- Utility 函数从 `lib/utils.ts` 导出(如 `cn()` 用于样式合并)
**Barrel 文件:**
- `lib/api/index.ts` 作为 barrel 文件,导出所有 API 函数以便统一导入
- 组件库采用 Radix UI + shadcn 风格,每个组件文件独立,无 barrel 文件
## React 和组件规范
**状态管理:**
- 使用 React Hooks`useState``useEffect``useCallback`)进行本地状态管理
- 在客户端组件中集中管理表单状态(见 `add-outfit-dialog.tsx` 第 24-36 行)
**组件模式:**
- 所有表单和交互组件用 `"use client"` 指令标记
- Page 组件(`app/*/page.tsx`)使用 `"use client"` 支持交互
- 使用 Radix UI 和 shadcn 风格组件库,组件文件在 `components/ui/`
- Props 接口扩展原生 HTML 属性(如 `ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>`
**样式:**
- 使用 Tailwind CSS 类名
- 使用 `cn()` 工具函数(基于 `clsx` + `tailwind-merge`)合并条件样式(见 `button.tsx` 第 47 行)
- 使用 `class-variance-authority` (CVA) 定义组件变体(见 `button.tsx` 第 7-34 行)
- 颜色、间距、圆角通过 Tailwind 配置变量CSS 变量)定义
## 表单与验证
**表单状态:**
- 使用 `useState` 管理表单字段状态
- 表单提交通过 `handleSubmit()` 函数处理(见 `add-outfit-dialog.tsx` 第 38-56 行)
**验证:**
- 基本的客户端验证(如检查空值:`if (!formData.name || !formData.description)`
- React Hook Form + Zod 在 package.json 中定义但代码中未广泛使用
- 服务器端验证通过 API 响应处理(返回 400/422 等错误状态)
**通知:**
- 成功/错误提示使用 Sonner toast 或 Radix Toast通过 `useToast()` hook
- toast 样式变体:`"default"``"destructive"`(见 `error-handler.ts` 第 71-75 行)
## 权限控制
**权限检查:**
- 运行时权限检查通过 `hasPermission(module)` 函数(见 `permissions.ts` 第 85-87 行)
- 权限矩阵定义在 `PERMISSION_MATRIX` 对象,映射角色到模块列表
- 路由保护:中间件 `middleware.ts` 检查 cookie 中的 token无 token 重定向到登录
- 组件级权限检查:`DashboardShell` 使用 `hasPathPermission()` 处理访问拒绝(见 `dashboard-shell.tsx` 第 23-24 行)
**角色存储:**
- 登录后在 `localStorage` 中存储 `user_role` 字符串(见 `auth.ts` 第 58 行)
- 超级管理员标识存储为 `is_superuser`(见 `auth.ts` 第 53 行)
- token 同时存储在 `localStorage` 和 Cookie 中(见 `auth.ts` 第 49、63 行)
## 异步操作
**模式:**
- 使用 `async/await` 处理异步 API 调用
- 加载状态通过 `isLoading` 布尔值跟踪(见 `login/page.tsx` 第 19、30 行)
- 模拟延迟用于开发和测试(见 `client.ts` `simulateDelay()``mockResponse()` 函数)
## API 集成
**Axios 配置:**
- 在 `lib/api/client.ts` 创建 Axios 实例,基础 URL 来自环境变量 `NEXT_PUBLIC_API_BASE_URL`
- 请求拦截器自动注入 Authorization 头Bearer token
- 响应拦截器处理 401 错误并触发重新登录
- API 端点前缀:`/api/v1/admin/`
**适配器模式:**
- 后端返回数据结构与前端显示结构不同
- 使用 `mapBackendOutfit()` 等适配器函数转换数据(见 `outfits.ts` 第 5-22 行)
- 适配器在 `lib/api/adapters.ts` 中集中管理
---
*规范分析日期2026-05-07*

View File

@ -0,0 +1,182 @@
# External Integrations
**Analysis Date:** 2026-05-07
## APIs & External Services
**qy_lty Backend (Django REST API):**
- Primary backend for all business logic
- URL: Base configured via `NEXT_PUBLIC_API_BASE_URL` (default: `http://localhost:8000/api`)
- SDK/Client: Axios (custom instance with interceptors)
- Auth: Bearer token via `Authorization: Bearer {token}` header
- Primary modules consumed:
- `/api/v1/admin/login/` - User authentication
- `/api/v1/admin/logout/` - User logout
- `/ai/bots/` - AI model CRUD
- `/card/category/clothing/` - Outfit/clothing items
- `/card/category/props/` - Props/accessories
- `/card/category/home-decor/` - Home decoration items
- `/card/category/food/` - Food items
- `/music/songs/` - Song management
- `/dances/` - Dance content
- `/achievements/` - Achievement system
- `/affinity/` - Affinity/favorability system
- `/common/upload/` - File upload endpoint
- `/common/upload/info/` - File metadata retrieval
**Request/Response Pattern:**
```typescript
// Axios configuration: lib/api/client.ts
const apiClient = axios.create({
baseURL: API_BASE_URL,
headers: { 'Content-Type': 'application/json' }
})
// Request interceptor: auto-injects token
apiClient.interceptors.request.use((config) => {
const token = localStorage.getItem('auth_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// Response interceptor: handles 401 (redirect to /login)
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem('auth_token')
window.location.href = '/login'
}
return Promise.reject(error)
}
)
```
## Data Storage
**Databases:**
- No direct database connection from frontend
- Backend (qy_lty) manages all persistent data (PostgreSQL/MySQL presumed)
- Frontend uses in-memory mock data for fallback/demo scenarios only
**File Storage:**
- Backend-managed via `/common/upload/` endpoint
- Upload types supported:
- **Images** (JPEG, PNG, GIF, WebP) - max 10MB
- **Avatars** (JPEG, PNG) - max 2MB
- **Audio** (MP3, WAV, OGG, AAC, FLAC, WMA, M4A) - max 20MB
- **Animations** (MP4, AVI, MOV, WMV, FLV, GIF, Lottie JSON) - max 50MB
- **General files** - multipart/form-data
- Upload library: File API (FormData) via Axios
- Progress tracking: `onUploadProgress` callback support
**Caching:**
- Browser localStorage for:
- `auth_token` - Authentication token (auto-removed on 401)
- `is_superuser` - Superuser flag
- `user_role` - User role string (used for permission checks)
- `isLoggedIn` - Session state flag
- No server-side caching configured (Redis presumed in backend qy_lty)
## Authentication & Identity
**Auth Provider:**
- Custom implementation via qy_lty backend
- Backend OAuth/token system: admin token key format `admin_token:{token}` (in Redis)
**Login Flow:**
1. POST `/api/v1/admin/login/` with email + password
2. Backend returns: `{ success, code, data: { token, is_superuser?, role? }, message }`
3. Frontend stores token in localStorage + cookies (7-day expiry)
4. All subsequent requests include `Authorization: Bearer {token}`
5. On 401 response: clear tokens, redirect to `/login`
**Token Storage:**
- Primary: `localStorage.auth_token` (checked on every request)
- Secondary: `js-cookie` cookie `auth_token` (7-day expiry) for middleware access
- Logout clears both storages
**Role-Based Access:**
- Roles stored in localStorage: `user_role`
- Permission matrix defined in `lib/permissions.ts`
- Supported roles: 超级管理员, 内容管理员, AI模型管理员, 卡牌管理员, 查看者, 管理员
- Module-level access control via `hasPermission()` and `hasPathPermission()` functions
**Protected Routes:**
- Middleware: `middleware.ts` checks for token on protected paths
- Protected paths: `/`, `/dashboard`, `/users`, `/roles`, `/ai-models`, `/outfits`, `/props`, `/songs`, `/settings`
- Public paths: `/login`, `/register`, `/forgot-password` (no token required)
## Monitoring & Observability
**Error Tracking:**
- Not detected - errors logged to console only
- Error messages mapped in `lib/api/error-handler.ts`
- Toast notifications via Sonner for user-facing errors
**Logs:**
- Console logging (development-focused)
- Request/response logging in Axios interceptors (logs token status, URLs, headers, status codes)
- Client-side logging only (no centralized log aggregation)
## CI/CD & Deployment
**Hosting:**
- Docker containerization: `Dockerfile` (multi-stage build)
- Runtime: Node.js 22.10.0 Alpine Linux
- Port: 3000
- Command: `yarn start` (runs Next.js production server)
**CI Pipeline:**
- Not detected in codebase (likely external to this repo)
**Build Output:**
- Format: Next.js standalone (self-contained, no `node_modules` in runtime image)
- Files included: `.next/standalone/`, `public/`
- Size optimization: devDependencies not included in runner stage
## Environment Configuration
**Required env vars:**
- `NEXT_PUBLIC_API_BASE_URL` - Backend API base URL (must be public, prefixed with `NEXT_PUBLIC_`)
- Example: `http://localhost:8000/api` (development), `https://api.production.com/api` (production)
**Optional env vars:**
- `NODE_ENV` - Set to `production` in Docker runner stage
- `.env.local` - Overrides all other env files (gitignored)
- `.env.development` - Dev-specific overrides
- `.env.production` - Production-specific overrides
**Secrets location:**
- Authentication tokens: browser localStorage + cookies
- No API keys or credentials hardcoded in source
- Environment variable `NEXT_PUBLIC_API_BASE_URL` is the sole configuration bridge to backend
## Webhooks & Callbacks
**Incoming:**
- None detected
- Backend (qy_lty) may have webhooks, but frontend is purely client-side consumer
**Outgoing:**
- None detected
- All communication is request-response (REST API calls to qy_lty)
## Cross-Repo Dependencies
**qy_lty Backend (Sibling Repo):**
- Location: `C:\Users\admin\Desktop\Lila-Server\qy_lty\` (Django)
- Contract: `/api/v1/admin/` endpoint suite
- Shared concerns: Token format (`admin_token:{token}`), role names, permission structure
- Change coordination required: Both `docs/修改记录.md` files must be updated when API contracts change
**Notes:**
- Frontend is tightly coupled to backend API schema (no API versioning detected)
- Backend controls: authentication, authorization, data persistence, file storage
- Frontend is purely a UI/UX layer consuming backend HTTP APIs
---
*Integration audit: 2026-05-07*

View File

@ -0,0 +1,152 @@
# Technology Stack
**Analysis Date:** 2026-05-07
## Languages
**Primary:**
- TypeScript 5 - All application code, types, and configuration
- React 19 - UI components and application logic (via Next.js)
- CSS / Tailwind - Styling
**Secondary:**
- JavaScript - Configuration files, optional runtime
## Runtime
**Environment:**
- Node.js 22.10.0 (Alpine Linux) - as specified in Dockerfile
**Package Manager:**
- Yarn (primary for Docker builds; npm/pnpm also supported locally)
- Lockfiles: `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock` present
- Registry: npm registry (Dockerfile uses Taobao mirror `registry.npmmirror.com` for builds)
## Frameworks
**Core:**
- Next.js 15.2.4 - App Router, React Server Components, standalone output mode
- Configuration: `next.config.mjs`
- Build output: standalone (optimized for Docker)
- Experimental features: webpackBuildWorker, parallelServerBuildTraces, parallelServerCompiles
**Styling:**
- Tailwind CSS 3.4.17 - Utility-first CSS framework
- Animation plugin: `tailwindcss-animate` 1.0.7
- Config: `tailwind.config.ts`
- CSS Variables enabled for theming (light/dark mode)
**UI Components:**
- Radix UI (15+ component libraries) - Headless, accessible primitives
- Core: accordion, alert-dialog, avatar, checkbox, dialog, dropdown-menu, label, popover, select, tabs, toast, tooltip, etc.
- shadcn/ui style components (local copies, not npm packages)
- Config: `components.json`
- Aliases: `@/components`, `@/components/ui`, `@/lib`, `@/hooks`
**Form Management:**
- React Hook Form (latest) - Lightweight form state management
- Zod (latest) - Schema validation and type-safe form schemas
- @hookform/resolvers (latest) - Integration between React Hook Form and validation libraries
**Data Visualization:**
- Recharts (latest) - React charting library built on D3.js
- Used for dashboard analytics/metrics visualization
**Notifications & Toasts:**
- Sonner 1.7.1 - Toast notification library
- Radix UI Toast 1.2.4 - Low-level toast primitive (wrapped by use-toast)
**Navigation & Theming:**
- next-themes 0.4.4 - Dark/light mode switching
- lucide-react 0.454.0 - Icon library (240+ icons)
**HTTP Client:**
- Axios 1.9.0 - Promise-based HTTP client
- Configured with request/response interceptors in `lib/api/client.ts`
- Automatic token injection via Authorization header
- 401 error handling (token expiry redirect to /login)
**Utilities:**
- js-cookie 3.0.5 - Cookie management (auth token persistence)
- date-fns 4.1.0 - Date utility library
- class-variance-authority 0.7.1 - Component variant utilities
- clsx 2.1.1 - Conditional className merging
- tailwind-merge 2.5.5 - Tailwind class conflict resolution
- cmdk 1.0.4 - Command palette component
- embla-carousel-react 8.5.1 - Carousel component
- react-day-picker 8.10.1 - Calendar/date picker
- input-otp 1.4.1 - OTP input component
- react-resizable-panels 2.1.7 - Resizable panel layout
- vaul 0.9.6 - Drawer component
## Testing & Development
**Type Checking:**
- TypeScript 5 (strict mode enabled)
- Type checking via `npm run lint`
**Linting:**
- Next.js ESLint (build-time linting)
- Config: ignored during builds (`eslint.ignoreDuringBuilds: true`)
**Development Server:**
- Next.js dev server: `npm run dev` (default port 3000)
## Configuration
**Environment:**
- Location: `.env.local` (local overrides), `.env.development`, `.env.production`, `.env.example`
- Example file: `c:\Users\admin\Desktop\Lila-Server\qy-lty-admin\.env.example`
- Key env vars:
- `NEXT_PUBLIC_API_BASE_URL` - Backend API base URL (e.g., `http://localhost:8000/api`)
- `NODE_ENV` - Set to `production` in Docker runtime
**TypeScript:**
- Config: `tsconfig.json`
- Strict mode: enabled
- Path aliases: `@/*` maps to project root
**Next.js:**
- Config: `next.config.mjs`
- Output mode: `standalone` (self-contained app)
- Images: unoptimized (external image optimization)
- Build optimization: parallel webpack, server build traces, server compilation
## Platform Requirements
**Development:**
- Node.js 22+ (or compatible version)
- Package manager: yarn, npm, or pnpm
- Environment variables: `.env.local` with `NEXT_PUBLIC_API_BASE_URL`
**Production:**
- Docker runtime: Node.js 22.10.0 Alpine
- Memory: Typical SPA requirements (~500MB minimum)
- Port: 3000 (exposed in Dockerfile)
- Build artifacts: `.next/standalone`, `public/`
## Build Process
**Development Build:**
```bash
npm install # Install dependencies
npm run dev # Start dev server (hot reload)
```
**Production Build:**
```bash
npm install # Install all dependencies
npm run build # Next.js standalone build → .next/ directory
npm run start # Start production server (requires .next/ + public/)
```
**Docker Build (CI/CD):**
- Multi-stage build (builder + runner)
- Builder stage: installs full dependencies, runs `npm run build`
- Runner stage: installs production-only dependencies, copies .next/ and public/
- Yarn mirror: Taobao registry (`registry.npmmirror.com`)
- Final CMD: `yarn start` (port 3000)
---
*Stack analysis: 2026-05-07*

View File

@ -0,0 +1,291 @@
# Codebase Structure
**Analysis Date:** 2026-05-07
## Directory Layout
```
qy-lty-admin/
├── app/ # Next.js App Router pages & layouts
│ ├── layout.tsx # Root layout with metadata
│ ├── page.tsx # Dashboard homepage
│ ├── login/page.tsx # Login page
│ ├── register/page.tsx # Registration page
│ ├── forgot-password/page.tsx # Password reset page
│ ├── ai-model/page.tsx # AI Model management
│ ├── outfits/ # Outfit content module
│ │ ├── page.tsx # List view
│ │ ├── [id]/page.tsx # Detail view
│ │ ├── edit/[id]/page.tsx # Edit form
│ │ └── loading.tsx # Loading skeleton
│ ├── props/ # Props module (same structure as outfits)
│ ├── home-decor/ # Home decor module
│ ├── food/ # Food module
│ ├── songs/ # Songs module
│ ├── dances/ # Dances module
│ ├── achievements/ # Achievements module
│ ├── affinity/ # Affinity/favorability module
│ ├── users/ # User management module
│ ├── permissions/ # Permissions management module
│ ├── settings/ # System settings module
│ └── globals.css # Global Tailwind + custom styles
├── components/ # Reusable React components
│ ├── ui/ # Shadcn-style primitive components (copy-paste)
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── dialog.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── table.tsx
│ │ ├── tabs.tsx
│ │ ├── select.tsx
│ │ ├── form.tsx
│ │ ├── alert-dialog.tsx
│ │ └── ... (30+ primitive components)
│ ├── outfits/ # Outfit feature components
│ │ ├── OutfitsList.tsx
│ │ ├── OutfitsTable.tsx
│ │ ├── AddOutfitDialog.tsx
│ │ └── OutfitDetailCard.tsx
│ ├── songs/ # Songs feature components
│ ├── dances/ # Dances feature components
│ ├── food/ # Food feature components
│ ├── home-decor/ # Home decor feature components
│ ├── props/ # Props feature components
│ ├── affinity/ # Affinity feature components
│ ├── achievements/ # Achievements feature components
│ ├── permissions/ # Permissions feature components
│ ├── users/ # Users feature components
│ ├── sidebar.tsx # Main navigation sidebar (client)
│ ├── dashboard-shell.tsx # Layout wrapper with permission checks (client)
│ ├── dashboard-header.tsx # Page header component
│ ├── overview.tsx # Dashboard overview chart
│ ├── recent-activity.tsx # Recent activity feed
│ ├── stat-card.tsx # KPI stat card
│ ├── delete-confirmation-dialog.tsx
│ ├── publish-confirmation-dialog.tsx
│ ├── theme-provider.tsx
│ └── add-outfit-dialog.tsx # Shared modal for outfit creation
├── lib/ # Utilities, types, API clients
│ ├── api/
│ │ ├── client.ts # Axios instance + interceptors + interfaces
│ │ ├── auth.ts # Login, logout, token management
│ │ ├── outfits.ts # Outfit CRUD operations
│ │ ├── props.ts # Props CRUD operations
│ │ ├── songs.ts # Songs CRUD operations
│ │ ├── dances.ts # Dances CRUD operations
│ │ ├── food.ts # Food CRUD operations
│ │ ├── home-decor.ts # Home decor CRUD operations
│ │ ├── achievements.ts # Achievements CRUD operations
│ │ ├── affinity.ts # Affinity system API
│ │ ├── ai-models.ts # AI model management
│ │ ├── users.ts # User management
│ │ ├── roles.ts # Role/permission management
│ │ ├── error-handler.ts # Centralized error handling
│ │ ├── adapters.ts # Response schema mappers
│ │ ├── types.ts # Shared API type definitions
│ │ ├── token-debug.ts # Token debugging utilities
│ │ ├── card.ts # Card-related API
│ │ └── index.ts # Export barrel
│ ├── permissions.ts # RBAC matrix + permission utilities
│ └── utils.ts # Utility functions (Tailwind cn)
├── hooks/ # Custom React hooks
│ ├── use-mobile.tsx # Mobile breakpoint detection hook
│ └── use-toast.ts # Toast notification hook
├── middleware.ts # Next.js route protection middleware
├── public/ # Static assets
├── styles/ # Additional stylesheets (if any)
├── package.json # Dependencies & scripts
├── tsconfig.json # TypeScript configuration
├── tailwind.config.ts # Tailwind CSS configuration
├── postcss.config.mjs # PostCSS configuration
├── next.config.mjs # Next.js configuration
├── Dockerfile # Docker build (yarn + Taobao mirror)
├── docker-compose.yml # Local dev Docker Compose
├── README.md # Project overview & permissions matrix
├── CLAUDE.md # Codebase instructions for Claude
└── docs/
└── 修改记录.md # Change log (Chinese)
```
## Directory Purposes
**app/:**
- Purpose: Next.js App Router pages and layouts; each module has a folder with page.tsx + optional nested routes
- Contains: Page components ("use client"), form pages, dynamic routes with [id]
- Key files: `page.tsx` (list), `[id]/page.tsx` (detail), `edit/[id]/page.tsx` (edit form), `loading.tsx` (skeleton)
**components/:**
- Purpose: Reusable React components organized by feature
- Contains: UI primitives (components/ui/), feature-specific components (components/[feature]/), layout components (sidebar, shell)
- Key pattern: Feature folders mirror app module structure; components within are sub-components (no subfolders unless complex)
**components/ui/:**
- Purpose: Shadcn-style copy-paste UI primitives
- Contains: 30+ Base UI components (Button, Card, Dialog, Form, Table, Tabs, Select, etc.)
- Note: These are copied into the repo, not npm packages; safe to modify directly
**lib/api/:**
- Purpose: HTTP client and API communication layer
- Contains: Axios singleton instance, interceptors, module-specific API functions, response adapters, type definitions
- Pattern: One file per backend module (outfits.ts, songs.ts, etc.) + shared client.ts
**lib/:**
- Purpose: Shared utilities, types, configuration
- Contains: permissions.ts (RBAC logic), utils.ts (Tailwind helpers), api/ subfolder
**hooks/:**
- Purpose: Custom React hooks
- Contains: use-mobile (responsive detection), use-toast (notification system)
**middleware.ts:**
- Purpose: Next.js request middleware for route protection
- Functionality: Checks Cookie auth_token; redirects to /login if missing; whitelists public routes
**public/:**
- Purpose: Static assets (images, icons, etc.)
**styles/:**
- Purpose: Additional CSS if needed (most styling via Tailwind in components)
## Key File Locations
**Entry Points:**
- `app/layout.tsx`: Root layout (metadata only)
- `app/page.tsx`: Dashboard homepage (stats, charts, links)
- `app/login/page.tsx`: Authentication entry point
- `middleware.ts`: Route protection middleware
**Configuration:**
- `next.config.mjs`: Next.js config (standalone output)
- `tsconfig.json`: TypeScript strict mode
- `tailwind.config.ts`: Tailwind CSS theme + animations
- `postcss.config.mjs`: PostCSS with Tailwind + autoprefixer
- `.env.local`: Environment variables (API_BASE_URL)
**Core Logic:**
- `lib/permissions.ts`: Role-based access control matrix & utilities
- `lib/api/client.ts`: Axios instance with auth/error interceptors
- `components/dashboard-shell.tsx`: Permission-checking layout wrapper
- `components/sidebar.tsx`: Role-filtered navigation menu
**Authentication:**
- `lib/api/auth.ts`: Login, logout, token persistence
- `middleware.ts`: Route-level token validation
**Testing:**
- No test files detected; testing not yet set up
## Naming Conventions
**Files:**
- **Page components:** `page.tsx` (App Router standard)
- **Feature pages:** `/app/[module]/page.tsx` (e.g., app/outfits/page.tsx)
- **Dynamic routes:** `/app/[module]/[id]/page.tsx` (e.g., app/outfits/[id]/page.tsx)
- **Layout files:** `layout.tsx`
- **Loading states:** `loading.tsx`
- **API modules:** `/lib/api/[feature].ts` (e.g., lib/api/outfits.ts)
- **Component files:** PascalCase (e.g., OutfitsList.tsx, AddOutfitDialog.tsx)
- **Hook files:** `use-[name].ts` (e.g., use-mobile.tsx)
**Directories:**
- **App modules:** kebab-case (e.g., `/app/ai-model`, `/app/home-decor`, `/app/forgot-password`)
- **Component folders:** kebab-case for feature groups, `ui` for primitives
- **API folder:** All in `lib/api/` (no nested folders)
**Functions:**
- **API functions:** camelCase, verb-first (e.g., `getOutfits()`, `createOutfit()`, `updateOutfit()`)
- **Utility functions:** camelCase (e.g., `hasPermission()`, `getUserRole()`)
- **Components:** PascalCase (e.g., `DashboardShell`, `Sidebar`, `OutfitsList`)
- **Hooks:** camelCase with `use` prefix (e.g., `useMobile`, `useToast`)
**Variables/Constants:**
- **Type definitions:** PascalCase (e.g., `Outfit`, `RoleName`, `ApiResponse`)
- **Constants:** UPPER_SNAKE_CASE (e.g., `API_BASE_URL`, `PERMISSION_MATRIX`)
- **Local variables:** camelCase (e.g., `outfits`, `isLoading`, `userRole`)
**Routes:**
- **Module routes:** kebab-case URLs (e.g., `/ai-model`, `/home-decor`, `/forgot-password`)
- **Dynamic routes:** `[paramName]` (e.g., `/outfits/[id]`, `/dances/[id]/edit`)
## Where to Add New Code
**New Feature Module (e.g., New Content Type):**
1. **Page component:** Create `app/[module-name]/page.tsx`
- Wrap with DashboardShell
- Import feature components from `components/[module-name]/`
- Call API functions from `lib/api/[module-name].ts`
2. **API client:** Create `lib/api/[module-name].ts`
- Export `get[Feature]s()`, `get[Feature]()`, `create[Feature]()`, `update[Feature]()`, `delete[Feature]()`
- Use `apiClient.get/post/put/delete()` from `lib/api/client.ts`
- Include adapter function to map backend response to frontend type
- Export types or import from `lib/api/types.ts`
3. **Components:** Create `components/[module-name]/` folder
- List component (e.g., `[Feature]List.tsx`)
- Detail/edit component
- Dialog/modal for creation
- Table component if displaying tabular data
4. **Permission matrix:** Add to `lib/permissions.ts`
- Add module key to `PermissionModule` type
- Add module to relevant roles in `PERMISSION_MATRIX`
- Add path mapping in `getModuleFromPath()`
5. **Sidebar:** Update `components/sidebar.tsx`
- Add menu item to appropriate section (AI Admin, Content Admin, or System Admin)
- Use lucide-react icon
6. **Middleware:** Update `middleware.ts`
- Add route to `protectedPaths` array if requiring auth
**New Component:**
- Location: `components/[feature-name]/[ComponentName].tsx`
- Import from UI primitives: `components/ui/button.tsx`, etc.
- Use React Hook Form for forms
- Use icons from lucide-react
- Mark as "use client" if using hooks/state
**New Utility Function:**
- Location: `lib/utils.ts` (for generic utilities) or `lib/api/[module].ts` (for API helpers)
- Type everything with TypeScript
- Export from `lib/api/index.ts` if API-related
**New Hook:**
- Location: `hooks/use-[name].ts`
- Follow React hook naming convention (use- prefix)
## Special Directories
**components/ui/:**
- Purpose: Shadcn-style primitives (Button, Card, Dialog, Form, Input, etc.)
- Generated: No (manually copied from shadcn/ui)
- Committed: Yes (safe to modify without breaking npm upgrades)
- Note: Each file is a single component; complex components like Dialog may have sub-exports
**public/:**
- Purpose: Static assets served at root level
- Generated: No
- Committed: Yes
**node_modules/:**
- Purpose: Installed dependencies
- Generated: Yes (by npm/pnpm/yarn)
- Committed: No (.gitignore)
**.next/:**
- Purpose: Next.js build output
- Generated: Yes (by `npm run build`)
- Committed: No (.gitignore)
**lib/api/:**
- Purpose: All API communication in one folder for easy discovery
- Generated: No (hand-written)
- Committed: Yes
- Note: No subfolders; keep flat for single-module-per-file pattern
---
*Structure analysis: 2026-05-07*

View File

@ -0,0 +1,279 @@
# 测试模式
**分析日期:** 2026-05-07
## 测试框架
**当前状态:** 未检测到测试基础设施
- **运行器:** 未配置package.json 中无 Jest、Vitest、Playwright 等依赖)
- **断言库:** 未配置
- **测试脚本:** 未在 package.json 中定义(仅有 `dev``build``start``lint`
## 测试文件组织
**当前:** 无测试文件
项目中未发现 `*.test.ts``*.test.tsx``*.spec.ts``*.spec.tsx``__tests__/` 目录等标准测试文件结构。
## 测试覆盖
**要求:** 未强制
**影响:**
- 新增代码(尤其是 API 集成、表单验证、权限逻辑)缺乏自动化验证
- 技术债风险:`next.config.mjs``typescript.ignoreBuildErrors: true``eslint.ignoreDuringBuilds: true`,导致构建时不检查类型和 lint 错误
- 手动测试依赖性高
## 关键需要测试的区域
### API 集成层
**位置:** `lib/api/*.ts`
**建议测试:**
- 认证流程(`emailLogin``saveAuthToken``logout`
- API 请求拦截器token 注入、过期处理)
- 响应拦截器401 处理、重定向)
- 数据适配器(如 `mapBackendOutfit()` 将后端结构映射到前端类型)
- 错误处理(`handleApiException``handleApiRequest` 的成功/失败路径)
**示例测试需求:**
```typescript
// 应测试token 自动注入
test('请求拦截器应在请求头中添加 Bearer token', () => {
// 前置条件localStorage 中有 auth_token
// 发送 API 请求
// 验证Authorization 头包含 "Bearer <token>"
})
// 应测试401 处理
test('响应拦截器应在收到 401 时清除 token 并重定向到登录', () => {
// 前置条件:发送请求后服务器返回 401
// 验证localStorage 中 auth_token 被移除
// 验证:浏览器重定向到 /login
})
// 应测试:数据适配
test('mapBackendOutfit 应正确转换后端服装数据', () => {
const backendData = { id: 1, name: '测试服装', image_url: '...' }
const result = mapBackendOutfit(backendData)
expect(result).toHaveProperty('imageUrl') // 字段映射
expect(result.id).toBe('1') // 类型转换
})
```
### 权限控制
**位置:** `lib/permissions.ts`
**建议测试:**
- `hasPermission(module)` 根据用户角色返回正确的权限
- `getUserRole()` 从 localStorage 获取当前角色
- `getModuleFromPath(pathname)` 正确提取路径中的模块
- `hasPathPermission(pathname)` 组合权限检查
**示例测试需求:**
```typescript
test('超级管理员应有所有模块的权限', () => {
// 前置localStorage.user_role = '超级管理员'
// 验证hasPermission('users') === true
// 验证hasPermission('ai-model') === true
// 等等所有模块
})
test('内容管理员应无权限访问用户管理', () => {
// 前置localStorage.user_role = '内容管理员'
// 验证hasPermission('users') === false
// 验证hasPermission('outfits') === true
})
```
### 表单验证与提交
**位置:** `components/*/add-*-dialog.tsx``app/*/page.tsx`
**当前模式:**
- 使用 `useState` 管理表单状态
- 基本的非空检查(如 `if (!formData.name || !formData.description)``add-achievement-dialog.tsx`
- 客户端验证不完整
**建议测试:**
- 必填字段验证(空值拒绝)
- 表单提交成功调用正确的 API
- 提交过程中加载状态变化(`isSubmitting`
- 表单重置(关闭后清空状态)
- 错误显示API 失败时显示 toast
**示例测试需求:**
```typescript
test('添加服装对话框应在名称为空时禁用提交', () => {
// 渲染 AddOutfitDialog
// 验证:提交按钮禁用状态
})
test('提交表单应调用 createOutfit API', () => {
// 填写表单字段
// 点击提交
// 验证createOutfit 被调用,参数正确
})
```
### 组件交互
**位置:** `components/`
**建议测试:**
- 对话框打开/关闭状态
- 模态框表单的步骤导航(`step` 状态变化)
- 确认对话框的确认/取消操作
- 列表分页和搜索
**示例测试需求:**
```typescript
test('AddOutfitDialog 应在提交后关闭', () => {
// 渲染对话框
// 填写并提交表单
// 验证对话框关闭open === false
})
test('多步表单应在点击"下一步"时更新步骤', () => {
// 验证:初始 step === 1
// 点击"下一步"按钮
// 验证step === 2
})
```
### 中间件和路由保护
**位置:** `middleware.ts`
**建议测试:**
- 无 token 访问受保护路由时重定向到 `/login`
- 有 token 时允许访问受保护路由
- 公共路由(登录、注册)不需要 token
**示例测试需求:**
```typescript
test('中间件应将无 token 的请求重定向到登录', () => {
// 模拟请求GET /outfits无 auth_token cookie
// 验证:重定向到 /login?callbackUrl=/outfits
})
test('中间件应允许有 token 的请求', () => {
// 模拟请求GET /users包含有效 auth_token cookie
// 验证:通过,继续处理
})
```
## 建议的测试设置
### 推荐框架组合
**单元/集成测试:**
- **运行器:** Vitest与 Next.js 15 兼容,开发体验优于 Jest
- **断言:** Vitest 内置(或 @testing-library/jest-dom
- **Mock 库:** vitest 的 `vi` 模块或 `@testing-library/react`
**E2E 测试(可选):**
- **框架:** Playwright 或 Cypress
- **用途:** 完整登录流、权限控制、多步表单
### 安装建议
```bash
# 安装 Vitest 和相关工具
npm install -D vitest @vitest/ui @testing-library/react @testing-library/jest-dom
# 创建配置文件 vitest.config.ts
```
### 最小化配置示例
```typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./test/setup.ts'],
},
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),
},
},
})
// test/setup.ts
import '@testing-library/jest-dom'
```
### package.json 脚本建议
```json
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage",
"test:run": "vitest run"
}
}
```
## 测试文件结构建议
```
project-root/
├── lib/
│ ├── api/
│ │ ├── auth.ts
│ │ ├── auth.test.ts # API 认证测试
│ │ ├── outfits.ts
│ │ ├── outfits.test.ts # API 服装测试
│ │ └── ...
│ ├── permissions.ts
│ └── permissions.test.ts # 权限逻辑测试
├── components/
│ ├── add-outfit-dialog.tsx
│ ├── add-outfit-dialog.test.tsx # 组件交互测试
│ └── ...
├── app/
│ ├── login/
│ │ ├── page.tsx
│ │ └── page.test.tsx # 登录页测试
│ └── ...
├── test/
│ ├── setup.ts # 测试环境初始化
│ ├── mocks/
│ │ ├── handlers.ts # MSW 请求拦截器
│ │ └── server.ts # MSW 服务器配置
│ └── fixtures/
│ ├── auth.fixture.ts # 认证数据 fixtures
│ └── outfits.fixture.ts # 服装数据 fixtures
└── vitest.config.ts
```
## 当前缺失的覆盖范围
**高优先级(生产关键):**
- ✗ API 集成和数据适配
- ✗ 认证流程登录、token 管理、登出)
- ✗ 权限检查和路由保护
- ✗ 表单验证和提交
**中优先级(功能完整性):**
- ✗ 错误处理和用户通知
- ✗ 组件交互(对话框、模态框)
- ✗ 列表管理(分页、搜索)
**低优先级UX 细节):**
- ✗ 样式和响应性
- ✗ 无障碍访问 (a11y)
---
*测试分析日期2026-05-07*
**关键建议:** 该项目缺乏测试基础设施强烈建议在新增核心功能特别是认证、权限、API 集成)时同步添加测试。考虑从 API 层和权限控制开始,这些是最容易产生 bug 的关键区域。

View File

@ -0,0 +1,49 @@
{
"model_profile": "balanced",
"commit_docs": true,
"parallelization": true,
"search_gitignored": false,
"brave_search": false,
"firecrawl": false,
"exa_search": false,
"git": {
"branching_strategy": "none",
"phase_branch_template": "gsd/phase-{phase}-{slug}",
"milestone_branch_template": "gsd/{milestone}-{slug}",
"quick_branch_template": null
},
"workflow": {
"research": true,
"plan_check": true,
"verifier": true,
"nyquist_validation": false,
"auto_advance": true,
"node_repair": true,
"node_repair_budget": 2,
"ui_phase": true,
"ui_safety_gate": true,
"ai_integration_phase": true,
"tdd_mode": false,
"text_mode": false,
"research_before_questions": false,
"discuss_mode": "discuss",
"skip_discuss": false,
"code_review": true,
"code_review_depth": "standard",
"code_review_command": null,
"pattern_mapper": true,
"plan_bounce": false,
"plan_bounce_script": null,
"plan_bounce_passes": 2,
"auto_prune_state": false
},
"hooks": {
"context_warnings": true
},
"project_code": null,
"phase_naming": "sequential",
"agent_skills": {},
"claude_md_path": "./CLAUDE.md",
"mode": "yolo",
"granularity": "coarse"
}

View File

@ -17,6 +17,10 @@ RUN yarn install --frozen-lockfile
# 复制源代码
COPY . .
# 注入 NEXT_PUBLIC_API_BASE_URLNext.js NEXT_PUBLIC_* 变量必须在 build 期注入才能进客户端 JS 包)
ARG NEXT_PUBLIC_API_BASE_URL
ENV NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL}
# 构建应用
RUN yarn build

View File

@ -25,6 +25,26 @@
<!-- 新的修改记录添加在此处下方,最新的在最前面 -->
### [2026-05-07] 修复 NEXT_PUBLIC_API_BASE_URL 注入时机错误(线上登录 Network Error
- **文件路径**:
- `qy-lty-admin/Dockerfile`
- `.gitea/workflows/deploy.yaml`(仓库根目录,与本前端构建/部署链直接相关)
- `k8s/admin-deployment-prod.yaml`(仓库根目录,与本前端构建/部署链直接相关)
- **修改类型**: 修复Bug
- **修改内容**:
- `qy-lty-admin/Dockerfile`:在 `COPY . .` 之后、`RUN yarn build` 之前新增 `ARG NEXT_PUBLIC_API_BASE_URL``ENV NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL}`,让该值在 build 期可被注入
- `.gitea/workflows/deploy.yaml`:在 admin 镜像 `docker build` 命令上加 `--build-arg NEXT_PUBLIC_API_BASE_URL=https://${DOMAIN_API}/api`test 环境拼成 `https://qy-lty.test.airlabs.art/api`prod 环境拼成 `https://qy-lty.airlabs.art/api`);同时删除 `Replace domain placeholders by environment` 段中已失效的 `sed -i "s|https://qy-lty.airlabs.art|https://${DOMAIN_API}|g" k8s/admin-deployment-prod.yaml`
- `k8s/admin-deployment-prod.yaml`:删除运行时无效的 `env: NEXT_PUBLIC_API_BASE_URL=https://qy-lty.airlabs.art`,改为注释说明该变量必须在 docker build 时通过 `--build-arg` 注入
- **修改原因**:
- 线上 https://qy-lty-admin.test.airlabs.art/login 点登录弹 "Network Error"。DevTools 抓到 Request URL 是 `http://localhost:8000/api/v1/admin/login/`,对应 `lib/api/client.ts``process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000/api"` 的 fallback 值。
- 根因Next.js 的 `NEXT_PUBLIC_*` 变量在 `next build` 时被静态编译进客户端 JS 包,运行时再设置容器环境变量已经无效。原 Dockerfile 没有 `ARG/ENV`,原 deploy.yaml 没有 `--build-arg`,只有 `k8s/admin-deployment-prod.yaml` 在容器运行时设了变量——所以打包出的镜像里硬编码的是默认 fallback `http://localhost:8000/api`,前端 HTTPS 页面去打本机的 8000 端口,浏览器报 `ERR_CONNECTION_REFUSED`axios 包装为 "Network Error"。
- 修复后构建期会把正确的 `https://qy-lty.test.airlabs.art/api` / `https://qy-lty.airlabs.art/api` 编译进 JS 包,登录请求会正确打到后端。
- 备注:原 `k8s/admin-deployment-prod.yaml` 写的是 `https://qy-lty.airlabs.art`**缺少 `/api` 后缀**),即便注入时机正确,路径也会拼错(`/v1/admin/login/` 而非 `/api/v1/admin/login/`),双重 bug。本次修复一并纠正。
- **服务端联动**: 本次修复仅涉及前端构建链与部署配置,未改动 `qy_lty` 后端代码,无需在服务端写联动条目。
---
### [2026-04-30] 初始化 CLAUDE.md 与 docs/修改记录.md 骨架
- **文件路径**: `CLAUDE.md``docs/修改记录.md`

165
qy_lty/.planning/PROJECT.md Normal file
View File

@ -0,0 +1,165 @@
# QY LTY Backend洛天依统一后端服务
## What This Is
QY LTY Backend 是「洛天依Luotianyi智能陪伴产品」生态的统一 Django 后端,同时服务于 **3 个客户端**Unity 设备端(`LTY_Project`、Unity 移动 App`LTY_App_Project_URP`、Web 管理后台(`qy-lty-admin`。提供用户认证、AI 对话含语音、设备实时通信WebSocket + RTC、卡片/二维码、成就、订阅等综合能力。
## Core Value
**设备端与手机端通过同一个用户身份实时互通**——`device_{user_id}` 分组模型必须始终成立。一旦绑定/控制权解析、WebSocket 路由、RTC 房间号三者偏离同一 user_id整个产品的"陪伴"价值就坍塌了。其他能力(卡片、成就、订阅)可以暂时降级,这条不行。
## Requirements
### Validated
<!-- 已交付且在生产链路上跑通的能力,从 .planning/codebase/ 推断 -->
**用户与认证userapp**
- ✓ **AUTH-01** 自定义用户模型 `ParadiseUser`(含 gender、MBTI、interests 等画像字段)— existing
- ✓ **AUTH-02** Redis 后端 token 认证(`RedisTokenAuthentication`30 天 TTLkey `token:{token}` / `admin_token:{token}`)— existing
- ✓ **AUTH-03** 手机号 + 阿里云 SMS 验证码登录 — existing
- ✓ **AUTH-04** WeChat 社交登录django-allauth + Weixin provider— existing
- ✓ **AUTH-05** 设备端通过 MAC 换 user-token`POST /api/user/mac-login/`,按 `-bound_at` 取最新绑定者)— existing
**AI 对话与语音aiapp**
- ✓ **AI-01** 单轮 / 多轮文本对话(接 KimiOpenAI 兼容协议)— existing
- ✓ **AI-02** 多服务商语音抽象 `AudioService`(火山引擎 / 阿里云 NLS / 腾讯via `AUDIO_SERVICE_PROVIDER`)— existing
- ✓ **AI-03** 语音合成TTS+ 语音识别ASR通用接口 — existing
- ✓ **AI-04** 字幕落库(`ConversationSubtitle`+ Bot 配置管理 — existing
**设备交互device_interaction**
- ✓ **DEV-01** WebSocket 双通道:`/ws/device/`Header 鉴权)和 `/ws/device/token/{token}/`URL 鉴权)— existing
- ✓ **DEV-02** Channel Layer 分组模型 `device_{user_id}`,端到端唯一身份 — existing
- ✓ **DEV-03** 设备绑定 / 解绑 / 设主 / 状态查询 REST 接口 — existing
- ✓ **DEV-04** 设备心跳:消息触发刷新 Redis key `device:last_seen:{mac}`5 min TTL— existing
- ✓ **DEV-05** 设备状态写库(`Device.status` connected/disconnected— existing
- ✓ **DEV-06** "后绑挤先绑"控制权语义(`UserDevice.Meta.ordering = ['-bound_at']`)— existing
- ✓ **DEV-07** 火山引擎 RTC token 签发(`/api/device/rtc-token/get_by_mac/``room_id = room_{user_id}`)— existing
- ✓ **DEV-08** WebSocket 消息类型完整chat/weather/sing/dance/touch/flow_light/device_info/device_state/conversation_status/conversation_subtitle/factory_reset — existing
**卡片系统card**
- ✓ **CARD-01** 卡片分类管理 + 属性配置 — existing
- ✓ **CARD-02** 卡片批量生成 + 二维码 — existing
- ✓ **CARD-03** 卡片使用追踪 + 数据分析 — existing
**成就achievement_app**
- ✓ **ACH-01** 成就定义 + 稀有度等级 + 用户进度追踪 — existing
- ⚠️ **ACH-02** 成就解锁条件校验 — **TODO 占位**`achievement_app/views.py:139` 标记未实现,当前 endpoint 任意客户端可主张任意成就,详见 CONCERNS.md
**订阅subscription_app**
- ✓ **SUB-01** 用户订阅模型 + 订阅状态管理 — existing
- ✓ **SUB-02** APScheduler 定时任务调度(生产环境)— existing
**好感度系统userapp / device_interaction2026-04 完成 P1**
- ✓ **AFF-01** 设备级好感度计数(`UserDevice.favorability`,从 `ParadiseUser.favorability` 迁移)— existing
- ✓ **AFF-02** AffinityRule / AffinityLevel / AffinityLog / AffinityCounter / AffinitySetting / AffinityRewardClaim 6 张表 — existing
- ✓ **AFF-03** 配置驱动的规则 / 等级 / 奖励 / 上限 / 冷却 / 区间随机 — existing
- ⏳ **AFF-04** Service 层P2+ 接口层P3+ 客户端集成P4**未交付**,待后续 milestone
**视觉智能ali_vi_app**
- ✓ **VI-01** 阿里云人脸检测 / 识别 SDK 集成 — existing
**通用基础设施common**
- ✓ **INF-01** `StandardResponseMiddleware` 统一响应包装(`success` / `code` / `message` / `data`)— existing
- ✓ **INF-02** `CustomPageNumberPagination` 自定义分页(`page_size` 可调)— existing
- ✓ **INF-03** Aliyun OSS 文件上传抽象(`common/oss.py`)— existing
- ✓ **INF-04** Aliyun Log Service 生产日志集成 — existing
- ✓ **INF-05** Swagger / ReDoc API 文档自动生成drf-yasg— existing
**管理后台admin**
- ✓ **ADM-01** Django Admin 深度定制SimpleUI 主题 + 中英双语)— existing
- ✓ **ADM-02** `/api/v1/admin/` 命名空间为 Web 管理后台提供 REST 接口 — existing
**部署deployment**
- ✓ **DEP-01** Docker + docker-compose 容器化(端口 12012— existing
- ✓ **DEP-02** Daphne ASGI同时承载 HTTP + WebSocket— existing
- ✓ **DEP-03** PostgreSQL主库+ Redis缓存 + Channel Layer— existing
- ✓ **DEP-04** i18n 双语zh_HAns / envia django-rosetta — existing
### Active
<!-- 当前正在建设的目标。GSD 通过 phase 推动这一段;移到 Validated 才算完成 -->
(暂无 — 本次 `/gsd-new-project` 仅做 brownfield 文档化。下次新增功能 / 子系统时使用 `/gsd-new-milestone` 启动新 milestone把当时要交付的能力加到这一段然后 `/gsd-plan-phase` 拆 phase。
### Out of Scope
<!-- 明确排除项 + 理由。防止后续被无意识地拉回来 -->
- **Web 管理后台前端实现** — 在独立项目 `../qy-lty-admin/` 中维护Next.js 15 + React 19。本仓库**只**提供 `/api/v1/admin/` REST 接口,不写任何前端代码。
- **Unity 客户端业务逻辑** — 在 `C:\Unity2022project\LTY_App_Project_URP`(手机 App`C:\Unity2022project\LTY_Project`(设备端)独立维护。本仓库提供 HTTP / WebSocket / RTC 协议接口,不耦合客户端实现。
- **跨项目的混合修改记录** — 服务端 / 管理后台改动**各自记录**到本仓库 `docs/修改记录.md``qy-lty-admin/docs/修改记录.md`,跨项目联动**两端各写一条互相引用**不混在同一条目里CLAUDE.md 显式规定)。
- **会话完整管控(如 Sentry / APM 全链路追踪)** — 当前用阿里云日志服务,没有上 APM。如需要再开 milestone 评估。
- **多 Redis 实例 / Sentinel 集群** — 当前是单 RedisCONCERNS.md 标记为 HA 风险,但不在本次范畴内;待性能/可用性 milestone 决议。
- **真正意义的测试套件** — 当前 ≈10 个 test 文件中只有 1 个真正在测,无 mock 基础设施。这是 CONCERNS.md 标的 HIGH-severity 工程债,但需要独立 milestone 系统性修。
## Context
**生态位**本服务是「设备—App—管理端」三角的中心节点对所有客户端来说**它是唯一的真理来源**。
**对接客户端清单**(以 CLAUDE.md 为准):
| 客户端 | 路径 | 通讯方式 |
|--------|------|---------|
| LTY_App_Project_URP手机端 Unity URP | `C:\Unity2022project\LTY_App_Project_URP` | HTTP REST + WebSocket `/ws/device/` |
| LTY_Project设备端 Unity | `C:\Unity2022project\LTY_Project` | HTTP REST + WebSocket + RTC火山引擎 |
| qy-lty-adminWeb 管理后台) | `../qy-lty-admin/` | HTTP REST `/api/v1/admin/` |
**最近活跃工作**git log + 修改记录):
- 2026-04-24 ~ 至今好感度系统从「用户级单值」演进到「设备级独立计数」P1 数据层已上线P2/P3/P4 待后续 milestone
- 设备交互视图持续更新(最近 commit `a13a081`
- CI 触发性 commit 多次(`6c1cfde` / `ba16766` / `df85773`)—— 暗示 CI/CD 配置仍在调试
**已知工程现状**(详见 `.planning/codebase/`
- 整体规模约 168 个 Python 文件
- 测试覆盖**极低**(实质 ≈ 0是最大的工程债
- `device_interaction/views.py` 单文件 1867 行,承载 30+ action method —— 需要拆分但风险高(无测试保护)
- `requirements.txt` 不锁版本(无 lockfile—— 部署一致性靠 Docker 镜像保证
- Python 3.8 已 EOL2024-10需要规划升级
## Constraints
- **Tech stack**: Django 4.2.13 + DRF + Channels + Daphne — 已锁定,迁移成本极高
- **Tech stack**: Python 3.8EOL— 当前限制,但近期需升级
- **Tech stack**: PostgreSQL+ Redis缓存 + Channel Layer— 不可替换
- **Tech stack**: ASGI 必须,因为 WebSocket 是核心 — WSGI 路径 (`wsgi.py`) 仅作历史保留
- **Compatibility**: WebSocket 消息协议11 种 message type被 2 个 Unity 客户端依赖 — 任何新增字段必须向前兼容,删除字段需协调 3 方排期
- **Compatibility**: REST API 响应包装格式(`StandardResponseMiddleware` 输出的 `{success, code, message, data}`)被 `qy-lty-admin` 依赖 — 整体结构不可改
- **Compatibility**: `device_{user_id}` 分组命名 + `room_{user_id}` 房间命名是端到端的隐式契约 — 改名要同步 Unity / Volcengine RTC 配置
- **Documentation**: 每次代码改动**必须**追加到 `docs/修改记录.md` 顶部CLAUDE.md 强制规则,结构性文档变更同样适用)
- **Communication**: 与用户沟通使用中文CLAUDE.md「沟通语言」章节强制
- **Independence**: `qy_lty``qy-lty-admin` 独立维护,**修改记录、planning 工件不混合**
- **Security**: `.env` 不入库,所有 SDK secret 必须通过环境变量加载CONCERNS.md 已标 SECRET_KEY / DEBUG / CORS 多处需收紧)
## Key Decisions
| Decision | Rationale | Outcome |
|----------|-----------|---------|
| WebSocket 分组用 `device_{user_id}`(不是 `device_{mac}` | 让"同一用户"的多个端点(手机 + 设备)天然共享同一通信空间;同时一个设备同一时刻只能被最新绑定的用户控制,符合"陪伴"产品语义 | ✓ Good — 已支撑生产 |
| `UserDevice.Meta.ordering = ['-bound_at']` 隐式驱动控制权解析 | 实现"后绑挤先绑"语义无需显式状态机,依赖 ORM 默认排序 | ⚠️ Revisit — 语义正确但隐式,新人容易误删旧记录或绕过 ordering 直接 query建议改写为显式 `current_owner` 字段 |
| 30 天 token TTLRedis 后端) | 设备 + 手机端长期在线,短 TTL 体验差 | ✓ Good — 与产品形态匹配 |
| 多服务商音频抽象(`AudioService` 工厂) | Aliyun/腾讯/火山随时切换、降本调优 | ✓ Good — 实测有效,火山为当前默认 |
| `StandardResponseMiddleware` 统一包装响应 | 客户端只需处理一种响应壳层 | ✓ Good — 已被 3 个客户端依赖 |
| 设备级好感度vs 用户级)— 2026-04 P1 | 同一用户多设备时不互相污染好感度 | — Pending — 需 P2 service 层落地后才能完整验证 |
| 测试 MAC `AA:BB:CC:DD:EE:FF` 硬编码绕过绑定校验 | 早期开发便利 | ⚠️ Revisit — CONCERNS.md 标 HIGH上规模前必须替换为环境变量开关 |
| `.planning/` 锚定在 `qy_lty\`(不是 `Lila-Server\` | `qy_lty``qy-lty-admin` 是独立项目CLAUDE.md 规定各自维护 | ✓ Good — 2026-05-07 通过预创建空目录强制锚定生效 |
## Evolution
This document evolves at phase transitions and milestone boundaries.
**After each phase transition** (via `/gsd-transition`):
1. Requirements invalidated? → Move to Out of Scope with reason
2. Requirements validated? → Move to Validated with phase reference
3. New requirements emerged? → Add to Active
4. Decisions to log? → Add to Key Decisions
5. "What This Is" still accurate? → Update if drifted
**After each milestone** (via `/gsd-complete-milestone`):
1. Full review of all sections
2. Core Value check — still the right priority?
3. Audit Out of Scope — reasons still valid?
4. Update Context with current state
---
*Last updated: 2026-05-07 after brownfield documentation initialization (existing system mapped, no Active milestone yet — use /gsd-new-milestone to start the next cycle)*

View File

@ -0,0 +1,138 @@
# Requirements — QY LTY Backend
**初始化日期**: 2026-05-07
**类型**: Brownfield 文档化(从 `.planning/codebase/` 推断)
**状态**: 已落地能力归档完成Active milestone 待 `/gsd-new-milestone` 启动
---
## Validated已交付能力
以下需求均为 **2026-05-07 之前已上线** 的能力,从代码与 `docs/` 推断而来。任何修改都需要走完整 phase 流程,不要直接动。
### 用户与认证AUTH
- [x] **AUTH-01** 自定义用户模型 `ParadiseUser`(继承 AbstractUser含画像字段
- [x] **AUTH-02** Redis 后端 token 认证30 天 TTL
- [x] **AUTH-03** 手机号 + 阿里云 SMS 验证码登录
- [x] **AUTH-04** WeChat OAuthdjango-allauth + Weixin provider
- [x] **AUTH-05** 设备端 MAC 换 user-token`POST /api/user/mac-login/`
### AI 对话与语音AI
- [x] **AI-01** Kimi 单轮 / 多轮文本对话
- [x] **AI-02** 多服务商语音抽象(火山 / 阿里云 NLS / 腾讯)
- [x] **AI-03** TTS文本→语音+ ASR语音→文本通用接口
- [x] **AI-04** 字幕落库 + Bot 配置管理
### 设备交互DEV
- [x] **DEV-01** 双通道 WebSocketHeader 鉴权 + URL 鉴权)
- [x] **DEV-02** Channel Layer 分组 `device_{user_id}`
- [x] **DEV-03** 设备绑定 / 解绑 / 设主 / 状态查询 REST 接口
- [x] **DEV-04** Redis 心跳(`device:last_seen:{mac}`5 min TTL
- [x] **DEV-05** 设备 connected/disconnected 状态写库
- [x] **DEV-06** "后绑挤先绑"控制权语义
- [x] **DEV-07** 火山引擎 RTC token 签发(`room_{user_id}`
- [x] **DEV-08** 11 种 WebSocket message type 完整支持chat/weather/sing/dance/touch/flow_light/device_info/device_state/conversation_status/conversation_subtitle/factory_reset
### 卡片系统CARD
- [x] **CARD-01** 卡片分类 + 属性配置
- [x] **CARD-02** 批量生成 + 二维码
- [x] **CARD-03** 使用追踪 + 数据分析
### 成就ACH
- [x] **ACH-01** 成就定义 + 稀有度等级 + 用户进度
- [ ] **ACH-02** 成就解锁条件校验 — ⚠️ **TODO 占位**`achievement_app/views.py:139` 未实现;任意客户端可主张任意成就。**需要单独 phase 修复**。
### 订阅SUB
- [x] **SUB-01** 用户订阅模型 + 状态管理
- [x] **SUB-02** APScheduler 定时任务
### 好感度系统AFF2026-04 P1
- [x] **AFF-01** 设备级好感度计数(`UserDevice.favorability`
- [x] **AFF-02** 6 张配套表Rule/Level/Log/Counter/Setting/RewardClaim
- [x] **AFF-03** 配置驱动规则(区间随机 / 上限 / 冷却 / 等级奖励)
- [ ] **AFF-04** Service 层P2— 未交付,下个 milestone
- [ ] **AFF-05** 接口层P3— 未交付,下个 milestone
- [ ] **AFF-06** 客户端集成P4— 未交付,下个 milestone
### 视觉智能VI
- [x] **VI-01** 阿里云人脸检测 / 识别(`ali_vi_app`
### 通用基础设施INF
- [x] **INF-01** `StandardResponseMiddleware` 统一响应包装
- [x] **INF-02** `CustomPageNumberPagination` 自定义分页
- [x] **INF-03** Aliyun OSS 上传抽象
- [x] **INF-04** Aliyun Log Service 生产日志
- [x] **INF-05** Swagger / ReDoc API 文档drf-yasg
### 管理后台ADM
- [x] **ADM-01** Django AdminSimpleUI 主题,中英双语)
- [x] **ADM-02** `/api/v1/admin/` REST 命名空间
### 部署DEP
- [x] **DEP-01** Docker + docker-compose端口 12012
- [x] **DEP-02** Daphne ASGIHTTP + WebSocket 同进程)
- [x] **DEP-03** PostgreSQL + Redis 双依赖
- [x] **DEP-04** i18n 双语zh_HAns / en
---
## Active当前 milestone 目标)
**(暂无)**
本次 `/gsd-new-project` 是 brownfield 文档化,没有指定新 milestone。
下一次启动新功能开发时,请用:
```
/gsd-new-milestone
```
GSD 会引导你确认 milestone 目标、把新需求加到本段(带 REQ-ID然后 `/gsd-plan-phase` 拆 phase。
**候选优先级**(来自 CONCERNS.md 与项目活动信号,按风险/价值排序,仅供参考):
1. **HIGH** 修复 `ACH-02` 成就条件校验缺失 — 当前任意客户端可主张任意成就
2. **HIGH** 修复 SMS 验证码无频率限制 — 生产 DoS 向量
3. **HIGH** 收紧 `DEBUG` / `CORS_ALLOW_ALL_ORIGINS` 默认值 — 信息泄露 + CSRF 邻接风险
4. **HIGH** 移除测试 MAC `AA:BB:CC:DD:EE:FF` 硬编码绑定绕过
5. **HIGH** 测试基础设施搭建pytest + pytest-django + 关键路径测试)
6. **MEDIUM** 完成好感度系统 P2/P3/P4Service 层 + 接口层 + 客户端集成)
7. **MEDIUM** Python 3.8 → 3.11/3.12 升级
8. **MEDIUM** WebSocket URL token 鉴权废弃(防 access log 泄露)
9. **MEDIUM** 拆分 `device_interaction/views.py`1867 行单文件)
---
## Out of Scope
(理由详见 PROJECT.md此处不重复
- 管理后台前端 — 在 `../qy-lty-admin/` 独立项目
- Unity 客户端业务逻辑 — 在 `LTY_App_Project_URP` / `LTY_Project` 独立项目
- APM / 全链路追踪 — 暂不引入
- 多 Redis 实例 / Sentinel — 暂不上 HA
- 跨项目混合修改记录 — 各自维护
---
## Traceability
<!-- 由 /gsd-plan-phase 在生成 phase 时回填:每个 phase 解决哪些 REQ-ID -->
(暂无 phase`/gsd-new-milestone` 后启动)
---
*Last updated: 2026-05-07 after brownfield documentation pass*

67
qy_lty/.planning/STATE.md Normal file
View File

@ -0,0 +1,67 @@
# Project State — QY LTY Backend
**最后更新**: 2026-05-07brownfield 文档化初始化)
## Project Reference
See: `.planning/PROJECT.md` (updated 2026-05-07)
**Core value**: 设备端与手机端通过同一个 user_id 实时互通——`device_{user_id}` 分组语义必须始终成立。
**Current focus**: 暂无 Active milestone — 待 `/gsd-new-milestone` 启动下一周期
## Status
| 项目 | 状态 |
|------|------|
| Codebase mapped | ✅ `.planning/codebase/` 7 文档commit `64a8cb8` |
| PROJECT.md | ✅ Validated 已从 codebase 推断填充Active 段空 |
| REQUIREMENTS.md | ✅ Validated 已拆 REQ-IDActive 段空Traceability 待 phase 回填 |
| Roadmap | ⏸️ 暂未生成(无 Active 需求 → 无 phase 可分) |
| Active phase | — |
| Active milestone | — |
## Next Action
**当你准备开始下一个开发周期**
```
/gsd-new-milestone
```
GSD 会:
1. 询问 milestone 目标(例如:好感度系统 P2、安全加固、测试基础设施……
2. 把需求加到 `.planning/REQUIREMENTS.md` 的 Active 段
3. 路由到 `/gsd-roadmap` 拆 phase
**候选优先级排序见 `REQUIREMENTS.md → Active → 候选优先级` 段**。
## Workflow Config
详见 `.planning/config.json`
- Mode: **YOLO**(自动通过审批,直接执行)
- Granularity: **Coarse**(每个 milestone 拆 3-5 phase
- Parallelization: **enabled**
- Workflow agents: research / plan_check / verifier 全部启用
- Model profile: **balanced**Sonnet 主力)
- `.planning/` commits to git: **yes**
随时可用 `/gsd-settings` 调整。
## Important Anchoring Note
`.planning/` 必须保持在 `c:\Users\admin\Desktop\Lila-Server\qy_lty\` 这一层(**不是父级 `Lila-Server\`**)。父级 `.git` 容易让 GSD CLI 误把 `Lila-Server` 当作 project_root本目录的存在就是锚定信号不要删。
## Project Rules Reminder
CLAUDE.md 中两条强制规则,做任何 phase 时必须遵守:
1. **沟通语言**所有面向用户的回复使用中文CLAUDE.md 顶部「沟通语言」章节)
2. **修改记录**:每次代码 / 配置 / 迁移 / CI / Docker / 文档结构性改动 **必须**追加到 `docs/修改记录.md` 顶部CLAUDE.md 「项目修改记录规则」章节)
`qy_lty``qy-lty-admin` 是独立项目,修改记录互不混合,跨项目联动两端各写一条。
---
*Generated by /gsd-new-project (brownfield doc pass) on 2026-05-07*

View File

@ -0,0 +1,251 @@
# Architecture
**Analysis Date:** 2026-05-07
## Pattern Overview
**Overall:** Multi-app Django with REST API + ASGI WebSocket support
**Key Characteristics:**
- Each Django app is a bounded context with models, views, serializers, and URLs
- HTTP API via Django REST Framework (DRF) ViewSets
- Real-time WebSocket via Django Channels for device interactions
- Custom token-based authentication with Redis backing
- Standardized response middleware for all API responses
- Message-driven device-to-device communication via WebSocket groups
## Layers
**API Layer (HTTP):**
- Purpose: Expose business logic as REST endpoints
- Location: Each app's `views.py` contains ViewSets and APIViews
- Contains: DRF ViewSets, APIViews, action decorators
- Depends on: Models, Serializers, Authentication (RedisTokenAuthentication)
- Used by: Mobile app (Unity), Web admin (Next.js), External integrations
**WebSocket Layer (ASGI):**
- Purpose: Real-time bidirectional communication for device control
- Location: `device_interaction/consumers.py` (DeviceConsumer), `device_interaction/routing.py`
- Contains: AsyncWebsocketConsumer, message handlers, channel layer group operations
- Depends on: Models, Channels, Redis channel layer, Token authentication
- Used by: Unity device terminal, Unity mobile app
**Serializer Layer:**
- Purpose: Validate and transform data between API and models
- Location: Each app's `serializers.py`
- Contains: DRF ModelSerializer subclasses, custom validation
- Depends on: Models, ValidationError exceptions
- Used by: ViewSets for request/response serialization
**Model Layer:**
- Purpose: Define data schema and business rules
- Location: Each app's `models.py`
- Contains: Django Models (ParadiseUser, Device, UserDevice, ChatMessage, etc.)
- Depends on: Django ORM
- Used by: Views, Serializers, Admin interface, WebSocket consumers
**Middleware Layer:**
- Purpose: Cross-cutting concerns
- Location: `common/middleware.py` (StandardResponseMiddleware), `device_interaction/auth.py` (TokenAuthMiddleware)
- Contains: StandardResponseMiddleware wraps all responses; TokenAuthMiddleware authenticates WebSocket
- Depends on: DRF Response, Channels scope
- Used by: All API responses, all WebSocket connections
**Authentication Layer:**
- Purpose: Validate and identify users
- Location: `userapp/authentication.py` (RedisTokenAuthentication), `device_interaction/auth.py` (TokenAuthMiddleware)
- Contains: Token lookup from Redis, user resolution
- Depends on: Redis cache, ParadiseUser model
- Used by: DRF (HTTP), WebSocket, Admin views
**Pagination Layer:**
- Purpose: Standardize list pagination
- Location: `common/pagination.py` (CustomPageNumberPagination)
- Contains: Page size configuration, dynamic page_size parameter
- Depends on: DRF pagination
- Used by: All list ViewSet actions
**Audio Service Abstraction:**
- Purpose: Provide vendor-agnostic audio synthesis/recognition
- Location: `aiapp/audio/AudioService.py`
- Contains: Multi-provider factory (Aliyun, Volcengine, Tencent)
- Depends on: External vendor SDKs
- Used by: `aiapp/views.py` for voice chat, speech-to-text
## Data Flow
**WebSocket Device Info Flow (Example):**
1. **Device Connection**`device_interaction/consumers.py:DeviceConsumer.connect()`
- Device sends WebSocket connect with token in URL: `ws://domain/ws/device/token/{token}/`
- `device_interaction/auth.py:TokenAuthMiddleware` intercepts, validates token via `userapp/authentication.py:RedisTokenAuthentication`
- Token key pattern: `token:{token_value}` → retrieves `user_id` from Redis (see `userapp/utils.py:generate_token`)
- User resolved, scope set; Consumer creates group `device_{user_id}` and joins
2. **Device Reports Status** → Device sends `type: device_info` JSON message
- Example payload:
```json
{
"type": "device_info",
"message": {
"battery_level": 85,
"firmware_version": "1.2.3",
"wifi_name": "MyWiFi"
}
}
```
3. **Consumer Processes**`device_interaction/consumers.py:DeviceConsumer.receive()`
- Line 196-198: Parses message type and content
- Line 211-212: Refreshes heartbeat: `cache.set(f"device:last_seen:{mac_address}", time.time(), timeout=300)`
- Device info not explicitly handled in receive() yet (TODO area)
4. **Database Update** → Would call `update_device_status()` (line 107-131)
- Updates `Device` model fields: `battery_level`, `firmware_version`, `wifi_name`, status='connected'
- Sets Redis heartbeat key `device:last_seen:{mac}` with 5-minute TTL
5. **Broadcast to Group** → Via `channel_layer.group_send()` (lines 221-227)
- Routes message type specific handlers (e.g., `async def weather()`)
- Broadcasts to all connections in group `device_{user_id}`
- Any other user/device connection to same user_id receives forwarded message
6. **State in Redis:**
- Token lookup: `token:{token}``user_id`
- Admin token lookup: `admin_token:{token}``user_id` (alternative for admin routes)
- Heartbeat tracking: `device:last_seen:{mac_address}` → unix timestamp (TTL 5 min)
**State Management:**
- **HTTP Tokens**: Stored in Redis with 30-day TTL (line 36 in `userapp/utils.py`)
- **WebSocket Groups**: In-memory Redis channel layer, groups scoped by `device_{user_id}`
- **Heartbeats**: Redis cache, auto-expire after 5 minutes (line 128, 151 in consumers.py)
- **Device Status**: PostgreSQL — `Device.status` set to 'connected' or 'disconnected' on connect/disconnect
## Device Binding & Control Semantics
**"Last-Bind-Wins":**
- `UserDevice.Meta.ordering = ['-bound_at']` (line 131 in `device_interaction/models.py`)
- When device MAC used to login, code fetches: `UserDevice.objects.filter(device=device).order_by('-bound_at').first()` (line 120 in `userapp/views.py`)
- That user gets the token; older bindings exist in DB but are superseded
**Control Flow Consequence:**
- WebSocket group is `device_{user_id}` where user_id is from latest binding
- Same device, same time, only **one user_id** in the group
- Old bindings' connections to same MAC get ignored — they can't authenticate to the new owner's user_id group
- `UserDevice` records not auto-deleted; explicit deletion required for "unbind" semantics
**RTC Integration:**
- Endpoint: `/api/device/rtc-token/get_by_mac/?mac_address=...` (no auth required)
- Resolves MAC → latest `UserDevice.user_id` (via `.first()` with ordering)
- Generates `room_id = f"room_{user_id}"` (consistent with WebSocket group)
- Returns Volcengine RTC token cached as `rtc_room:{user_id}:{task_id}`
## Key Abstractions
**ParadiseUser:**
- Purpose: Custom user model extending AbstractUser
- Location: `userapp/models.py` (line 8)
- Pattern: Single table inheritance; adds fields like `favorability`, `gender`, `mbti`, `interests`
- Relations: M2M to Group/Permission (custom related_names), 1-M to Device via UserDevice
**Device & UserDevice:**
- Purpose: Track physical devices and user-device bindings
- Location: `device_interaction/models.py` (lines 43-136)
- Pattern: Device is the hardware; UserDevice is the ownership/interaction record
- Key: `UserDevice` ordering by `-bound_at` enforces "last-bind-wins"
**DeviceConsumer:**
- Purpose: Handle WebSocket connections for real-time device messaging
- Location: `device_interaction/consumers.py` (line 10)
- Pattern: AsyncWebsocketConsumer subclass; manages connect/receive/disconnect lifecycle
- Methods:
- `connect()`: Auth, group add, accept
- `receive()`: Parse message type, route to group via `group_send()`
- `disconnect()`: Mark device offline, group discard
**RedisTokenAuthentication:**
- Purpose: Validate tokens stored in Redis
- Location: `userapp/authentication.py` (line 10)
- Pattern: DRF BaseAuthentication subclass; used by HTTP views and WebSocket middleware
- Flow: Extract token from Authorization header, call `get_user_id_from_token()` (utils.py:39), resolve user, return (user, None)
**StandardResponseMiddleware:**
- Purpose: Wrap all API responses in standard format
- Location: `common/middleware.py` (line 6)
- Pattern: Django middleware; intercepts Response objects, adds `success`, `code`, `message`, `data` fields
- Behavior: Line 61 sets `success = 200 <= status_code < 300`, extracts/transforms `response.data`
**AudioService:**
- Purpose: Abstract audio provider differences
- Location: `aiapp/audio/AudioService.py`
- Pattern: Factory pattern with provider configuration
- Providers: Aliyun NLS, Volcengine, Tencent (via `AUDIO_SERVICE_PROVIDER` setting)
## Entry Points
**HTTP Entry Points:**
- Location: `qy_lty/urls.py` (line 49-60)
- URL pattern: `/api/<app>/<endpoint>/`
- Examples:
- `/api/user/mac-login/``userapp.views.MacAddressLoginView.post()`
- `/api/device/types/``device_interaction.views.DeviceTypeViewSet.list()`
- `/api/ai/chat/``aiapp.views.ChatView` (for multi-turn chat)
- Router: Standard DRF routing via `include('app.urls')`
**WebSocket Entry Points:**
- Location: `qy_lty/asgi.py` (lines 14-26), `device_interaction/routing.py` (lines 4-7)
- URL pattern: `ws://domain/ws/device/` (header auth) or `ws://domain/ws/device/token/{token}/` (URL auth)
- Consumer: `device_interaction.consumers.DeviceConsumer.as_asgi()`
- Middleware stack: TokenAuthMiddleware → URLRouter → DeviceConsumer
**Admin Entry Points:**
- Location: `/admin/` (Django admin)
- Config: `userapp/admin.py`, each app's `admin.py`
- Customization: `simpleui` integration for enhanced UI
**API Documentation:**
- Swagger UI: `/swagger/` (via drf-yasg)
- ReDoc: `/redoc/` (via drf-yasg)
- Config: Generated from openapi.Info (line 32-39 in qy_lty/urls.py)
## Error Handling
**Strategy:** Exception-aware middleware + standardized response format
**Patterns:**
- **DRF Exceptions**: Caught by `StandardResponseMiddleware.process_exception()` (line 120), wrapped in standard format (line 133)
- **Validation Errors**: Nested extraction of field errors (line 19-43), flattened into readable message
- **HTTP Status Codes**: Mapped to `success` boolean (line 61: `200 <= code < 300`)
- **Custom Error Codes**: HTTP response includes `code` field, e.g., `code=4010` for "device not bound" (line 127 in userapp/views.py)
- **WebSocket Errors**: Consumer catches exceptions, logs, and closes connection with specific code (e.g., 4001 for auth failure, line 24)
## Cross-Cutting Concerns
**Logging:**
- Framework: Python `logging` module
- Configured in: `qy_lty/settings.py` (logging config)
- Patterns: Each module instantiates logger: `logger = logging.getLogger(__name__)` (line 8 in consumers.py)
- Aliyun integration: `common/aliyun_logging.py:setup_logging()` for production
**Validation:**
- HTTP: DRF serializer validation via `is_valid()`, errors in response
- WebSocket: Inline JSON parsing with try/except, error response via `send()`
- Custom: Model `clean()` methods (if defined), field constraints
**Authentication:**
- HTTP: `RedisTokenAuthentication` on ViewSet `authentication_classes` (line 79 in device_interaction/views.py)
- WebSocket: `TokenAuthMiddleware` in ASGI app (line 21 in qy_lty/asgi.py)
- Token generation: 30-day TTL, Redis-backed, pattern `token:{uuid}` or `admin_token:{uuid}`
**Authorization:**
- HTTP: `permission_classes = [IsAuthenticated]` on ViewSets (line 80)
- WebSocket: Implicit via token — anonymous users get 4001 close code (line 24)
- Admin routes: `/api/v1/admin/` (separate URL namespace), likely checked via user.is_staff
**Pagination:**
- Default: 10 items per page (settings.py line 283)
- Override: Pass `page_size` query parameter (e.g., `?page_size=50`)
- Response: Wrapped with `count`, `next`, `previous`, `results` (lines 93-95 in common/middleware.py)
---
*Architecture analysis: 2026-05-07*

View File

@ -0,0 +1,335 @@
# Codebase Concerns
**Analysis Date:** 2026-05-07
## Tech Debt
### Hardcoded Test MAC Bypass (Security Risk)
- **Issue:** Test MAC `AA:BB:CC:DD:EE:FF` is hardcoded to skip "device already bound" validation
- **Files:**
- `device_interaction/serializers.py:125`
- `device_interaction/views.py:702`
- **Code Pattern:**
```python
if value != 'AA:BB:CC:DD:EE:FF' and UserDevice.objects.filter(device=device).exists():
raise serializers.ValidationError("设备已被其他用户绑定")
```
- **Impact:** In production, any request with this MAC bypasses binding validation entirely. If MAC is leaked or discovered, attacker can bypass device permission controls.
- **Fix approach:** Remove hardcoded bypass before production scale-out. Replace with environment-based test mode flag (`ALLOW_TEST_MAC=false` in prod, check at startup). Document test procedures that don't require code branches.
- **Severity:** HIGH — Production-blocking if scale-out planned
### Device Binding Semantics ("Last-Bind-Wins") Implicit in Ordering
- **Issue:** CLAUDE.md (line 233) documents "后绑的挤掉先绑的" (last binder controls device) via `UserDevice.Meta.ordering = ['-bound_at']`. Control resolution in `views.py` uses `.first()` implicitly depending on this ordering, with no explicit comment at call sites.
- **Files:**
- `device_interaction/models.py``UserDevice` model ordering
- `device_interaction/views.py:702` (bind_status), `views.py:1120` (rtc-token/get_by_mac) — implicit `.first()` usage
- `userapp/views.py` — MAC login endpoint (explicit `.order_by('-bound_at').first()`)
- **Impact:** New developers may not understand that multiple `UserDevice` rows for same device are intentional, and may try to auto-delete old records thinking they're bugs. Silent control hand-off between users without explicit notification.
- **Fix approach:** Add explicit docstring to `UserDevice.Meta` explaining ordering rationale. Add comments at control-resolution sites: "Implicit: `.first()` depends on Meta.ordering = ['-bound_at']". Implement user notification when control is lost (older user's device suddenly stops responding).
- **Severity:** MEDIUM — Operational surprise risk
### is_primary Ambiguity (Not "Primary Controller")
- **Issue:** CLAUDE.md (line 237) notes `is_primary` is "user view of primary device" (each user has ≤1), NOT "device view of primary user". Same physical device can have multiple `is_primary=True` rows.
- **Files:**
- `device_interaction/models.py``UserDevice.is_primary` field
- `device_interaction/views.py:746``set_primary` action
- **Impact:** Code querying `UserDevice.objects.filter(device=device, is_primary=True)` will return multiple rows; calling `.first()` silently picks arbitrary one. Data migrations may assume primary uniqueness per device and fail.
- **Fix approach:** Rename field to `is_user_primary` to clarify scope. Add unique constraint on `(user, is_primary=True)` at DB level (add migration). Audit all code touching is_primary to ensure correct semantics.
- **Severity:** MEDIUM — Data consistency risk in multi-user scenarios
### Affinity System Field Backward Compatibility
- **Issue:** `AffinityRule` and `AffinityLevel` have old fields (`points`, `daily_limit`, `is_active` for Rule; `required_points`, `rewards` for Level) marked "已弃用" (deprecated) in comments, pending deletion next version.
- **Files:**
- `userapp/models.py` — field definitions with deprecation comments
- `userapp/migrations/0005_affinitysetting_affinitylevel_is_deleted_and_more.py` — schema
- `docs/修改记录.md:47` — explicit note: "保留作为兼容字段,注释标记 '已弃用',下个版本删除"
- **Impact:** Two parallel field sets in model creates confusion. Serializers/views must avoid referencing old fields. Data migrations targeting old fields will silently succeed but risk ignored values if migration rolls back.
- **Fix approach:** Plan deprecation timeline: 1) mark version when old fields will be deleted (e.g., "v2.0"), 2) add migration warning log at deploy time listing affected rows still using old values, 3) drop fields in scheduled release with clear release notes.
- **Severity:** MEDIUM — Code maintainability; low runtime risk if new field set is complete
### ParadiseUser.favorability Not Yet Deleted After Migration
- **Issue:** Data migrated from `ParadiseUser.favorability` to `UserDevice.favorability` (migration 0006), but old field on ParadiseUser is NOT deleted. Docs (修改记录.md:116) explicitly state "旧 ParadiseUser.favorability 字段保留不删,由后续版本统一清理".
- **Files:**
- `userapp/models.py``ParadiseUser.favorability` field still exists
- `userapp/migrations/0006_migrate_favorability_to_userdevice.py:11` — migration note
- **Impact:** Source-of-truth split: old field may be modified by legacy code path or manual admin actions; new device-level field won't see updates. Risks silent inconsistency.
- **Fix approach:** Create follow-up migration to drop `ParadiseUser.favorability` once P2 service layer is validated. Until then, add application-level guard: `AffinityService` should only read/write `UserDevice.favorability`, and log warning if `ParadiseUser.favorability > 0` and doesn't match primary device value.
- **Severity:** LOW-MEDIUM — Handled by migration discipline; plan removal in next minor release
## Known Bugs
### Missing TODO in Achievement Conditions Check
- **Issue:** `achievement_app/views.py:139` has explicit TODO comment: "# TODO: 根据成就的conditions字段和前端传递的check_data检查是否满足条件" (achievement condition validation not implemented)
- **Files:** `achievement_app/views.py:139`
- **Impact:** Achievement unlock endpoint does not validate if conditions are met; always grants achievement if user requests it. Any client can claim arbitrary achievements.
- **Trigger:** Call POST `/api/achievement/check/` with any `check_data`
- **Workaround:** None; feature is stub-only
- **Fix approach:** Implement `AchievementService.check_conditions(achievement, check_data)` with per-condition validator registry. Add unit tests for each condition type.
- **Severity:** HIGH — Functional gap in released feature
### Missing Conversation Source Field in WebSocket Callback
- **Issue:** `device_interaction/views.py:1427` has TODO: "# TODO 决策点 #3 落定后填充 'phone' / 'device'" — callback routing doesn't tag which client sent conversation (phone app vs device endpoint).
- **Files:** `device_interaction/views.py:1427` (in `conversation_status` action)
- **Impact:** All WebSocket message routing broadcasts assume single source; can't distinguish if conversation came from phone client or device endpoint. Breaks analytics, replay, and client-side dedup logic that relies on source.
- **Trigger:** Ongoing RTC chat with device and phone both active
- **Workaround:** Clients must infer source from message timing/content patterns
- **Fix approach:** Add `'source': 'phone' | 'device'` field in WebSocket payload. Update all consumers and message routers. Coordinate with client teams to unify source interpretation.
- **Severity:** MEDIUM — Missing feature; blocks analytics correctness
### SMS Verification Code Rate Limiting Not Enforced
- **Issue:** `userapp/views.py:297-318` (SendVerifyCodeView) has no rate limit, throttle, or per-phone backoff on SMS sending.
- **Files:** `userapp/views.py:297-318`
- **Code:**
```python
def post(self, request):
phone_number = request.data.get('phone_number')
# ... no rate limit check ...
code = str(random.randint(100000, 999999))
response = send_sms(phone_number, code) # Directly calls Aliyun SMS
cache.set(phone_number, code, timeout=600) # Stores code in Redis
```
- **Impact:** SMS spam risk: attacker can hammer endpoint with same phone number to exhaust SMS quota or bankrupt via Aliyun charges (SMS is pay-per-send). No cooldown enforced.
- **Trigger:** `while True: POST /api/user/send_verify_code/ {"phone_number": "13800000000"}`
- **Workaround:** IP-level blocking or firewall; not app-level protection
- **Fix approach:** Implement rate limiting: 1) Add Redis key `sms_sent:{phone}:{YYYYMMDD}` with counter, max 5 per day per phone, 2) Add `sms_cooldown:{phone}` with 60s TTL to block rapid retries, 3) Use DRF throttle class or custom decorator for IP-level limits (5 SMS per minute per IP).
- **Severity:** HIGH — Production DoS vector in authentication endpoint
## Security Considerations
### DEBUG Mode Hardcoded to True in Production Settings
- **Issue:** `qy_lty/settings.py:31` has `DEBUG = True # 开发环境下设置为True`
- **Files:** `qy_lty/settings.py:31`
- **Impact:** DEBUG=True in production exposes full stack traces, local variables, SQL queries, and installed packages to any error page. Leaks internal URLs, file paths, and API keys from traceback context.
- **Current mitigation:** Comment implies intent for dev-only, but no env var check
- **Recommendations:** Change to `DEBUG = config('DEBUG', default=False, cast=bool)` so prod .env can override. Add pre-deploy check in CI to fail if DEBUG=True in prod build.
- **Severity:** HIGH — Information disclosure
### CORS Allows All Origins in Production
- **Issue:** `qy_lty/settings.py:98` sets `CORS_ALLOW_ALL_ORIGINS = True # 允许所有来源的跨域请求,适用于开发环境`
- **Files:** `qy_lty/settings.py:98`
- **Impact:** Any website can make authenticated requests to the API on behalf of users (e.g., attacker site makes POST requests using victim's stored token). Defeats CORS origin validation.
- **Current mitigation:** Comment indicates dev-only intent; commented-out whitelist provided
- **Recommendations:** Load whitelist from env var: `CORS_ALLOWED_ORIGINS = config('CORS_ALLOWED_ORIGINS', default='http://localhost:3000').split(',')`. Restrict to known Unity client domains + web admin domain + localhost in dev.
- **Severity:** HIGH — CSRF-adjacent vulnerability if session auth used
### Token-in-URL WebSocket Auth Logged in Access Logs
- **Issue:** CLAUDE.md (line 117) documents WebSocket URL-based auth: `ws://domain/ws/device/token/{token}/`
- **Files:**
- `device_interaction/consumers.py:54-71` — token extraction from URL
- CLAUDE.md line 117 — design documentation
- **Impact:** Token appears in nginx/reverse-proxy access logs, browser history, referrer headers, CloudFront logs. Any log aggregation breach exposes all active tokens.
- **Current mitigation:** Header-based auth also supported (`ws://domain/ws/device/`)
- **Recommendations:** Deprecate URL-based token auth (mark as legacy). Document that tokens should only be passed in Headers (`Authorization: Bearer {token}`). For legacy WebSocket clients that can't set custom headers, recommend binding device MAC early and using MAC-login instead.
- **Severity:** HIGH — Token exposure in logs
### Credentials Handling in Audio Service Config
- **Issue:** Audio service provider credentials (Aliyun, Tencent, VolcEngine) are stored in .env and loaded via `config()` calls throughout codebase. If .env is accidentally committed or exposed, all external API keys are compromised.
- **Files:**
- `qy_lty/settings.py` — loads `ALIYUN_*`, `VOLCENGINE_*` from env
- `userapp/utils.py:8-30` — uses `ALIYUN_SMS_ACCESS_KEY_*` directly
- Multiple other service files
- **Current mitigation:** .env is in .gitignore (assumed)
- **Recommendations:** 1) Add .env.example template with dummy values to repo, 2) Pre-deploy check to verify SECRET_KEY, DEBUG, and all *_KEY env vars are set (fail if default), 3) Rotate credentials if any .env branch is accidentally pushed, 4) Use cloud IAM (Aliyun RAM roles) for server-to-service auth instead of long-lived keys where possible.
- **Severity:** HIGH — If .env leaked, all integrations compromised
### CSRF Middleware Disabled
- **Issue:** `qy_lty/settings.py:88` has CSRF middleware commented out: `# 'django.middleware.csrf.CsrfViewMiddleware',`
- **Files:** `qy_lty/settings.py:88`
- **Impact:** POST/PUT/DELETE endpoints accept requests without CSRF tokens, enabling form-based CSRF attacks from third-party sites.
- **Current mitigation:** Token-based REST API auth is used instead of session cookies
- **Recommendations:** If using token auth (Redis tokens, not session cookies), CSRF can be relaxed, but should have: 1) explicit comment explaining trade-off, 2) CSRF exemption decorator `@csrf_exempt` only on REST endpoints, not HTML pages, 3) Consider re-enabling for browser-based admin endpoints (if any).
- **Severity:** MEDIUM — Mitigated by token auth, but could surprise new developers
### File Upload Without Type/Size Validation
- **Issue:** `common/views.py:66-114` (upload_file endpoint) accepts any file type and has no maximum size limit
- **Files:** `common/views.py:66-114`
- **Code:**
```python
file_obj = request.FILES['file']
file_size = file_obj.size # Logged but not validated
content_type = getattr(file_obj, 'content_type', 'unknown') # Logged but not checked
result = uploader.upload_file(file_obj, is_permanent, filename) # No size/type check
```
- **Impact:** Users can upload arbitrary files (executables, archives, etc.) to OSS, consuming storage quota and potentially enabling supply-chain attacks (if files are later distributed). No DoS protection against huge uploads.
- **Trigger:** `POST /api/upload/` with `multipart/form-data` containing 5GB file
- **Workaround:** None at app level; depends on OSS bucket policy
- **Recommendations:** Add validation: 1) Max file size (default 100MB, configurable), 2) Whitelist MIME types (audio, image, document only), 3) File extension check, 4) Virus scan if possible (ClamAV integration), 5) Add Content-Length header check before parsing.
- **Severity:** MEDIUM-HIGH — Storage exhaustion + potential malware vector
## Performance Bottlenecks
### N+1 Query Risk in Device/Card List Endpoints
- **Issue:** `device_interaction/views.py:77`, `device_interaction/views.py:193`, `card/views.py:262` define viewsets with plain `queryset = Model.objects.all()` without `select_related()` or `prefetch_related()`.
- **Files:**
- `device_interaction/views.py:77``DeviceTypeViewSet`
- `device_interaction/views.py:135``DeviceBatchViewSet`
- `device_interaction/views.py:193``DeviceViewSet`
- `card/views.py:262``CardViewSet`
- **Patterns:** Serializers reference related fields (`device_type_name = ReadOnlyField(source='device_type.name')`), so each list item triggers separate query for related object.
- **Impact:** Listing 100 devices results in 101 queries (1 for list + 100 for device_type lookup). Scales linearly with result count. Noticeable on slow networks.
- **Trigger:** `GET /api/device/` with large device list
- **Fix approach:** Override `get_queryset()` in each ViewSet to add prefetch_related. Example:
```python
def get_queryset(self):
qs = super().get_queryset()
if self.action == 'list':
qs = qs.select_related('device_type', 'batch')
return qs
```
Add Django Debug Toolbar in dev to verify queries.
- **Severity:** MEDIUM — Affects list performance as data grows
### WebSocket Consumer Synchronous DB Calls
- **Issue:** `device_interaction/consumers.py:106-131` uses `@database_sync_to_async` decorator on `update_device_status()` and `mark_device_offline()`, which is correct, but the decorator wraps individual methods, not the whole consumer. If `receive()` calls multiple sync methods in sequence, they're not batched.
- **Files:** `device_interaction/consumers.py:106-144`
- **Pattern:**
```python
@database_sync_to_async
def update_device_status(self, mac_address, device_data):
device = Device.objects.filter(mac_address=mac_address).first()
device.status = 'connected'
device.save()
cache.set(...)
```
- **Impact:** Each WebSocket message triggers separate DB transaction. If device sends 100 messages/sec, that's 100 DB round-trips/sec per device × device count = potential connection pool exhaustion.
- **Trigger:** Device rapidly sending device_info messages
- **Fix approach:** Batch writes using bulk_update() for multiple devices. Use Redis for frequent updates (heartbeat), sync to DB on schedule (5-min batch job). Consider using `select_for_update()` to prevent race conditions during concurrent updates.
- **Severity:** MEDIUM — Affects high-frequency scenarios (concurrent devices)
### Redis Heartbeat TTL Race Condition
- **Issue:** CLAUDE.md (line 230) documents: "设备心跳:每次收消息时刷新 `device:last_seen:{mac}`TTL 5 分钟),断连时把 `Device.status` 标记为 `disconnected`"
- **Files:**
- `device_interaction/consumers.py:147-151``refresh_device_heartbeat()` refreshes Redis
- `device_interaction/consumers.py:153-169``disconnect()` marks DB offline
- **Pattern:** Heartbeat is Redis-only (fast); offline mark is DB-only. Gap between Redis expiry and disconnect handler creates brief window where Redis says offline but DB says online.
- **Impact:** Race condition: if device loses connection and immediately reconnects within 5 minutes, Redis has fresh key but DB still marked disconnected. List endpoints show device as offline even though it's online.
- **Trigger:** Device disconnects and reconnects within 5 minutes
- **Fix approach:** On reconnect (in `connect()` method), check Redis and sync DB status: if Redis key exists, update Device.status to 'connected'. Add sync_task that periodically reconciles Redis heartbeat state with DB.
- **Severity:** LOW-MEDIUM — Offline view is slightly stale, but eventually consistent
### Channel Layer Single Redis Instance
- **Issue:** CLAUDE.md (line 116) mentions "基于 Redis 的通道层用于实时消息传递" but Docker compose and settings don't explicitly configure channel layer sharding/replication.
- **Files:**
- `docker-compose.yml` — no Redis cluster config
- `qy_lty/settings.py` — CHANNEL_LAYERS likely not set (uses default)
- **Impact:** Single Redis instance is single point of failure for WebSocket message routing. If Redis crashes, all device-to-phone communication drops until restart.
- **Current mitigation:** Docker `restart: always` policy restarts container on Redis crash
- **Recommendations:** For production: 1) Add Redis Sentinel or Redis Cluster for failover, 2) Explicitly configure CHANNEL_LAYERS in settings, 3) Add monitoring/alerting on Redis health, 4) Document failover procedure.
- **Severity:** MEDIUM — High availability concern for production
## Test Coverage Gaps
### No Unit or Integration Tests Found
- **Issue:** Codebase has 168 Python files but zero test files (`find . -name "test_*.py" -o -name "*.test.py"` returns nothing)
- **Files:** Entire codebase
- **Risk Areas (no test coverage):**
- `device_interaction/consumers.py` (WebSocket consumer logic, token auth, message routing)
- `device_interaction/views.py` (device binding, RTC token generation, message sending)
- `aiapp/views.py` (chat API, audio service integration)
- `card/views.py` (batch generation, QR code handling)
- `userapp/views.py` (login endpoints, device MAC binding)
- `userapp/utils.py` (token generation, SMS sending)
- **Impact:** No regression detection for bug fixes. Refactoring is high-risk. New developers can't verify behavior. Critical paths (auth, device control, chat) have zero test coverage.
- **Priority:** HIGH — This is the single largest gap. Recommend: 1) Set up pytest + pytest-django, 2) Implement tests for all auth endpoints (10-15 tests), 3) Test device binding logic (5-10 tests), 4) WebSocket consumer tests (unit, 10-15 tests), 5) Gradually expand to 70%+ coverage.
- **Severity:** HIGH — Operational risk due to zero safety net
## Fragile Areas
### Device Interaction Views (1,867 lines)
- **Files:** `device_interaction/views.py`
- **Why fragile:** Single mega-file contains 30+ action methods for device management, message routing, RTC token generation, WebSocket callbacks, admin endpoints. Changes to one subsystem affect entire file's import dependencies.
- **Safe modification:** Use code folding tools (IDE regions). Create feature branches. Add tests for each action before refactoring. Extract cohesive groups into separate views (e.g., `DeviceRTCViewSet`, `MessageDispatchViewSet`, `VolcEngineCallbackViewSet`).
- **Test coverage:** Zero. Recommend starting with 10 happy-path + 5 error case tests.
### Card System Batch Generation (`card/views.py:262-364`)
- **Files:** `card/views.py` batch_create action (~100 lines)
- **Why fragile:** Batch generation calls external services (QR generation), creates multiple DB records in transaction, generates Excel exports. If any step fails mid-batch, partial state is left.
- **Safe modification:** Use `@transaction.atomic` (already present) but verify rollback covers all side effects. Add dry-run mode. Test with batch size = 1000+.
- **Test coverage:** Zero.
### WebSocket Token Authentication (`device_interaction/consumers.py:78-104`)
- **Files:** `device_interaction/consumers.py`
- **Why fragile:** Token extraction from URL (`get_token_from_url()`) has two fallback paths (url_route kwargs, manual path parsing). Order matters; if first path silently fails, second might not fire.
- **Safe modification:** Add explicit logging at each path. Unit test both extraction methods independently.
- **Test coverage:** Zero. Recommend: 2-3 token auth scenario tests.
## Migration & Data Debt
### Favorability Data Migration Dependency Chain
- **Issue:** Affinity system (P1 phase, commit 2d82b2e) depends on three sequential migrations:
1. `device_interaction/migrations/0003_userdevice_affinity_*.py` — schema changes
2. `userapp/migrations/0005_affinitysetting_*.py` — schema + new tables
3. `userapp/migrations/0006_migrate_favorability_to_userdevice.py` — data migration (RunPython)
- **Files:** All three migration files
- **Impact:** If migration #3 runs before #2, FK constraint fails. If rolled back out-of-order, data loss. Migration file 0006 is irreversible if both forward and backward have been applied (ParadiseUser.favorability loses history).
- **Fix approach:** Document exact migration sequence in CLAUDE.md deploy section. Add migration dependency comments. Test migration path on staging before prod deploy.
- **Severity:** MEDIUM — One-time risk during affinity system rollout
### Schema Churn Around UserDevice
- **Issue:** Recent commits added 4 new columns to UserDevice (favorability, affinity_level, last_active_at, is_active). This is 5th major change to UserDevice in recent history (based on migrations count).
- **Files:** `device_interaction/models.py`, `device_interaction/migrations/0003_*`
- **Impact:** Existing deployments will have migration backlog. Large tables see downtime during migration if index creation locks table.
- **Recommendations:** For future schema changes: 1) Use zero-downtime migration strategy (add column with default, backfill async, add constraint later), 2) Test migration time on prod-sized DB before deploy, 3) Plan maintenance window if >10GB table affected.
- **Severity:** LOW — Manageable if migrations are small; communicate with ops team
## Internationalization Concerns
### i18n Setup Incomplete (en / zh_HAns Only)
- **Issue:** CLAUDE.md (line 85) documents: `django-admin makemessages -l en` and `-l zh_HAns`, but only two language files present. No translation workflow defined (who translates, quality assurance, etc.).
- **Files:** Locale directories (if present)
- **Impact:** English and Simplified Chinese only. Any text from Chinese source code will show untranslated when language=en. Admin UI (SimpleUI) may have partial translations.
- **Recommendations:** 1) Run makemessages and compilemessages at deploy time (add to Docker CMD), 2) Define translation workflow (assign translator, review process), 3) Consider using gettext_lazy for all user-facing strings, 4) Add i18n test to CI (verify no missing keys).
- **Severity:** LOW — UI concern; functional impact minimal if default language is Chinese
## Known TODOs in Codebase
1. **`achievement_app/views.py:139`** — "TODO: 根据成就的conditions字段和前端传递的check_data检查是否满足条件" (achievement condition validation missing)
2. **`device_interaction/views.py:1427`** — "TODO 决策点 #3 落定后填充 'phone' / 'device'" (conversation source field in WebSocket callback)
3. **`subscription_app/apps.py:17`** — "TODO:: 测试环境不启动调度器" (scheduler disabled in test env, needs decision)
## Deployment Concerns
### Docker Image Uses Python 3.8 (EOL)
- **Issue:** `Dockerfile:2` uses `python:3.8` which reached EOL in October 2024. No security patches available.
- **Files:** `Dockerfile:2`
- **Impact:** CVE fixes in Python runtime are no longer released. New dependencies may drop Python 3.8 support.
- **Recommendations:** Upgrade to Python 3.11 or 3.12. Update requirements.txt with compatible versions (test in staging first).
- **Severity:** MEDIUM — Security risk for production
### Single Daphne Process in Docker (No Workers)
- **Issue:** `Dockerfile:25` and `run.sh:2` start single `daphne` instance with no worker scaling. No load balancing, no graceful shutdown.
- **Files:**
- `Dockerfile:20-26`
- `run.sh:2`
- **Impact:** Single process is bottleneck. Restart drops all active WebSocket connections. No way to scale without manual container replication.
- **Recommendations:** 1) Use Docker Compose or Kubernetes to run multiple daphne replicas, 2) Add reverse proxy (nginx) to load balance, 3) Implement graceful shutdown handler (drain connections before exit), 4) Add health check endpoint (Django health_check app).
- **Severity:** MEDIUM — High availability concern
### No Database Healthcheck
- **Issue:** `docker-compose.yml` has no healthcheck for postgres or redis. If DB is down, container still shows "running".
- **Files:** `docker-compose.yml:1-33`
- **Impact:** Deployment tools can't detect DB failures. Restart policies won't trigger on DB outage.
- **Recommendations:** Add healthcheck to docker-compose:
```yaml
healthcheck:
test: ["CMD", "python", "-c", "import django; django.setup(); from django.db import connection; connection.ensure_connection()"]
interval: 30s
timeout: 5s
retries: 3
```
- **Severity:** LOW — Operational tool concern
### Missing Database Migrations in Dockerfile Startup
- **Issue:** `Dockerfile:25` startup script (start.sh) runs daphne directly without calling `python manage.py migrate`. If migrations are pending, runtime errors occur.
- **Files:** `Dockerfile:20-26`
- **Impact:** Fresh deploy or rolling update fails if new migrations exist. Manual SSH to container required to fix.
- **Recommendations:** Modify start.sh:
```bash
python manage.py migrate
daphne -b 0.0.0.0 -p 8000 qy_lty.asgi:application
```
- **Severity:** MEDIUM — Deployment failure risk on schema changes
---
*Concerns audit: 2026-05-07*

View File

@ -0,0 +1,292 @@
# Coding Conventions
**Analysis Date:** 2026-05-07
## Naming Patterns
**Files:**
- Django apps follow structure: `{app_name}/models.py`, `{app_name}/serializers.py`, `{app_name}/views.py`, `{app_name}/urls.py`, `{app_name}/tests.py`
- Model files: lowercase underscore naming (e.g., `device_interaction`, `ali_vi_app`, `achievement_app`)
- Serializer files: grouped with models in app directory, separate classes for different use cases (e.g., `CardTemplateSerializer`, `CardDetailSerializer`, `CardBatchSerializer` in `card/serializers.py`)
**Functions:**
- snake_case for all functions: `send_sms()`, `get_user_id_from_token()`, `generate_device_code()`, `authenticate_with_token()`
- Async functions use `async def`: `async def connect()`, `async def receive()`
- Helper/private functions prefixed with underscore: `_extract_validation_error_message()`, `_merge_subv_segments()`
**Variables:**
- snake_case for local variables and instance attributes: `user_id`, `device_mac`, `error_message`, `phone_number`
- Boolean flags use is_/has_ prefixes: `is_active`, `is_primary`, `is_authenticated`, `has_achievement`
- Temporary/loop variables: `i`, `j`, `obj`, `result` (minimal context only)
- Constants: UPPERCASE with underscores: `MESSAGE_TYPE_TEXT`, `GENDER_CHOICES`, `TRIGGER_TYPE_CHOICES`
**Types:**
- Model names: PascalCase (e.g., `ParadiseUser`, `AffinityRule`, `UserDevice`, `DeviceBatch`)
- Serializer names: PascalCase with "Serializer" suffix (e.g., `ParadiseUserSerializer`, `UserInfoSerializer`, `DeviceTypeSerializer`)
- ViewSet names: PascalCase with "ViewSet" suffix (e.g., `AchievementViewSet`, `DeviceTypeViewSet`, `UserAchievementViewSet`)
- Permission classes: PascalCase (e.g., `IsAdminOrReadOnly`)
- Middleware classes: PascalCase (e.g., `StandardResponseMiddleware`, `TokenAuthMiddleware`)
## Code Style
**Formatting:**
- No explicit linter specified in configuration — project uses Django/DRF conventions implicitly
- Indentation: 4 spaces (Django standard)
- Line length: appears to follow PEP 8 (~79-99 chars), but not strictly enforced
- Imports organized: Django → third-party → local (visible in `userapp/views.py`, `device_interaction/views.py`)
**Linting:**
- No `.flake8`, `pyproject.toml`, or `setup.cfg` found
- No explicit linter configured in `requirements.txt`
- Code style appears self-enforced through code review rather than automated tools
## Import Organization
**Order:**
1. Django imports (django, django.db, django.conf, etc.)
2. Django REST Framework (rest_framework, rest_framework.*)
3. Third-party (channels, aliyun, decouple, etc.)
4. Local app imports (relative or full path within project)
**Example from `userapp/views.py`:**
```python
from dj_rest_auth.registration.views import RegisterView
from rest_framework import viewsets, status
from .models import ParadiseUser, AffinityRule
from device_interaction.models import Device, UserDevice
from .serializers import ParadiseUserSerializer, CustomRegisterSerializer
from rest_framework.response import Response
from rest_framework.decorators import action, permission_classes
from common.responses import success_response, error_response
import logging
```
**Path Aliases:**
- No `@` path aliases configured (e.g., no `@/services`) — full relative paths used
- Common local patterns: `from common.responses import ...`, `from userapp.authentication import ...`, `from .models import ...`
## Error Handling
**Patterns:**
- Try-except blocks catch specific exceptions: `Device.DoesNotExist`, `ParadiseUser.DoesNotExist`, `ValidationError`
- Generic `except Exception as e:` used for unexpected errors with logging
- DRF exceptions converted to `Response` with status codes: `status.HTTP_400_BAD_REQUEST`, `status.HTTP_404_NOT_FOUND`, `status.HTTP_201_CREATED`
- Validation errors: use serializer validation + raise `serializers.ValidationError()` in validators
- WebSocket closure on auth failure: `await self.close(code=4001)` for authentication failure
**Example from `userapp/views.py` (MAC login):**
```python
try:
device = Device.objects.get(mac_address=mac_address)
user_device = UserDevice.objects.filter(device=device).order_by('-bound_at').first()
if user_device is None:
logger.warning(f"Device not bound to any user: {mac_address}")
return error_response(message="设备未绑定用户")
except Device.DoesNotExist:
logger.warning(f"Device not found: {mac_address}")
return error_response(message="设备不存在", status_code=404)
except Exception as e:
logger.error(f"MAC address login failed: {str(e)}")
return error_response(message="登录失败")
```
**Standard Response Format:**
- All API responses wrap through `common.middleware.StandardResponseMiddleware`
- Response format: `{"success": true/false, "code": status_code, "message": "...", "data": {...}}`
- Helper functions: `success_response()`, `error_response()`, `created_response()` from `common.responses`
## Logging
**Framework:** Python's built-in `logging` module (no custom wrapper library)
**Logger setup pattern:**
```python
import logging
logger = logging.getLogger(__name__)
```
**Dedicated loggers configured in settings:**
- `aiapp` - AI dialogue system logs
- `userapp` - User authentication and management logs
- `common` - Shared utility logs
- `device_interaction` - Device connection logs
- `card` - Card system logs
**Aliyun integration:** `common.aliyun_logging.AliyunLogHandler` sends INFO+ level logs to Aliyun Log Service in production
**Logging patterns observed:**
- `logger.info()` - Login success, token generation, device activation
- `logger.warning()` - Validation failures, auth attempts, device not found
- `logger.error()` - Unexpected exceptions, external service failures
**Example from `device_interaction/consumers.py`:**
```python
import logging
logger = logging.getLogger(__name__)
async def connect(self):
try:
logger.info(f'WebSocket connected for user {self.user_id}')
except Exception as e:
logger.error(f"Error in WebSocket connect: {str(e)}")
```
## Comments
**Language:** Predominantly Chinese for inline comments and docstrings (business domain, user-facing logic)
**When to Comment:**
- Complex algorithm explanations (e.g., `_merge_subv_segments()` in `device_interaction/views.py` — subtitle merging logic)
- Non-obvious validation rules (e.g., device binding "last-bind-wins" semantics)
- WebSocket message type handlers and channel routing
- Async/await patterns and database sync-to-async wrappers
**JSDoc/TSDoc:**
- Models use Django docstrings in triple-quotes: `"""自定义用户模型"""` (appears in `userapp/models.py`)
- ViewSet actions use `@action` decorator with docstrings describing endpoint behavior
- Swagger schema classes defined inline with docstrings for API documentation
**Example from `userapp/models.py`:**
```python
class ParadiseUser(AbstractUser):
"""
自定义用户模型
"""
phone_number = models.CharField('手机号', max_length=20, unique=True, null=True, blank=True)
```
**Field docstrings:**
- Django model fields use `verbose_name` (Chinese) and `help_text` for field-level docs
- Example: `models.CharField('手机号', max_length=20, unique=True, null=True, blank=True)`
## Function Design
**Size:** Functions stay reasonably focused (50-100 lines max observed, with WebSocket consumers being larger)
**Parameters:**
- Functions accept `self` (methods), `request` (view methods), `obj` (serializer methods)
- No heavy use of **kwargs; explicit named parameters preferred
- Async functions have `async def` signature with minimal parameters
**Return Values:**
- View methods return DRF `Response` objects
- Model methods return model instances or strings (`__str__`)
- Serializer methods return serialized data (dict/list)
- Async consumers return None (side effects via channel layer messaging)
## Module Design
**Exports:**
- Apps explicitly import from `models.py`, `serializers.py`, `views.py` — no star imports (`from .models import *`)
- URLconf imports specific views/viewsets: `from .views import DeviceTypeViewSet`
- Serializers list all field serializers in their module, not hidden
**Barrel Files:**
- No barrel files (index.py/`__init__.py` exports) used
- Direct imports from app modules instead
## Authentication & Authorization
**Authentication:**
- Custom `RedisTokenAuthentication` in `userapp/authentication.py` — token stored in Redis with 30-day TTL
- Token format: `token:{token}` for regular users, `admin_token:{token}` for admins
- Used via `authentication_classes = [RedisTokenAuthentication]` on views/viewsets
- AllowAny permission for login/registration endpoints
**Authorization:**
- `permission_classes = [IsAuthenticated]` for user-only endpoints
- `permission_classes = [IsAdminOrReadOnly]` custom class for admin write, others read (used in `BotViewSet`, `AchievementViewSet`)
- Method-level permissions with `@permission_classes([...])` decorator
**Example from `achievement_app/views.py`:**
```python
class AchievementViewSet(viewsets.ModelViewSet):
permission_classes = [IsAdminOrReadOnly]
authentication_classes = [RedisTokenAuthentication]
```
## Swagger/API Documentation
**Pattern:**
- Use `@swagger_auto_schema()` decorator from `drf_yasg` for endpoint docs
- Define request/response schema classes inline (inherit from `serializers.Serializer`)
- Specify operation_summary, operation_description, request_body, responses parameters
- Use `get_standardized_response_schema()` from `common.swagger_utils` for consistent response wrapping
**Example from `userapp/views.py`:**
```python
@swagger_auto_schema(
request_body=MacAddressLoginRequestSchema,
responses={
200: openapi.Response('登录成功', get_standardized_response_schema()),
404: openapi.Response('设备不存在或未绑定', get_standardized_response_schema())
},
operation_description="使用设备MAC地址进行登录返回认证令牌"
)
def post(self, request):
```
## Device Binding Convention (Critical)
**"Last-bind-wins" semantics:**
- `UserDevice.Meta.ordering = ['-bound_at']` — orders by most recent binding first
- When a device is bound by multiple users, the most recently bound user controls it
- MAC login in `userapp/views.py`: `UserDevice.objects.filter(device=device).order_by('-bound_at').first()` explicitly gets latest binder
- Old `UserDevice` records are NOT auto-deleted; they're just ignored in control resolution
- `is_primary` field is "user's primary device" (per-user), NOT "device's controlling user" — same device can have multiple `is_primary=True` records
**Test MAC affordance:**
- `AA:BB:CC:DD:EE:FF` is hardcoded in `device_interaction/serializers.py` and `views.py` to skip "device already bound" validation — test-only
## Project Modification Record Rule (CRITICAL)
**Every meaningful code change MUST be recorded in `docs/修改记录.md`** at session time, appended to the top (newest first). This is a load-bearing convention.
**Format (from `docs/修改记录.md` header):**
```markdown
### [日期] 修改简述
- **文件路径**: 相对于项目根目录的文件路径
- **修改类型**: 新增 / 修改 / 删除 / 重构 / 修复Bug
- **修改内容**: 具体修改了什么
- **修改原因**: 为什么要做这个修改
```
**Applies to:**
- Business code, configuration, migration files, CI/k8s/Dockerfile, structural doc changes → MUST record
- Typo fixes, comment tweaks → Can omit
- Temp debug scripts, .gitignore files, local experiments → Do not record
**Cross-project coordination:**
- `qy_lty/docs/修改记录.md` records backend changes ONLY
- `qy-lty-admin/docs/修改记录.md` records frontend changes ONLY
- Cross-project changes: Write separate entries, cross-reference each other
## Settings Configuration
**Environment variables via `decouple.config()`:**
- Pattern: `config('VAR_NAME')` or `config('VAR_NAME', default=value, cast=type)`
- No .env reading in code — all via settings.py config layer
- Critical vars: `SECRET_KEY`, `DEBUG`, `POSTGRESQL_DATABASE_*`, `REDIS_LOCATION`, `REDIS_PASSWORD`, `KIMI_API_KEY`, `ALIYUN_*`, `VOLCENGINE_*`
**Settings access in views:**
- `from django.conf import settings` then `settings.VAR_NAME`
- Example from `userapp/views.py`: `if settings.DEBUG else 'https://...'`
## Async/WebSocket Patterns
**Async consumers:**
- Inherit from `AsyncWebsocketConsumer` (channels)
- Use `async def` for all methods
- Database calls wrapped with `@database_sync_to_async` decorator
- Channel layer messaging: `await self.channel_layer.group_add()`, `await self.channel_layer.group_send()`
- Connection groups by user ID: `device_{user_id}` (fixed convention per CLAUDE.md)
**Token authentication in WebSocket:**
- Extract token from URL path: `ws://domain/ws/device/token/{token}/`
- Token validated via mock request object calling `RedisTokenAuthentication.authenticate()`
- Close on auth failure: `await self.close(code=4001)`
---
*Convention analysis: 2026-05-07*

View File

@ -0,0 +1,179 @@
# External Integrations
**Analysis Date:** 2026-05-07
## APIs & External Services
**AI & Language Models:**
- Kimi (OpenAI-compatible) - LLM for multi-turn AI conversations
- SDK: `openai` Python package (in `requirements.txt`)
- Implementation: `aiapp/kimi.py` (lines 1-29)
- Config: `KIMI_API_KEY`, `KIMI_BASE_URL` (set at `qy_lty/settings.py:349-350`, loaded from `.env`)
- Purpose: Single/multi-turn chat with users via POST `/api/ai/chat/` and `/api/ai/multi-chat/`
**Real-Time Communications (RTC):**
- Volcengine (ByteDance) - RTC service for voice/video calls
- SDK: Custom token generation in `common/VolcEngineAccessToken.py` (lines 1-204), HTTP API calls in `device_interaction/volcengine_api.py`
- Config: `VOLCENGINE_ACCESS_KEY`, `VOLCENGINE_SECRET_KEY`, `VOLCENGINE_APP_ID`, `VOLCENGINE_APP_KEY` (at `qy_lty/settings.py:241-243`)
- Purpose: Generate RTC tokens for device-to-device or device-to-app real-time communication
- Endpoint: `POST /api/device/rtc-token/get_by_mac/?mac_address=...` (no auth required, returns RTC token for device's bound user)
- Room structure: `room_id = f"room_{user_id}"` (shared with WebSocket group `device_{user_id}`)
**Audio/Speech Services (Multi-Provider):**
- Configured via `AUDIO_SERVICE_PROVIDER` setting (default: `'huoshan'`) at `qy_lty/settings.py:415`
**Aliyun NLS (Natural Language Service):**
- SDK: `aliyun-python-sdk-core` + custom integration
- Implementation: `aiapp/audio/AliyunAudioService.py`
- Config keys: `ALIYUN_NLS_ACCESS_KEY_ID`, `ALIYUN_NLS_ACCESS_KEY_SECRET`, `ALIYUN_NLS_APP_ID` (in `qy_lty/settings.py:419-421`)
- Purpose: Speech synthesis (text→voice) and recognition (voice→text)
- Storage: Audio uploaded to Aliyun OSS (see OSS section)
**Tencent Audio:**
- Implementation: `aiapp/audio/TencentAudioService.py`
- Config keys: `AUDIO_SERVICE_TENCENT_API_KEY`, `AUDIO_SERVICE_TENCENT_API_SECRET` (at `qy_lty/settings.py:430-431`)
- Purpose: Text-to-speech and speech-to-text via Tencent APIs
**Volcengine/Huoshan (ByteDance):**
- Implementation: `aiapp/audio/HuoshanAudioService.py`, `aiapp/audio/HuoshanAudioService_new.py`
- Config keys: `AUDIO_SERVICE_HUOSHAN_APPID`, `AUDIO_SERVICE_HUOSHAN_ACCESS_TOKEN`, `AUDIO_SERVICE_HUOSHAN_CLUSTER`, `AUDIO_SERVICE_HUOSHAN_VOICE_TYPE`, `AUDIO_SERVICE_HUOSHAN_STORAGE_DIR`, `AUDIO_SERVICE_HUOSHAN_BASE_URL` (at `qy_lty/settings.py:434-439`)
- Purpose: TTS (text→speech) and ASR (speech→text) with local storage fallback
- Also includes nested Aliyun ASR config (lines 440-451) as fallback
**Audio Service Factory:**
- Entry point: `aiapp/audio/AudioService.py:get_audio_service()` (lines 8-19)
- Base class: `aiapp/audio/BaseAudioService.py` (abstract interface at lines 1-35)
- Used by: `aiapp/views.py` (ChatBotAPIView, MultiChatAPIView) and tests
## Data Storage
**Databases:**
- **PostgreSQL** (Primary)
- Connection: `POSTGRESQL_DATABASE_*` env vars (configured at `qy_lty/settings.py:166-172`)
- Client: psycopg2-binary
- Contains: User models, AI conversations, device bindings, card data, achievement records, subscription info
- **Redis** (Cache + Message Queue)
- Connection: `REDIS_LOCATION`, `REDIS_PASSWORD` (configured at `qy_lty/settings.py:252-260`)
- Client: django-redis
- Purpose: Token storage (key format: `token:{token}` or `admin_token:{token}` with 30-day TTL, see `CLAUDE.md` line 122), channel layer for WebSocket, caching
- Channel layer config: `CHANNEL_LAYERS.default.BACKEND = 'channels_redis.core.RedisChannelLayer'` (at `qy_lty/settings.py:504-511`)
**File Storage:**
- **Aliyun OSS (Object Storage Service)** - Production storage for audio, images, documents
- SDK: `oss2` Python package (in `requirements.txt`)
- Implementation: `common/oss.py` (OSSUploader class at lines 1-90)
- Config: `AUDIO_SERVICE_CONFIG['aliyun']` with `oss_key_id`, `oss_key_secret`, `oss_bucket`, `oss_endpoint`, `oss_host` (at `qy_lty/settings.py:422-427`)
- Used by: Audio service, image processing endpoints, media uploads
- Path pattern: `{base_dir}/[permanent|temporary]/{YYYYMMDD}/{filename}`
- Dev fallback: Local filesystem storage at `MEDIA_ROOT` (`qy_lty/settings.py:238`)
**Caching:**
- Redis (see above)
- TTL for RTC tokens: `rtc_room:{user_id}:{task_id}` (see `CLAUDE.md` line 172)
- Device last-seen cache: `device:last_seen:{mac}` with 5-min TTL (see `CLAUDE.md` line 230)
## Authentication & Identity
**Auth Provider:**
- Custom Redis-backed token authentication + django-allauth social auth
**Custom Token Auth:**
- Implementation: `userapp/authentication.RedisTokenAuthentication` (at `userapp/authentication.py:10-34`)
- Storage: Redis with keys `token:{token}` (users) and `admin_token:{token}` (admins)
- TTL: 30 days
- Used for: API requests via `Authorization: Bearer {token}` header
- Generated by: `userapp/utils.py:generate_token()` (referenced in `CLAUDE.md` line 122)
**WeChat Social Auth:**
- Provider: django-allauth with Weixin provider (configured at `qy_lty/settings.py:56`)
- Backend: `allauth.account.auth_backends.AuthenticationBackend` (at `qy_lty/settings.py:324`)
**WebSocket Auth:**
- Custom middleware: `device_interaction/auth.TokenAuthMiddleware` (lines 1-50)
- Reuses `RedisTokenAuthentication` logic with mock request adapter
- Routes: `ws://domain/ws/device/` (Header auth) and `ws://domain/ws/device/token/{token}/` (URL auth)
- Group model: Messages routed to `device_{user_id}` (same user controls device and app)
**Device MAC Login:**
- Endpoint: `POST /api/user/mac-login/` (referenced in `CLAUDE.md` line 125)
- Returns: User-token for device's bound user (latest binding via `order_by('-bound_at').first()`)
- Model: `UserDevice` relationship with `bound_at` ordering
## Monitoring & Observability
**Error Tracking:**
- No dedicated external error tracking (Sentry, etc.) detected
**Logs:**
- **Aliyun Log Service** - Production logging integration
- SDK: `aliyun-log-python-sdk` (in `requirements.txt`)
- Implementation: `common/aliyun_logging.py` (AliyunLogHandler at lines 16-36, setup_logging() at lines 32-36)
- Config: `ALIYUN_LOG_PROJECT`, `ALIYUN_LOG_STORE`, `ALIYUN_LOG_ENDPOINT`, `ALIYUN_LOG_ACCESS_KEY_ID`, `ALIYUN_LOG_ACCESS_KEY_SECRET` (env vars, loaded at lines 7-11)
- Logging config: `qy_lty/settings.py:372-411` configures handlers for `django`, `django.request`, `aiapp`, `common`, `userapp` loggers
- Log levels: INFO for production, DEBUG for console in dev
## CI/CD & Deployment
**Hosting:**
- Docker containers (self-managed or cloud-hosted)
- Image base: Python 3.8 via docker.m.daocloud.io (China mirror)
**CI Pipeline:**
- No dedicated CI/CD service detected in code (GitHub Actions, GitLab CI, etc. config not present)
**Deployment:**
- Docker Compose for local/staging (docker-compose.yml:1-33)
- Production via Docker Compose or Kubernetes (see Dockerfile for structure)
- Port exposure: 12012 (maps to ASGI 8000)
## Environment Configuration
**Required env vars (from settings.py and CLAUDE.md):**
- `SECRET_KEY` - Django secret
- `DEBUG` - Debug mode
- `POSTGRESQL_DATABASE_NAME`, `POSTGRESQL_DATABASE_USER`, `POSTGRESQL_DATABASE_PASSWORD`, `POSTGRESQL_DATABASE_HOST`, `POSTGRESQL_DATABASE_PORT`
- `REDIS_LOCATION`, `REDIS_PASSWORD`
- `KIMI_API_KEY`, `KIMI_BASE_URL` (default: https://api.moonshot.cn/v1)
- `ALIYUN_SMS_ACCESS_KEY_ID`, `ALIYUN_SMS_ACCESS_KEY_SECRET`, `ALIYUN_SMS_SIGN_NAME`, `ALIYUN_SMS_TEMPLATE_CODE`
- `ALIYUN_NLS_ACCESS_KEY_ID`, `ALIYUN_NLS_ACCESS_KEY_SECRET`, `ALIYUN_NLS_APP_ID`
- `ALIYUN_OSS_ACCESS_KEY_ID`, `ALIYUN_OSS_ACCESS_KEY_SECRET`, `ALIYUN_OSS_BUCKET`, `ALIYUN_OSS_ENDPOINT`, `ALIYUN_OSS_HOST`, `ALIYUN_OSS_AUDIO_BASE_DIR`
- `ALIYUN_VI_ACCESS_KEY_ID`, `ALIYUN_VI_ACCESS_KEY_SECRET`, `ALIYUN_VI_ENDPOINT`, `ALIYUN_VI_REGION`
- `ALIYUN_LOG_PROJECT`, `ALIYUN_LOG_STORE`, `ALIYUN_LOG_ENDPOINT`, `ALIYUN_LOG_ACCESS_KEY_ID`, `ALIYUN_LOG_ACCESS_KEY_SECRET`
- `VOLCENGINE_ACCESS_KEY`, `VOLCENGINE_SECRET_KEY`, `VOLCENGINE_APP_ID`, `VOLCENGINE_APP_KEY`
- `AUDIO_SERVICE_PROVIDER` (values: 'aliyun', 'tencent', 'huoshan'; default: 'huoshan')
- `AUDIO_SERVICE_HUOSHAN_APPID`, `AUDIO_SERVICE_HUOSHAN_ACCESS_TOKEN`, `AUDIO_SERVICE_HUOSHAN_CLUSTER`, `AUDIO_SERVICE_HUOSHAN_VOICE_TYPE`, `AUDIO_SERVICE_HUOSHAN_STORAGE_DIR`, `AUDIO_SERVICE_HUOSHAN_BASE_URL`
- `AUDIO_SERVICE_TENCENT_API_KEY`, `AUDIO_SERVICE_TENCENT_API_SECRET` (if provider='tencent')
**Secrets location:**
- `.env` file (git-ignored, copy from `.env.bak` template during setup per `CLAUDE.md` line 44)
## Webhooks & Callbacks
**Incoming:**
- WebSocket device messages routed to `device_{user_id}` group (see `CLAUDE.md` line 118)
- Volcengine RTC conversation callbacks: `conversation_status`, `conversation_subtitle` message types forwarded via WebSocket (see `CLAUDE.md` line 167)
- Device heartbeat: `device:last_seen:{mac}` refreshed on message receipt (see `CLAUDE.md` line 230)
**Outgoing:**
- No outbound webhooks detected (integrations are request/response based)
## API Documentation
**Documentation Endpoints:**
- Swagger UI: `http://localhost:8000/swagger/`
- ReDoc: `http://localhost:8000/redoc/`
- Generated by: drf-yasg (configured at `qy_lty/settings.py:513-549`)
**Main API Routes:**
- `/api/user/` - User auth & management (MAC login, token generation)
- `/api/ai/` - AI chat (single-turn, multi-turn, voice synthesis/recognition)
- `/api/device/` - Device interaction, RTC tokens, binding status
- `/api/card/` - Card system (batch generation, QR codes, usage tracking)
- `/api/achievement/` - Achievement tracking
- `/api/v1/admin/` - Admin operations
- `/ws/device/` - WebSocket for real-time device communication
---
*Integration audit: 2026-05-07*

View File

@ -0,0 +1,125 @@
# Technology Stack
**Analysis Date:** 2026-05-07
## Languages
**Primary:**
- Python 3.8 - Entire backend application (specified in `Dockerfile` line 2)
## Runtime
**Environment:**
- Python 3.8 via Docker container (docker.m.daocloud.io/python:3.8)
- ASGI server: Daphne for WebSocket + HTTP support (see `run.sh`)
**Package Manager:**
- pip 3.8+ (Python package manager)
- Lockfile: No pinned versions in `requirements.txt` (line 1-42) — dependencies are unpinned, allowing flexibility but risking compatibility issues
## Frameworks
**Core:**
- Django 4.2.13 - Primary web framework (declared in `qy_lty/settings.py:4`)
- Django REST Framework (DRF) - REST API implementation (in `requirements.txt`)
- Django Channels - WebSocket support for real-time device communication (in `requirements.txt`, configured in `qy_lty/settings.py:501`)
**Real-Time & Async:**
- Daphne - ASGI server to serve both HTTP and WebSocket (in `requirements.txt`, launched via `run.sh`)
- channels-redis - Channel layer using Redis for inter-process messaging (in `requirements.txt`, configured at `qy_lty/settings.py:504-511`)
- aioredis - Async Redis client (in `requirements.txt`)
**API Documentation:**
- drf-yasg - Generates Swagger/OpenAPI documentation for DRF (in `requirements.txt`, configured at `qy_lty/settings.py:513-549`)
**Database & Caching:**
- PostgreSQL - Primary relational database (configured at `qy_lty/settings.py:166-172`)
- Redis - Cache backend and WebSocket channel layer (configured at `qy_lty/settings.py:252-260` for cache, `504-511` for channels)
- Client: django-redis for Django integration (in `requirements.txt`)
- Password-authenticated via environment variables
**Admin & UI:**
- django-simpleui - Enhanced admin interface with Chinese localization (in `requirements.txt`, configured at `qy_lty/settings.py:475-498`)
- django-rosetta - Translation management UI (in `requirements.txt`)
**Authentication & Authorization:**
- django-allauth - Social authentication framework including WeChat provider (in `requirements.txt`, configured at `qy_lty/settings.py:52-56`, backend at `qy_lty/settings.py:321-325`)
- dj_rest_auth - Token-based REST authentication bridge (in `requirements.txt`)
- django-phone-verify - Phone verification support (in `requirements.txt`)
- Custom: `userapp.authentication.RedisTokenAuthentication` - Token stored in Redis with 30-day TTL (see `CLAUDE.md` line 122)
**Development & Debugging:**
- django-debug-toolbar - Development-time debugging (in `requirements.txt`)
- django-cors-headers - CORS middleware for cross-origin requests (in `requirements.txt`, middleware at `qy_lty/settings.py:85`)
**Task Scheduling:**
- APScheduler (apscheduler) - Background task scheduling (in `requirements.txt`)
**File Storage & Image Processing:**
- Pillow - Image processing library (in `requirements.txt`)
- oss2 - Aliyun OSS Python SDK for file uploads (in `requirements.txt`, used in `common/oss.py`)
**Data Export & Processing:**
- openpyxl - Excel file generation (in `requirements.txt`)
**Utilities:**
- requests - HTTP client for external API calls (in `requirements.txt`, used throughout for service integrations)
- cryptography - Encryption/decryption utilities (in `requirements.txt`)
- python-decouple - Environment variable loading (in `requirements.txt`, used at `qy_lty/settings.py:14`)
- openai - OpenAI Python SDK for Kimi API (compatible with OpenAI spec, in `requirements.txt`)
## Key Dependencies
**Critical Infrastructure:**
- mysqlclient - MySQL database adapter (in `requirements.txt`, though PostgreSQL is primary; MySQL config commented at `qy_lty/settings.py:159-164`)
- psycopg2-binary - PostgreSQL database adapter (in `requirements.txt`, active at `qy_lty/settings.py:166`)
**Aliyun (Alibaba Cloud) SDKs:**
- aliyun-python-sdk-core - Core SDK for Aliyun services (in `requirements.txt`)
- aliyun-python-sdk-dysmsapi - SMS service SDK (in `requirements.txt`, configured at `qy_lty/settings.py:300-304`)
- oss2 - OSS (Object Storage Service) SDK (in `requirements.txt`, integrated at `common/oss.py:1-90`)
- alibabacloud_facebody20191230 - Face detection/recognition service (in `requirements.txt`)
- alibabacloud_tea_console, alibabacloud_tea_util, alibabacloud_tea_openapi - Aliyun SDK utilities (in `requirements.txt`)
- aliyun-log-python-sdk - Logging service SDK (in `requirements.txt`, used at `common/aliyun_logging.py:1-36`)
**AI & LLM:**
- openai - OpenAI Python client, used for Kimi (OpenAI-compatible API) at `aiapp/kimi.py:2` (configured at `qy_lty/settings.py:349-350`)
## Configuration
**Environment:**
- Loaded via `python-decouple.config()` from `.env` file (see `qy_lty/settings.py:14`)
- Critical env vars required:
- `SECRET_KEY` - Django secret key
- `DEBUG` - Debug mode flag
- `POSTGRESQL_DATABASE_NAME`, `POSTGRESQL_DATABASE_USER`, `POSTGRESQL_DATABASE_PASSWORD`, `POSTGRESQL_DATABASE_HOST`, `POSTGRESQL_DATABASE_PORT` - PostgreSQL connection
- `REDIS_LOCATION`, `REDIS_PASSWORD` - Redis cache and channel layer
- `KIMI_API_KEY`, `KIMI_BASE_URL` - Kimi AI configuration
- `ALIYUN_*` family - SMS, OSS, NLS, VI, logging credentials
- `VOLCENGINE_ACCESS_KEY`, `VOLCENGINE_SECRET_KEY` - RTC service credentials
- Audio service provider env vars (AUDIO_SERVICE_HUOSHAN_*, AUDIO_SERVICE_TENCENT_*)
**Build:**
- `Dockerfile` - Docker image definition (lines 1-33), uses Python 3.8 with China mirror for pip
- `docker-compose.yml` - Service orchestration (lines 1-33), exposes port 12012 → 8000
- `run.sh` - Local development entrypoint (lines 1-2), launches Daphne on 0.0.0.0:8000
## Platform Requirements
**Development:**
- Python 3.8+
- pip for dependency installation
- PostgreSQL database server (or MySQL as fallback)
- Redis server for cache + channel layer
- Docker & docker-compose for containerized development
**Production:**
- Docker container (see `Dockerfile` and `docker-compose.yml`)
- External PostgreSQL service
- External Redis service
- HTTPS reverse proxy (e.g., Nginx) recommended for production
- Deployment target: Linux/Docker (accessible via port 12012 in example)
---
*Stack analysis: 2026-05-07*

View File

@ -0,0 +1,421 @@
# Codebase Structure
**Analysis Date:** 2026-05-07
## Directory Layout
```
qy_lty/
├── manage.py # Django management script
├── run.sh # Start development server (daphne)
├── requirements.txt # Python dependencies
├── Dockerfile # Container image build
├── docker-compose.yml # Docker compose for dev environment
├── .env # Environment variables (not committed)
├── qy_lty/ # Project configuration package
│ ├── __init__.py
│ ├── settings.py # Django settings (INSTALLED_APPS, middleware, auth, etc.)
│ ├── asgi.py # ASGI app entry (Channels + HTTP routing)
│ ├── wsgi.py # WSGI app entry (for traditional servers)
│ └── urls.py # Root URL router (includes all app URLs)
├── userapp/ # User authentication & profile management
│ ├── models.py # ParadiseUser (custom user model), AffinityRule, AffinityLevel
│ ├── views.py # MacAddressLoginView, PhoneLoginView, UserViewSet, ProfileView
│ ├── serializers.py # ParadiseUserSerializer, ProfileUpdateSerializer
│ ├── urls.py # User API routes: /api/user/
│ ├── auth_urls.py # Auth-specific routes (deprecated/legacy)
│ ├── admin_urls.py # Admin API routes: /api/v1/admin/
│ ├── authentication.py # RedisTokenAuthentication class
│ ├── utils.py # generate_token(), get_user_id_from_token(), send_sms()
│ ├── admin.py # Django admin customization
│ ├── apps.py # App configuration
│ ├── migrations/ # Database migrations
│ ├── management/ # Custom management commands
│ └── tests.py # Unit tests
├── aiapp/ # AI chat & voice integration
│ ├── models.py # ChatMessage, Bot, ConversationSubtitle
│ ├── views.py # BotViewSet, ChatView (Kimi API integration, multi-turn chat)
│ ├── serializers.py # ChatMessageSerializer
│ ├── urls.py # AI API routes: /api/ai/
│ ├── kimi.py # Kimi API client (OpenAI compatible)
│ ├── audio/ # Audio service abstraction
│ │ ├── AudioService.py # Multi-provider audio (Aliyun/Volcengine/Tencent)
│ │ ├── aliyun_audio.py # Aliyun NLS implementation
│ │ └── [other provider implementations]
│ ├── admin.py # Admin interface for Chat models
│ ├── apps.py # App configuration
│ ├── migrations/ # Database migrations
│ └── tests.py
├── device_interaction/ # WebSocket real-time device communication
│ ├── models.py # Device, DeviceType, DeviceBatch, UserDevice
│ ├── views.py # DeviceTypeViewSet, DeviceViewSet, UserDeviceViewSet, RTC endpoints
│ ├── serializers.py # DeviceSerializer, UserDeviceSerializer, DeviceBindSerializer
│ ├── urls.py # Device HTTP API routes: /api/device/
│ ├── routing.py # WebSocket URL routing (ws://domain/ws/device/)
│ ├── consumers.py # DeviceConsumer (AsyncWebsocketConsumer, message handlers)
│ ├── auth.py # TokenAuthMiddleware (WebSocket auth)
│ ├── amap_api.py # Amap (高德地图) location/nearby search
│ ├── weather.py # QWeather API for weather data
│ ├── volcengine_api.py # Volcengine RTC token generation
│ ├── swagger.py # Swagger schema definitions
│ ├── admin.py # Admin interface for Device models
│ ├── apps.py # App configuration
│ ├── middleware.py # Custom middleware (if any)
│ ├── services.py # Business logic services
│ ├── scheduler.py # Async task scheduling (e.g., device heartbeat monitoring)
│ ├── management/ # Custom management commands
│ ├── migrations/ # Database migrations
│ └── tests.py
├── card/ # Card management system
│ ├── models.py # CardCategory, CardBatch, Card, CardUsageLog
│ ├── views.py # CardViewSet, batch generation, QR code endpoints
│ ├── serializers.py # CardSerializer
│ ├── urls.py # Card API routes: /api/card/
│ ├── admin.py
│ ├── apps.py
│ ├── migrations/
│ └── tests.py
├── achievement_app/ # User achievements & progress tracking
│ ├── models.py # Achievement, AchievementProgress, AchievementBadge
│ ├── views.py # AchievementViewSet, ProgressView
│ ├── serializers.py # AchievementSerializer
│ ├── urls.py # Achievement API routes: /api/achievement/
│ ├── admin.py
│ ├── apps.py
│ ├── migrations/
│ └── tests.py
├── subscription_app/ # Subscription & billing management
│ ├── models.py # Subscription, SubscriptionPlan, Payment
│ ├── views.py # SubscriptionViewSet
│ ├── serializers.py # SubscriptionSerializer
│ ├── urls.py # Subscription API routes (if exposed)
│ ├── admin.py
│ ├── apps.py
│ ├── migrations/
│ ├── templates/ # Email templates for subscription notifications
│ └── tests.py
├── ali_vi_app/ # Aliyun Visual Intelligence integration
│ ├── models.py # VIRequest, VIResult
│ ├── views.py # Aliyun VI API wrappers
│ ├── urls.py # VI API routes (currently disabled in root urls.py line 52)
│ ├── admin.py
│ ├── apps.py
│ ├── migrations/
│ └── tests.py
├── food_app/ # Food management system (new)
│ ├── models.py # Food, FoodInventory
│ ├── views.py # FoodViewSet
│ ├── serializers.py
│ ├── urls.py # Food API routes: /api/food/
│ ├── admin.py
│ ├── apps.py
│ ├── migrations/
│ └── tests.py
├── workflow_app/ # Multi-tenant workflow management (dev)
│ ├── models.py # Workflow, WorkflowStep, WorkflowTask
│ ├── views.py
│ ├── serializers.py
│ ├── urls.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations/
│ └── tests.py
├── common/ # Shared utilities & middleware
│ ├── middleware.py # StandardResponseMiddleware (standardizes all responses)
│ ├── pagination.py # CustomPageNumberPagination (DRF pagination)
│ ├── responses.py # Helper functions: success_response(), error_response()
│ ├── swagger_utils.py # Swagger/OpenAPI utilities
│ ├── aliyun_logging.py # Aliyun log service setup
│ ├── VolcEngineAccessToken.py # Volcengine RTC token generation
│ ├── admin.py
│ ├── apps.py
│ └── [other shared modules]
├── templates/ # HTML templates for Django admin & error pages
│ └── [admin templates, error pages]
├── locale/ # i18n translation catalogs
│ ├── en/ # English translations
│ │ └── LC_MESSAGES/
│ │ ├── django.po # Compiled message file
│ │ └── django.mo # Binary compiled messages
│ └── zh_HAns/ # Simplified Chinese translations
│ └── LC_MESSAGES/
├── static/ # Static files (JS, CSS, images) — dev only
│ └── [served by STATIC_URL]
├── media/ # User-uploaded files — dev only
│ └── [served by MEDIA_URL]
├── docs/ # Documentation
│ ├── README.md # Project overview
│ ├── 修改记录.md # Change log (Chinese, auto-maintained)
│ ├── 设备动态绑定方案.md # Device dynamic binding design
│ ├── 设备聊天记录_字幕落库方案.md # Chat transcript & subtitle storage design
│ ├── device_api.md # WebSocket device API reference
│ ├── device_commands.md # Device command reference
│ ├── device_errors.md # Device error codes
│ ├── device_module.md # Device module overview
│ ├── user_authentication_api.md # User auth API reference
│ ├── api/ # API endpoint documentation
│ ├── development/ # Development guides
│ ├── features/ # Feature documentation
│ └── integrations/ # Integration guides
└── .planning/ # GSD codebase mapping (auto-generated)
└── codebase/
├── ARCHITECTURE.md # Architecture patterns & data flows
└── STRUCTURE.md # This file
```
## Directory Purposes
**qy_lty/ (Project Config):**
- Purpose: Django project configuration and root URL routing
- Contains: Settings, ASGI/WSGI entry points, URL dispatcher
- Key files:
- `settings.py` (line 1-400+): All Django configuration, app list, middleware, DB, cache, auth
- `urls.py` (line 49-60): Centralizes all API routes via `include()`
- `asgi.py` (line 19-26): ProtocolTypeRouter for HTTP + WebSocket
**userapp/:**
- Purpose: User authentication, profile management, affinity system
- Contains: ParadiseUser model, MAC/phone login, token generation, user info endpoints
- Key files:
- `models.py` (line 8-145): ParadiseUser, AffinityRule, AffinityLevel (favorability system)
- `authentication.py` (line 10-34): RedisTokenAuthentication class
- `utils.py` (line 33-37): `generate_token()` stores in Redis with 30-day TTL
**device_interaction/:**
- Purpose: Real-time device communication via WebSocket, device management
- Contains: WebSocket consumer, device CRUD, user-device binding, RTC token endpoints
- Key files:
- `consumers.py` (line 10-320+): DeviceConsumer handles connect/receive/disconnect, group messaging
- `routing.py` (line 4-7): WebSocket URL patterns
- `auth.py` (line 4-50): TokenAuthMiddleware authenticates WebSocket connections
- `models.py` (line 43-145): Device, UserDevice with "last-bind-wins" ordering
**aiapp/:**
- Purpose: AI chat (Kimi), multi-provider audio services
- Contains: Chat message models, Kimi API client, audio synthesis/recognition
- Key files:
- `views.py` (line 1-60+): BotViewSet, ChatView for multi-turn conversations
- `audio/` (submodule): AudioService factory + provider implementations
**card/:**
- Purpose: Card management (categories, batches, QR codes, usage tracking)
- Contains: Card CRUD, batch generation, QR code generation/scanning
- Key files: models.py, views.py (batch endpoints), serializers.py
**achievement_app/:**
- Purpose: User achievements, progress tracking, badges
- Contains: Achievement definitions, user progress models
- Key files: models.py, views.py (progress endpoints)
**subscription_app/:**
- Purpose: Billing and subscription management
- Contains: Subscription plans, payment records
- Key files: models.py
**ali_vi_app/, food_app/, workflow_app/:**
- Purpose: Specialized modules (Aliyun Visual Intelligence, Food management, Workflow automation)
- Contains: Domain-specific models, views, serializers
- Note: ali_vi_app routes disabled in qy_lty/urls.py line 52
**common/:**
- Purpose: Shared utilities across all apps
- Contains: Middleware, pagination, response helpers, external API clients
- Key files:
- `middleware.py` (line 6-145): StandardResponseMiddleware wraps all responses
- `pagination.py` (line 1-9): CustomPageNumberPagination for DRF list views
- `responses.py`: Helper functions for standardized responses
**locale/:**
- Purpose: i18n translation catalogs (Chinese/English)
- Contains: Django message files (.po/.mo)
- Generated by: `django-admin makemessages -l en`, `compilemessages`
**docs/:**
- Purpose: Human-readable documentation
- Contains: Design docs, API references, development guides
- Key files:
- `修改记录.md`: Auto-maintained change log (new entries at top)
- `设备动态绑定方案.md`: Device binding strategy & semantics
- `device_api.md`: WebSocket message types & format
## Key File Locations
**Entry Points:**
- `qy_lty/urls.py`: HTTP root router (line 63-73)
- `qy_lty/asgi.py`: ASGI app with Channels (line 19-26)
- `device_interaction/routing.py`: WebSocket URL patterns (line 4-7)
- `device_interaction/consumers.py`: WebSocket message handler (line 10+)
**Configuration:**
- `qy_lty/settings.py`: All Django configuration (INSTALLED_APPS, middleware, auth, databases)
- `.env`: Environment variables (NEVER committed; copy from `.env.bak` if it exists)
- `requirements.txt`: Python package dependencies
**Authentication & Authorization:**
- `userapp/authentication.py`: RedisTokenAuthentication (HTTP)
- `device_interaction/auth.py`: TokenAuthMiddleware (WebSocket)
- `userapp/utils.py`: Token generation/lookup with Redis
- `userapp/models.py`: ParadiseUser (custom user model)
**Core Models:**
- `userapp/models.py`: ParadiseUser, AffinityRule, AffinityLevel
- `device_interaction/models.py`: Device, DeviceType, DeviceBatch, UserDevice
- `aiapp/models.py`: ChatMessage, Bot, ConversationSubtitle
- `card/models.py`: CardCategory, CardBatch, Card, CardUsageLog
- `achievement_app/models.py`: Achievement, AchievementProgress
- `subscription_app/models.py`: Subscription, SubscriptionPlan
**Middleware & Cross-Cutting:**
- `common/middleware.py`: StandardResponseMiddleware (response wrapper)
- `common/pagination.py`: CustomPageNumberPagination (DRF pagination)
- `common/responses.py`: `success_response()`, `error_response()` helpers
- `qy_lty/settings.py` line 82-95: All middleware configured
**API Documentation:**
- Swagger UI: Generated from code, served at `/swagger/`
- ReDoc: Alternative documentation UI at `/redoc/`
- drf-yasg integration: Configured in qy_lty/urls.py line 22-46
**Logging:**
- Configuration: `qy_lty/settings.py` (logging config, or via `common/aliyun_logging.py`)
- Entry point: `setup_logging()` in common/aliyun_logging.py (called from settings.py line 16)
**i18n Catalogs:**
- English: `locale/en/LC_MESSAGES/django.{po,mo}`
- Chinese: `locale/zh_HAns/LC_MESSAGES/django.{po,mo}`
- Generation: `django-admin makemessages -l {lang}`
- Compilation: `django-admin compilemessages`
## Naming Conventions
**Files:**
- Django app folders: lowercase_underscore (e.g., `device_interaction`, `user_app`)
- Python modules: lowercase_underscore (e.g., `models.py`, `serializers.py`, `auth.py`)
- Migrations: auto-generated format `000X_auto_YYYYMMDD_HHMM.py`
- Documentation: Chinese filename or English snake_case
**Directories:**
- Apps: snake_case (userapp, device_interaction, achievement_app)
- Packages: lowercase without underscores (audio/, management/, migrations/)
**URL Prefixes:**
- `/api/user/` → userapp endpoints
- `/api/device/` → device_interaction endpoints
- `/api/ai/` → aiapp endpoints
- `/api/card/` → card endpoints
- `/api/achievement/` → achievement_app endpoints
- `/api/food/` → food_app endpoints
- `/api/v1/admin/` → admin endpoints (via userapp.admin_urls)
- `/ws/device/` → WebSocket endpoint
**Database Models:**
- User model: `ParadiseUser` (extends AbstractUser)
- Device-related: `Device`, `DeviceBatch`, `DeviceType`, `UserDevice`
- Message models: `ChatMessage`, `ConversationSubtitle`
- Achievement models: `Achievement`, `AchievementProgress`, `AchievementBadge`
## Where to Add New Code
**New Feature (e.g., "Affinity Decay Task"):**
- Models: `device_interaction/models.py` (if device-related) or `userapp/models.py` (if user-related)
- Views/API: `device_interaction/views.py` or `userapp/views.py` → new ViewSet or APIView
- Serializers: `device_interaction/serializers.py` or `userapp/serializers.py` → new ModelSerializer
- URLs: Edit `device_interaction/urls.py` or `userapp/urls.py` → add path/route
- Async tasks: `device_interaction/scheduler.py` or new `tasks.py` file
- Tests: `device_interaction/tests.py` or `userapp/tests.py`
**New Component/Module (e.g., "Chat Analytics"):**
- Create new Django app: `python manage.py startapp analytics`
- Standard files: `models.py`, `views.py`, `serializers.py`, `urls.py`, `admin.py`, `apps.py`, `tests.py`
- Register in `qy_lty/settings.py` INSTALLED_APPS (line 43+)
- Include URLs in `qy_lty/urls.py` (line 49-60)
**Shared Utilities:**
- Common middleware/helpers: Add to `common/` directory
- Examples: `common/responses.py`, `common/swagger_utils.py`
- New integration (e.g., API client): Create in `common/` or within relevant app (e.g., `device_interaction/amap_api.py`)
**WebSocket Message Handler:**
- Edit `device_interaction/consumers.py:DeviceConsumer.receive()` (line 171+)
- Add new `elif message_type == 'new_type':` branch
- Call appropriate handler or `group_send()` to broadcast
**Serializer Customization:**
- Override `to_representation()` for custom output
- Override `to_internal_value()` for custom input processing
- Add `validate_<field>()` for field-specific validation
- Add `validate()` for cross-field validation
## Special Directories
**migrations/:**
- Purpose: Track database schema changes
- Generated: `python manage.py makemigrations`
- Applied: `python manage.py migrate`
- Committed: Yes (part of version control)
**.planning/codebase/:**
- Purpose: GSD (Generate Specification Documents) auto-generated codebase maps
- Generated: By `/gsd-map-codebase` orchestrator
- Committed: Yes (reference docs for other GSD commands)
- Contents: ARCHITECTURE.md, STRUCTURE.md, CONVENTIONS.md, TESTING.md, CONCERNS.md, INTEGRATIONS.md, STACK.md
**docs/:**
- Purpose: Human documentation
- Committed: Yes
- Auto-maintained: `修改记录.md` (change log, appended by developers after each commit)
**locale/:**
- Purpose: i18n translation catalogs
- Generated: `django-admin makemessages` (creates .po files)
- Compiled: `django-admin compilemessages` (creates .mo files)
- Committed: Yes (both .po and .mo)
**static/ & media/:**
- Purpose: Served files (CSS, JS, images; user uploads)
- Generated: No (user-managed)
- Committed: No (in .gitignore)
- Dev only: Yes (production uses CloudStorage/OSS)
## Management Commands
**Standard Django:**
```bash
python manage.py migrate # Apply database migrations
python manage.py makemigrations # Create new migration files
python manage.py createsuperuser # Create admin user
python manage.py shell # Interactive Python shell
python manage.py dbshell # Interactive database shell
```
**i18n:**
```bash
django-admin makemessages -l en # Extract translatable strings (English)
django-admin makemessages -l zh_HAns # Extract translatable strings (Simplified Chinese)
django-admin compilemessages # Compile .po to .mo files
```
**Custom Commands (if any):**
- Location: `<app>/management/commands/<command_name>.py`
- Run: `python manage.py <command_name>`
---
*Structure analysis: 2026-05-07*

View File

@ -0,0 +1,292 @@
# Testing Patterns
**Analysis Date:** 2026-05-07
## Test Framework
**Runner:**
- Django's built-in `TestCase` from `django.test`
- NO pytest or pytest-django detected in `requirements.txt`
- Tests run via: `python manage.py test`
**Assertion Library:**
- Django's `TestCase` built-in assertions (inherited from `unittest.TestCase`)
- `self.assertEqual()`, `self.assertTrue()`, `self.assertRaises()`, etc.
**Run Commands:**
```bash
python manage.py test # Run all tests
python manage.py test app_name # Run tests for specific app
python manage.py test app_name.tests.TestClassName # Run specific test class
```
**Coverage:**
- No `.coveragerc` or coverage configuration found
- No coverage requirement enforced
## Test File Organization
**Location:**
- Each Django app has a `tests.py` file in app root: `{app_name}/tests.py`
- Pattern: co-located with models, serializers, views (one file per app)
**Naming:**
- Single file: `tests.py`
- Test classes inherit from `django.test.TestCase`
- Test methods prefixed with `test_`: `test_audio()`, `test_model_creation()`, `test_view_response()`
**File Structure:**
```
app_name/
├── models.py
├── serializers.py
├── views.py
├── urls.py
└── tests.py # All tests for this app
```
## Current Test Coverage Status
**Honest Assessment: MINIMAL**
Test files examined show very sparse coverage:
- `userapp/tests.py`: 4 lines (empty TestCase class)
- `aiapp/tests.py`: 24 lines (only 1 actual test: `test_audio()`)
- `card/tests.py`: 3 lines (empty)
- `device_interaction/tests.py`: 3 lines (empty)
- `achievement_app/tests.py`: 3 lines (empty)
**Total active test methods: 1** (audio synthesis in `aiapp/tests.py`)
This represents **critical test coverage gap** — almost all API endpoints, models, and serializers are untested.
## Test Structure
**Suite Organization from `aiapp/tests.py`:**
```python
from django.test import TestCase
from django.urls import reverse
from .audio.AudioService import get_audio_service
class YourModelTest(TestCase):
def test_audio(self):
# Setup (minimal)
audio_ser = get_audio_service()
audio_ser.synthesize_speech('你好,你是谁啊')
# Commented-out template tests below (never implemented)
# def test_model_creation(self):
# def test_view_response(self):
```
**Patterns:**
- `setUp()` method not used; tests don't create fixtures
- No tearDown() cleanup observed
- No assertion calls in active test (test_audio just calls synthesize_speech without checking result)
## Mocking
**Framework:** No mocking library detected (no `unittest.mock`, `responses`, `pytest-mock` in requirements.txt)
**Current Practice:**
- Tests that DO exist (test_audio) call real external services (AudioService.synthesize_speech)
- No mocking observed for:
- Aliyun API calls (SMS, OSS, NLS)
- Volcengine RTC token generation
- Tencent audio service
- Kimi AI API
- Redis cache operations
- Database operations (use real test DB)
**Risk:** Tests that call production services (Aliyun, Kimi, Volcengine) require:
- Valid credentials in .env
- Network access to external services
- Potential cost (SMS sends, API calls)
## Database Testing
**Test Database:**
- Django uses a separate test database (by default: test_qy_lty or suffixed with `test_`)
- No custom settings for test DB observed
- No fixtures or data factories defined
**Patterns:**
- Models have no test data factories (no factory_boy or similar)
- No `setUp()` creating test objects
- Manual creation would be required: `Model.objects.create(...)`
## Fixtures and Test Data
**Test Data:**
- NO fixture files found (.json, .yaml, .fixture)
- NO factory definitions (factory_boy not in requirements)
- NO model-level test data builders
**How to Add:**
If implementing tests, would need:
```python
def setUp(self):
self.user = ParadiseUser.objects.create(
username='testuser',
email='test@example.com'
)
self.device = Device.objects.create(
device_type=...,
batch=...,
mac_address='AA:BB:CC:DD:EE:FF'
)
```
## WebSocket Consumer Testing
**Current State:** NO WebSocket consumer tests
**Channels provides `channels.testing` package** for async testing, but:
- No `conftest.py` or pytest configuration
- No async test runner configured
- Would require adding pytest + pytest-asyncio + pytest-django
**If implementing WebSocket tests, would require:**
```python
from channels.testing import WebsocketCommunicator
from device_interaction.consumers import DeviceConsumer
async def test_device_consumer_connect():
communicator = WebsocketCommunicator(DeviceConsumer.as_asgi(), '/ws/device/')
connected, subprotocol = await communicator.connect()
assert connected
```
## External Service Testing
**Services integrated WITHOUT mocking in current codebase:**
- `aiapp.audio.AudioService` — calls real Aliyun/Tencent/Volcengine
- Aliyun SMS (send_sms in userapp/utils.py)
- Aliyun OSS (file uploads)
- Kimi API (AI chat)
- Volcengine RTC token generation
- WeChat OAuth (django-allauth)
**No test doubles, stubs, or mocks exist** — tests would hit production services.
## API Endpoint Testing
**Current State:** NO API tests
**Endpoints that should be tested but are NOT:**
- User authentication: `/api/user/register/`, `/api/user/mac-login/`, `/api/user/phone-login/`
- AI chat: `/api/ai/chat/{bot_id}/`, `/api/ai/multi-chat/`
- Device binding: `/api/device/bind/`, `/api/device/bind-status/`
- Card operations: `/api/card/scan/`, `/api/card/use/`, `/api/card/batches/generate/`
- Achievement tracking: `/api/achievement/user-achievements/`
- WebSocket: `/ws/device/` connection and message handling
**If implementing, would use DRF test client:**
```python
from rest_framework.test import APIClient
class UserAuthTest(TestCase):
def setUp(self):
self.client = APIClient()
def test_mac_login(self):
response = self.client.post('/api/user/mac-login/', {
'mac_address': 'AA:BB:CC:DD:EE:FF'
})
self.assertEqual(response.status_code, 200)
self.assertIn('token', response.json()['data'])
```
## Test Coverage Gaps
**CRITICAL UNTESTED AREAS:**
1. **Authentication layer** — RedisTokenAuthentication never tested
- Token generation in userapp/utils.py:generate_token()
- Token validation in userapp/authentication.py
- Device MAC login flow
- Phone verification login
2. **Device control semantics** — "last-bind-wins" ordering never verified
- UserDevice.Meta.ordering = ['-bound_at'] not tested
- Multiple users binding same device — control resolution untested
- Hardcoded test MAC (AA:BB:CC:DD:EE:FF) skip logic untested
3. **WebSocket messaging** — All consumer methods untested
- DeviceConsumer.connect() authentication
- Message routing in receive()
- Group messaging (device_{user_id})
- Disconnect and heartbeat (device:last_seen:{mac})
4. **Serializer validation** — No validation tests
- All custom validators in serializers.py files untested
- Foreign key constraints untested
- Unique field constraints untested
5. **External service integration** — No mocking
- Aliyun SMS send (send_sms)
- Aliyun OSS upload
- Kimi AI API calls
- Volcengine RTC token generation
- Tencent audio service
6. **Async task processing** — No tests
- Affinity rule evaluation (coming in P2)
- Device heartbeat refresh (5-min TTL)
- Daily affinity counter aggregation
7. **Model business logic** — No tests
- AffinityRule.get_solo() singleton pattern
- Device code generation (generate_device_code)
- User achievement unlock logic
- Card batch generation logic
## Recommendations for Test Implementation
**Phase 1 - Setup (High Priority):**
- Add pytest + pytest-django + pytest-asyncio to requirements.txt
- Create conftest.py for shared fixtures
- Create factory definitions (factory_boy) for common models
- Mock external services (responses, unittest.mock)
**Phase 2 - Unit Tests (High Priority):**
- Test authentication (token generation, validation, expiry)
- Test device binding semantics (last-bind-wins)
- Test serializer validation
- Test model methods (AffinitySetting.get_solo, generate_device_code)
**Phase 3 - Integration Tests (Medium Priority):**
- Test full auth flows (MAC login, phone login, registration)
- Test API endpoints with mocked external services
- Test WebSocket consumer connection and messaging
- Test card batch generation workflow
**Phase 4 - E2E Tests (Lower Priority):**
- Test device binding → WebSocket connection → message exchange flow
- Test achievement unlock on user action flow
- Test affinity rule trigger and log creation
**Minimum Coverage Target for Production:**
- Authentication/authorization: 80%+
- Device control logic: 100% (critical for device binding semantics)
- Serializer validation: 70%+
- API endpoints: 60%+ (focus on public-facing, critical flows)
---
*Testing analysis: 2026-05-07*
## Test Execution Example
Current working test (from `aiapp/tests.py`):
```bash
$ python manage.py test aiapp.tests.YourModelTest.test_audio
```
This test:
1. Gets AudioService instance
2. Calls synthesize_speech with Chinese text
3. Returns (no assertions — test just checks for uncaught exceptions)
This represents the ONLY active test in the codebase and does not verify behavior, only that the call completes.

View File

@ -0,0 +1,49 @@
{
"model_profile": "balanced",
"commit_docs": true,
"parallelization": true,
"search_gitignored": false,
"brave_search": false,
"firecrawl": false,
"exa_search": false,
"git": {
"branching_strategy": "none",
"phase_branch_template": "gsd/phase-{phase}-{slug}",
"milestone_branch_template": "gsd/{milestone}-{slug}",
"quick_branch_template": null
},
"workflow": {
"research": true,
"plan_check": true,
"verifier": true,
"nyquist_validation": false,
"auto_advance": true,
"node_repair": true,
"node_repair_budget": 2,
"ui_phase": true,
"ui_safety_gate": true,
"ai_integration_phase": true,
"tdd_mode": false,
"text_mode": false,
"research_before_questions": false,
"discuss_mode": "discuss",
"skip_discuss": false,
"code_review": true,
"code_review_depth": "standard",
"code_review_command": null,
"pattern_mapper": true,
"plan_bounce": false,
"plan_bounce_script": null,
"plan_bounce_passes": 2,
"auto_prune_state": false
},
"hooks": {
"context_warnings": true
},
"project_code": null,
"phase_naming": "sequential",
"agent_skills": {},
"claude_md_path": "./CLAUDE.md",
"mode": "yolo",
"granularity": "coarse"
}

View File

@ -23,6 +23,34 @@
<!-- 新的修改记录添加在此处下方,最新的在最前面 -->
### [2026-05-07] 引入 GSD 工作流并完成 brownfield 文档化初始化
- **文件路径**:
- `.planning/config.json`(新增)
- `.planning/PROJECT.md`(新增)
- `.planning/REQUIREMENTS.md`(新增)
- `.planning/STATE.md`(新增)
- `.planning/codebase/STACK.md` / `INTEGRATIONS.md` / `ARCHITECTURE.md` / `STRUCTURE.md` / `CONVENTIONS.md` / `TESTING.md` / `CONCERNS.md`(前序 commit `64a8cb8` 已建)
- **修改类型**: 新增
- **修改内容**: 在 `qy_lty/` 下引入 [GSDGet Shit Done](https://github.com/anthropics/get-shit-done) 工作流目录 `.planning/`,包含:
1. `.planning/codebase/` — 7 份 codebase 反向工程文档(栈 / 集成 / 架构 / 目录 / 规约 / 测试 / 隐患)
2. `.planning/PROJECT.md` — 项目愿景 + Core Value + 已交付能力Validated+ 关键决策记录
3. `.planning/REQUIREMENTS.md` — 把已上线能力拆为带 REQ-ID 的清单AUTH/AI/DEV/CARD/ACH/SUB/AFF/VI/INF/ADM/DEPActive 段留空待 `/gsd-new-milestone` 启动
4. `.planning/STATE.md` — 工作流状态机入口
5. `.planning/config.json` — 工作流偏好YOLO / Coarse / Parallel / 三类辅助 agent 全开 / Balanced 模型档)
- **修改原因**:
- 后续新功能 / 重构通过 GSD 走「discuss → plan → execute → verify」标准流程避免无规划的散弹式提交
- 反向梳理一遍现状形成文档基线,方便新成员(含 AI agent秒级进入上下文
- `.planning/` 锚定在 `qy_lty\` 而非父级 `Lila-Server\`,遵循 CLAUDE.md「`qy_lty``qy-lty-admin` 是独立项目」原则;通过预创建空 `.planning/` 目录强制锚定生效
- **后续动作**: 新功能开发使用 `/gsd-new-milestone` 启动候选优先级HIGH 项含成就条件校验缺失、SMS 限流、DEBUG/CORS 收紧、测试 MAC 后门移除、测试基础设施搭建)见 `.planning/REQUIREMENTS.md`
### [2026-05-07] CLAUDE.md 新增「沟通语言」规则 — 强制中文回复
- **文件路径**: `CLAUDE.md`
- **修改类型**: 新增
- **修改内容**: 在文件顶部(项目概述之前)新增 `## 沟通语言(重要 — 始终生效)` 章节明确所有面向用户的回复统一使用中文内部思考可用任意语言工具调用参数、commit message、代码注释保持项目原有约定此规则覆盖默认英文输出倾向仅在用户显式要求时切换。
- **修改原因**: 用户要求把"思考后的回答用中文显示"沉淀为本仓库长期生效的工作规则,避免每次会话重复声明,并让后续任何 Claude/Copilot 会话进入仓库即自动遵循。
### [2026-04-24] 好感度系统 P1 阶段 — 数据模型扩展 + 迁移 + seed 命令
配套设计文档:[docs/好感度系统功能与规则设计.md](好感度系统功能与规则设计.md)