937 lines
25 KiB
Markdown
937 lines
25 KiB
Markdown
# 技术债务分析报告
|
||
|
||
> 本文档总结了 TravelContentCreator 项目中的主要技术债务,为后续重构提供参考。
|
||
|
||
---
|
||
|
||
## 1. 数据库双端访问问题
|
||
|
||
### 现状
|
||
|
||
Python 端和 Java 端**同时直接访问同一个数据库**,各自维护独立的连接池和查询逻辑。
|
||
|
||
```
|
||
┌─────────────────┐ ┌─────────────────┐
|
||
│ Java 后端 │ │ Python AIGC │
|
||
│ (zwy_picture) │ │ (TravelContent) │
|
||
└────────┬────────┘ └────────┬────────┘
|
||
│ │
|
||
│ 独立连接池 │ 独立连接池
|
||
│ 独立 ORM │ 独立 SQL
|
||
▼ ▼
|
||
┌─────────────────────────────────┐
|
||
│ MySQL (travel_content) │
|
||
└─────────────────────────────────┘
|
||
```
|
||
|
||
### 问题
|
||
|
||
| 问题 | 影响 |
|
||
|-----|------|
|
||
| **数据一致性风险** | 两端可能读到不同状态的数据 |
|
||
| **连接池资源浪费** | 两套连接池,占用更多连接数 |
|
||
| **Schema 同步困难** | 表结构变更需要同时修改两端代码 |
|
||
| **事务边界模糊** | 跨服务操作无法保证事务一致性 |
|
||
| **代码重复** | 相同的查询逻辑在两端各写一遍 |
|
||
|
||
### 涉及文件
|
||
|
||
**Python 端:**
|
||
- `api/services/database_service.py` - 独立的数据库服务
|
||
- `api/services/prompt_service.py` - 也有自己的连接池
|
||
- `infrastructure/database/connection.py` - 新架构的连接管理
|
||
|
||
**Java 端:**
|
||
- `ProductPackageService` - 套餐查询
|
||
- `MaterialService` - 素材查询
|
||
- 各种 Mapper/Repository
|
||
|
||
### 建议方案
|
||
|
||
**方案 A: Python 作为纯算法服务**
|
||
```
|
||
Java 后端 ──HTTP──> Python AIGC
|
||
│ │
|
||
│ │ (无数据库访问)
|
||
▼ │
|
||
MySQL <──────────────┘
|
||
```
|
||
- Python 只接收 Java 传来的完整数据
|
||
- 所有数据库操作由 Java 统一管理
|
||
- Python 变成无状态的计算服务
|
||
|
||
**方案 B: 数据库访问层抽象**
|
||
- 创建统一的数据访问 API (REST/gRPC)
|
||
- 两端通过 API 访问数据,不直接连数据库
|
||
|
||
---
|
||
|
||
## 2. 图片 Base64 传输问题
|
||
|
||
### 现状
|
||
|
||
Java 端从 S3 下载图片 → 转 Base64 → HTTP 传给 Python → Python 解码 → 处理
|
||
|
||
```python
|
||
# poster.py L266
|
||
image_bytes = base64.b64decode(first_image_base64)
|
||
```
|
||
|
||
```java
|
||
// PosterGenerateServiceImpl.java L327
|
||
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
|
||
```
|
||
|
||
### 问题
|
||
|
||
| 问题 | 影响 |
|
||
|-----|------|
|
||
| **带宽浪费** | Base64 编码增加 33% 数据量 |
|
||
| **内存压力** | 大图片需要完整加载到内存 |
|
||
| **延迟增加** | 编解码耗时 + 传输耗时 |
|
||
| **HTTP 请求体过大** | 多图场景可能超过限制 |
|
||
| **重复传输** | 同一图片可能被多次传输 |
|
||
|
||
### 数据示例
|
||
|
||
```
|
||
原始图片: 2MB
|
||
Base64 后: 2.67MB (+33%)
|
||
HTTP 传输: 2.67MB
|
||
Python 解码: 2MB 内存占用
|
||
```
|
||
|
||
### 建议方案
|
||
|
||
**方案 A: URL 引用模式**
|
||
```
|
||
Java 端: 上传图片到 S3,返回 URL
|
||
Python 端: 直接从 S3/CDN 下载
|
||
```
|
||
|
||
**方案 B: 共享存储**
|
||
```
|
||
Java 端: 保存图片到共享目录 /data/images/{id}.png
|
||
Python 端: 直接读取文件路径
|
||
```
|
||
|
||
**方案 C: 图片 ID 引用**
|
||
```json
|
||
{
|
||
"image_ids": [123, 456],
|
||
"image_source": "s3"
|
||
}
|
||
```
|
||
Python 端根据 ID 自行获取图片
|
||
|
||
---
|
||
|
||
## 3. ppid / sid / pid 混乱问题
|
||
|
||
### 现状
|
||
|
||
系统中存在多种 ID 体系,命名不一致,转换逻辑分散:
|
||
|
||
| 缩写 | 全称 | 说明 |
|
||
|-----|------|------|
|
||
| `ppid` | Product Package ID | 套餐 ID (Java 端主用) |
|
||
| `pid` | Product ID | 产品 ID |
|
||
| `sid` | Scenic Spot ID | 景区 ID |
|
||
| `scenic_spot_id` | - | Python 端用的景区 ID |
|
||
| `product_id` | - | Python 端用的产品 ID |
|
||
|
||
### 问题
|
||
|
||
```python
|
||
# topic_generate.py - 需要手动解析 ppid
|
||
if ppid and (not scenic_spot_id or not product_id):
|
||
resolved = await self.db.resolve_ppid(ppid)
|
||
scenic_spot_id = resolved.get('scenic_spot_id')
|
||
product_id = resolved.get('product_id')
|
||
```
|
||
|
||
```java
|
||
// AigcCompatibilityServiceImpl.java - Java 端也要解析
|
||
OriginalIds originalIds = getOriginalIdsByPackageId(packageId);
|
||
```
|
||
|
||
| 问题 | 影响 |
|
||
|-----|------|
|
||
| **命名不一致** | `ppid` vs `packageId` vs `product_package_id` |
|
||
| **解析逻辑重复** | Java 和 Python 各自实现一遍 |
|
||
| **参数传递混乱** | 有时传 ppid,有时传 sid+pid |
|
||
| **向后兼容负担** | 需要同时支持多种参数格式 |
|
||
|
||
### 涉及文件
|
||
|
||
- `domain/aigc/engines/*.py` - 每个引擎都要处理 ppid
|
||
- `infrastructure/database/repositories/product_package_repository.py`
|
||
- `AigcCompatibilityServiceImpl.java`
|
||
|
||
### 建议方案
|
||
|
||
**统一入口点:**
|
||
```
|
||
前端/调用方 ──ppid──> Java 后端 ──解析──> sid + pid ──> Python
|
||
```
|
||
|
||
- 所有 ppid 解析在 Java 端完成
|
||
- Python 端只接收已解析的 `scenic_spot_id` 和 `product_id`
|
||
- 消除 Python 端的 ppid 解析逻辑
|
||
|
||
---
|
||
|
||
## 4. Prompt 拼接方式问题
|
||
|
||
### 现状
|
||
|
||
Prompt 构建分散在多个地方,使用字符串拼接和模板文件混合:
|
||
|
||
```python
|
||
# prompt_builder.py L84-90
|
||
user_prompt = template.build_user_prompt(
|
||
style_content=style_content,
|
||
demand_content=demand_content,
|
||
object_content=object_content,
|
||
product_content=product_content,
|
||
refer_content=refer_content
|
||
)
|
||
```
|
||
|
||
```python
|
||
# prompts.py L64-72
|
||
def build_user_prompt(self, **kwargs) -> str:
|
||
return self.user_template.format(**kwargs)
|
||
```
|
||
|
||
### 问题
|
||
|
||
| 问题 | 影响 |
|
||
|-----|------|
|
||
| **模板文件分散** | 模板在 `prompts/` 目录,配置在 `config/` |
|
||
| **变量命名不统一** | `style_content` vs `style` vs `styleName` |
|
||
| **难以调试** | 最终 prompt 难以追踪 |
|
||
| **版本管理困难** | prompt 变更难以追踪和回滚 |
|
||
| **缺乏验证** | 缺少 prompt 有效性检查 |
|
||
|
||
### 涉及文件
|
||
|
||
- `utils/prompts.py` - 基础模板类
|
||
- `api/services/prompt_builder.py` - 内容 prompt 构建
|
||
- `api/services/prompt_service.py` - prompt 服务
|
||
- `prompts/*.txt` - 模板文件
|
||
|
||
### 建议方案
|
||
|
||
**方案 A: 集中式 Prompt 管理**
|
||
```python
|
||
class PromptRegistry:
|
||
def get_prompt(self, name: str, version: str = "latest") -> PromptTemplate:
|
||
"""从数据库或配置中心获取 prompt"""
|
||
pass
|
||
|
||
def render(self, name: str, context: dict) -> str:
|
||
"""渲染 prompt"""
|
||
pass
|
||
```
|
||
|
||
**方案 B: Prompt 配置化**
|
||
```yaml
|
||
# prompts.yaml
|
||
topic_generate:
|
||
version: "1.2.0"
|
||
system: |
|
||
你是一个旅游营销专家...
|
||
user: |
|
||
请为 {scenic_spot} 生成 {num_topics} 个选题...
|
||
variables:
|
||
- scenic_spot: required
|
||
- num_topics: default=5
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 工具使用方式问题
|
||
|
||
### 现状
|
||
|
||
各种工具类和服务的使用方式不统一:
|
||
|
||
```python
|
||
# 方式1: 全局单例
|
||
from api.dependencies import get_ai_agent, get_config
|
||
|
||
# 方式2: 构造函数注入
|
||
class TopicGenerator:
|
||
def __init__(self, ai_agent: AIAgent, config_manager: ConfigManager):
|
||
|
||
# 方式3: 延迟加载
|
||
def _get_tweet_service(self):
|
||
if self._tweet_service:
|
||
return self._tweet_service
|
||
from api.services.tweet import TweetService
|
||
self._tweet_service = TweetService(...)
|
||
|
||
# 方式4: 直接实例化
|
||
db_service = DatabaseService(config_manager)
|
||
```
|
||
|
||
### 问题
|
||
|
||
| 问题 | 影响 |
|
||
|-----|------|
|
||
| **依赖注入不一致** | 有的用单例,有的用构造函数 |
|
||
| **循环导入风险** | 延迟导入是为了避免循环依赖 |
|
||
| **测试困难** | 难以 mock 依赖 |
|
||
| **生命周期不清晰** | 不知道对象何时创建/销毁 |
|
||
| **资源泄漏风险** | 多个实例可能创建多个连接池 |
|
||
|
||
### 涉及文件
|
||
|
||
- `api/dependencies.py` - 依赖获取
|
||
- `domain/aigc/shared/component_factory.py` - 组件工厂
|
||
- 各个 Engine 类的 `_get_xxx_service()` 方法
|
||
|
||
### 建议方案
|
||
|
||
**统一依赖注入容器:**
|
||
```python
|
||
class Container:
|
||
_instances = {}
|
||
|
||
@classmethod
|
||
def get(cls, service_class: Type[T]) -> T:
|
||
if service_class not in cls._instances:
|
||
cls._instances[service_class] = cls._create(service_class)
|
||
return cls._instances[service_class]
|
||
|
||
@classmethod
|
||
def _create(cls, service_class):
|
||
# 根据类型创建实例,自动解析依赖
|
||
pass
|
||
```
|
||
|
||
**或使用现有框架:**
|
||
- `dependency-injector` 库
|
||
- FastAPI 的 `Depends` 系统
|
||
|
||
---
|
||
|
||
## 总结:优先级建议
|
||
|
||
| 优先级 | 问题 | 原因 |
|
||
|-------|------|------|
|
||
| 🔴 高 | 数据库双端访问 | 数据一致性风险最大 |
|
||
| 🔴 高 | ppid 混乱 | 影响所有 AIGC 调用 |
|
||
| 🟡 中 | 图片 Base64 传输 | 性能问题,但功能正常 |
|
||
| 🟡 中 | Prompt 拼接 | 维护困难,但不影响功能 |
|
||
| 🟢 低 | 工具使用方式 | 代码质量问题,可渐进改进 |
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 6. 临时文件堆积问题
|
||
|
||
### 现状
|
||
|
||
每次 API 请求都会在 `result/` 目录下创建新的子目录,保存中间产物:
|
||
|
||
```bash
|
||
$ du -sh result/
|
||
943M result/
|
||
|
||
$ ls -1 result/ | wc -l
|
||
6931
|
||
```
|
||
|
||
**6931 个目录,占用 943MB 磁盘空间!**
|
||
|
||
### 问题
|
||
|
||
| 问题 | 影响 |
|
||
|-----|------|
|
||
| **磁盘空间浪费** | 近 1GB 的临时文件 |
|
||
| **无清理机制** | 文件只增不减 |
|
||
| **目录数量过多** | 影响文件系统性能 |
|
||
| **敏感信息泄露风险** | prompt、生成内容可能包含敏感数据 |
|
||
|
||
### 涉及文件
|
||
|
||
- `utils/file_io.py` - `OutputManager` 类
|
||
- 每次请求创建: `result/api_request-{timestamp}-{uuid}/`
|
||
|
||
### 建议方案
|
||
|
||
1. **定期清理**: 添加定时任务清理 7 天前的目录
|
||
2. **按需保存**: 只在调试模式下保存中间产物
|
||
3. **内存缓存**: 中间结果保存在内存,不落盘
|
||
4. **统一存储**: 重要结果上传到 S3,本地只做临时缓存
|
||
|
||
---
|
||
|
||
## 7. 配置分散问题
|
||
|
||
### 现状
|
||
|
||
配置文件分散在多个 JSON 文件中:
|
||
|
||
```
|
||
config/
|
||
├── ai_model.json # AI 模型配置
|
||
├── content_gen.json # 内容生成配置 (含 prompt 路径)
|
||
├── topic_gen.json # 选题生成配置 (含 prompt 路径)
|
||
├── poster_gen.json # 海报生成配置
|
||
├── database.json # 数据库配置
|
||
├── paths.json # 路径配置
|
||
├── resource.json # 资源配置
|
||
├── system.json # 系统配置
|
||
└── cookies.json # Cookie 配置
|
||
```
|
||
|
||
### 问题
|
||
|
||
| 问题 | 影响 |
|
||
|-----|------|
|
||
| **配置分散** | 9 个配置文件,难以统一管理 |
|
||
| **路径嵌套** | 配置中引用其他配置路径 |
|
||
| **环境切换困难** | 没有 dev/prod 环境区分 |
|
||
| **敏感信息明文** | 数据库密码、API Key 明文存储 |
|
||
|
||
### 建议方案
|
||
|
||
1. **环境变量优先**: 敏感信息从环境变量读取
|
||
2. **配置合并**: 合并为 `config.yaml` + 环境覆盖
|
||
3. **配置中心**: 使用 Consul/Nacos 等配置中心
|
||
|
||
---
|
||
|
||
## 8. 错误处理不一致
|
||
|
||
### 现状
|
||
|
||
不同模块的错误处理方式不统一:
|
||
|
||
```python
|
||
# 方式1: 返回 None
|
||
if not topics:
|
||
return None
|
||
|
||
# 方式2: 返回错误字典
|
||
return {"error": str(e)}
|
||
|
||
# 方式3: 抛出异常
|
||
raise ValueError(f"生成海报失败: {str(e)}")
|
||
|
||
# 方式4: 返回元组
|
||
return str(uuid.uuid4()), []
|
||
```
|
||
|
||
### 问题
|
||
|
||
| 问题 | 影响 |
|
||
|-----|------|
|
||
| **调用方难以处理** | 需要检查多种错误格式 |
|
||
| **错误信息丢失** | 有些地方只返回 None |
|
||
| **日志不统一** | 有的记录,有的不记录 |
|
||
| **没有错误码** | 难以区分错误类型 |
|
||
|
||
### 建议方案
|
||
|
||
统一使用 `Result` 模式:
|
||
|
||
```python
|
||
@dataclass
|
||
class Result(Generic[T]):
|
||
success: bool
|
||
data: Optional[T] = None
|
||
error: Optional[str] = None
|
||
error_code: Optional[str] = None
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Prompt 管理方案 (详细设计)
|
||
|
||
### 当前问题分析
|
||
|
||
```
|
||
config/content_gen.json
|
||
└── "content_system_prompt": "resource/prompt/generateContent/system.txt"
|
||
│
|
||
▼
|
||
resource/prompt/generateContent/system.txt (87 行硬编码的 prompt)
|
||
│
|
||
▼
|
||
utils/prompts.py PromptTemplate.format(**kwargs)
|
||
│
|
||
▼
|
||
api/services/prompt_builder.py 拼接变量
|
||
```
|
||
|
||
**问题:**
|
||
1. Prompt 内容硬编码在 `.txt` 文件中
|
||
2. 变量占位符 `{style_content}` 与代码强耦合
|
||
3. 无版本管理,修改后无法回滚
|
||
4. 无 A/B 测试能力
|
||
5. 无效果追踪
|
||
|
||
### 推荐方案: YAML + 版本化 Prompt Registry
|
||
|
||
#### 目录结构
|
||
|
||
```
|
||
prompts/
|
||
├── registry.yaml # Prompt 注册表
|
||
├── topic_generate/
|
||
│ ├── v1.0.0.yaml # 版本化的 prompt
|
||
│ ├── v1.1.0.yaml
|
||
│ └── latest -> v1.1.0.yaml # 软链接
|
||
├── content_generate/
|
||
│ ├── v1.0.0.yaml
|
||
│ └── latest -> v1.0.0.yaml
|
||
└── content_judge/
|
||
└── v1.0.0.yaml
|
||
```
|
||
|
||
#### Prompt 定义格式 (YAML)
|
||
|
||
```yaml
|
||
# prompts/content_generate/v1.0.0.yaml
|
||
meta:
|
||
name: content_generate
|
||
version: "1.0.0"
|
||
description: "小红书风格内容生成"
|
||
author: "team"
|
||
created_at: "2024-12-08"
|
||
|
||
# 模型参数
|
||
model:
|
||
temperature: 0.3
|
||
top_p: 0.5
|
||
presence_penalty: 1.2
|
||
|
||
# 变量定义 (带类型和默认值)
|
||
variables:
|
||
style_content:
|
||
type: string
|
||
required: true
|
||
description: "风格描述"
|
||
demand_content:
|
||
type: string
|
||
required: true
|
||
description: "受众需求"
|
||
object_content:
|
||
type: string
|
||
required: true
|
||
description: "景区/产品信息"
|
||
product_content:
|
||
type: string
|
||
required: false
|
||
default: ""
|
||
description: "产品详情"
|
||
refer_content:
|
||
type: string
|
||
required: false
|
||
default: ""
|
||
description: "参考范文"
|
||
|
||
# System Prompt
|
||
system: |
|
||
你是景区小红书爆款文案策划,你将根据要求创作爆款文案。
|
||
|
||
## 标题创作规则
|
||
1. 必加1个emoji,标题字数18字以内
|
||
2. 有网感,结合所在地
|
||
3. 标题必须结合给出的标题参考格式进行高相似度仿写
|
||
|
||
## 正文创作规则
|
||
1. 以"你"这种有人味的人称代词视角创作
|
||
2. 不要出现"宝子们""姐妹们"这些很假的称呼
|
||
3. 直击用户痛点,有场景感
|
||
4. 分段+分点论述,巧用emoji
|
||
|
||
## 输出格式
|
||
```json
|
||
{
|
||
"title": "标题内容",
|
||
"content": "正文内容",
|
||
"tag": "#标签1 #标签2 ..."
|
||
}
|
||
```
|
||
|
||
# User Prompt (使用 Jinja2 模板语法)
|
||
user: |
|
||
请根据以下材料创作一篇小红书文案:
|
||
|
||
【风格要求】
|
||
{{ style_content }}
|
||
|
||
【目标受众】
|
||
{{ demand_content }}
|
||
|
||
【景区/产品信息】
|
||
{{ object_content }}
|
||
|
||
{% if product_content %}
|
||
【产品详情】
|
||
{{ product_content }}
|
||
{% endif %}
|
||
|
||
{% if refer_content %}
|
||
【参考范文】
|
||
{{ refer_content }}
|
||
{% endif %}
|
||
```
|
||
|
||
#### Prompt Registry 实现
|
||
|
||
```python
|
||
# domain/prompt/registry.py
|
||
|
||
from pathlib import Path
|
||
from typing import Dict, Any, Optional
|
||
import yaml
|
||
from jinja2 import Template
|
||
from dataclasses import dataclass
|
||
|
||
@dataclass
|
||
class PromptConfig:
|
||
"""Prompt 配置"""
|
||
name: str
|
||
version: str
|
||
system: str
|
||
user: str
|
||
variables: Dict[str, Any]
|
||
model: Dict[str, float]
|
||
|
||
class PromptRegistry:
|
||
"""
|
||
Prompt 注册表
|
||
|
||
功能:
|
||
1. 加载和缓存 prompt 配置
|
||
2. 版本管理 (latest, v1.0.0, v1.1.0)
|
||
3. 变量验证
|
||
4. 模板渲染
|
||
"""
|
||
|
||
def __init__(self, prompts_dir: str = "prompts"):
|
||
self.prompts_dir = Path(prompts_dir)
|
||
self._cache: Dict[str, PromptConfig] = {}
|
||
|
||
def get(self, name: str, version: str = "latest") -> PromptConfig:
|
||
"""
|
||
获取 prompt 配置
|
||
|
||
Args:
|
||
name: prompt 名称 (如 "content_generate")
|
||
version: 版本号 (如 "v1.0.0" 或 "latest")
|
||
"""
|
||
cache_key = f"{name}:{version}"
|
||
|
||
if cache_key not in self._cache:
|
||
self._cache[cache_key] = self._load(name, version)
|
||
|
||
return self._cache[cache_key]
|
||
|
||
def render(self, name: str, context: Dict[str, Any],
|
||
version: str = "latest") -> tuple[str, str]:
|
||
"""
|
||
渲染 prompt
|
||
|
||
Returns:
|
||
(system_prompt, user_prompt)
|
||
"""
|
||
config = self.get(name, version)
|
||
|
||
# 验证必填变量
|
||
self._validate_variables(config, context)
|
||
|
||
# 渲染模板
|
||
system = config.system # system 通常不需要变量
|
||
user = Template(config.user).render(**context)
|
||
|
||
return system, user
|
||
|
||
def _load(self, name: str, version: str) -> PromptConfig:
|
||
"""加载 prompt 配置文件"""
|
||
if version == "latest":
|
||
# 读取 latest 软链接指向的文件
|
||
prompt_path = self.prompts_dir / name / "latest.yaml"
|
||
if prompt_path.is_symlink():
|
||
prompt_path = prompt_path.resolve()
|
||
elif not prompt_path.exists():
|
||
# 找最新版本
|
||
versions = sorted(
|
||
(self.prompts_dir / name).glob("v*.yaml"),
|
||
reverse=True
|
||
)
|
||
if versions:
|
||
prompt_path = versions[0]
|
||
else:
|
||
prompt_path = self.prompts_dir / name / f"{version}.yaml"
|
||
|
||
if not prompt_path.exists():
|
||
raise FileNotFoundError(f"Prompt not found: {name}:{version}")
|
||
|
||
with open(prompt_path, 'r', encoding='utf-8') as f:
|
||
data = yaml.safe_load(f)
|
||
|
||
return PromptConfig(
|
||
name=data['meta']['name'],
|
||
version=data['meta']['version'],
|
||
system=data['system'],
|
||
user=data['user'],
|
||
variables=data.get('variables', {}),
|
||
model=data.get('model', {})
|
||
)
|
||
|
||
def _validate_variables(self, config: PromptConfig, context: Dict[str, Any]):
|
||
"""验证变量"""
|
||
for var_name, var_config in config.variables.items():
|
||
if var_config.get('required', False) and var_name not in context:
|
||
raise ValueError(f"Missing required variable: {var_name}")
|
||
|
||
def list_versions(self, name: str) -> list[str]:
|
||
"""列出所有版本"""
|
||
prompt_dir = self.prompts_dir / name
|
||
if not prompt_dir.exists():
|
||
return []
|
||
|
||
versions = []
|
||
for f in prompt_dir.glob("v*.yaml"):
|
||
versions.append(f.stem)
|
||
return sorted(versions, reverse=True)
|
||
```
|
||
|
||
#### 使用示例
|
||
|
||
```python
|
||
# 使用新的 Prompt Registry
|
||
registry = PromptRegistry("prompts")
|
||
|
||
# 渲染 prompt
|
||
system, user = registry.render(
|
||
name="content_generate",
|
||
context={
|
||
"style_content": "攻略风格,实用性强",
|
||
"demand_content": "亲子家庭,周末出游",
|
||
"object_content": "天津冒险湾主题乐园...",
|
||
"product_content": "门票299元/人",
|
||
},
|
||
version="latest" # 或指定 "v1.0.0"
|
||
)
|
||
|
||
# 获取模型参数
|
||
config = registry.get("content_generate")
|
||
model_params = config.model # {"temperature": 0.3, ...}
|
||
```
|
||
|
||
#### 优势
|
||
|
||
| 特性 | 说明 |
|
||
|-----|------|
|
||
| **版本管理** | 每个版本独立文件,可回滚 |
|
||
| **变量验证** | 自动检查必填变量 |
|
||
| **模板语法** | Jinja2 支持条件、循环 |
|
||
| **模型参数绑定** | prompt 和模型参数一起管理 |
|
||
| **缓存** | 加载后缓存,避免重复 IO |
|
||
| **A/B 测试** | 可指定不同版本测试效果 |
|
||
|
||
---
|
||
|
||
## 下一步行动
|
||
|
||
### 已确认的改进方向
|
||
|
||
| # | 问题 | 方案 |
|
||
|---|------|------|
|
||
| 1 | 数据库双端访问 | Python 不访问数据库,改为接口传输 |
|
||
| 2 | 图片 Base64 传输 | 改为 URL/路径引用 |
|
||
| 3 | ppid 混乱 | 放弃 ppid,Java 直接传完整数据 |
|
||
| 4 | Prompt 管理 | YAML + 版本化 Registry |
|
||
| 5 | 依赖注入 | 统一容器模式 |
|
||
| 6 | 临时文件堆积 | 添加清理机制 |
|
||
| 7 | 配置分散 | 合并 + 环境变量 |
|
||
| 8 | 错误处理不一致 | 统一 Result 模式 |
|
||
|
||
### 优先级排序
|
||
|
||
1. **🔴 高优先级** (影响架构)
|
||
- 数据库访问改接口
|
||
- 图片传输改 URL
|
||
- 放弃 ppid
|
||
|
||
2. **🟡 中优先级** (影响维护)
|
||
- Prompt Registry
|
||
- 错误处理统一
|
||
- 临时文件清理
|
||
|
||
3. **🟢 低优先级** (代码质量)
|
||
- 依赖注入优化
|
||
- 配置合并
|
||
|
||
---
|
||
|
||
## 9. 巨型文件问题
|
||
|
||
### 现状
|
||
|
||
部分文件代码量过大,难以维护:
|
||
|
||
| 文件 | 行数 | 问题 |
|
||
|-----|------|------|
|
||
| `demo_refactored_templates.py` | 4219 | 示例/废弃代码? |
|
||
| `poster_template.py` | 3421 | 根目录下的模板文件 |
|
||
| `api/services/poster.py` | 3031 | 海报服务,职责过多 |
|
||
| `poster/templates/vibrant_template.py` | 1756 | 单个模板文件过大 |
|
||
| `api/services/database_service.py` | 1054 | 数据库服务 |
|
||
| `core/xhs_spider/apis/xhs_pc_apis.py` | 1019 | 小红书 API |
|
||
|
||
### 问题
|
||
|
||
| 问题 | 影响 |
|
||
|-----|------|
|
||
| **难以理解** | 3000+ 行代码难以阅读 |
|
||
| **难以测试** | 职责混杂,难以单元测试 |
|
||
| **合并冲突** | 多人修改同一文件容易冲突 |
|
||
| **废弃代码** | 根目录下的 `poster_template.py` 和 `demo_*.py` 可能是废弃代码 |
|
||
|
||
### 建议方案
|
||
|
||
1. **拆分大文件**: `poster.py` 拆分为多个模块 (已在新架构中完成)
|
||
2. **清理废弃代码**: 删除根目录下的 `poster_template.py`, `demo_*.py`
|
||
3. **单一职责**: 每个类/模块只做一件事
|
||
|
||
---
|
||
|
||
## 10. 重复代码问题
|
||
|
||
### 现状
|
||
|
||
存在多处重复或相似的代码:
|
||
|
||
```
|
||
# 目录重复
|
||
/root/TravelContentCreator/document/ # 旧位置
|
||
/root/TravelContentCreator/core/document/ # 新位置 (重复)
|
||
|
||
# 文件重复
|
||
./document/content_transformer.py
|
||
./core/document/content_transformer.py
|
||
|
||
./document/content_integrator.py
|
||
./core/document/content_integrator.py
|
||
```
|
||
|
||
### 问题
|
||
|
||
| 问题 | 影响 |
|
||
|-----|------|
|
||
| **不知道用哪个** | 两个位置都有相同文件 |
|
||
| **修改遗漏** | 改了一个忘了另一个 |
|
||
| **导入混乱** | `from document.xxx` vs `from core.document.xxx` |
|
||
|
||
### 建议方案
|
||
|
||
1. 确定唯一位置
|
||
2. 删除重复目录
|
||
3. 更新所有 import
|
||
|
||
---
|
||
|
||
## 11. API 路由分散问题
|
||
|
||
### 现状
|
||
|
||
API 路由分散在多个文件中,功能有重叠:
|
||
|
||
```
|
||
api/routers/
|
||
├── aigc.py # 新的统一 AIGC API
|
||
├── tweet.py # 旧的选题/内容 API (470 行)
|
||
├── poster.py # 旧的海报 API
|
||
├── data.py # 数据查询 API
|
||
├── document.py # 文档处理 API
|
||
├── integration.py # 集成 API
|
||
├── content_integration.py # 内容集成 API
|
||
└── prompt.py # 提示词 API
|
||
```
|
||
|
||
### 问题
|
||
|
||
| 问题 | 影响 |
|
||
|-----|------|
|
||
| **功能重叠** | `aigc.py` 和 `tweet.py`/`poster.py` 功能重复 |
|
||
| **命名不一致** | `tweet` vs `content`, `integration` vs `content_integration` |
|
||
| **API 版本混乱** | 没有清晰的 v1/v2 区分 |
|
||
|
||
### 建议方案
|
||
|
||
1. 统一使用 `/api/v2/aigc/*` 作为新入口
|
||
2. 旧 API 标记为 deprecated
|
||
3. 逐步迁移后删除旧路由
|
||
|
||
---
|
||
|
||
## 12. 日志配置问题
|
||
|
||
### 现状
|
||
|
||
每个文件都单独配置 logger:
|
||
|
||
```python
|
||
# 几乎每个 .py 文件都有这行
|
||
logger = logging.getLogger(__name__)
|
||
```
|
||
|
||
但没有统一的日志配置:
|
||
- 日志格式不统一
|
||
- 日志级别分散控制
|
||
- 没有日志轮转
|
||
- 没有结构化日志
|
||
|
||
### 建议方案
|
||
|
||
```python
|
||
# 统一日志配置
|
||
LOGGING_CONFIG = {
|
||
"version": 1,
|
||
"formatters": {
|
||
"default": {
|
||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||
},
|
||
"json": {
|
||
"class": "pythonjsonlogger.jsonlogger.JsonFormatter"
|
||
}
|
||
},
|
||
"handlers": {
|
||
"console": {"class": "logging.StreamHandler", "formatter": "default"},
|
||
"file": {"class": "logging.handlers.RotatingFileHandler", ...}
|
||
},
|
||
"root": {"level": "INFO", "handlers": ["console", "file"]}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 完整问题清单
|
||
|
||
| # | 问题 | 严重程度 | 状态 |
|
||
|---|------|---------|------|
|
||
| 1 | 数据库双端访问 | 🔴 高 | ✅ V2 引擎已解决 |
|
||
| 2 | 图片 Base64 传输 | 🟡 中 | ✅ V2 引擎支持 URL |
|
||
| 3 | ppid 混乱 | 🔴 高 | ✅ 已废弃,Java 端传完整对象 |
|
||
| 4 | Prompt 管理分散 | 🟡 中 | ✅ PromptRegistry 已实现 |
|
||
| 5 | 依赖注入不统一 | 🟢 低 | ✅ Container 已实现 |
|
||
| 6 | 临时文件堆积 | 🟡 中 | ✅ 清理脚本已创建 |
|
||
| 7 | 配置分散 | 🟢 低 | ✅ UnifiedConfig 已实现 |
|
||
| 8 | 错误处理不一致 | 🟡 中 | ✅ 统一异常类已定义 |
|
||
| 9 | 巨型文件 | 🟡 中 | 部分已拆分 |
|
||
| 10 | 重复代码/目录 | 🟡 中 | ✅ document 已统一 |
|
||
| 11 | API 路由分散 | 🟢 低 | 新架构已统一 |
|
||
| 12 | 日志配置缺失 | 🟢 低 | ✅ logging_config 已实现 |
|
||
|
||
> 详细实施记录见 [REFACTORING_IMPLEMENTATION.md](./REFACTORING_IMPLEMENTATION.md)
|