125 lines
6.2 KiB
Python
125 lines
6.2 KiB
Python
"""定制简历存取 + 数据转换模块
|
|
|
|
提供定制简历的保存(数据库持久化)、查询、回滚(Redis临时备份)、从 ResumeDetail 构建能力。
|
|
各 Service 统一复用,不直接操作数据库表和 Redis key。
|
|
"""
|
|
|
|
import random
|
|
import string
|
|
|
|
from sqlalchemy import select, update
|
|
|
|
from app.core.database import async_session_factory
|
|
from app.core.redis import RedisManager
|
|
from app.models.user_job_customize_resume import UserJobCustomizeResume
|
|
from app.schemas.customize_resume import (
|
|
CustomizeResume, ResumeProfile, Education, Work, Internship, Project, Competition, Paragraph,
|
|
)
|
|
from app.services.resume_loader import ResumeDetail, load_default_resume_detail
|
|
from app.tool.snowflake import next_id
|
|
|
|
# Redis 回滚常量
|
|
ROLLBACK_PREFIX = "customize:resume:rollback:"
|
|
ROLLBACK_EXPIRE = 30 * 60 # 30分钟
|
|
|
|
_CHARS = string.ascii_letters + string.digits
|
|
|
|
|
|
def _rand_id() -> str:
|
|
"""生成随机8位字符串标识"""
|
|
return "".join(random.choices(_CHARS, k=8))
|
|
|
|
|
|
def _build_paragraphs(description: list[dict] | None) -> list[Paragraph]:
|
|
"""将数据库 description [{id, text}] 转为 Paragraph 列表"""
|
|
if not description:
|
|
return []
|
|
return [Paragraph(id=_rand_id(), text=item.get("text", "")) for item in description if isinstance(item, dict)]
|
|
|
|
|
|
def build_from_detail(detail: ResumeDetail) -> CustomizeResume:
|
|
"""从 ResumeDetail 组装 CustomizeResume"""
|
|
resume = detail.resume
|
|
profile = ResumeProfile(
|
|
avatarUrl=resume.avatar_url or "", name=resume.name or "", email=resume.email or "",
|
|
mobileNumber=resume.mobile_number or "", city=resume.city or "",
|
|
wechatNumber=resume.wechat_number or "", portfolioUrl=resume.portfolio_url or "",
|
|
skills=resume.skills or [], certificates=resume.certificates or [],
|
|
summary=resume.summary or "",
|
|
)
|
|
return CustomizeResume(
|
|
resume=profile,
|
|
education=[Education(id=_rand_id(), school=r.school or "", major=r.major or "",
|
|
degree=r.degree or "", studyType=r.study_type or "",
|
|
startDate=r.start_date or "", endDate=r.end_date or "",
|
|
description=_build_paragraphs(r.description)) for r in detail.education],
|
|
work=[Work(id=_rand_id(), companyName=r.company_name or "", position=r.position or "",
|
|
startDate=r.start_date or "", endDate=r.end_date or "",
|
|
description=_build_paragraphs(r.description)) for r in detail.work],
|
|
internship=[Internship(id=_rand_id(), companyName=r.company_name or "", position=r.position or "",
|
|
startDate=r.start_date or "", endDate=r.end_date or "",
|
|
description=_build_paragraphs(r.description)) for r in detail.internship],
|
|
project=[Project(id=_rand_id(), companyName=r.company_name or "", projectName=r.project_name or "",
|
|
role=r.role or "", startDate=r.start_date or "", endDate=r.end_date or "",
|
|
description=_build_paragraphs(r.description)) for r in detail.project],
|
|
competition=[Competition(id=_rand_id(), competitionName=r.competition_name or "", award=r.award or "",
|
|
awardDate=r.award_date or "",
|
|
description=_build_paragraphs(r.description)) for r in detail.competition],
|
|
)
|
|
|
|
|
|
def _rollback_key(user_id: int, job_id: int) -> str:
|
|
return f"{ROLLBACK_PREFIX}{user_id}:{job_id}"
|
|
|
|
|
|
async def save(user_id: int, job_id: int, cr: CustomizeResume) -> None:
|
|
"""保存定制简历:备份旧版本到 Redis 用于回滚 + 写数据库"""
|
|
content = cr.model_dump(by_alias=True)
|
|
|
|
async with async_session_factory() as session:
|
|
# 查已有记录,备份旧版本到 Redis 用于回滚
|
|
result = await session.execute(select(UserJobCustomizeResume).where(UserJobCustomizeResume.user_id == user_id, UserJobCustomizeResume.job_id == job_id))
|
|
record = result.scalar_one_or_none()
|
|
if record:
|
|
old_cr = CustomizeResume.model_validate(record.content)
|
|
await RedisManager.client.set(_rollback_key(user_id, job_id), old_cr.model_dump_json(by_alias=True), ex=ROLLBACK_EXPIRE)
|
|
await session.execute(update(UserJobCustomizeResume).where(
|
|
UserJobCustomizeResume.id == record.id).values(content=content))
|
|
else:
|
|
session.add(UserJobCustomizeResume(id=next_id(), user_id=user_id, job_id=job_id, content=content))
|
|
await session.commit()
|
|
|
|
|
|
async def get(user_id: int, job_id: int) -> dict | None:
|
|
"""查询定制简历,查不到则加载默认简历构建返回"""
|
|
async with async_session_factory() as session:
|
|
result = await session.execute(select(UserJobCustomizeResume).where(
|
|
UserJobCustomizeResume.user_id == user_id, UserJobCustomizeResume.job_id == job_id))
|
|
record = result.scalar_one_or_none()
|
|
if record:
|
|
return CustomizeResume.model_validate(record.content).model_dump(by_alias=True)
|
|
# 没有定制简历,加载默认简历构建
|
|
detail = await load_default_resume_detail(session, user_id)
|
|
return build_from_detail(detail).model_dump(by_alias=True)
|
|
|
|
|
|
async def rollback(user_id: int, job_id: int) -> None:
|
|
"""回滚定制简历到上一版本:从 Redis 取回滚数据写入数据库"""
|
|
rollback_k = _rollback_key(user_id, job_id)
|
|
data = await RedisManager.client.get(rollback_k)
|
|
if not data:
|
|
raise ValueError("没有可回滚的版本")
|
|
content = CustomizeResume.model_validate_json(data).model_dump(by_alias=True)
|
|
|
|
async with async_session_factory() as session:
|
|
result = await session.execute(select(UserJobCustomizeResume).where(
|
|
UserJobCustomizeResume.user_id == user_id, UserJobCustomizeResume.job_id == job_id))
|
|
record = result.scalar_one_or_none()
|
|
if record:
|
|
await session.execute(update(UserJobCustomizeResume).where(
|
|
UserJobCustomizeResume.id == record.id).values(content=content))
|
|
else:
|
|
session.add(UserJobCustomizeResume(id=next_id(), user_id=user_id, job_id=job_id, content=content))
|
|
await session.commit()
|
|
await RedisManager.client.delete(rollback_k)
|