docs(07): create phase 7 login auth plans
This commit is contained in:
@@ -90,12 +90,12 @@ Plans:
|
||||
2. 扫码失败时用户可以手动设置 Cookie 进行认证
|
||||
3. Cookie 自动保存到本地文件,下次运行自动复用
|
||||
4. Cookie 过期时系统自动检测并提示用户重新登录
|
||||
**Plans**: TBD
|
||||
**Plans**: 3 plans
|
||||
|
||||
Plans:
|
||||
- [ ] 07-01: 二维码扫码登录实现 (AUTH-01)
|
||||
- [ ] 07-02: 手动 Cookie 设置和持久化 (AUTH-02, AUTH-03)
|
||||
- [ ] 07-03: Cookie 过期检测机制 (AUTH-04)
|
||||
- [ ] 07-01-PLAN.md — 二维码扫码登录核心逻辑 (AUTH-01) + Wave 0 测试脚手架
|
||||
- [ ] 07-02-PLAN.md — Cookie 导入和持久化管理 (AUTH-02, AUTH-03)
|
||||
- [ ] 07-03-PLAN.md — Cookie 验证 + CLI login 子命令 (AUTH-04)
|
||||
|
||||
#### Phase 8: 内容爬取实现
|
||||
**Goal**: 实现知乎问答、专栏/文章、评论的完整爬取功能,包含网络反爬措施
|
||||
@@ -140,6 +140,6 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 →
|
||||
| 4. Pipeline | v1.0 | 2/2 | Complete | 2026-03-15 |
|
||||
| 5. 归档与目录优化 | v1.1 | 3/3 | Complete | 2026-03-16 |
|
||||
| 6. 代码整合 | v1.1 | 3/3 | Complete | 2026-03-16 |
|
||||
| 7. 登录认证体系 | v1.2 | 0/3 | Not started | - |
|
||||
| 7. 登录认证体系 | v1.2 | 0/3 | Ready to execute | - |
|
||||
| 8. 内容爬取实现 | v1.2 | 0/3 | Not started | - |
|
||||
| 9. Pipeline 整合 | v1.2 | 0/1 | Not started | - |
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
---
|
||||
phase: 07-login-auth
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- kb_workflow/auth/__init__.py
|
||||
- kb_workflow/auth/zhihu_login.py
|
||||
- tests/test_auth/__init__.py
|
||||
- tests/test_auth/conftest.py
|
||||
- tests/test_auth/test_qrcode_login.py
|
||||
autonomous: true
|
||||
requirements:
|
||||
- AUTH-01
|
||||
user_setup: []
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "用户可以访问知乎登录页面"
|
||||
- "系统可以从 Canvas 元素截取二维码图片"
|
||||
- "二维码图片保存到中间目录"
|
||||
- "系统可以轮询检测 z_c0 Cookie 判断登录状态"
|
||||
artifacts:
|
||||
- path: "kb_workflow/auth/zhihu_login.py"
|
||||
provides: "二维码登录核心逻辑"
|
||||
min_lines: 100
|
||||
exports: ["ZhihuLogin", "ZhihuLoginResult"]
|
||||
- path: "tests/test_auth/test_qrcode_login.py"
|
||||
provides: "登录逻辑单元测试"
|
||||
exports: ["test_login_page_access", "test_qrcode_screenshot", "test_cookie_detection"]
|
||||
key_links:
|
||||
- from: "kb_workflow/auth/zhihu_login.py"
|
||||
to: "PlaywrightBrowser"
|
||||
via: "import from kb_workflow.metadata.zhihu_extractor"
|
||||
pattern: "from kb_workflow.metadata.zhihu_extractor import PlaywrightBrowser"
|
||||
- from: "ZhihuLogin.login_by_qrcode()"
|
||||
to: "canvas.screenshot()"
|
||||
via: "Playwright element screenshot API"
|
||||
pattern: "canvas.*screenshot"
|
||||
---
|
||||
|
||||
<objective>
|
||||
实现知乎二维码扫码登录核心逻辑。
|
||||
|
||||
Purpose: 提供二维码登录能力,访问登录页 → Canvas 截图 → 轮询检测 Cookie
|
||||
Output: kb_workflow/auth/zhihu_login.py 模块 + Wave 0 测试脚手架
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/starmen1265/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/07-login-auth/07-CONTEXT.md
|
||||
@.planning/phases/07-login-auth/07-RESEARCH.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Key interfaces from existing codebase -->
|
||||
|
||||
From kb_workflow/metadata/zhihu_extractor.py:
|
||||
```python
|
||||
class PlaywrightBrowser:
|
||||
def __init__(self, headless: bool = True, timeout: int = 30)
|
||||
def start(self, cookie: str = None)
|
||||
def close(self)
|
||||
def goto(self, url: str, wait_until: str = "networkidle")
|
||||
def wait_for_selector(self, selector: str, timeout: int = None)
|
||||
def query_selector(self, selector: str)
|
||||
def evaluate(self, script: str)
|
||||
# context 属性可用于获取 cookies
|
||||
self.context # BrowserContext
|
||||
self.page # Page
|
||||
```
|
||||
|
||||
From kb_workflow/pipeline/config.py:
|
||||
```python
|
||||
class KBSettings(BaseSettings):
|
||||
intermediate_path: str # 中间目录路径
|
||||
zhihu_cookie: SecretStr # 知乎 Cookie
|
||||
```
|
||||
|
||||
From kb_workflow/pipeline/schemas.py:
|
||||
```python
|
||||
class StepOutput(BaseModel):
|
||||
success: bool
|
||||
error_message: Optional[str]
|
||||
completed_at: datetime
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: 创建测试模块和 fixtures</name>
|
||||
<files>tests/test_auth/__init__.py, tests/test_auth/conftest.py</files>
|
||||
<action>
|
||||
创建 Wave 0 测试脚手架:
|
||||
|
||||
1. 创建 `tests/test_auth/__init__.py` 空文件
|
||||
|
||||
2. 创建 `tests/test_auth/conftest.py` 包含:
|
||||
- `mock_browser` fixture: 使用 unittest.mock 模拟 PlaywrightBrowser
|
||||
- `mock_env_file` fixture: 使用 tmp_path 创建临时 ~/.kb_env
|
||||
- `sample_cookies` fixture: 返回测试用 Cookie 数据
|
||||
|
||||
参考 pytest fixture 模式,确保 mock_browser 可以:
|
||||
- 模拟 page.wait_for_selector() 返回 mock canvas
|
||||
- 模拟 canvas.screenshot() 返回 bytes
|
||||
- 模拟 context.cookies() 返回 Cookie 列表
|
||||
</action>
|
||||
<verify>
|
||||
<automated>python -c "from tests.test_auth.conftest import *; print('fixtures loaded')"</automated>
|
||||
</verify>
|
||||
<done>测试模块和 fixtures 可被 pytest 加载</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: 实现 ZhihuLogin 核心类</name>
|
||||
<files>kb_workflow/auth/__init__.py, kb_workflow/auth/zhihu_login.py, tests/test_auth/test_qrcode_login.py</files>
|
||||
<behavior>
|
||||
- Test 1: login_page_access - 访问登录页成功,URL 正确
|
||||
- Test 2: qrcode_screenshot - Canvas 截图返回 bytes,保存到正确路径
|
||||
- Test 3: cookie_detection - 检测 z_c0 Cookie 存在返回 True,不存在返回 False
|
||||
- Test 4: login_timeout - 60秒超时后抛出 TimeoutError
|
||||
- Test 5: cookie_extraction - 登录成功后提取 Cookie 字符串
|
||||
</behavior>
|
||||
<action>
|
||||
创建 `kb_workflow/auth/` 模块并实现 ZhihuLogin 类:
|
||||
|
||||
1. 创建 `kb_workflow/auth/__init__.py`:
|
||||
```python
|
||||
from .zhihu_login import ZhihuLogin, ZhihuLoginResult
|
||||
__all__ = ["ZhihuLogin", "ZhihuLoginResult"]
|
||||
```
|
||||
|
||||
2. 创建 `kb_workflow/auth/zhihu_login.py`:
|
||||
- `ZhihuLoginResult` Pydantic model: success, cookie_str, qrcode_path, error_message
|
||||
- `ZhihuLogin` 类:
|
||||
- `LOGIN_URL = "https://www.zhihu.com/signin"`
|
||||
- `CANVAS_SELECTORS = ["canvas.SignIn-qrcode", "canvas[class*='qrcode']", "canvas"]`
|
||||
- `__init__(self, intermediate_path: str, headless: bool = True, timeout: int = 60)`
|
||||
- `async def _find_qrcode_canvas(self) -> ElementHandle` - 尝试多个选择器
|
||||
- `async def _save_qrcode_image(self, screenshot: bytes) -> Path` - 保存到中间目录
|
||||
- `async def check_login_cookie(self) -> bool` - 检测 z_c0 Cookie
|
||||
- `async def get_cookies_str(self) -> str` - 提取 Cookie 字符串
|
||||
- `async def login_by_qrcode(self) -> ZhihuLoginResult` - 主流程
|
||||
|
||||
使用 tenacity 的 @retry 装饰器实现 60 秒轮询:
|
||||
```python
|
||||
from tenacity import retry, stop_after_delay, wait_fixed, RetryError
|
||||
|
||||
@retry(stop=stop_after_delay(60), wait=wait_fixed(1), reraise=True)
|
||||
async def _wait_for_login(self):
|
||||
if not await self.check_login_cookie():
|
||||
raise Exception("Not logged in yet")
|
||||
```
|
||||
|
||||
二维码图片路径格式: `~/work/intermediate/zhihu_qrcode_{timestamp}.png`
|
||||
|
||||
3. 创建 `tests/test_auth/test_qrcode_login.py`:
|
||||
- test_login_page_access: mock browser.goto 验证 URL
|
||||
- test_qrcode_screenshot: mock canvas.screenshot 验证保存
|
||||
- test_cookie_detection: mock context.cookies 验证检测逻辑
|
||||
- test_login_timeout: mock check_login_cookie 始终返回 False
|
||||
- test_cookie_extraction: 验证 Cookie 字符串格式
|
||||
|
||||
安装依赖: pip install tenacity
|
||||
</action>
|
||||
<verify>
|
||||
<automated>pytest tests/test_auth/test_qrcode_login.py -x -v</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- ZhihuLogin 类可实例化
|
||||
- 所有单元测试通过
|
||||
- 二维码截图功能可用
|
||||
- Cookie 检测功能可用
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
1. 单元测试全部通过: `pytest tests/test_auth/test_qrcode_login.py -v`
|
||||
2. ZhihuLogin 类可导入: `python -c "from kb_workflow.auth import ZhihuLogin; print(ZhihuLogin)"`
|
||||
3. tenacity 依赖已安装: `pip show tenacity`
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Wave 0 测试脚手架就绪 (tests/test_auth/)
|
||||
- ZhihuLogin 类实现二维码截图和 Cookie 检测
|
||||
- 5 个单元测试全部通过
|
||||
- 代码符合现有项目模式 (pydantic, Playwright)
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/07-login-auth/07-01-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,299 @@
|
||||
---
|
||||
phase: 07-login-auth
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- kb_workflow/auth/cookie_manager.py
|
||||
- tests/test_auth/test_cookie_import.py
|
||||
- tests/test_auth/test_cookie_persistence.py
|
||||
autonomous: true
|
||||
requirements:
|
||||
- AUTH-02
|
||||
- AUTH-03
|
||||
user_setup: []
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "用户可以从 JSON 文件导入 Cookie"
|
||||
- "Cookie 可以保存到 ~/.kb_env 文件"
|
||||
- "保存的 Cookie 下次运行时可以自动加载"
|
||||
- "JSON Cookie 格式被正确解析为字符串格式"
|
||||
artifacts:
|
||||
- path: "kb_workflow/auth/cookie_manager.py"
|
||||
provides: "Cookie 导入和持久化"
|
||||
min_lines: 80
|
||||
exports: ["CookieManager", "parse_json_cookies"]
|
||||
- path: "tests/test_auth/test_cookie_import.py"
|
||||
provides: "Cookie 导入测试"
|
||||
- path: "tests/test_auth/test_cookie_persistence.py"
|
||||
provides: "Cookie 持久化测试"
|
||||
key_links:
|
||||
- from: "CookieManager.save_cookie()"
|
||||
to: "~/.kb_env"
|
||||
via: "文件写入"
|
||||
pattern: "Path\\.home\\(\\).*\\.kb_env"
|
||||
- from: "parse_json_cookies()"
|
||||
to: "Chrome Cookie Editor JSON"
|
||||
via: "json.load + 过滤 zhihu.com"
|
||||
pattern: "zhihu\\.com.*domain"
|
||||
---
|
||||
|
||||
<objective>
|
||||
实现 Cookie 导入和持久化管理。
|
||||
|
||||
Purpose: 支持 JSON Cookie 导入和 ~/.kb_env 文件持久化
|
||||
Output: kb_workflow/auth/cookie_manager.py 模块 + 单元测试
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/starmen1265/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/07-login-auth/07-CONTEXT.md
|
||||
@.planning/phases/07-login-auth/07-RESEARCH.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Key interfaces from existing codebase -->
|
||||
|
||||
From kb_workflow/pipeline/config.py:
|
||||
```python
|
||||
class KBSettings(BaseSettings):
|
||||
zhihu_cookie: SecretStr = Field(default=SecretStr(""), description="知乎 Cookie")
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=str(Path.home() / ".kb_env"),
|
||||
env_file_encoding="utf-8",
|
||||
env_prefix="KB_",
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
@lru_cache
|
||||
def get_settings() -> KBSettings:
|
||||
return KBSettings()
|
||||
```
|
||||
|
||||
Chrome Cookie Editor JSON 格式示例:
|
||||
```json
|
||||
[
|
||||
{"name": "z_c0", "value": "xxx", "domain": ".zhihu.com"},
|
||||
{"name": "_zap", "value": "yyy", "domain": ".zhihu.com"}
|
||||
]
|
||||
```
|
||||
|
||||
目标存储格式 (~/.kb_env):
|
||||
```
|
||||
ZHIHU_COOKIE="z_c0=xxx; _zap=yyy"
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: 实现 Cookie JSON 解析功能</name>
|
||||
<files>kb_workflow/auth/cookie_manager.py, tests/test_auth/test_cookie_import.py</files>
|
||||
<behavior>
|
||||
- Test 1: parse_json_cookies - 解析 Chrome Cookie Editor JSON 格式
|
||||
- Test 2: parse_json_cookies_filters_domain - 只保留 zhihu.com 域名
|
||||
- Test 3: parse_json_cookies_empty - 空数组返回空字符串
|
||||
- Test 4: parse_json_cookies_malformed - 格式错误抛出 ValueError
|
||||
</behavior>
|
||||
<action>
|
||||
在 `kb_workflow/auth/cookie_manager.py` 中实现 Cookie 解析功能:
|
||||
|
||||
1. 创建 `kb_workflow/auth/cookie_manager.py`:
|
||||
|
||||
```python
|
||||
"""Cookie 管理模块
|
||||
|
||||
处理 Cookie 的导入、转换和持久化。
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Any
|
||||
|
||||
def parse_json_cookies(json_path: str) -> str:
|
||||
"""从 JSON 文件解析 Cookie 为字符串格式
|
||||
|
||||
支持 Chrome Cookie Editor 导出的 JSON 格式:
|
||||
[{"name": "z_c0", "value": "xxx", "domain": ".zhihu.com"}, ...]
|
||||
|
||||
Args:
|
||||
json_path: JSON 文件路径
|
||||
|
||||
Returns:
|
||||
Cookie 字符串,格式: "name1=value1; name2=value2"
|
||||
|
||||
Raises:
|
||||
ValueError: JSON 格式无效或文件不存在
|
||||
"""
|
||||
path = Path(json_path)
|
||||
if not path.exists():
|
||||
raise ValueError(f"Cookie file not found: {json_path}")
|
||||
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
cookies = json.load(f)
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"Invalid JSON format: {e}")
|
||||
|
||||
if not isinstance(cookies, list):
|
||||
raise ValueError("Expected JSON array")
|
||||
|
||||
# 过滤知乎域名,转换为 name=value 格式
|
||||
zhihu_cookies = []
|
||||
for c in cookies:
|
||||
if not isinstance(c, dict):
|
||||
continue
|
||||
domain = c.get('domain', '')
|
||||
if 'zhihu.com' in domain:
|
||||
name = c.get('name', '')
|
||||
value = c.get('value', '')
|
||||
if name and value:
|
||||
zhihu_cookies.append(f"{name}={value}")
|
||||
|
||||
return '; '.join(zhihu_cookies)
|
||||
```
|
||||
|
||||
2. 创建 `tests/test_auth/test_cookie_import.py`:
|
||||
- 使用 tmp_path fixture 创建临时 JSON 文件
|
||||
- 测试正常解析、域名过滤、空数组、格式错误等情况
|
||||
</action>
|
||||
<verify>
|
||||
<automated>pytest tests/test_auth/test_cookie_import.py -x -v</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- parse_json_cookies 函数可用
|
||||
- 4 个单元测试全部通过
|
||||
- 正确过滤 zhihu.com 域名
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: 实现 Cookie 持久化管理器</name>
|
||||
<files>kb_workflow/auth/cookie_manager.py, tests/test_auth/test_cookie_persistence.py</files>
|
||||
<behavior>
|
||||
- Test 1: save_cookie_creates_file - 保存 Cookie 创建 ~/.kb_env 文件
|
||||
- Test 2: save_cookie_updates_existing - 更新现有 ZHIHU_COOKIE 行
|
||||
- Test 3: save_cookie_appends_new - 不存在时追加新行
|
||||
- Test 4: save_cookie_preserves_other_lines - 保留其他配置行
|
||||
- Test 5: load_cookie_returns_saved - 加载已保存的 Cookie
|
||||
- Test 6: load_cookie_empty_if_not_exists - 文件不存在返回空字符串
|
||||
</behavior>
|
||||
<action>
|
||||
扩展 `kb_workflow/auth/cookie_manager.py` 添加 CookieManager 类:
|
||||
|
||||
```python
|
||||
from functools import lru_cache
|
||||
|
||||
class CookieManager:
|
||||
"""Cookie 持久化管理器
|
||||
|
||||
管理 Cookie 在 ~/.kb_env 文件中的存储和读取。
|
||||
"""
|
||||
|
||||
ENV_FILE_NAME = ".kb_env"
|
||||
COOKIE_KEY = "ZHIHU_COOKIE"
|
||||
|
||||
def __init__(self, env_path: Path = None):
|
||||
"""初始化管理器
|
||||
|
||||
Args:
|
||||
env_path: 环境文件路径,默认 ~/.kb_env
|
||||
"""
|
||||
self.env_path = env_path or (Path.home() / self.ENV_FILE_NAME)
|
||||
|
||||
def save_cookie(self, cookie_str: str) -> None:
|
||||
"""保存 Cookie 到环境文件
|
||||
|
||||
格式: ZHIHU_COOKIE="name1=value1; name2=value2"
|
||||
|
||||
Args:
|
||||
cookie_str: Cookie 字符串
|
||||
"""
|
||||
# 读取现有内容
|
||||
lines = []
|
||||
if self.env_path.exists():
|
||||
lines = self.env_path.read_text(encoding='utf-8').splitlines()
|
||||
|
||||
# 更新或添加 ZHIHU_COOKIE 行
|
||||
cookie_line = f'{self.COOKIE_KEY}="{cookie_str}"'
|
||||
found = False
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith(f"{self.COOKIE_KEY}="):
|
||||
lines[i] = cookie_line
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
lines.append(cookie_line)
|
||||
|
||||
# 写回文件,设置权限 600
|
||||
self.env_path.write_text('\n'.join(lines) + '\n', encoding='utf-8')
|
||||
self.env_path.chmod(0o600)
|
||||
|
||||
# 清除配置缓存
|
||||
from kb_workflow.pipeline.config import get_settings
|
||||
get_settings.cache_clear()
|
||||
|
||||
def load_cookie(self) -> str:
|
||||
"""从环境文件加载 Cookie
|
||||
|
||||
Returns:
|
||||
Cookie 字符串,不存在时返回空字符串
|
||||
"""
|
||||
if not self.env_path.exists():
|
||||
return ""
|
||||
|
||||
content = self.env_path.read_text(encoding='utf-8')
|
||||
for line in content.splitlines():
|
||||
if line.startswith(f"{self.COOKIE_KEY}="):
|
||||
# 提取引号内的值
|
||||
value = line[len(f"{self.COOKIE_KEY}="):]
|
||||
if value.startswith('"') and value.endswith('"'):
|
||||
value = value[1:-1]
|
||||
return value
|
||||
return ""
|
||||
```
|
||||
|
||||
创建 `tests/test_auth/test_cookie_persistence.py`:
|
||||
- 使用 tmp_path fixture 创建临时环境文件
|
||||
- 测试保存、更新、追加、加载等功能
|
||||
- 测试文件权限设置
|
||||
</action>
|
||||
<verify>
|
||||
<automated>pytest tests/test_auth/test_cookie_persistence.py -x -v</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- CookieManager 类可用
|
||||
- 6 个单元测试全部通过
|
||||
- Cookie 可正确保存到 ~/.kb_env
|
||||
- 保存后自动清除配置缓存
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
1. Cookie 导入测试通过: `pytest tests/test_auth/test_cookie_import.py -v`
|
||||
2. Cookie 持久化测试通过: `pytest tests/test_auth/test_cookie_persistence.py -v`
|
||||
3. 模块可导入: `python -c "from kb_workflow.auth.cookie_manager import CookieManager, parse_json_cookies; print('OK')"`
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- parse_json_cookies 函数支持 Chrome Cookie Editor 格式
|
||||
- CookieManager 类支持保存和加载 Cookie
|
||||
- ~/.kb_env 文件权限正确设置为 600
|
||||
- 保存后自动清除 get_settings 缓存
|
||||
- 10 个单元测试全部通过
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/07-login-auth/07-02-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,341 @@
|
||||
---
|
||||
phase: 07-login-auth
|
||||
plan: 03
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on:
|
||||
- 07-01
|
||||
- 07-02
|
||||
files_modified:
|
||||
- kb_workflow/auth/zhihu_login.py
|
||||
- kb_workflow/auth/cookie_manager.py
|
||||
- kb_workflow/kb_workflow.py
|
||||
- tests/test_auth/test_cookie_validation.py
|
||||
- tests/test_auth/test_cli_login.py
|
||||
autonomous: false
|
||||
requirements:
|
||||
- AUTH-04
|
||||
user_setup: []
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "用户可以通过 python3 kb-workflow/kb_workflow.py login 启动登录流程"
|
||||
- "登录成功后 Cookie 自动保存到 ~/.kb_env"
|
||||
- "Cookie 过期时系统自动检测并提示用户"
|
||||
- "用户可以手动导入 Cookie 文件"
|
||||
- "用户可以查看当前 Cookie 状态"
|
||||
artifacts:
|
||||
- path: "kb_workflow/auth/zhihu_login.py"
|
||||
provides: "Cookie 验证方法"
|
||||
exports: ["validate_cookie"]
|
||||
- path: "kb_workflow/kb_workflow.py"
|
||||
provides: "login CLI 子命令"
|
||||
contains: "subparsers.add_parser('login')"
|
||||
key_links:
|
||||
- from: "kb_workflow.py login"
|
||||
to: "ZhihuLogin.login_by_qrcode()"
|
||||
via: "asyncio.run()"
|
||||
pattern: "login_by_qrcode"
|
||||
- from: "ZhihuLogin.login_by_qrcode()"
|
||||
to: "CookieManager.save_cookie()"
|
||||
via: "登录成功后保存"
|
||||
pattern: "save_cookie"
|
||||
- from: "validate_cookie()"
|
||||
to: "/api/v4/me"
|
||||
via: "Playwright request API"
|
||||
pattern: "api/v4/me"
|
||||
---
|
||||
|
||||
<objective>
|
||||
实现 Cookie 验证机制和 CLI login 子命令。
|
||||
|
||||
Purpose: 整合登录功能到 CLI,支持 Cookie 验证和状态查询
|
||||
Output: 完整的 login 命令 + Cookie 验证 + 端到端测试
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/starmen1265/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/07-login-auth/07-CONTEXT.md
|
||||
@.planning/phases/07-login-auth/07-RESEARCH.md
|
||||
@.planning/phases/07-login-auth/07-01-PLAN.md
|
||||
@.planning/phases/07-login-auth/07-02-PLAN.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Key interfaces from prior plans -->
|
||||
|
||||
From kb_workflow/auth/zhihu_login.py (Plan 01):
|
||||
```python
|
||||
class ZhihuLogin:
|
||||
async def login_by_qrcode(self) -> ZhihuLoginResult
|
||||
async def check_login_cookie(self) -> bool
|
||||
async def get_cookies_str(self) -> str
|
||||
```
|
||||
|
||||
From kb_workflow/auth/cookie_manager.py (Plan 02):
|
||||
```python
|
||||
def parse_json_cookies(json_path: str) -> str
|
||||
|
||||
class CookieManager:
|
||||
def save_cookie(self, cookie_str: str) -> None
|
||||
def load_cookie(self) -> str
|
||||
```
|
||||
|
||||
From kb_workflow/kb_workflow.py (existing):
|
||||
```python
|
||||
def process_command(url: str, ...) -> Dict[str, Any]
|
||||
|
||||
def main():
|
||||
subparsers = parser.add_subparsers(dest='command')
|
||||
# 添加 login 子命令到此处
|
||||
```
|
||||
|
||||
知乎 API:
|
||||
- 验证接口: GET https://www.zhihu.com/api/v4/me
|
||||
- 成功返回: {"id": 123, "name": "用户名", ...}
|
||||
- 失败返回: 401 状态码
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: 实现 Cookie 验证功能</name>
|
||||
<files>kb_workflow/auth/zhihu_login.py, tests/test_auth/test_cookie_validation.py</files>
|
||||
<behavior>
|
||||
- Test 1: validate_cookie_valid - 有效 Cookie 返回 valid=True 和用户信息
|
||||
- Test 2: validate_cookie_expired - 过期 Cookie 返回 valid=False
|
||||
- Test 3: validate_cookie_empty - 空 Cookie 返回 valid=False
|
||||
- Test 4: validate_cookie_malformed - 格式错误 Cookie 返回 valid=False
|
||||
</behavior>
|
||||
<action>
|
||||
扩展 `kb_workflow/auth/zhihu_login.py` 添加 Cookie 验证功能:
|
||||
|
||||
1. 添加 `CookieValidationResult` Pydantic model:
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
class CookieValidationResult(BaseModel):
|
||||
"""Cookie 验证结果"""
|
||||
valid: bool
|
||||
username: Optional[str] = None
|
||||
user_id: Optional[str] = None
|
||||
reason: Optional[str] = None
|
||||
```
|
||||
|
||||
2. 在 `ZhihuLogin` 类中添加验证方法:
|
||||
```python
|
||||
VALIDATION_URL = "https://www.zhihu.com/api/v4/me"
|
||||
|
||||
async def validate_cookie(self, cookie_str: str = None) -> CookieValidationResult:
|
||||
"""验证 Cookie 有效性
|
||||
|
||||
通过请求知乎用户信息接口检测 Cookie 是否有效。
|
||||
|
||||
Args:
|
||||
cookie_str: 要验证的 Cookie 字符串,为空时使用已加载的 Cookie
|
||||
|
||||
Returns:
|
||||
CookieValidationResult 包含验证状态和用户信息
|
||||
"""
|
||||
# 如果提供了 cookie_str,需要重新启动浏览器并设置 Cookie
|
||||
if cookie_str:
|
||||
if self.browser.browser:
|
||||
self.browser.close()
|
||||
self.browser.start(cookie=cookie_str)
|
||||
elif not self.browser.page:
|
||||
return CookieValidationResult(valid=False, reason="No cookie provided")
|
||||
|
||||
try:
|
||||
# 使用 Playwright 的 request API
|
||||
response = await self.browser.page.request.get(self.VALIDATION_URL)
|
||||
|
||||
if response.status == 200:
|
||||
user_info = await response.json()
|
||||
return CookieValidationResult(
|
||||
valid=True,
|
||||
username=user_info.get("name", ""),
|
||||
user_id=str(user_info.get("id", ""))
|
||||
)
|
||||
elif response.status == 401:
|
||||
return CookieValidationResult(valid=False, reason="Cookie expired")
|
||||
else:
|
||||
return CookieValidationResult(
|
||||
valid=False,
|
||||
reason=f"Validation failed with status {response.status}"
|
||||
)
|
||||
except Exception as e:
|
||||
return CookieValidationResult(valid=False, reason=str(e))
|
||||
```
|
||||
|
||||
3. 创建 `tests/test_auth/test_cookie_validation.py`:
|
||||
- 使用 mock 模拟 page.request.get() 返回不同状态码
|
||||
- 测试有效、过期、空、格式错误等场景
|
||||
</action>
|
||||
<verify>
|
||||
<automated>pytest tests/test_auth/test_cookie_validation.py -x -v</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- validate_cookie 方法可用
|
||||
- 4 个单元测试全部通过
|
||||
- 返回 CookieValidationResult 结构
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: 实现 login CLI 子命令</name>
|
||||
<files>kb_workflow/kb_workflow.py, tests/test_auth/test_cli_login.py</files>
|
||||
<behavior>
|
||||
- Test 1: login_default_mode - 默认启动二维码登录
|
||||
- Test 2: login_import_cookie - --import-cookie 参数导入 JSON
|
||||
- Test 3: login_status - --status 参数检查 Cookie 状态
|
||||
- Test 4: login_integration - 登录成功后 Cookie 保存到文件
|
||||
</behavior>
|
||||
<action>
|
||||
扩展 `kb_workflow/kb_workflow.py` 添加 login 子命令:
|
||||
|
||||
1. 添加 login_command 函数:
|
||||
```python
|
||||
def login_command(
|
||||
import_cookie: Optional[str] = None,
|
||||
status: bool = False
|
||||
) -> Dict[str, Any]:
|
||||
"""Execute login command
|
||||
|
||||
Args:
|
||||
import_cookie: Path to JSON cookie file
|
||||
status: Check current cookie status
|
||||
|
||||
Returns:
|
||||
Dictionary with login result
|
||||
"""
|
||||
try:
|
||||
from kb_workflow.auth import ZhihuLogin
|
||||
from kb_workflow.auth.cookie_manager import CookieManager, parse_json_cookies
|
||||
from kb_workflow.pipeline.config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
manager = CookieManager()
|
||||
|
||||
# 检查 Cookie 状态
|
||||
if status:
|
||||
current_cookie = settings.zhihu_cookie.get_secret_value()
|
||||
if not current_cookie:
|
||||
return {"valid": False, "reason": "No cookie found in ~/.kb_env"}
|
||||
|
||||
async def check_status():
|
||||
login = ZhihuLogin(intermediate_path=settings.intermediate_path)
|
||||
try:
|
||||
login.browser.start(cookie=current_cookie)
|
||||
return await login.validate_cookie(current_cookie)
|
||||
finally:
|
||||
login.browser.close()
|
||||
|
||||
result = asyncio.run(check_status())
|
||||
return result.model_dump()
|
||||
|
||||
# 导入 Cookie
|
||||
if import_cookie:
|
||||
cookie_str = parse_json_cookies(import_cookie)
|
||||
manager.save_cookie(cookie_str)
|
||||
return {"success": True, "message": f"Cookie imported from {import_cookie}"}
|
||||
|
||||
# 二维码登录
|
||||
async def qrcode_login():
|
||||
login = ZhihuLogin(intermediate_path=settings.intermediate_path)
|
||||
try:
|
||||
result = await login.login_by_qrcode()
|
||||
if result.success and result.cookie_str:
|
||||
manager.save_cookie(result.cookie_str)
|
||||
return result.model_dump()
|
||||
finally:
|
||||
if login.browser.browser:
|
||||
login.browser.close()
|
||||
|
||||
result = asyncio.run(qrcode_login())
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
```
|
||||
|
||||
2. 在 main() 函数中添加 login 子命令:
|
||||
```python
|
||||
# Login subcommand
|
||||
login_parser = subparsers.add_parser('login', help='Zhihu login authentication')
|
||||
login_parser.add_argument('--import-cookie', '-i', type=str,
|
||||
help='Import cookie from JSON file')
|
||||
login_parser.add_argument('--status', action='store_true',
|
||||
help='Check current cookie status')
|
||||
|
||||
# Handle login subcommand
|
||||
if args.command == 'login':
|
||||
result = login_command(
|
||||
import_cookie=args.import_cookie,
|
||||
status=args.status
|
||||
)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
```
|
||||
|
||||
3. 创建 `tests/test_auth/test_cli_login.py`:
|
||||
- 使用 mock 模拟 ZhihuLogin 和 CookieManager
|
||||
- 测试各种命令行参数组合
|
||||
</action>
|
||||
<verify>
|
||||
<automated>pytest tests/test_auth/test_cli_login.py -x -v</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- login 子命令可用
|
||||
- 支持 --import-cookie 和 --status 参数
|
||||
- 4 个单元测试全部通过
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>
|
||||
完整的知乎登录认证系统:
|
||||
- 二维码扫码登录 (python3 kb-workflow/kb_workflow.py login)
|
||||
- JSON Cookie 导入 (python3 kb-workflow/kb_workflow.py login --import-cookie cookies.json)
|
||||
- Cookie 状态检查 (python3 kb-workflow/kb_workflow.py login --status)
|
||||
- Cookie 自动持久化到 ~/.kb_env
|
||||
</what-built>
|
||||
<how-to-verify>
|
||||
1. 运行 `python3 kb-workflow/kb_workflow.py login`
|
||||
2. 验证二维码图片生成在 `~/work/intermediate/zhihu_qrcode_*.png`
|
||||
3. 使用知乎 APP 扫码确认登录
|
||||
4. 验证命令返回 `{"success": true, "cookie_str": "..."}`
|
||||
5. 检查 `~/.kb_env` 文件包含 `ZHIHU_COOKIE="..."` 行
|
||||
6. 运行 `python3 kb-workflow/kb_workflow.py login --status`
|
||||
7. 验证返回用户信息
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" if login works, or describe issues</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
1. 所有测试通过: `pytest tests/test_auth/ -v`
|
||||
2. login 命令可用: `python3 kb-workflow/kb_workflow.py login --help`
|
||||
3. Cookie 验证功能: `python3 -c "from kb_workflow.auth import ZhihuLogin; print(ZhihuLogin.validate_cookie)"`
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 用户可以通过命令行扫码登录知乎
|
||||
- 扫码成功后 Cookie 自动保存
|
||||
- 用户可以手动导入 JSON Cookie
|
||||
- 用户可以查看 Cookie 状态
|
||||
- Cookie 过期时系统提示重新登录
|
||||
- 所有单元测试通过 (18+ tests)
|
||||
- 手动验证登录流程成功
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/07-login-auth/07-03-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user