123
This commit is contained in:
566
.kiro/specs/multi-user-signin/design.md
Normal file
566
.kiro/specs/multi-user-signin/design.md
Normal file
@@ -0,0 +1,566 @@
|
||||
# 设计文档:Weibo-HotSign 多用户签到系统
|
||||
|
||||
## 概述
|
||||
|
||||
本设计文档描述 Weibo-HotSign 系统的架构重构与核心功能实现方案。核心目标是:
|
||||
|
||||
1. 引入 `backend/shared/` 共享模块,统一 ORM 模型、数据库连接和加密工具,消除各服务间的代码重复
|
||||
2. 完善 `auth_service`,实现 Refresh Token 机制
|
||||
3. 从零实现 `api_service`,提供微博账号 CRUD、任务配置和签到日志查询 API
|
||||
4. 将 `signin_executor` 和 `task_scheduler` 中的 Mock 实现替换为真实数据库交互
|
||||
5. 所有 API 遵循统一响应格式
|
||||
|
||||
技术栈:Python 3.11 + FastAPI + SQLAlchemy (async) + Celery + MySQL (aiomysql) + Redis
|
||||
|
||||
## 架构
|
||||
|
||||
### 重构后的服务架构
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph "客户端"
|
||||
FE[Web Frontend / API Client]
|
||||
end
|
||||
|
||||
subgraph "后端服务层"
|
||||
API[API_Service :8000<br/>账号/任务/日志管理]
|
||||
AUTH[Auth_Service :8001<br/>注册/登录/Token刷新]
|
||||
SCHED[Task_Scheduler<br/>Celery Beat]
|
||||
EXEC[Signin_Executor<br/>Celery Worker]
|
||||
end
|
||||
|
||||
subgraph "共享层"
|
||||
SHARED[shared/<br/>ORM Models + DB Session<br/>+ Crypto Utils + Response Format]
|
||||
end
|
||||
|
||||
subgraph "基础设施"
|
||||
MYSQL[(MySQL)]
|
||||
REDIS[(Redis<br/>Cache + Message Queue)]
|
||||
PROXY[Proxy Pool]
|
||||
end
|
||||
|
||||
FE -->|REST API| API
|
||||
FE -->|REST API| AUTH
|
||||
API -->|导入| SHARED
|
||||
AUTH -->|导入| SHARED
|
||||
SCHED -->|导入| SHARED
|
||||
EXEC -->|导入| SHARED
|
||||
SHARED -->|aiomysql| MYSQL
|
||||
SCHED -->|发布任务| REDIS
|
||||
EXEC -->|消费任务| REDIS
|
||||
EXEC -->|获取代理| PROXY
|
||||
EXEC -->|签到请求| WEIBO[Weibo.com]
|
||||
|
||||
```
|
||||
|
||||
### 关键架构决策
|
||||
|
||||
1. **共享模块而非微服务间 RPC**:各服务通过 Python 包导入 `shared/` 模块访问数据库,而非通过 HTTP 调用其他服务查询数据。这简化了部署,减少了网络延迟,适合当前规模。
|
||||
2. **API_Service 作为唯一对外网关**:所有账号管理、任务配置、日志查询 API 集中在 `api_service` 中,`auth_service` 仅负责认证。
|
||||
3. **Celery 同时承担调度和执行**:`task_scheduler` 运行 Celery Beat(调度),`signin_executor` 运行 Celery Worker(执行),通过 Redis 消息队列解耦。
|
||||
4. **Dockerfile 多阶段构建**:保持现有的多阶段 Dockerfile 结构,新增 `shared/` 目录的 COPY 步骤。
|
||||
|
||||
### 目录结构(重构后)
|
||||
|
||||
```
|
||||
backend/
|
||||
├── shared/ # 新增:共享模块
|
||||
│ ├── __init__.py
|
||||
│ ├── models/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── base.py # SQLAlchemy Base + engine + session
|
||||
│ │ ├── user.py # User ORM model
|
||||
│ │ ├── account.py # Account ORM model
|
||||
│ │ ├── task.py # Task ORM model
|
||||
│ │ └── signin_log.py # SigninLog ORM model
|
||||
│ ├── crypto.py # AES-256-GCM 加密/解密工具
|
||||
│ ├── response.py # 统一响应格式工具
|
||||
│ └── config.py # 共享配置(DB URL, Redis URL 等)
|
||||
├── auth_service/
|
||||
│ └── app/
|
||||
│ ├── main.py # 重构:使用 shared models
|
||||
│ ├── config.py
|
||||
│ ├── schemas/
|
||||
│ │ └── user.py # 增加 RefreshToken schema
|
||||
│ ├── services/
|
||||
│ │ └── auth_service.py # 增加 refresh token 逻辑
|
||||
│ └── utils/
|
||||
│ └── security.py # 增加 refresh token 生成/验证
|
||||
├── api_service/
|
||||
│ └── app/
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py # 新增:FastAPI 应用入口
|
||||
│ ├── config.py
|
||||
│ ├── dependencies.py # JWT 认证依赖
|
||||
│ ├── schemas/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── account.py # 账号请求/响应 schema
|
||||
│ │ ├── task.py # 任务请求/响应 schema
|
||||
│ │ └── signin_log.py # 签到日志响应 schema
|
||||
│ └── routers/
|
||||
│ ├── __init__.py
|
||||
│ ├── accounts.py # 账号 CRUD 路由
|
||||
│ ├── tasks.py # 任务 CRUD 路由
|
||||
│ └── signin_logs.py # 签到日志查询路由
|
||||
├── signin_executor/
|
||||
│ └── app/
|
||||
│ ├── main.py
|
||||
│ ├── config.py
|
||||
│ ├── services/
|
||||
│ │ ├── signin_service.py # 重构:使用 shared models 查询真实数据
|
||||
│ │ └── weibo_client.py # 重构:实现真实加密/解密
|
||||
│ └── models/
|
||||
│ └── signin_models.py # 保留 Pydantic 请求/响应模型
|
||||
├── task_scheduler/
|
||||
│ └── app/
|
||||
│ ├── celery_app.py # 重构:从 DB 动态加载任务
|
||||
│ ├── config.py
|
||||
│ └── tasks/
|
||||
│ └── signin_tasks.py # 重构:使用真实 DB 查询
|
||||
├── Dockerfile # 更新:各阶段 COPY shared/
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
## 组件与接口
|
||||
|
||||
### 1. shared 模块
|
||||
|
||||
#### 1.1 数据库连接管理 (`shared/models/base.py`)
|
||||
|
||||
```python
|
||||
# 提供异步 engine 和 session factory
|
||||
# 所有服务通过 get_db() 获取 AsyncSession
|
||||
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
yield session
|
||||
finally:
|
||||
await session.close()
|
||||
```
|
||||
|
||||
#### 1.2 加密工具 (`shared/crypto.py`)
|
||||
|
||||
```python
|
||||
def encrypt_cookie(plaintext: str, key: bytes) -> tuple[str, str]:
|
||||
"""AES-256-GCM 加密,返回 (密文base64, iv_base64)"""
|
||||
|
||||
def decrypt_cookie(ciphertext_b64: str, iv_b64: str, key: bytes) -> str:
|
||||
"""AES-256-GCM 解密,返回原始 Cookie 字符串"""
|
||||
```
|
||||
|
||||
#### 1.3 统一响应格式 (`shared/response.py`)
|
||||
|
||||
```python
|
||||
def success_response(data: Any, message: str = "Operation successful") -> dict
|
||||
def error_response(message: str, code: str, details: list = None, status_code: int = 400) -> JSONResponse
|
||||
```
|
||||
|
||||
### 2. Auth_Service 接口
|
||||
|
||||
| 方法 | 路径 | 描述 | 需求 |
|
||||
|------|------|------|------|
|
||||
| POST | `/auth/register` | 用户注册 | 1.1, 1.2, 1.8 |
|
||||
| POST | `/auth/login` | 用户登录,返回 access_token + refresh_token | 1.3, 1.4 |
|
||||
| POST | `/auth/refresh` | 刷新 Token | 1.5, 1.6 |
|
||||
| GET | `/auth/me` | 获取当前用户信息 | 1.7 |
|
||||
|
||||
### 3. API_Service 接口
|
||||
|
||||
| 方法 | 路径 | 描述 | 需求 |
|
||||
|------|------|------|------|
|
||||
| POST | `/api/v1/accounts` | 添加微博账号 | 2.1, 2.7, 2.8 |
|
||||
| GET | `/api/v1/accounts` | 获取账号列表 | 2.2, 2.8 |
|
||||
| GET | `/api/v1/accounts/{id}` | 获取账号详情 | 2.3, 2.6, 2.8 |
|
||||
| PUT | `/api/v1/accounts/{id}` | 更新账号信息 | 2.4, 2.6, 2.8 |
|
||||
| DELETE | `/api/v1/accounts/{id}` | 删除账号 | 2.5, 2.6, 2.8 |
|
||||
| POST | `/api/v1/accounts/{id}/tasks` | 创建签到任务 | 4.1, 4.2, 4.6 |
|
||||
| GET | `/api/v1/accounts/{id}/tasks` | 获取任务列表 | 4.3 |
|
||||
| PUT | `/api/v1/tasks/{id}` | 启用/禁用任务 | 4.4 |
|
||||
| DELETE | `/api/v1/tasks/{id}` | 删除任务 | 4.5 |
|
||||
| GET | `/api/v1/accounts/{id}/signin-logs` | 查询签到日志 | 8.1, 8.2, 8.3, 8.4, 8.5 |
|
||||
|
||||
### 4. Task_Scheduler 内部接口
|
||||
|
||||
Task_Scheduler 不对外暴露 HTTP 接口,通过以下方式工作:
|
||||
|
||||
- **启动时**:从 DB 加载 `is_enabled=True` 的任务,注册到 Celery Beat
|
||||
- **运行时**:根据 Cron 表达式触发 `execute_signin_task` Celery task
|
||||
- **动态更新**:通过 Redis pub/sub 接收任务变更通知,动态更新调度
|
||||
|
||||
### 5. Signin_Executor 内部流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Queue as Redis Queue
|
||||
participant Exec as Signin_Executor
|
||||
participant DB as MySQL
|
||||
participant Weibo as Weibo.com
|
||||
participant Proxy as Proxy Pool
|
||||
|
||||
Queue->>Exec: 消费签到任务 (task_id, account_id)
|
||||
Exec->>DB: 查询 Account (by account_id)
|
||||
Exec->>Exec: 解密 Cookie (AES-256-GCM)
|
||||
Exec->>Weibo: 验证 Cookie 有效性
|
||||
alt Cookie 无效
|
||||
Exec->>DB: 更新 account.status = "invalid_cookie"
|
||||
Exec->>DB: 写入失败日志
|
||||
else Cookie 有效
|
||||
Exec->>Weibo: 获取超话列表
|
||||
loop 每个未签到超话
|
||||
Exec->>Proxy: 获取代理 IP
|
||||
Exec->>Exec: 随机延迟 (1-3s)
|
||||
Exec->>Weibo: 执行签到请求
|
||||
Exec->>DB: 写入 signin_log
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## 数据模型
|
||||
|
||||
### ORM 模型定义(shared/models/)
|
||||
|
||||
#### User 模型
|
||||
|
||||
```python
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid4()))
|
||||
username = Column(String(50), unique=True, nullable=False, index=True)
|
||||
email = Column(String(255), unique=True, nullable=False, index=True)
|
||||
hashed_password = Column(String(255), nullable=False)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
is_active = Column(Boolean, default=True)
|
||||
# Relationships
|
||||
accounts = relationship("Account", back_populates="user", cascade="all, delete-orphan")
|
||||
```
|
||||
|
||||
#### Account 模型
|
||||
|
||||
```python
|
||||
class Account(Base):
|
||||
__tablename__ = "accounts"
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid4()))
|
||||
user_id = Column(String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
|
||||
weibo_user_id = Column(String(20), nullable=False)
|
||||
remark = Column(String(100))
|
||||
encrypted_cookies = Column(Text, nullable=False)
|
||||
iv = Column(String(32), nullable=False)
|
||||
status = Column(String(20), default="pending") # pending, active, invalid_cookie, banned
|
||||
last_checked_at = Column(DateTime, nullable=True)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
# Relationships
|
||||
user = relationship("User", back_populates="accounts")
|
||||
tasks = relationship("Task", back_populates="account", cascade="all, delete-orphan")
|
||||
signin_logs = relationship("SigninLog", back_populates="account")
|
||||
```
|
||||
|
||||
#### Task 模型
|
||||
|
||||
```python
|
||||
class Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid4()))
|
||||
account_id = Column(String(36), ForeignKey("accounts.id", ondelete="CASCADE"), nullable=False)
|
||||
cron_expression = Column(String(50), nullable=False)
|
||||
is_enabled = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
# Relationships
|
||||
account = relationship("Account", back_populates="tasks")
|
||||
```
|
||||
|
||||
#### SigninLog 模型
|
||||
|
||||
```python
|
||||
class SigninLog(Base):
|
||||
__tablename__ = "signin_logs"
|
||||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||
account_id = Column(String(36), ForeignKey("accounts.id"), nullable=False)
|
||||
topic_title = Column(String(100))
|
||||
status = Column(String(20), nullable=False) # success, failed_already_signed, failed_network, failed_banned
|
||||
reward_info = Column(JSON, nullable=True)
|
||||
error_message = Column(Text, nullable=True)
|
||||
signed_at = Column(DateTime, server_default=func.now())
|
||||
# Relationships
|
||||
account = relationship("Account", back_populates="signin_logs")
|
||||
```
|
||||
|
||||
### Refresh Token 存储
|
||||
|
||||
Refresh Token 使用 Redis 存储,key 格式为 `refresh_token:{token_hash}`,value 为 `user_id`,TTL 为 7 天。这避免了在数据库中增加额外的表,同时利用 Redis 的自动过期机制。
|
||||
|
||||
```python
|
||||
# 存储
|
||||
await redis.setex(f"refresh_token:{token_hash}", 7 * 24 * 3600, user_id)
|
||||
|
||||
# 验证
|
||||
user_id = await redis.get(f"refresh_token:{token_hash}")
|
||||
|
||||
# 刷新时删除旧 token,生成新 token(Token Rotation)
|
||||
await redis.delete(f"refresh_token:{old_token_hash}")
|
||||
await redis.setex(f"refresh_token:{new_token_hash}", 7 * 24 * 3600, user_id)
|
||||
```
|
||||
|
||||
## 正确性属性 (Correctness Properties)
|
||||
|
||||
*属性(Property)是指在系统所有有效执行中都应成立的特征或行为——本质上是对系统应做什么的形式化陈述。属性是人类可读规格说明与机器可验证正确性保证之间的桥梁。*
|
||||
|
||||
### Property 1: Cookie 加密 Round-trip
|
||||
|
||||
*For any* 有效的 Cookie 字符串,使用 AES-256-GCM 加密后再用相同密钥和 IV 解密,应产生与原始字符串完全相同的结果。
|
||||
|
||||
**Validates: Requirements 3.1, 3.2, 3.3**
|
||||
|
||||
### Property 2: 用户注册后可登录获取信息
|
||||
|
||||
*For any* 有效的注册信息(用户名、邮箱、符合强度要求的密码),注册后使用相同邮箱和密码登录应成功返回 Token,使用该 Token 调用 `/auth/me` 应返回与注册时一致的用户名和邮箱。
|
||||
|
||||
**Validates: Requirements 1.1, 1.3, 1.7**
|
||||
|
||||
### Property 3: 用户名/邮箱唯一性约束
|
||||
|
||||
*For any* 已注册的用户,使用相同用户名或相同邮箱再次注册应返回 409 Conflict 错误。
|
||||
|
||||
**Validates: Requirements 1.2**
|
||||
|
||||
### Property 4: 无效凭证登录拒绝
|
||||
|
||||
*For any* 不存在的邮箱或错误的密码,登录请求应返回 401 Unauthorized 错误。
|
||||
|
||||
**Validates: Requirements 1.4**
|
||||
|
||||
### Property 5: Refresh Token 轮换
|
||||
|
||||
*For any* 已登录用户的有效 Refresh Token,刷新操作应返回新的 Access Token 和新的 Refresh Token,且旧的 Refresh Token 应失效(再次使用应返回 401)。
|
||||
|
||||
**Validates: Requirements 1.5, 1.6**
|
||||
|
||||
### Property 6: 弱密码拒绝
|
||||
|
||||
*For any* 不满足强度要求的密码(缺少大写字母、小写字母、数字或特殊字符,或长度不足8位),注册请求应返回 400 Bad Request。
|
||||
|
||||
**Validates: Requirements 1.8**
|
||||
|
||||
### Property 7: 账号创建与列表一致性
|
||||
|
||||
*For any* 用户和任意数量的有效微博账号数据,创建 N 个账号后查询列表应返回恰好 N 条记录,每条记录的状态应为 "pending",且响应中不应包含解密后的 Cookie 明文。
|
||||
|
||||
**Validates: Requirements 2.1, 2.2, 2.7**
|
||||
|
||||
### Property 8: 账号详情 Round-trip
|
||||
|
||||
*For any* 已创建的微博账号,通过详情接口查询应返回与创建时一致的备注和微博用户 ID。
|
||||
|
||||
**Validates: Requirements 2.3**
|
||||
|
||||
### Property 9: 账号更新反映
|
||||
|
||||
*For any* 已创建的微博账号和任意新的备注字符串,更新备注后再次查询应返回更新后的值。
|
||||
|
||||
**Validates: Requirements 2.4**
|
||||
|
||||
### Property 10: 账号删除级联
|
||||
|
||||
*For any* 拥有关联 Task 和 SigninLog 的账号,删除该账号后,查询该账号的 Task 列表和 SigninLog 列表应返回空结果。
|
||||
|
||||
**Validates: Requirements 2.5**
|
||||
|
||||
### Property 11: 跨用户资源隔离
|
||||
|
||||
*For any* 两个不同用户 A 和 B,用户 A 尝试访问、修改或删除用户 B 的账号、任务或签到日志时,应返回 403 Forbidden。
|
||||
|
||||
**Validates: Requirements 2.6, 4.6, 8.5**
|
||||
|
||||
### Property 12: 受保护接口认证要求
|
||||
|
||||
*For any* 受保护的 API 端点(账号管理、任务管理、日志查询),不携带 JWT Token 或携带无效 Token 的请求应返回 401 Unauthorized。
|
||||
|
||||
**Validates: Requirements 2.8, 8.4, 9.4**
|
||||
|
||||
### Property 13: 有效 Cron 表达式创建任务
|
||||
|
||||
*For any* 有效的 Cron 表达式和已存在的账号,创建任务应成功,且查询该账号的任务列表应包含新创建的任务。
|
||||
|
||||
**Validates: Requirements 4.1, 4.3**
|
||||
|
||||
### Property 14: 无效 Cron 表达式拒绝
|
||||
|
||||
*For any* 无效的 Cron 表达式字符串,创建任务请求应返回 400 Bad Request。
|
||||
|
||||
**Validates: Requirements 4.2**
|
||||
|
||||
### Property 15: 任务启用/禁用切换
|
||||
|
||||
*For any* 已创建的任务,切换 `is_enabled` 状态后查询应反映新的状态值。
|
||||
|
||||
**Validates: Requirements 4.4**
|
||||
|
||||
### Property 16: 任务删除
|
||||
|
||||
*For any* 已创建的任务,删除后查询该任务应返回 404 或不在列表中出现。
|
||||
|
||||
**Validates: Requirements 4.5**
|
||||
|
||||
### Property 17: 调度器加载已启用任务
|
||||
|
||||
*For any* 数据库中的任务集合,Task_Scheduler 启动时加载的任务数量应等于 `is_enabled=True` 的任务数量。
|
||||
|
||||
**Validates: Requirements 5.1**
|
||||
|
||||
### Property 18: 分布式锁防重复调度
|
||||
|
||||
*For any* 签到任务,同一时刻并发触发两次应只产生一次实际执行。
|
||||
|
||||
**Validates: Requirements 5.5**
|
||||
|
||||
### Property 19: 签到结果持久化
|
||||
|
||||
*For any* 签到执行结果(成功或失败),`signin_logs` 表中应存在对应的记录,且记录的 `account_id`、`status` 和 `topic_title` 与执行结果一致。
|
||||
|
||||
**Validates: Requirements 6.1, 6.4**
|
||||
|
||||
### Property 20: Cookie 失效时更新账号状态
|
||||
|
||||
*For any* Cookie 已失效的账号,执行签到时应将账号状态更新为 "invalid_cookie"。
|
||||
|
||||
**Validates: Requirements 6.5, 3.4**
|
||||
|
||||
### Property 21: 随机延迟范围
|
||||
|
||||
*For any* 调用反爬虫延迟函数的结果,延迟值应在配置的 `[min, max]` 范围内。
|
||||
|
||||
**Validates: Requirements 7.1**
|
||||
|
||||
### Property 22: User-Agent 来源
|
||||
|
||||
*For any* 调用 User-Agent 选择函数的结果,返回的 UA 字符串应属于预定义列表中的某一个。
|
||||
|
||||
**Validates: Requirements 7.2**
|
||||
|
||||
### Property 23: 签到日志时间倒序
|
||||
|
||||
*For any* 包含多条签到日志的账号,查询返回的日志列表应按 `signed_at` 降序排列。
|
||||
|
||||
**Validates: Requirements 8.1**
|
||||
|
||||
### Property 24: 签到日志分页
|
||||
|
||||
*For any* 包含 N 条日志的账号和分页参数 (page, size),返回的记录数应等于 `min(size, N - (page-1)*size)` 且总记录数应等于 N。
|
||||
|
||||
**Validates: Requirements 8.2**
|
||||
|
||||
### Property 25: 签到日志状态过滤
|
||||
|
||||
*For any* 状态过滤参数,返回的所有日志记录的 `status` 字段应与过滤参数一致。
|
||||
|
||||
**Validates: Requirements 8.3**
|
||||
|
||||
### Property 26: 统一响应格式
|
||||
|
||||
*For any* API 调用,成功响应应包含 `success=true` 和 `data` 字段;错误响应应包含 `success=false`、`data=null` 和 `error` 字段。
|
||||
|
||||
**Validates: Requirements 9.1, 9.2, 9.3**
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 错误分类与处理策略
|
||||
|
||||
| 错误类型 | HTTP 状态码 | 错误码 | 处理策略 |
|
||||
|----------|------------|--------|----------|
|
||||
| 请求参数校验失败 | 400 | VALIDATION_ERROR | 返回字段级错误详情 |
|
||||
| 未认证 | 401 | UNAUTHORIZED | 返回标准 401 响应 |
|
||||
| 权限不足 | 403 | FORBIDDEN | 返回资源不可访问提示 |
|
||||
| 资源不存在 | 404 | NOT_FOUND | 返回资源未找到提示 |
|
||||
| 资源冲突 | 409 | CONFLICT | 返回冲突字段说明 |
|
||||
| 服务器内部错误 | 500 | INTERNAL_ERROR | 记录详细日志,返回通用错误提示 |
|
||||
|
||||
### 签到执行错误处理
|
||||
|
||||
- **Cookie 解密失败**:标记账号为 `invalid_cookie`,记录错误日志,终止该账号签到
|
||||
- **Cookie 验证失败**(微博返回未登录):同上
|
||||
- **网络超时/连接错误**:记录 `failed_network` 日志,不更改账号状态(可能是临时问题)
|
||||
- **微博返回封禁**:标记账号为 `banned`,记录日志,发送通知
|
||||
- **代理池不可用**:降级为直连,记录警告日志
|
||||
- **Celery 任务失败**:自动重试最多3次,间隔60秒,最终失败记录日志
|
||||
|
||||
### 全局异常处理
|
||||
|
||||
所有 FastAPI 服务注册统一的异常处理器:
|
||||
|
||||
```python
|
||||
@app.exception_handler(HTTPException)
|
||||
async def http_exception_handler(request, exc):
|
||||
return error_response(exc.detail, f"HTTP_{exc.status_code}", status_code=exc.status_code)
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(request, exc):
|
||||
details = [{"field": e["loc"][-1], "message": e["msg"]} for e in exc.errors()]
|
||||
return error_response("Validation failed", "VALIDATION_ERROR", details, 400)
|
||||
```
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 测试框架选择
|
||||
|
||||
- **单元测试**:`pytest` + `pytest-asyncio`(异步测试支持)
|
||||
- **属性测试**:`hypothesis`(Python 属性测试库)
|
||||
- **HTTP 测试**:`httpx` + FastAPI `TestClient`
|
||||
- **数据库测试**:使用 SQLite in-memory 或测试专用 MySQL 实例
|
||||
|
||||
### 测试分层
|
||||
|
||||
#### 1. 单元测试
|
||||
- 加密/解密函数的边界情况
|
||||
- 密码强度验证的各种组合
|
||||
- Cron 表达式验证
|
||||
- 响应格式化函数
|
||||
- 具体的错误场景(网络超时、解密失败等)
|
||||
|
||||
#### 2. 属性测试(Property-Based Testing)
|
||||
- 使用 `hypothesis` 库,每个属性测试至少运行 100 次迭代
|
||||
- 每个测试用注释标注对应的设计文档属性编号
|
||||
- 标注格式:`# Feature: multi-user-signin, Property {N}: {property_text}`
|
||||
- 每个正确性属性对应一个独立的属性测试函数
|
||||
|
||||
#### 3. 集成测试
|
||||
- API 端点的完整请求/响应流程
|
||||
- 数据库 CRUD 操作的正确性
|
||||
- 服务间通过 Redis 消息队列的交互
|
||||
- Celery 任务的调度和执行
|
||||
|
||||
### 属性测试配置
|
||||
|
||||
```python
|
||||
from hypothesis import given, settings, strategies as st
|
||||
|
||||
@settings(max_examples=100)
|
||||
@given(cookie=st.text(min_size=1, max_size=1000))
|
||||
def test_cookie_encryption_roundtrip(cookie):
|
||||
"""Feature: multi-user-signin, Property 1: Cookie 加密 Round-trip"""
|
||||
key = generate_test_key()
|
||||
ciphertext, iv = encrypt_cookie(cookie, key)
|
||||
decrypted = decrypt_cookie(ciphertext, iv, key)
|
||||
assert decrypted == cookie
|
||||
```
|
||||
|
||||
### 测试目录结构
|
||||
|
||||
```
|
||||
backend/
|
||||
├── tests/
|
||||
│ ├── conftest.py # 共享 fixtures(DB session, test client 等)
|
||||
│ ├── unit/
|
||||
│ │ ├── test_crypto.py # 加密/解密单元测试
|
||||
│ │ ├── test_password.py # 密码验证单元测试
|
||||
│ │ └── test_cron.py # Cron 表达式验证单元测试
|
||||
│ ├── property/
|
||||
│ │ ├── test_crypto_props.py # Property 1
|
||||
│ │ ├── test_auth_props.py # Property 2-6
|
||||
│ │ ├── test_account_props.py # Property 7-12
|
||||
│ │ ├── test_task_props.py # Property 13-18
|
||||
│ │ ├── test_signin_props.py # Property 19-22
|
||||
│ │ └── test_log_props.py # Property 23-26
|
||||
│ └── integration/
|
||||
│ ├── test_auth_flow.py # 完整认证流程
|
||||
│ ├── test_account_flow.py # 账号管理流程
|
||||
│ └── test_signin_flow.py # 签到执行流程
|
||||
```
|
||||
138
.kiro/specs/multi-user-signin/requirements.md
Normal file
138
.kiro/specs/multi-user-signin/requirements.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# 需求文档:Weibo-HotSign 多用户签到系统
|
||||
|
||||
## 简介
|
||||
|
||||
Weibo-HotSign 是一个分布式微博超话自动签到系统,采用微服务架构(FastAPI + Celery + MySQL + Redis)。本需求文档覆盖系统的五大核心模块:用户认证(含 Token 刷新)、微博账号管理、定时签到任务配置、签到执行引擎、以及整体架构重构。目标是将当前分散的、含大量 Mock 实现的代码库重构为一个真正可运行的、模块间紧密集成的生产级系统。
|
||||
|
||||
## 术语表
|
||||
|
||||
- **System**: 指 Weibo-HotSign 后端系统整体
|
||||
- **Auth_Service**: 用户认证与授权服务(`backend/auth_service`)
|
||||
- **API_Service**: API 网关与账号/任务管理服务(`backend/api_service`)
|
||||
- **Task_Scheduler**: 基于 Celery Beat 的定时任务调度服务(`backend/task_scheduler`)
|
||||
- **Signin_Executor**: 签到执行 Worker 服务(`backend/signin_executor`)
|
||||
- **User**: 使用本系统的注册用户
|
||||
- **Weibo_Account**: 用户绑定到系统中的微博账号,以 Cookie 形式存储凭证
|
||||
- **Task**: 用户为某个 Weibo_Account 配置的定时签到任务
|
||||
- **Cookie**: 微博网站的登录凭证,用于模拟已登录状态
|
||||
- **Cron_Expression**: 标准 Cron 表达式,用于定义任务调度时间
|
||||
- **Signin_Log**: 每次签到执行的结果记录
|
||||
- **JWT**: JSON Web Token,用于用户身份认证
|
||||
- **Refresh_Token**: 用于在 Access Token 过期后获取新 Token 的长期凭证
|
||||
- **AES-256-GCM**: 对称加密算法,用于加密存储 Cookie
|
||||
|
||||
## 需求
|
||||
|
||||
### 需求 1:用户认证与 Token 管理
|
||||
|
||||
**用户故事:** 作为用户,我希望能够注册、登录并安全地维持会话,以便长期使用系统而无需频繁重新登录。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 用户提交有效的注册信息(用户名、邮箱、密码),THE Auth_Service SHALL 创建用户账户并返回用户信息
|
||||
2. WHEN 用户提交的用户名或邮箱已存在,THE Auth_Service SHALL 返回 409 Conflict 错误并指明冲突字段
|
||||
3. WHEN 用户提交有效的邮箱和密码进行登录,THE Auth_Service SHALL 返回包含 Access Token 和 Refresh Token 的认证响应
|
||||
4. WHEN 用户提交无效的邮箱或密码进行登录,THE Auth_Service SHALL 返回 401 Unauthorized 错误
|
||||
5. WHEN 用户携带有效的 Refresh Token 请求刷新,THE Auth_Service SHALL 返回新的 Access Token 和新的 Refresh Token
|
||||
6. WHEN 用户携带过期或无效的 Refresh Token 请求刷新,THE Auth_Service SHALL 返回 401 Unauthorized 错误
|
||||
7. WHEN 用户携带有效的 Access Token 请求 `/auth/me`,THE Auth_Service SHALL 返回当前用户的完整信息
|
||||
8. IF 用户密码不满足强度要求(至少8位,含大小写字母、数字和特殊字符),THEN THE Auth_Service SHALL 返回 400 Bad Request 并说明密码强度不足
|
||||
|
||||
### 需求 2:微博账号管理
|
||||
|
||||
**用户故事:** 作为用户,我希望能够添加、查看、更新和删除我的微博账号,以便集中管理多个微博账号的签到。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 用户提交微博 Cookie 和备注信息,THE API_Service SHALL 使用 AES-256-GCM 加密 Cookie 后存储,并返回新创建的账号信息
|
||||
2. WHEN 用户请求获取账号列表,THE API_Service SHALL 返回该用户拥有的所有 Weibo_Account(不包含解密后的 Cookie)
|
||||
3. WHEN 用户请求获取单个账号详情,THE API_Service SHALL 返回该账号的状态、备注和最近签到信息
|
||||
4. WHEN 用户请求更新账号的备注或 Cookie,THE API_Service SHALL 更新对应字段并返回更新后的账号信息
|
||||
5. WHEN 用户请求删除一个账号,THE API_Service SHALL 级联删除该账号关联的所有 Task 和 Signin_Log,并返回成功响应
|
||||
6. IF 用户尝试操作不属于自己的账号,THEN THE API_Service SHALL 返回 403 Forbidden 错误
|
||||
7. WHEN 账号被创建时,THE API_Service SHALL 将账号状态初始化为 "pending"
|
||||
8. THE API_Service SHALL 对所有账号管理接口要求有效的 JWT Access Token 认证
|
||||
|
||||
### 需求 3:Cookie 加密与验证
|
||||
|
||||
**用户故事:** 作为用户,我希望我的微博 Cookie 被安全存储,并且系统能自动检测 Cookie 是否失效,以便我及时更新。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 存储 Cookie 时,THE API_Service SHALL 使用 AES-256-GCM 算法加密,并将密文和 IV 分别存储到 `encrypted_cookies` 和 `iv` 字段
|
||||
2. WHEN 读取 Cookie 用于签到时,THE Signin_Executor SHALL 使用对应的 IV 解密 Cookie 并还原为原始字符串
|
||||
3. FOR ALL 有效的 Cookie 字符串,加密后再解密 SHALL 产生与原始字符串完全相同的结果(Round-trip 属性)
|
||||
4. IF 解密过程中发生错误(密钥不匹配、数据损坏),THEN THE System SHALL 将账号状态标记为 "invalid_cookie" 并记录错误日志
|
||||
|
||||
### 需求 4:定时签到任务配置
|
||||
|
||||
**用户故事:** 作为用户,我希望能够为每个微博账号配置独立的签到时间计划,以便灵活控制签到频率和时间。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 用户为某个账号创建签到任务并提供有效的 Cron_Expression,THE API_Service SHALL 创建任务记录并将任务注册到 Task_Scheduler
|
||||
2. WHEN 用户提交无效的 Cron_Expression,THE API_Service SHALL 返回 400 Bad Request 并说明表达式格式错误
|
||||
3. WHEN 用户请求获取某个账号的任务列表,THE API_Service SHALL 返回该账号关联的所有 Task 及其启用状态
|
||||
4. WHEN 用户启用或禁用一个任务,THE API_Service SHALL 更新数据库中的 `is_enabled` 字段,并同步更新 Task_Scheduler 中的调度状态
|
||||
5. WHEN 用户删除一个任务,THE API_Service SHALL 从数据库删除任务记录,并从 Task_Scheduler 中移除对应的调度
|
||||
6. IF 用户尝试为不属于自己的账号创建任务,THEN THE API_Service SHALL 返回 403 Forbidden 错误
|
||||
|
||||
### 需求 5:任务调度引擎
|
||||
|
||||
**用户故事:** 作为系统,我需要根据用户配置的 Cron 表达式准时触发签到任务,以确保签到按时执行。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN Task_Scheduler 启动时,THE Task_Scheduler SHALL 从数据库加载所有 `is_enabled=True` 的任务并注册到 Celery Beat 调度器
|
||||
2. WHEN Celery Beat 根据 Cron_Expression 触发一个任务,THE Task_Scheduler SHALL 向消息队列发送包含 `task_id` 和 `account_id` 的签到消息
|
||||
3. WHEN 新任务被创建或现有任务被更新,THE Task_Scheduler SHALL 动态更新 Celery Beat 的调度配置而无需重启服务
|
||||
4. IF 任务执行失败,THEN THE Task_Scheduler SHALL 按照配置的重试策略(最多3次,间隔60秒)进行重试
|
||||
5. WHILE Task_Scheduler 运行中,THE Task_Scheduler SHALL 使用 Redis 分布式锁确保同一任务不会被重复调度
|
||||
|
||||
### 需求 6:签到执行引擎
|
||||
|
||||
**用户故事:** 作为系统,我需要真正执行微博超话签到操作,并将结果持久化到数据库,以替代当前的 Mock 实现。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN Signin_Executor 从消息队列接收到签到任务,THE Signin_Executor SHALL 从数据库查询对应的 Weibo_Account 信息(替代 Mock 数据)
|
||||
2. WHEN 执行签到前,THE Signin_Executor SHALL 解密 Cookie 并验证其有效性
|
||||
3. WHEN Cookie 有效时,THE Signin_Executor SHALL 获取该账号关注的超话列表并逐一执行签到
|
||||
4. WHEN 单个超话签到完成后,THE Signin_Executor SHALL 将结果(成功/失败/已签到、奖励信息、错误信息)写入 `signin_logs` 表
|
||||
5. IF Cookie 已失效,THEN THE Signin_Executor SHALL 将账号状态更新为 "invalid_cookie" 并终止该账号的签到流程
|
||||
6. IF 签到过程中遇到网络错误,THEN THE Signin_Executor SHALL 记录错误日志并将该超话的签到状态标记为 "failed_network"
|
||||
|
||||
### 需求 7:反爬虫防护
|
||||
|
||||
**用户故事:** 作为系统,我需要在执行签到时采取反爬虫措施,以降低被微博风控系统检测和封禁的风险。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 执行签到请求时,THE Signin_Executor SHALL 在每次请求之间插入随机延迟(1-3秒可配置)
|
||||
2. WHEN 构造 HTTP 请求时,THE Signin_Executor SHALL 从预定义的 User-Agent 列表中随机选择一个
|
||||
3. WHEN 代理池服务可用时,THE Signin_Executor SHALL 为每次签到请求分配一个代理 IP
|
||||
4. IF 代理池服务不可用,THEN THE Signin_Executor SHALL 使用直连方式继续执行签到并记录警告日志
|
||||
|
||||
### 需求 8:签到日志与查询
|
||||
|
||||
**用户故事:** 作为用户,我希望能够查看每个微博账号的签到历史记录,以便了解签到执行情况。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 用户请求查看某个账号的签到日志,THE API_Service SHALL 返回按时间倒序排列的 Signin_Log 列表
|
||||
2. WHEN 用户请求签到日志时提供分页参数,THE API_Service SHALL 返回对应页码的日志数据和总记录数
|
||||
3. WHEN 用户请求签到日志时提供状态过滤参数,THE API_Service SHALL 仅返回匹配该状态的日志记录
|
||||
4. THE API_Service SHALL 对签到日志查询接口要求有效的 JWT Access Token 认证
|
||||
5. IF 用户尝试查看不属于自己账号的签到日志,THEN THE API_Service SHALL 返回 403 Forbidden 错误
|
||||
|
||||
### 需求 9:统一 API 响应格式与错误处理
|
||||
|
||||
**用户故事:** 作为 API 消费者,我希望所有接口返回统一格式的响应,以便前端能够一致地处理成功和错误情况。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE API_Service SHALL 对所有成功响应返回 `{"success": true, "data": ..., "message": ...}` 格式
|
||||
2. THE API_Service SHALL 对所有错误响应返回 `{"success": false, "data": null, "message": ..., "error": {"code": ..., "details": [...]}}` 格式
|
||||
3. WHEN 请求参数校验失败,THE API_Service SHALL 返回 400 状态码,并在 `error.details` 中列出每个字段的具体错误
|
||||
4. WHEN 未认证用户访问受保护接口,THE API_Service SHALL 返回 401 状态码和标准错误响应
|
||||
|
||||
185
.kiro/specs/multi-user-signin/tasks.md
Normal file
185
.kiro/specs/multi-user-signin/tasks.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# 实现计划:Weibo-HotSign 多用户签到系统
|
||||
|
||||
## 概述
|
||||
|
||||
按照自底向上的顺序实现:先构建共享基础层,再逐步实现各微服务。每个阶段包含核心实现和对应的测试任务。
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] 1. 创建共享模块 (shared/)
|
||||
- [x] 1.1 创建 `backend/shared/` 包结构和共享配置
|
||||
- 创建 `shared/__init__.py`、`shared/config.py`
|
||||
- 配置项包括 DATABASE_URL、REDIS_URL、JWT_SECRET_KEY、COOKIE_ENCRYPTION_KEY
|
||||
- 使用 pydantic-settings 从环境变量加载
|
||||
- _Requirements: 10.1, 10.2_
|
||||
- [x] 1.2 创建共享 ORM 模型和数据库连接管理
|
||||
- 创建 `shared/models/base.py`:AsyncEngine、AsyncSessionLocal、Base、get_db()
|
||||
- 创建 `shared/models/user.py`:User 模型(含 accounts relationship)
|
||||
- 创建 `shared/models/account.py`:Account 模型(含 tasks、signin_logs relationship)
|
||||
- 创建 `shared/models/task.py`:Task 模型
|
||||
- 创建 `shared/models/signin_log.py`:SigninLog 模型
|
||||
- 所有模型与 `init-db.sql` 中的表结构对齐
|
||||
- _Requirements: 10.1, 10.2, 10.3_
|
||||
- [x] 1.3 实现 Cookie 加密/解密工具 (`shared/crypto.py`)
|
||||
- 使用 pycryptodome 实现 AES-256-GCM 加密/解密
|
||||
- `encrypt_cookie(plaintext, key) -> (ciphertext_b64, iv_b64)`
|
||||
- `decrypt_cookie(ciphertext_b64, iv_b64, key) -> plaintext`
|
||||
- 密钥从环境变量 COOKIE_ENCRYPTION_KEY 派生(使用 SHA-256 哈希为32字节)
|
||||
- _Requirements: 3.1, 3.2, 10.4_
|
||||
- [ ]* 1.4 编写 Cookie 加密 Round-trip 属性测试
|
||||
- **Property 1: Cookie 加密 Round-trip**
|
||||
- 使用 hypothesis 生成随机字符串,验证 encrypt 后 decrypt 还原
|
||||
- **Validates: Requirements 3.1, 3.2, 3.3**
|
||||
- [x] 1.5 实现统一响应格式工具 (`shared/response.py`)
|
||||
- `success_response(data, message)` 返回标准成功格式
|
||||
- `error_response(message, code, details, status_code)` 返回标准错误格式
|
||||
- _Requirements: 9.1, 9.2_
|
||||
|
||||
- [x] 2. 重构 Auth_Service(Token 刷新机制)
|
||||
- [x] 2.1 重构 Auth_Service 使用 shared 模块
|
||||
- 修改 `auth_service/app/main.py` 导入 shared models 和 get_db
|
||||
- 删除 `auth_service/app/models/database.py` 中的重复 User 模型定义
|
||||
- 更新 `auth_service/app/services/auth_service.py` 使用 shared User 模型
|
||||
- _Requirements: 10.3_
|
||||
- [x] 2.2 实现 Refresh Token 机制
|
||||
- 在 `auth_service/app/utils/security.py` 中添加 `create_refresh_token()` 和 `verify_refresh_token()`
|
||||
- Refresh Token 使用 Redis 存储(key: `refresh_token:{hash}`, value: `user_id`, TTL: 7天)
|
||||
- 登录接口返回 access_token + refresh_token
|
||||
- 实现 `/auth/refresh` 端点:验证旧 token → 删除旧 token → 生成新 token 对(Token Rotation)
|
||||
- 更新 `auth_service/app/schemas/user.py` 添加 RefreshToken 相关 schema
|
||||
- _Requirements: 1.3, 1.5, 1.6_
|
||||
- [x] 2.3 为 Auth_Service 所有响应应用统一格式
|
||||
- 注册、登录、刷新、获取用户信息接口使用 `shared/response.py` 格式化响应
|
||||
- 注册全局异常处理器(HTTPException、RequestValidationError)
|
||||
- _Requirements: 9.1, 9.2, 9.3, 9.4_
|
||||
- [ ]* 2.4 编写认证流程属性测试
|
||||
- **Property 2: 用户注册后可登录获取信息**
|
||||
- **Property 3: 用户名/邮箱唯一性约束**
|
||||
- **Property 4: 无效凭证登录拒绝**
|
||||
- **Property 5: Refresh Token 轮换**
|
||||
- **Property 6: 弱密码拒绝**
|
||||
- **Validates: Requirements 1.1-1.8**
|
||||
|
||||
- [x] 3. Checkpoint - 确保共享模块和认证服务测试通过
|
||||
- 运行所有测试,确认 shared 模块和 auth_service 工作正常
|
||||
- 如有问题请向用户确认
|
||||
|
||||
- [x] 4. 实现 API_Service(账号管理)
|
||||
- [x] 4.1 创建 API_Service 基础结构
|
||||
- 创建 `api_service/app/__init__.py`、`main.py`、`config.py`、`dependencies.py`
|
||||
- `main.py`:FastAPI 应用,注册 CORS、全局异常处理器、路由
|
||||
- `dependencies.py`:JWT 认证依赖(`get_current_user`),复用 shared 的 JWT 验证逻辑
|
||||
- _Requirements: 2.8, 9.1, 9.2, 9.3, 9.4_
|
||||
- [x] 4.2 实现微博账号 CRUD 路由
|
||||
- 创建 `api_service/app/schemas/account.py`:AccountCreate、AccountUpdate、AccountResponse
|
||||
- 创建 `api_service/app/routers/accounts.py`:
|
||||
- `POST /api/v1/accounts`:加密 Cookie 后存储,状态初始化为 "pending"
|
||||
- `GET /api/v1/accounts`:返回当前用户的账号列表(不含 Cookie 明文)
|
||||
- `GET /api/v1/accounts/{id}`:返回账号详情
|
||||
- `PUT /api/v1/accounts/{id}`:更新备注或 Cookie(更新 Cookie 时重新加密)
|
||||
- `DELETE /api/v1/accounts/{id}`:删除账号(级联删除 tasks 和 logs)
|
||||
- 所有接口验证资源归属(user_id 匹配)
|
||||
- _Requirements: 2.1-2.8_
|
||||
- [ ]* 4.3 编写账号管理属性测试
|
||||
- **Property 7: 账号创建与列表一致性**
|
||||
- **Property 8: 账号详情 Round-trip**
|
||||
- **Property 9: 账号更新反映**
|
||||
- **Property 10: 账号删除级联**
|
||||
- **Property 11: 跨用户资源隔离**
|
||||
- **Property 12: 受保护接口认证要求**
|
||||
- **Validates: Requirements 2.1-2.8, 4.6, 8.5, 8.4, 9.4**
|
||||
|
||||
- [-] 5. 实现 API_Service(任务配置)
|
||||
- [x] 5.1 实现签到任务 CRUD 路由
|
||||
- 创建 `api_service/app/schemas/task.py`:TaskCreate、TaskUpdate、TaskResponse
|
||||
- 创建 `api_service/app/routers/tasks.py`:
|
||||
- `POST /api/v1/accounts/{id}/tasks`:验证 Cron 表达式有效性,创建任务
|
||||
- `GET /api/v1/accounts/{id}/tasks`:获取账号的任务列表
|
||||
- `PUT /api/v1/tasks/{id}`:更新任务(启用/禁用)
|
||||
- `DELETE /api/v1/tasks/{id}`:删除任务
|
||||
- 使用 `croniter` 库验证 Cron 表达式
|
||||
- 任务创建/更新/删除时通过 Redis pub/sub 通知 Task_Scheduler
|
||||
- _Requirements: 4.1-4.6_
|
||||
- [ ]* 5.2 编写任务配置属性测试
|
||||
- **Property 13: 有效 Cron 表达式创建任务**
|
||||
- **Property 14: 无效 Cron 表达式拒绝**
|
||||
- **Property 15: 任务启用/禁用切换**
|
||||
- **Property 16: 任务删除**
|
||||
- **Validates: Requirements 4.1-4.6**
|
||||
|
||||
- [x] 6. 实现 API_Service(签到日志查询)
|
||||
- [x] 6.1 实现签到日志查询路由
|
||||
- 创建 `api_service/app/schemas/signin_log.py`:SigninLogResponse、PaginatedResponse
|
||||
- 创建 `api_service/app/routers/signin_logs.py`:
|
||||
- `GET /api/v1/accounts/{id}/signin-logs`:支持分页(page, size)和状态过滤(status)
|
||||
- 返回按 `signed_at` 降序排列的日志
|
||||
- 返回总记录数用于前端分页
|
||||
- 验证账号归属权限
|
||||
- _Requirements: 8.1-8.5_
|
||||
- [ ]* 6.2 编写签到日志查询属性测试
|
||||
- **Property 23: 签到日志时间倒序**
|
||||
- **Property 24: 签到日志分页**
|
||||
- **Property 25: 签到日志状态过滤**
|
||||
- **Validates: Requirements 8.1-8.3**
|
||||
|
||||
- [ ] 7. Checkpoint - 确保 API_Service 所有测试通过
|
||||
- 运行所有测试,确认账号管理、任务配置、日志查询功能正常
|
||||
- 如有问题请向用户确认
|
||||
|
||||
- [ ] 8. 重构 Task_Scheduler(真实数据库交互)
|
||||
- [ ] 8.1 重构 Task_Scheduler 使用 shared 模块
|
||||
- 修改 `task_scheduler/app/celery_app.py` 导入 shared models
|
||||
- 实现 `load_scheduled_tasks()`:从 DB 查询 `is_enabled=True` 的 Task,动态注册到 Celery Beat
|
||||
- 实现 Redis pub/sub 监听:接收任务变更通知,动态更新调度
|
||||
- 替换 `signin_tasks.py` 中的 mock 账号列表为真实 DB 查询
|
||||
- _Requirements: 5.1, 5.2, 5.3_
|
||||
- [ ] 8.2 实现分布式锁和重试机制
|
||||
- 使用 Redis SETNX 实现分布式锁,防止同一任务重复调度
|
||||
- 配置 Celery 任务重试:`max_retries=3`、`default_retry_delay=60`
|
||||
- _Requirements: 5.4, 5.5_
|
||||
- [ ]* 8.3 编写调度器属性测试
|
||||
- **Property 17: 调度器加载已启用任务**
|
||||
- **Property 18: 分布式锁防重复调度**
|
||||
- **Validates: Requirements 5.1, 5.5**
|
||||
|
||||
- [ ] 9. 重构 Signin_Executor(真实数据库交互)
|
||||
- [ ] 9.1 重构 Signin_Executor 使用 shared 模块
|
||||
- 修改 `signin_service.py` 中的 `_get_account_info()` 从 DB 查询真实 Account 数据
|
||||
- 修改 `weibo_client.py` 中的 `_decrypt_cookies()` 使用 `shared/crypto.py`
|
||||
- 实现签到结果写入 `signin_logs` 表(替代 mock)
|
||||
- 实现 Cookie 失效时更新 `account.status = "invalid_cookie"`
|
||||
- _Requirements: 6.1, 6.2, 6.4, 6.5_
|
||||
- [ ] 9.2 实现反爬虫防护模块
|
||||
- 实现随机延迟函数:返回 `[min, max]` 范围内的随机值
|
||||
- 实现 User-Agent 轮换:从预定义列表中随机选择
|
||||
- 实现代理池集成:调用 proxy pool 服务获取代理,不可用时降级为直连
|
||||
- _Requirements: 7.1, 7.2, 7.3, 7.4_
|
||||
- [ ]* 9.3 编写签到执行属性测试
|
||||
- **Property 19: 签到结果持久化**
|
||||
- **Property 20: Cookie 失效时更新账号状态**
|
||||
- **Property 21: 随机延迟范围**
|
||||
- **Property 22: User-Agent 来源**
|
||||
- **Validates: Requirements 6.1, 6.4, 6.5, 7.1, 7.2**
|
||||
|
||||
- [ ] 10. 更新 Dockerfile 和集成配置
|
||||
- [ ] 10.1 更新 `backend/Dockerfile`
|
||||
- 在每个构建阶段添加 `COPY shared/ ./shared/`
|
||||
- 确保 shared 模块在所有服务容器中可用
|
||||
- _Requirements: 10.1, 10.3_
|
||||
- [ ] 10.2 更新 `backend/requirements.txt`
|
||||
- 添加 `croniter`(Cron 表达式解析)
|
||||
- 添加 `hypothesis`(属性测试)
|
||||
- 添加 `pytest`、`pytest-asyncio`(测试框架)
|
||||
- 确认 `pycryptodome`、`redis`、`celery` 等已存在
|
||||
|
||||
- [ ] 11. 最终 Checkpoint - 全量测试
|
||||
- 运行所有单元测试和属性测试
|
||||
- 验证各服务可独立启动
|
||||
- 如有问题请向用户确认
|
||||
|
||||
## 备注
|
||||
|
||||
- 标记 `*` 的任务为可选测试任务,可跳过以加快 MVP 进度
|
||||
- 每个任务引用了具体的需求编号以确保可追溯性
|
||||
- 属性测试使用 `hypothesis` 库,每个测试至少运行 100 次迭代
|
||||
- Checkpoint 任务用于阶段性验证,确保增量开发的正确性
|
||||
Reference in New Issue
Block a user