diff --git a/app/api/skill_gap.py b/app/api/skill_gap.py index 3349bd4..27cdf8e 100644 --- a/app/api/skill_gap.py +++ b/app/api/skill_gap.py @@ -6,6 +6,7 @@ from app.core.context import RequestContext from app.core.database import get_db from app.schemas.skill_gap import SkillGapParam, CustomizeResumeParam, AiEditParam, CustomizeResume from app.services.skill_gap_service import SkillGapService +from app.services import customize_resume_store router = APIRouter(prefix="/job", tags=["岗位简历"]) @@ -37,28 +38,21 @@ async def generate_customize_resume(param: CustomizeResumeParam): async def get_customize_resume(): """查询当前用户的定制简历""" user_id = RequestContext.user_id.get() - async for session in get_db(): - service = SkillGapService(session) - result = await service.get_customize_resume(user_id) - return result + return await customize_resume_store.get(user_id) @router.put("/customize-resume", summary="修改定制简历") async def update_customize_resume(data: CustomizeResume): """手动编辑定制简历(整体覆盖)""" user_id = RequestContext.user_id.get() - async for session in get_db(): - service = SkillGapService(session) - await service.update_customize_resume(user_id, data.model_dump(by_alias=True)) + await customize_resume_store.save(user_id, data) @router.post("/customize-resume/rollback", summary="回滚定制简历") async def rollback_customize_resume(): """回滚到上一版本的定制简历""" user_id = RequestContext.user_id.get() - async for session in get_db(): - service = SkillGapService(session) - await service.rollback_customize_resume(user_id) + await customize_resume_store.rollback(user_id) @router.post("/customize-resume/ai-edit", summary="AI对话编辑定制简历") diff --git a/app/services/customize_resume_store.py b/app/services/customize_resume_store.py new file mode 100644 index 0000000..cfd0fd6 --- /dev/null +++ b/app/services/customize_resume_store.py @@ -0,0 +1,46 @@ +"""定制简历 Redis 存取模块 + +提供定制简历的保存(自动回滚备份)、查询、回滚能力。 +各 Service 统一复用,不直接操作 Redis key。 +""" + +from app.core.redis import RedisManager +from app.schemas.skill_gap import CustomizeResume + +# Redis 常量 +KEY_PREFIX = "customize:resume:" +EXPIRE = 12 * 60 * 60 # 12小时 +ROLLBACK_KEY_PREFIX = "customize:resume:rollback:" +ROLLBACK_EXPIRE = 30 * 60 # 30分钟 + + +async def save(user_id: int, cr: CustomizeResume) -> None: + """保存定制简历,自动备份旧版本到回滚 key""" + key = f"{KEY_PREFIX}{user_id}" + rollback_key = f"{ROLLBACK_KEY_PREFIX}{user_id}" + # 备份旧数据 + old_data = await RedisManager.client.get(key) + if old_data: + await RedisManager.client.set(rollback_key, old_data, ex=ROLLBACK_EXPIRE) + # 存新数据 + await RedisManager.client.set(key, cr.model_dump_json(by_alias=True), ex=EXPIRE) + + +async def get(user_id: int) -> dict | None: + """查询定制简历,返回 dict 或 None""" + key = f"{KEY_PREFIX}{user_id}" + data = await RedisManager.client.get(key) + if data: + return CustomizeResume.model_validate_json(data).model_dump(by_alias=True) + return None + + +async def rollback(user_id: int) -> None: + """回滚定制简历到上一版本""" + rollback_key = f"{ROLLBACK_KEY_PREFIX}{user_id}" + data = await RedisManager.client.get(rollback_key) + if not data: + raise ValueError("没有可回滚的版本") + key = f"{KEY_PREFIX}{user_id}" + await RedisManager.client.set(key, data, ex=EXPIRE) + await RedisManager.client.delete(rollback_key) diff --git a/app/services/skill_gap_service.py b/app/services/skill_gap_service.py index 2963d5c..08c9d54 100644 --- a/app/services/skill_gap_service.py +++ b/app/services/skill_gap_service.py @@ -20,19 +20,13 @@ from app.ai.skill_gap_analyzer.analyzer import ( ) from app.ai.skill_gap_analyzer.prompts import MODULE_SCHEMAS from app.core.logger import log -from app.core.redis import RedisManager from app.schemas.skill_gap import ( CustomizeResume, ResumeProfile, Education, Work, Internship, Project, Competition, Paragraph, ) from app.models.job import Job from app.models.user_resume import UserResume from app.services.resume_loader import ResumeDetail, load_resume_detail, load_default_resume_detail - -# Redis 常量 -CUSTOMIZE_RESUME_KEY_PREFIX = "customize:resume:" -CUSTOMIZE_RESUME_EXPIRE = 12 * 60 * 60 # 12小时 -CUSTOMIZE_RESUME_ROLLBACK_KEY_PREFIX = "customize:resume:rollback:" -CUSTOMIZE_RESUME_ROLLBACK_EXPIRE = 30 * 60 # 30分钟 +from app.services import customize_resume_store _CHARS = string.ascii_letters + string.digits @@ -149,7 +143,7 @@ class SkillGapService: existing = set(cr.resume.skills) cr.resume.skills.extend([s for s in add_skills if s not in existing]) # 5. 存 Redis - await self._save_customize_resume(user_id, cr) + await customize_resume_store.save(user_id, cr) @staticmethod def _experience_tasks(cr: CustomizeResume, job_title: str, job_desc: str) -> list[tuple[str, str]]: @@ -177,42 +171,16 @@ class SkillGapService: elif key == "competition" and isinstance(result, list): cr.competition = [Competition.model_validate(item) for item in result] - # ===== 查询 / 修改 / 回滚 ===== - - async def get_customize_resume(self, user_id: int) -> dict | None: - """查询定制简历""" - key = f"{CUSTOMIZE_RESUME_KEY_PREFIX}{user_id}" - data = await RedisManager.client.get(key) - if data: - return CustomizeResume.model_validate_json(data).model_dump(by_alias=True) - return None - - async def update_customize_resume(self, user_id: int, data: dict) -> None: - """手动编辑定制简历(整体覆盖)""" - cr = CustomizeResume.model_validate(data) - await self._save_customize_resume(user_id, cr) - - async def rollback_customize_resume(self, user_id: int) -> None: - """回滚定制简历""" - rollback_key = f"{CUSTOMIZE_RESUME_ROLLBACK_KEY_PREFIX}{user_id}" - data = await RedisManager.client.get(rollback_key) - if not data: - raise ValueError("没有可回滚的版本") - key = f"{CUSTOMIZE_RESUME_KEY_PREFIX}{user_id}" - await RedisManager.client.set(key, data, ex=CUSTOMIZE_RESUME_EXPIRE) - await RedisManager.client.delete(rollback_key) - # ===== AI 对话编辑 ===== async def ai_edit_customize_resume(self, user_id: int, job_id: int, instruction: str, chat_history: list) -> dict: """AI 对话式编辑定制简历(原子化操作版)""" # 1. 取当前定制简历 - key = f"{CUSTOMIZE_RESUME_KEY_PREFIX}{user_id}" - raw = await RedisManager.client.get(key) - if not raw: + cr_data = await customize_resume_store.get(user_id) + if not cr_data: raise ValueError("定制简历不存在,请先生成") - cr = CustomizeResume.model_validate_json(raw) + cr = CustomizeResume.model_validate(cr_data) resume_json = cr.model_dump_json(by_alias=True) # 2. 查岗位 job = await self._get_job(job_id) @@ -275,10 +243,8 @@ class SkillGapService: self._apply_record_update(cr, mod_name, record_id, result) elif op_type == "add": self._apply_record_add(cr, mod_name, result) - # 6. 保存回滚 + 新版本 - rollback_key = f"{CUSTOMIZE_RESUME_ROLLBACK_KEY_PREFIX}{user_id}" - await RedisManager.client.set(rollback_key, raw, ex=CUSTOMIZE_RESUME_ROLLBACK_EXPIRE) - await self._save_customize_resume(user_id, cr) + # 6. 保存(自动备份回滚) + await customize_resume_store.save(user_id, cr) # 拼接更新模块标签 updated_modules = list(dict.fromkeys(op.get("module", "") for op in operations)) label = "、".join(_MODULE_LABELS.get(m, m) for m in updated_modules if m) @@ -404,8 +370,3 @@ class SkillGapService: description=_build_paragraphs(r.description)) for r in detail.competition], ) - @staticmethod - async def _save_customize_resume(user_id: int, cr: CustomizeResume) -> None: - """存定制简历到 Redis""" - key = f"{CUSTOMIZE_RESUME_KEY_PREFIX}{user_id}" - await RedisManager.client.set(key, cr.model_dump_json(by_alias=True), ex=CUSTOMIZE_RESUME_EXPIRE)