xiangqinxiaochengxu/backend/app/services/user_service.py
2026-04-17 19:26:32 +08:00

227 lines
6.2 KiB
Python

from collections.abc import Iterable
from datetime import datetime
from sqlalchemy import select
from sqlalchemy.exc import DataError, IntegrityError, SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.user import User
from app.schemas.user import UserUpdateRequest
REQUIRED_AUDIT_FIELDS = (
"nickname",
"gender",
"birth_year",
"city",
"education",
"job_industry",
"personality_tags",
"hobbies",
"wechat_id",
"phone",
"self_intro",
)
async def get_user_by_id(session: AsyncSession, user_id: int) -> User | None:
result = await session.execute(select(User).where(User.id == user_id))
return result.scalar_one_or_none()
def calculate_profile_completeness(user: User) -> int:
fields = {
"nickname": 5,
"gender": 5,
"birth_year": 5,
"city": 5,
"height": 3,
"education": 5,
"job_industry": 5,
"personality_tags": 8,
"hobbies": 8,
"wechat_id": 6,
"phone": 6,
"self_intro": 8,
"prefer_age_min": 3,
"prefer_city": 3,
"prefer_education": 3,
"job_company": 4,
"income_range": 5,
}
total_weight = sum(fields.values())
earned = 0.0
for field, weight in fields.items():
value = getattr(user, field, None)
if field in {"personality_tags", "hobbies"}:
length = len(value or [])
if length >= 3:
earned += weight
elif length >= 1:
earned += weight * 0.5
elif field == "value_answers":
length = len(value or {})
if length >= 4:
earned += weight
elif length >= 2:
earned += weight * 0.5
elif value not in (None, ""):
earned += weight
return round((earned / total_weight) * 100)
def _missing_required_fields(user: User) -> list[str]:
missing: list[str] = []
for field in REQUIRED_AUDIT_FIELDS:
value = getattr(user, field, None)
if value in (None, ""):
missing.append(field)
continue
if isinstance(value, Iterable) and not isinstance(value, (str, bytes, dict)) and len(value) == 0:
missing.append(field)
continue
if isinstance(value, dict) and len(value) == 0:
missing.append(field)
return missing
def _sanitize_update_value(field: str, value):
if value is None:
return None
if field in {"personality_tags", "hobbies"}:
if isinstance(value, list):
return [item.strip() for item in value if isinstance(item, str) and item.strip()]
return []
if field in {
"nickname",
"real_name",
"city",
"job_industry",
"job_company",
"wechat_id",
"phone",
"prefer_city",
"self_intro",
"avatar_url",
"avatar_blur_url",
} and isinstance(value, str):
value = value.strip()
return value or None
return value
async def update_user_profile(
session: AsyncSession,
user: User,
payload: UserUpdateRequest,
) -> User:
update_data = payload.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(user, field, _sanitize_update_value(field, value))
user.profile_completeness = calculate_profile_completeness(user)
if user.audit_status in {0, 2, 3} and update_data:
user.audit_status = 1
user.audit_remark = None
session.add(user)
try:
await session.commit()
except (DataError, IntegrityError) as exc:
await session.rollback()
raise ValueError("资料内容不符合要求,请检查长度或格式") from exc
except SQLAlchemyError as exc:
await session.rollback()
raise ValueError("保存资料失败,请稍后重试") from exc
await session.refresh(user)
return user
async def submit_user_audit(session: AsyncSession, user: User) -> tuple[User, list[str]]:
missing_fields = _missing_required_fields(user)
if missing_fields:
return user, missing_fields
if user.audit_status in {0, 3}:
user.audit_status = 1
user.audit_remark = None
session.add(user)
await session.commit()
await session.refresh(user)
return user, []
async def get_user_by_openid(session: AsyncSession, openid: str) -> User | None:
result = await session.execute(select(User).where(User.openid == openid))
return result.scalar_one_or_none()
async def create_user_from_wechat(
session: AsyncSession,
*,
openid: str,
unionid: str | None,
subscribe_audit: bool,
subscribe_match: bool,
) -> User:
user = User(
openid=openid,
unionid=unionid,
subscribe_audit=1 if subscribe_audit else 0,
subscribe_match=1 if subscribe_match else 0,
audit_status=0,
profile_completeness=0,
created_at=datetime.now(),
updated_at=datetime.now(),
)
session.add(user)
await session.commit()
await session.refresh(user)
return user
async def get_or_create_wechat_user(
session: AsyncSession,
*,
openid: str,
unionid: str | None,
subscribe_audit: bool,
subscribe_match: bool,
) -> tuple[User, bool]:
user = await get_user_by_openid(session, openid)
if user is not None:
updated = False
new_subscribe_audit = 1 if subscribe_audit else user.subscribe_audit
new_subscribe_match = 1 if subscribe_match else user.subscribe_match
if new_subscribe_audit != user.subscribe_audit:
user.subscribe_audit = new_subscribe_audit
updated = True
if new_subscribe_match != user.subscribe_match:
user.subscribe_match = new_subscribe_match
updated = True
if unionid and not user.unionid:
user.unionid = unionid
updated = True
if updated:
session.add(user)
await session.commit()
await session.refresh(user)
return user, False
user = await create_user_from_wechat(
session,
openid=openid,
unionid=unionid,
subscribe_audit=subscribe_audit,
subscribe_match=subscribe_match,
)
return user, True