Files
dreamweaver/.claude/specs/code-architecture/REFACTORING-PRD.md
torin b8d3cb4644
Some checks are pending
Build and Push Docker Images / changes (push) Waiting to run
Build and Push Docker Images / build-backend (push) Blocked by required conditions
Build and Push Docker Images / build-frontend (push) Blocked by required conditions
Build and Push Docker Images / build-admin-frontend (push) Blocked by required conditions
wip: snapshot full local workspace state
2026-04-17 18:58:11 +08:00

417 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 代码架构重构 PRD
> 版本: 1.0
> 日期: 2025-01-21
> 状态: 待实施
---
## 1. 背景与目标
### 1.1 现状问题
经过代码审计,发现以下架构债务:
| 问题 | 位置 | 影响 |
|------|------|------|
| 缺少 Schemas 层 | Backend API | 类型不安全,文档生成差 |
| Fat Controller | `stories.py` 562行 | 难测试,职责混乱 |
| 重复加载逻辑 | Frontend 5+ 组件 | 代码冗余,维护困难 |
| 无 Repository 层 | Backend | DB 查询散落,难复用 |
### 1.2 重构目标
- **后端**:引入 Pydantic Schemas 层,分离请求/响应模型
- **前端**:抽取 Vue Composables消除重复逻辑
- **原则**:渐进式重构,不破坏现有功能
---
## 2. 后端重构Schemas 层
### 2.1 目标结构
```
backend/app/
├── api/ # 路由层(瘦身)
│ ├── stories.py
│ └── ...
├── schemas/ # 新增Pydantic 模型
│ ├── __init__.py
│ ├── auth.py
│ ├── story.py
│ ├── profile.py
│ ├── universe.py
│ ├── memory.py
│ ├── push_config.py
│ └── common.py # 分页、错误响应等通用结构
├── models/ # SQLAlchemy ORM原 db/
└── services/ # 业务逻辑
```
### 2.2 Schema 设计规范
#### 命名约定
| 类型 | 命名模式 | 示例 |
|------|----------|------|
| 创建请求 | `{Entity}Create` | `StoryCreate` |
| 更新请求 | `{Entity}Update` | `ProfileUpdate` |
| 响应模型 | `{Entity}Response` | `StoryResponse` |
| 列表响应 | `{Entity}ListResponse` | `StoryListResponse` |
#### 示例Story Schema
```python
# schemas/story.py
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, Field
class StoryCreate(BaseModel):
"""创建故事请求"""
type: Literal["keywords", "enhance", "storybook"]
data: str = Field(..., min_length=1, max_length=2000)
education_theme: str | None = None
child_profile_id: str | None = None
universe_id: str | None = None
class StoryResponse(BaseModel):
"""故事响应"""
id: int
title: str
story_text: str | None
pages: list[dict] | None
image_url: str | None
mode: str
created_at: datetime
class Config:
from_attributes = True # 支持 ORM 模型转换
class StoryListResponse(BaseModel):
"""故事列表响应"""
stories: list[StoryResponse]
total: int
page: int
page_size: int
```
### 2.3 迁移步骤
| 阶段 | 任务 | 文件 |
|------|------|------|
| P1 | 创建 `schemas/common.py` | 分页、错误响应 |
| P2 | 创建 `schemas/story.py` | 故事相关模型 |
| P3 | 创建 `schemas/profile.py` | 档案相关模型 |
| P4 | 创建 `schemas/universe.py` | 宇宙相关模型 |
| P5 | 创建 `schemas/auth.py` | 认证相关模型 |
| P6 | 创建 `schemas/memory.py` | 记忆相关模型 |
| P7 | 创建 `schemas/push_config.py` | 推送配置模型 |
| P8 | 重构 `api/stories.py` | 使用新 schemas |
| P9 | 重构其他 API 文件 | 逐个迁移 |
### 2.4 验收标准
- [ ] 所有 API endpoint 使用 `response_model` 参数
- [ ] 请求体使用 Pydantic 模型而非 `Body(...)`
- [ ] OpenAPI 文档自动生成完整类型
- [ ] 现有测试全部通过
- [ ] `ruff check` 无错误
---
## 3. 前端重构Composables
### 3.1 目标结构
```
frontend/src/
├── composables/ # 新增:可复用逻辑
│ ├── index.ts
│ ├── useAsyncData.ts # 异步数据加载
│ ├── useFormValidation.ts # 表单验证
│ └── useDateFormat.ts # 日期格式化
├── components/
├── stores/
└── views/
```
### 3.2 Composable 设计
#### 3.2.1 useAsyncData
**用途**:统一处理 API 数据加载的 loading/error 状态
```typescript
// composables/useAsyncData.ts
import { ref, type Ref } from 'vue'
interface AsyncDataOptions<T> {
immediate?: boolean // 立即执行,默认 true
initialData?: T // 初始数据
onError?: (e: Error) => void
}
interface AsyncDataReturn<T> {
data: Ref<T | null>
loading: Ref<boolean>
error: Ref<string>
execute: () => Promise<void>
reset: () => void
}
export function useAsyncData<T>(
fetcher: () => Promise<T>,
options: AsyncDataOptions<T> = {}
): AsyncDataReturn<T> {
const { immediate = true, initialData = null, onError } = options
const data = ref<T | null>(initialData) as Ref<T | null>
const loading = ref(false)
const error = ref('')
async function execute() {
loading.value = true
error.value = ''
try {
data.value = await fetcher()
} catch (e) {
const message = e instanceof Error ? e.message : '加载失败'
error.value = message
onError?.(e instanceof Error ? e : new Error(message))
} finally {
loading.value = false
}
}
function reset() {
data.value = initialData
loading.value = false
error.value = ''
}
if (immediate) {
execute()
}
return { data, loading, error, execute, reset }
}
```
**使用示例**
```typescript
// views/MyStories.vue重构前 40 行 → 重构后 10 行)
import { useAsyncData } from '@/composables'
import { api } from '@/api/client'
const { data: response, loading, error } = useAsyncData(
() => api.get<StoryListResponse>('/api/stories')
)
const stories = computed(() => response.value?.stories ?? [])
```
#### 3.2.2 useFormValidation
**用途**:统一表单验证逻辑
```typescript
// composables/useFormValidation.ts
import { ref, reactive } from 'vue'
type ValidationRule = (value: unknown) => string | true
interface FieldRules {
[field: string]: ValidationRule[]
}
export function useFormValidation<T extends Record<string, unknown>>(
initialData: T,
rules: FieldRules
) {
const form = reactive({ ...initialData })
const errors = reactive<Record<string, string>>({})
function validate(): boolean {
let valid = true
for (const [field, fieldRules] of Object.entries(rules)) {
const value = form[field]
for (const rule of fieldRules) {
const result = rule(value)
if (result !== true) {
errors[field] = result
valid = false
break
} else {
errors[field] = ''
}
}
}
return valid
}
function reset() {
Object.assign(form, initialData)
Object.keys(errors).forEach(k => errors[k] = '')
}
return { form, errors, validate, reset }
}
// 常用验证规则
export const required = (msg = '此字段必填') =>
(v: unknown) => (v !== null && v !== undefined && v !== '') || msg
export const minLength = (min: number, msg?: string) =>
(v: unknown) => (typeof v === 'string' && v.length >= min) || msg || `至少 ${min} 个字符`
export const maxLength = (max: number, msg?: string) =>
(v: unknown) => (typeof v === 'string' && v.length <= max) || msg || `最多 ${max} 个字符`
```
#### 3.2.3 useDateFormat
**用途**:统一日期格式化
```typescript
// composables/useDateFormat.ts
export function useDateFormat() {
function formatDate(dateStr: string | Date, format: 'date' | 'datetime' | 'relative' = 'date'): string {
const date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr
if (format === 'relative') {
return formatRelative(date)
}
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
if (format === 'date') {
return `${year}-${month}-${day}`
}
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}`
}
function formatRelative(date: Date): string {
const now = new Date()
const diffMs = now.getTime() - date.getTime()
const diffMins = Math.floor(diffMs / 60000)
const diffHours = Math.floor(diffMs / 3600000)
const diffDays = Math.floor(diffMs / 86400000)
if (diffMins < 1) return '刚刚'
if (diffMins < 60) return `${diffMins} 分钟前`
if (diffHours < 24) return `${diffHours} 小时前`
if (diffDays < 7) return `${diffDays} 天前`
return formatDate(date, 'date')
}
return { formatDate, formatRelative }
}
```
### 3.3 迁移步骤
| 阶段 | 任务 | 影响文件 |
|------|------|----------|
| P1 | 创建 `composables/useAsyncData.ts` | 新文件 |
| P2 | 创建 `composables/useDateFormat.ts` | 新文件 |
| P3 | 创建 `composables/useFormValidation.ts` | 新文件 |
| P4 | 创建 `composables/index.ts` | 统一导出 |
| P5 | 重构 `views/MyStories.vue` | 使用 useAsyncData |
| P6 | 重构 `views/ChildProfiles.vue` | 使用 useAsyncData |
| P7 | 重构 `views/Universes.vue` | 使用 useAsyncData |
| P8 | 重构 `components/CreateStoryModal.vue` | 使用 useFormValidation |
| P9 | 同步重构 `admin-frontend/` | 复制 composables |
### 3.4 验收标准
- [ ] `composables/` 目录包含 3 个核心 composable
- [ ] 至少 3 个 view 组件使用 `useAsyncData`
- [ ] `CreateStoryModal` 使用 `useFormValidation`
- [ ] `npm run build` 无类型错误
- [ ] 现有功能正常运行
---
## 4. 风险与缓解
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| 重构引入 bug | 高 | 每个阶段运行测试 |
| 类型不兼容 | 中 | 使用 `from_attributes = True` |
| 前端构建失败 | 中 | 逐文件迁移,及时 commit |
---
## 5. 时间估算
| 模块 | 工作量 | 预计时间 |
|------|--------|----------|
| Backend Schemas | 10 个新文件 + 9 个 API 重构 | 3-4 小时 |
| Frontend Composables | 4 个新文件 + 6 个组件重构 | 2-3 小时 |
| 测试验证 | 全量回归 | 1 小时 |
| **总计** | | **6-8 小时** |
---
## 6. 后续迭代
本次重构完成后,可继续:
1. **Repository 层**:抽象 DB 查询逻辑
2. **Service 层瘦身**:拆分 `provider_router.py` (433行)
3. **前端共享包**`frontend``admin-frontend` 共用 UI 组件
4. **API 版本化**:引入 `/api/v1/` 前缀
---
## 附录:文件清单
### 新增文件
```
backend/app/schemas/
├── __init__.py
├── common.py
├── auth.py
├── story.py
├── profile.py
├── universe.py
├── memory.py
└── push_config.py
frontend/src/composables/
├── index.ts
├── useAsyncData.ts
├── useFormValidation.ts
└── useDateFormat.ts
```
### 修改文件
```
backend/app/api/
├── stories.py
├── profiles.py
├── universes.py
├── memories.py
├── push_configs.py
├── reading_events.py
└── auth.py
frontend/src/views/
├── MyStories.vue
├── ChildProfiles.vue
├── Universes.vue
└── ...
frontend/src/components/
└── CreateStoryModal.vue
```