263 lines
8.9 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.

---
phase: 4
plan: 1
wave: 1
title: "迁移 facade 层 import 至 spiderJobs.platforms.* + asyncio.to_thread 桥接"
depends_on: []
files_modified:
- app/services/crawler/boss.py
- app/services/crawler/qcwy.py
- app/services/crawler/zhilian.py
autonomous: true
requirements:
- ARCH-06
- ARCH-07
---
# Phase 4 Plan 01: 迁移 facade 层 import 至 spiderJobs.platforms.* + asyncio.to_thread 桥接
## Objective
`app/services/crawler/` 的三个 facade 文件boss.py/qcwy.py/zhilian.py
引用内部私有复制文件(`_boss_api.py``_boss_client.py` 等)改为直接引用
`spiderJobs.platforms.*`(已基于 crawler_core满足 ARCH-06/ARCH-07。
对外接口(`set_proxy()``get_job_detail()` 等)完全不变。
同时为每个 Service 添加 `asyncio.to_thread()` 异步包装方法ARCH-06
## Must Haves
- [ ] `boss.py` 改导入 `spiderJobs.platforms.boss.{api,client,sign}`
- [ ] `qcwy.py` 改导入 `spiderJobs.platforms.job51.{api,client}`
- [ ] `zhilian.py` 改导入 `spiderJobs.platforms.zhilian.{api,client,sign}`
- [ ] 三个 Service 各添加 async 方法(`asyncio.to_thread` 包装)
- [ ] `python -c "from app.services.crawler.boss import BossService"` 无 ImportError
- [ ] `pytest tests/ -v` 全部通过(无回归)
---
## Wave 1
### Task 1.1: 更新 boss.py
<read_first>
- `app/services/crawler/boss.py`当前内容116 行)
- `spiderJobs/platforms/boss/api.py`GetBrandDetail/GetJobDetail/SearchBrandJobs/SearchRecJobs 导出)
- `spiderJobs/platforms/boss/client.py`BossClient/create_client 导出,含 batch()
- `spiderJobs/platforms/boss/sign.py`BossSign → crawler_core 桩)
</read_first>
<action>
修改 `app/services/crawler/boss.py`
1. 将 import 块(第 12-19 行)替换为:
```python
from spiderJobs.platforms.boss.api import (
GetBrandDetail,
GetJobDetail,
SearchBrandJobs,
SearchRecJobs,
)
from spiderJobs.platforms.boss.client import BossClient, create_client
from spiderJobs.platforms.boss.sign import BossSign
```
2. 在 `BossService` 类末尾添加异步包装方法:
```python
# ── asyncio.to_thread 桥接ARCH-06────────
async def async_get_job_detail(
self, job_id: str, lid: str = "", security_id: str = ""
) -> Optional[Dict]:
import asyncio
return await asyncio.to_thread(self.get_job_detail_by_id, job_id, lid, security_id)
async def async_get_company_detail(self, company_id: str) -> Optional[Dict]:
import asyncio
return await asyncio.to_thread(self.get_company_detail_by_id, company_id)
async def async_get_company_jobs(
self, company_id: str, page: int = 1
) -> Optional[Dict]:
import asyncio
return await asyncio.to_thread(self.get_company_jobs_by_id, company_id, page)
async def async_search_jobs(
self, keyword: str, city_code: str = "101010100", page: int = 1
) -> Optional[Dict]:
import asyncio
return await asyncio.to_thread(self.search_jobs, keyword, city_code, page)
```
</action>
<acceptance_criteria>
- `grep "from spiderJobs.platforms.boss" app/services/crawler/boss.py` 有输出
- `grep "app.services.crawler._boss" app/services/crawler/boss.py` 无输出
- `grep "asyncio.to_thread" app/services/crawler/boss.py` 有输出
- `pipenv run python -c "from app.services.crawler.boss import BossService"` 成功
</acceptance_criteria>
---
### Task 1.2: 更新 qcwy.py
<read_first>
- `app/services/crawler/qcwy.py`当前内容103 行)
- `spiderJobs/platforms/job51/api.py`GetCompanyInfo/GetJobDetail/SearchCompanyJobs/SearchRecommendJobs 导出)
- `spiderJobs/platforms/job51/client.py`Job51Client/create_client 导出)
</read_first>
<action>
修改 `app/services/crawler/qcwy.py`
1. 将 import 块(第 12-18 行)替换为:
```python
from spiderJobs.platforms.job51.api import (
GetCompanyInfo,
GetJobDetail,
SearchCompanyJobs,
SearchRecommendJobs,
)
from spiderJobs.platforms.job51.client import Job51Client, create_client
```
2. 在 `QcwyService` 类末尾添加异步包装方法:
```python
# ── asyncio.to_thread 桥接ARCH-06────────
async def async_get_job_detail(self, job_id: str) -> Dict:
import asyncio
return await asyncio.to_thread(self.get_job_detail, job_id)
async def async_get_company_info(self, company_id: str) -> Dict:
import asyncio
return await asyncio.to_thread(self.get_company_info, company_id)
async def async_get_company_jobs(
self, company_id: str, page: int = 1, page_size: int = 30, **kwargs
) -> Dict:
import asyncio
return await asyncio.to_thread(
self.get_company_jobs_by_id, company_id, page, page_size
)
async def async_search_jobs(
self, keyword: str, job_area: str = "020000", page: int = 1
) -> List:
import asyncio
return await asyncio.to_thread(self.search_jobs, keyword, job_area, page)
```
</action>
<acceptance_criteria>
- `grep "from spiderJobs.platforms.job51" app/services/crawler/qcwy.py` 有输出
- `grep "app.services.crawler._job51" app/services/crawler/qcwy.py` 无输出
- `grep "asyncio.to_thread" app/services/crawler/qcwy.py` 有输出
- `pipenv run python -c "from app.services.crawler.qcwy import QcwyService"` 成功
</acceptance_criteria>
---
### Task 1.3: 更新 zhilian.py
<read_first>
- `app/services/crawler/zhilian.py`当前内容143 行)
- `spiderJobs/platforms/zhilian/api.py`GetCompanyDetail/GetPositionDetail/SearchCompanyPositions/SearchPositions 导出)
- `spiderJobs/platforms/zhilian/client.py`ZhilianClient/create_capi_client/create_cgate_client 导出)
- `spiderJobs/platforms/zhilian/sign.py`ZhilianSign → crawler_core 桩)
</read_first>
<action>
修改 `app/services/crawler/zhilian.py`
1. 将 import 块(第 12-23 行)替换为:
```python
from spiderJobs.platforms.zhilian.api import (
GetCompanyDetail,
GetPositionDetail,
SearchCompanyPositions,
SearchPositions,
)
from spiderJobs.platforms.zhilian.client import (
ZhilianClient,
create_capi_client,
create_cgate_client,
)
from spiderJobs.platforms.zhilian.sign import ZhilianSign
```
2. 在 `ZhilianService` 类末尾添加异步包装方法:
```python
# ── asyncio.to_thread 桥接ARCH-06────────
async def async_get_job_detail(self, job_number: str) -> Optional[Dict]:
import asyncio
return await asyncio.to_thread(self.get_job_detail, job_number)
async def async_get_company_detail(self, company_number: str) -> Optional[Dict]:
import asyncio
return await asyncio.to_thread(self.get_company_detail, company_number)
async def async_get_company_jobs(
self, company_number: str, page_index: int = 1, page_size: int = 30,
work_city: Optional[int] = None,
) -> Optional[Dict]:
import asyncio
return await asyncio.to_thread(
self.get_company_jobs_by_id, company_number, page_index, page_size, work_city
)
async def async_search_jobs(
self, city_id: int = 801, page_size: int = 15, page_index: int = 1,
job_level3_code: Optional[str] = None,
) -> List:
import asyncio
return await asyncio.to_thread(
self.search_jobs, city_id, page_size, page_index, job_level3_code
)
```
</action>
<acceptance_criteria>
- `grep "from spiderJobs.platforms.zhilian" app/services/crawler/zhilian.py` 有输出
- `grep "app.services.crawler._zhilian" app/services/crawler/zhilian.py` 无输出
- `grep "asyncio.to_thread" app/services/crawler/zhilian.py` 有输出
- `pipenv run python -c "from app.services.crawler.zhilian import ZhilianService"` 成功
</acceptance_criteria>
---
## Verification
```bash
# 1. 验证三个 facade 模块 import 正确
pipenv run python -c "
from app.services.crawler.boss import BossService
from app.services.crawler.qcwy import QcwyService
from app.services.crawler.zhilian import ZhilianService
print('✅ 三个 facade 模块 import 成功')
# 验证无旧导入
import inspect, sys
for svc in [BossService, QcwyService, ZhilianService]:
src = inspect.getsourcefile(svc)
with open(src) as f:
content = f.read()
assert '_boss_' not in content and '_job51_' not in content and '_zhilian_' not in content, f'{src} 仍有旧导入!'
print('✅ 无旧导入残留')
# 验证 async 方法存在
assert hasattr(BossService, 'async_get_job_detail')
assert hasattr(QcwyService, 'async_get_company_info')
assert hasattr(ZhilianService, 'async_get_company_detail')
print('✅ asyncio 桥接方法存在')
"
# 2. 验证旧导入无残留
grep -rn "from app.services.crawler._" app/services/crawler/boss.py app/services/crawler/qcwy.py app/services/crawler/zhilian.py && echo "❌ 旧导入残留" || echo "✅ 无旧导入"
# 3. 全量回归
pipenv run python -m pytest tests/ -v --tb=short
```