227 lines
6.2 KiB
Python
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
|