# 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 天 TTL，key `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** 单轮 / 多轮文本对话（接 Kimi，OpenAI 兼容协议）— 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_interaction，2026-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 / en）via 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 集群** — 当前是单 Redis，CONCERNS.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-admin（Web 管理后台） | `../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 已 EOL（2024-10），需要规划升级

## Constraints

- **Tech stack**: Django 4.2.13 + DRF + Channels + Daphne — 已锁定，迁移成本极高
- **Tech stack**: Python 3.8（EOL）— 当前限制，但近期需升级
- **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 TTL（Redis 后端） | 设备 + 手机端长期在线，短 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)*
