12 KiB
📋 实施计划:项目功能修复与优化
生成时间:2026-03-20 工作目录:/Users/win/2025/AICoding/JobData
一、问题全景(为什么很多功能用不了)
经过深度代码审查,共发现 22 个问题,其中多个问题会直接导致功能完全不可用。以下按功能模块分组。
🔴 数据清洗功能(6 个问题 → 功能基本不可用)
| # | 严重度 | 文件 | 行号 | 问题 | 影响 |
|---|---|---|---|---|---|
| C1 | 严重 | services/cleaning.py |
13 | from jobs_spider.qcwy.search_company_jobs import _extract_items 引入私有函数,若该文件/函数不存在则整个模块 ImportError,所有清洗 API 直接 500 |
全部清洗功能不可用 |
| C2 | 严重 | services/cleaning.py |
多处 | 所有爬虫调用(boss_service.get_job_detail_by_id 等)是同步 HTTP 阻塞调用,在 async def 中直接执行,阻塞整个事件循环 |
高并发时应用无响应 |
| C3 | 高 | services/cleaning.py |
28-36 | Boss Token 加载后 _boss_token_loaded = True 永不刷新,Token 过期后 Boss 清洗静默失败 |
Boss 平台清洗失效 |
| C4 | 高 | api/v1/cleaning/cleaning.py |
285-308 | process_task API 无超时保护,爬虫卡住则 HTTP 连接永久挂起 |
客户端超时 |
| C5 | 高 | 前端缺失 | — | 后端菜单注册了 /cleaning/index 和 /cleaning/monitor,但 web/src/views/ 下不存在对应组件文件 |
点菜单白屏/404 |
| C6 | 中 | api/v1/cleaning/cleaning.py |
71-75 | source/status 直接拼入 ClickHouse SQL,SQL 注入风险 |
安全漏洞 |
🔴 定时任务功能(6 个问题 → 任务可能永久跳过)
| # | 严重度 | 文件 | 行号 | 问题 | 影响 |
|---|---|---|---|---|---|
| S1 | 严重 | core/locks.py |
43 | 文件锁用 os.mkdir 实现,无 TTL 过期机制,Worker 崩溃后锁目录永久残留,该任务永久跳过 |
任务永久失效 |
| S2 | 严重 | core/locks.py |
38 | 异步函数中使用同步 redis.Redis,阻塞事件循环 |
全局性能问题 |
| S3 | 高 | core/init_app.py |
— | 启动锁 .startup_lock 同样无 TTL,崩溃后迁移和种子数据初始化永不再执行 |
启动异常 |
| S4 | 高 | core/locks.py |
17 | 锁文件路径为相对路径 .lock_xxx,多 Worker 以不同 CWD 启动时锁完全失效 |
任务并发执行 |
| S5 | 中 | core/scheduler.py |
— | stats_job 与 ecs_full_pipeline_job 调度时间完全重合(*/6h),同时执行压力大 |
资源竞争 |
| S6 | 中 | core/scheduler.py |
181 | company_cleaning_job 处理 30 个公司可能超过 5 分钟调度间隔,任务堆积被 skip |
清洗停滞 |
🔴 安全问题(4 个 → 凭据泄漏)
| # | 严重度 | 文件 | 行号 | 问题 |
|---|---|---|---|---|
| X1 | 严重 | ecs_full_pipeline.py |
487-488 | 阿里云 AK/SK 硬编码在代码中,已在 git 历史里 |
| X2 | 严重 | settings/config.py |
44-52 | MySQL root 密码、SMTP 授权码、ClickHouse 密码硬编码 |
| X3 | 严重 | services/job.py |
535 | 第三方 API 签名 salt 硬编码 |
| X4 | 严重 | core/dependency.py |
26-28 | token == "dev" 开发后门在生产环境同样有效 |
🟡 IP 告警功能(3 个问题)
| # | 严重度 | 文件 | 行号 | 问题 |
|---|---|---|---|---|
| I1 | 中 | core/scheduler.py |
273 | 邮件模板用 a.get('date') 但实际字段是 last_report_at,告警日期列永远为空 |
| I2 | 中 | core/ip_tracking.py |
— | 中间件读 response.body 对流式响应无效,IP 计数不准 |
| I3 | 低 | core/ip_tracking.py |
73 | save() 未指定 update_fields,并发写存在竞态 |
🟡 分析功能(2 个问题)
| # | 严重度 | 文件 | 行号 | 问题 |
|---|---|---|---|---|
| A1 | 高 | api/v1/analytics.py |
— | backports.zoneinfo 未在 Pipfile 中声明,若 Python 3.8 则 ImportError,整个分析路由挂 |
| A2 | 低 | api/v1/analytics.py |
— | Query(regex=...) 在 Pydantic v2 已弃用,应改 pattern |
🟡 Ruff 报告的代码缺陷(之前已诊断,此处不重复)
共 34 个 lint 错误,其中 3 个 F821(未定义变量 udt/fpt/json)会导致运行时崩溃。
二、实施步骤(按优先级排序)
Phase 1:修复致命问题(功能完全不可用)
1.1 修复文件锁 — 添加 TTL 过期机制
文件:app/core/locks.py
# 修改 _try_file_lock 方法
# 在 acquire 时写入时间戳到锁目录内的文件
# 在 acquire 失败时检查时间戳,若超过 TTL 则强制删除旧锁
async def acquire(self) -> bool:
# Redis 路径不变
if self._redis:
return bool(self._redis.set(self._key, "locked", nx=True, ex=self.ttl))
# 文件锁路径:改用绝对路径 + TTL 检查
lock_dir = Path(tempfile.gettempdir()) / f"jobdata_lock_{self.name}"
lock_meta = lock_dir / "meta"
try:
lock_dir.mkdir()
lock_meta.write_text(str(time.time()))
return True
except FileExistsError:
# 检查是否过期
if lock_meta.exists():
created = float(lock_meta.read_text())
if time.time() - created > self.ttl:
shutil.rmtree(lock_dir) # 强制清理过期锁
return await self.acquire() # 重试
return False
1.2 修复 Redis 同步阻塞 → 异步
文件:app/core/locks.py
将 redis.Redis 替换为 redis.asyncio.Redis,所有 self._redis.set/get/delete 改为 await self._redis.set/get/delete。
1.3 修复清洗模块 ImportError 风险
文件:app/services/cleaning.py:13
# 修改前
from jobs_spider.qcwy.search_company_jobs import _extract_items as qcwy_extract_items
# 修改后:安全导入 + 降级
try:
from jobs_spider.qcwy.search_company_jobs import _extract_items as qcwy_extract_items
except ImportError:
logger.warning("qcwy search_company_jobs 模块不可用,公司职位提取功能降级")
qcwy_extract_items = None
1.4 修复清洗中同步阻塞调用
文件:app/services/cleaning.py 多处
将所有同步爬虫调用包装为 asyncio.to_thread:
# 修改前
data = self.boss_service.get_job_detail_by_id(target)
# 修改后
data = await asyncio.to_thread(self.boss_service.get_job_detail_by_id, target)
涉及的方法:clean_by_job_id、clean_by_company_name、clean_boss_company_jobs、clean_qcwy_company_jobs、clean_zhilian_company_jobs(共约 12 处调用)。
app/services/company_cleaner.py 中同样的模式也需要修复(同步爬虫调用包装为 to_thread)。
1.5 修复 Boss Token 永久缓存问题
文件:app/services/cleaning.py:28-36,app/services/company_cleaner.py:28-36
# 修改前
async def _ensure_boss_token_loaded(self) -> None:
if self._boss_token_loaded and self.boss_service.login_data.get("mpt"):
return # 永不刷新
# 修改后:添加过期时间检查
async def _ensure_boss_token_loaded(self) -> None:
now = time.time()
if (self._boss_token_loaded
and self.boss_service.login_data.get("mpt")
and now - self._token_loaded_at < 3600): # 1小时刷新一次
return
token_obj = await BossToken.filter(is_active=True).order_by("-updated_at").first()
if token_obj:
self.boss_service.login_data["mpt"] = token_obj.mpt_value
self._boss_token_loaded = True
self._token_loaded_at = now
Phase 2:修复安全问题
2.1 凭据迁移(config.py)
创建 .env.example + 修改 config.py 用 pydantic-settings 从环境变量读取(详见之前的 ruff-optimization 计划)。
2.2 移除 dev 后门
文件:app/core/dependency.py:26-28
# 修改前
if token == "dev":
user = await User.filter().first()
return user
# 修改后:仅在开发环境允许
import os
if token == "dev" and os.getenv("APP_ENV", "production") == "development":
user = await User.filter().first()
return user
2.3 阿里云 AK/SK 移入环境变量
文件:ecs_full_pipeline.py:487-488
# 修改前
ak = "LTAI5tBgW3hAzcnHBkZywxkD"
sk = "Il7M4bkJvdZIutkJH8pxhuMLrMvj5x"
# 修改后
ak = os.environ["ALIBABA_CLOUD_ACCESS_KEY_ID"]
sk = os.environ["ALIBABA_CLOUD_ACCESS_KEY_SECRET"]
Phase 3:修复 IP 告警和分析功能
3.1 修复邮件模板字段名
文件:app/core/scheduler.py:273
# 修改前
f"<td>{a.get('date')}</td>"
# 修改后
f"<td>{a.get('last_report_at', 'N/A')}</td>"
3.2 修复 analytics Query 参数弃用警告
文件:app/api/v1/analytics.py
# 修改前
interval: str = Query("day", regex="^(day|hour|week|month)$")
# 修改后
interval: str = Query("day", pattern="^(day|hour|week|month)$")
3.3 修复 zoneinfo 导入
确认 Python 版本为 3.13(项目 Pipfile 声明),zoneinfo 是标准库,无需 backports。可直接删除 try/except,只保留 from zoneinfo import ZoneInfo。
Phase 4:修复 Ruff 34 个 lint 错误
# 自动修复 22 个
pipenv run ruff check app/ --fix
# 手动修复剩余 12 个(F821 × 3、E722 × 1、E402 × 5、其他 × 3)
F821 重点修复:
job.py:348—udt未定义(需确认应为update_date_time)job.py:374—fpt未定义(需确认应为first_publish_time)crawler/zhilian.py:60— 添加import json
Phase 5:代码去重和可维护性优化
- 合并
job.py中 7 个重复的_check_*_duplicate为 1 个通用方法 - 删除死代码
_check_qcwy_company_duplicate_by_name - 将
job.py中requests.post替换为httpx.AsyncClient - 错调度时间:将
ecs_full_pipeline_job偏移 30 分钟,避免与stats_job重合
三、关键文件索引
| 文件 | 操作 | Phase | 说明 |
|---|---|---|---|
app/core/locks.py |
重构 | 1 | 文件锁 TTL + Redis 异步化 |
app/services/cleaning.py |
修复 | 1 | ImportError 防护 + async 阻塞 + Token 刷新 |
app/services/company_cleaner.py |
修复 | 1 | async 阻塞 + Token 刷新 |
app/core/dependency.py |
修复 | 2 | dev 后门加环境判断 |
app/settings/config.py |
重构 | 2 | 凭据移入环境变量 |
ecs_full_pipeline.py |
修复 | 2 | AK/SK 移入环境变量 |
app/core/scheduler.py |
修复 | 3 | 邮件字段名 + 调度时间偏移 |
app/api/v1/analytics.py |
修复 | 3 | regex→pattern + zoneinfo |
app/services/job.py |
修复 | 4+5 | F821 + E722 + requests→httpx + 去重方法合并 |
app/services/crawler/zhilian.py |
修复 | 4 | 添加 import json |
web/src/views/cleaning/ |
新建 | 5 | 创建前端清洗页面组件(可选) |
.env.example |
新建 | 2 | 环境变量模板 |
四、风险与缓解
| 风险 | 缓解措施 |
|---|---|
| 文件锁改造后旧锁目录残留 | 部署时手动清理 .lock_* 目录 |
| Redis 异步化后连接池配置不同 | 保持相同连接参数,仅换客户端类 |
asyncio.to_thread 增加线程池压力 |
设置 max_workers=10 限制并发 |
| 凭据迁移后服务启动失败 | 先创建 .env 文件再部署 |
| 前端清洗页面组件工作量大 | 可先做最小 MVP(列表 + 手动触发) |
五、执行顺序
Phase 1(2h) → 修复致命问题:锁机制 + 清洗模块 + async 阻塞
Phase 2(1h) → 安全问题:凭据迁移 + dev 后门
Phase 3(30m) → IP 告警 + 分析功能修复
Phase 4(30m) → Ruff 34 个 lint 错误
Phase 5(2h) → 代码去重 + 前端组件(可选)
SESSION_ID(供 /ccg:execute 使用)
- CODEX_SESSION: N/A(本次分析由 Claude 本地执行)
- GEMINI_SESSION: N/A