upload project source code

This commit is contained in:
2026-04-30 18:49:43 +08:00
commit 9b394ba682
2277 changed files with 660945 additions and 0 deletions

View File

@@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
from fastapi import APIRouter, Depends
from fastapi.responses import JSONResponse
from app.api.v1.module_system.auth.schema import AuthSchema
from app.core.dependencies import AuthPermission
from app.common.response import SuccessResponse
from app.core.base_params import PaginationQueryParam
from app.core.logger import log
from app.api.v1.module_yifan.yifan_affinity.service import YifanAffinityService
from app.api.v1.module_yifan.yifan_affinity.schema import (
YifanAffinityCreateSchema, YifanAffinityUpdateSchema, YifanAffinityQueryParam,
AffinityCalculateRequestSchema
)
YifanAffinityRouter = APIRouter(prefix="/yifan_affinity", tags=["缘分合盘"])
@YifanAffinityRouter.post("/calculate", summary="缘分合盘测算", description="缘分合盘测算,创建任务并异步生成分析结果")
async def calculate_affinity_controller(
data: AffinityCalculateRequestSchema,
auth: AuthSchema = Depends(AuthPermission([]))
) -> JSONResponse:
"""缘分合盘测算接口 - 创建任务,后台异步生成分析结果"""
result_dict = await YifanAffinityService.calculate_affinity_service(auth=auth, data=data)
log.info(f"缘分合盘任务已创建: {data.person1.name} & {data.person2.name} - {data.relationship}")
return SuccessResponse(data=result_dict, msg="缘分合盘任务已创建,正在测算中")
@YifanAffinityRouter.get("/result/{affinity_id}", summary="获取缘分合盘结果", description="根据ID获取缘分合盘测算结果")
async def get_affinity_result_controller(
affinity_id: int,
auth: AuthSchema = Depends(AuthPermission([]))
) -> JSONResponse:
"""获取缘分合盘结果接口"""
result_dict = await YifanAffinityService.get_affinity_result_service(auth=auth, affinity_id=affinity_id)
log.info(f"获取缘分合盘结果成功: ID={affinity_id}")
return SuccessResponse(data=result_dict.model_dump(), msg="获取缘分合盘结果成功")
@YifanAffinityRouter.get("/list", summary="查询缘分合盘列表", description="查询缘分合盘列表")
async def get_yifan_affinity_list_controller(
page: PaginationQueryParam = Depends(),
search: YifanAffinityQueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission([]))
) -> JSONResponse:
"""查询缘分合盘列表接口(数据库分页)"""
result_dict = await YifanAffinityService.page_yifan_affinity_service(
auth=auth,
page_no=page.page_no if page.page_no is not None else 1,
page_size=page.page_size if page.page_size is not None else 10,
search=search,
order_by=page.order_by
)
log.info("查询缘分合盘列表成功")
return SuccessResponse(data=result_dict, msg="查询缘分合盘列表成功")
@YifanAffinityRouter.get("/{affinity_id}", summary="获取缘分合盘详情", description="根据ID获取缘分合盘详情")
async def get_yifan_affinity_controller(
affinity_id: int,
auth: AuthSchema = Depends(AuthPermission([]))
) -> JSONResponse:
"""获取缘分合盘详情接口"""
result_dict = await YifanAffinityService.get_yifan_affinity_service(auth=auth, affinity_id=affinity_id)
log.info("获取缘分合盘详情成功")
return SuccessResponse(data=result_dict, msg="获取缘分合盘详情成功")
@YifanAffinityRouter.post("/create", summary="创建缘分合盘", description="创建缘分合盘")
async def create_yifan_affinity_controller(
data: YifanAffinityCreateSchema,
auth: AuthSchema = Depends(AuthPermission(["module_yifan:yifan_affinity:create"]))
) -> JSONResponse:
"""创建缘分合盘接口"""
result_dict = await YifanAffinityService.create_yifan_affinity_service(auth=auth, data=data)
log.info("创建缘分合盘成功")
return SuccessResponse(data=result_dict, msg="创建缘分合盘成功")
@YifanAffinityRouter.put("/{affinity_id}", summary="更新缘分合盘", description="根据ID更新缘分合盘")
async def update_yifan_affinity_controller(
affinity_id: int,
data: YifanAffinityUpdateSchema,
auth: AuthSchema = Depends(AuthPermission(["module_yifan:yifan_affinity:update"]))
) -> JSONResponse:
"""更新缘分合盘接口"""
result_dict = await YifanAffinityService.update_yifan_affinity_service(auth=auth, affinity_id=affinity_id, data=data)
log.info("更新缘分合盘成功")
return SuccessResponse(data=result_dict, msg="更新缘分合盘成功")
@YifanAffinityRouter.delete("/{affinity_id}", summary="删除缘分合盘", description="根据ID删除缘分合盘")
async def delete_yifan_affinity_controller(
affinity_id: int,
auth: AuthSchema = Depends(AuthPermission(["module_yifan:yifan_affinity:delete"]))
) -> JSONResponse:
"""删除缘分合盘接口"""
result = await YifanAffinityService.delete_yifan_affinity_service(auth=auth, affinity_id=affinity_id)
log.info("删除缘分合盘成功")
return SuccessResponse(data=result, msg="删除缘分合盘成功")

View File

@@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
from typing import Sequence
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.base_crud import CRUDBase
from app.api.v1.module_system.auth.schema import AuthSchema
from app.api.v1.module_yifan.yifan_affinity.model import YifanAffinityModel
from app.api.v1.module_yifan.yifan_affinity.schema import YifanAffinityCreateSchema, YifanAffinityUpdateSchema
class YifanAffinityCRUD(CRUDBase[YifanAffinityModel, YifanAffinityCreateSchema, YifanAffinityUpdateSchema]):
"""缘分合盘CRUD操作"""
def __init__(self, auth: AuthSchema):
super().__init__(model=YifanAffinityModel, auth=auth)
async def create_yifan_affinity_crud(self, data: YifanAffinityCreateSchema) -> YifanAffinityModel:
"""
创建缘分合盘记录
参数:
- data (YifanAffinityCreateSchema): 创建数据
返回:
- YifanAffinityModel: 模型实例
"""
return await self.create(data=data)
async def get_yifan_affinity_crud(self, affinity_id: int, preload: list | None = None) -> YifanAffinityModel | None:
"""
获取单个缘分合盘记录
参数:
- affinity_id (int): 记录ID
- preload (list | None): 预加载关系,未提供时使用模型默认项
返回:
- YifanAffinityModel | None: 模型实例或None
"""
return await self.get(id=affinity_id, preload=preload)
async def update_yifan_affinity_crud(self, affinity_id: int, data: YifanAffinityUpdateSchema) -> YifanAffinityModel:
"""
更新缘分合盘记录
参数:
- affinity_id (int): 记录ID
- data (YifanAffinityUpdateSchema): 更新数据
返回:
- YifanAffinityModel: 更新后的模型实例
"""
return await self.update(id=affinity_id, data=data)
async def delete_yifan_affinity_crud(self, affinity_id: int) -> bool:
"""
删除缘分合盘记录
参数:
- affinity_id (int): 记录ID
返回:
- bool: 删除是否成功
"""
return await self.delete(id=affinity_id)
async def list_yifan_affinity_crud(self, search: dict | None = None, order_by: list[dict] | None = None, preload: list | None = None) -> Sequence[YifanAffinityModel]:
"""
列表查询
参数:
- search (dict | None): 查询参数
- order_by (list[dict] | None): 排序参数,未提供时使用模型默认项
- preload (list | None): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[YifanAffinityModel]: 模型实例序列
"""
return await self.list(search=search, order_by=order_by, preload=preload)
async def page_yifan_affinity_crud(self, page_no: int, page_size: int, search: dict | None = None, order_by: list[dict] | None = None, preload: list | None = None) -> dict:
"""
分页查询
参数:
- page_no (int): 页码
- page_size (int): 每页数量
- search (dict | None): 查询参数
- order_by (list[dict] | None): 排序参数,未提供时使用模型默认项
- preload (list | None): 预加载关系,未提供时使用模型默认项
返回:
- dict: 分页结果字典
"""
from app.api.v1.module_yifan.yifan_affinity.schema import YifanAffinityOutSchema
# 将页码转换为偏移量
offset = (page_no - 1) * page_size
# 设置默认排序
if order_by is None:
order_by = [{"field": "created_time", "order": "desc"}]
return await self.page(
offset=offset,
limit=page_size,
search=search or {},
order_by=order_by,
out_schema=YifanAffinityOutSchema,
preload=preload
)

View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
import datetime
from sqlalchemy import Integer, DateTime, SmallInteger, Text, String, JSON, Numeric
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base_model import ModelMixin, UserMixin
class YifanAffinityModel(ModelMixin, UserMixin):
"""
缘分合盘表
"""
__tablename__: str = 'yifan_affinity'
__table_args__: dict[str, str] = {'comment': '缘分合盘'}
__loader_options__: list[str] = ["created_by", "updated_by"]
is_deleted: Mapped[int | None] = mapped_column(SmallInteger, nullable=True, comment='是否删除(0否 1是)')
task_status: Mapped[int | None] = mapped_column(SmallInteger, nullable=True, comment='任务状态(1已创建 2测算中 5测算成功 3测算超时 0任务失败)')
user_id: Mapped[int | None] = mapped_column(Integer, nullable=True, comment='用户ID')
# 缘分类型
relationship: Mapped[str | None] = mapped_column(String(32), nullable=True, comment='缘分类型(couple情侣 married夫妻 crush暗恋 partner合伙 friend知己 family亲缘)')
# 甲方信息
person1_name: Mapped[str | None] = mapped_column(String(100), nullable=True, comment='甲方姓名')
person1_gender: Mapped[str | None] = mapped_column(String(10), nullable=True, comment='甲方性别(male男 female女)')
person1_birth_date: Mapped[str | None] = mapped_column(String(100), nullable=True, comment='甲方生辰显示格式')
person1_birth_date_api: Mapped[datetime.datetime | None] = mapped_column(DateTime, nullable=True, comment='甲方生辰API格式')
# 乙方信息
person2_name: Mapped[str | None] = mapped_column(String(100), nullable=True, comment='乙方姓名')
person2_gender: Mapped[str | None] = mapped_column(String(10), nullable=True, comment='乙方性别(male男 female女)')
person2_birth_date: Mapped[str | None] = mapped_column(String(100), nullable=True, comment='乙方生辰显示格式')
person2_birth_date_api: Mapped[datetime.datetime | None] = mapped_column(DateTime, nullable=True, comment='乙方生辰API格式')
# 测算结果
score: Mapped[int | None] = mapped_column(Integer, nullable=True, comment='默契指数总分(0-100)')
score_badge: Mapped[str | None] = mapped_column(String(32), nullable=True, comment='婚配等级(上上婚 上等婚 中等婚 下等婚)')
# 六维契合度评分(JSON格式存储)
six_dimension: Mapped[str | None] = mapped_column(JSON, nullable=True, comment='六维契合度评分JSON')
radar_desc: Mapped[str | None] = mapped_column(Text, nullable=True, comment='六维雷达图描述')
# 分析卡片(JSON格式存储)
analysis_cards: Mapped[str | None] = mapped_column(JSON, nullable=True, comment='分析卡片列表JSON')
# 解锁状态
is_unlocked: Mapped[int | None] = mapped_column(SmallInteger, nullable=True, comment='是否已解锁深度报告(0否 1是)')
unlock_price: Mapped[str | None] = mapped_column(Numeric(10, 2), nullable=True, comment='解锁价格')
# 解锁后的深度内容(JSON格式存储)
unlocked_content: Mapped[str | None] = mapped_column(JSON, nullable=True, comment='解锁后的深度内容JSON')
# AI原始数据
ai_source_data: Mapped[str | None] = mapped_column(Text, nullable=True, comment='AI原始数据')
error_message: Mapped[str | None] = mapped_column(Text, nullable=True, comment='错误信息')
remark: Mapped[str | None] = mapped_column(String(255), nullable=True, comment='备注')

View File

@@ -0,0 +1,244 @@
# -*- coding: utf-8 -*-
import datetime
from typing import List, Dict, Any, Optional
from pydantic import BaseModel, ConfigDict, Field
from fastapi import Query
from app.core.validator import DateTimeStr
from app.core.base_schema import BaseSchema, UserBySchema
class PersonSchema(BaseModel):
"""人员信息模型"""
name: str = Field(..., description='姓名', example='张三')
gender: str = Field(..., description='性别', example='male')
birth_date: str = Field(..., description='生辰八字(显示格式)', example='1990年1月1日 子时')
birth_date_api: datetime.datetime = Field(..., description='生辰八字API格式', example='1990-01-01 00:30:00')
class AffinityCalculateRequestSchema(BaseModel):
"""缘分合盘测算请求模型"""
relationship: str = Field(..., description='缘分类型', example='couple')
person1: PersonSchema = Field(..., description='甲方信息(君)')
person2: PersonSchema = Field(..., description='乙方信息(卿)')
class SixDimensionSchema(BaseModel):
"""六维契合度评分模型"""
emotional: int = Field(..., description='情感共鸣', ge=0, le=100, example=92)
communication: int = Field(..., description='沟通模式', ge=0, le=100, example=78)
trust: int = Field(..., description='信任安全', ge=0, le=100, example=95)
values: int = Field(..., description='价值观', ge=0, le=100, example=85)
compatibility: int = Field(..., description='互补性', ge=0, le=100, example=88)
luck: int = Field(..., description='互旺运势', ge=0, le=100, example=88)
class AnalysisCardSchema(BaseModel):
"""分析卡片模型"""
id: str = Field(..., description='卡片ID', example='emotional')
icon: str = Field(..., description='图标emoji', example='[表情]')
icon_bg: str = Field(..., description='图标背景色', example='rgba(236, 72, 153, 0.2)')
title: str = Field(..., description='标题', example='情感共鸣')
score: str = Field(..., description='评分', example='92')
summary: str = Field(..., description='摘要(折叠时显示)')
content: str = Field(..., description='详细内容(展开时显示)')
class PastLifeSchema(BaseModel):
"""前世羁绊模型"""
description: str = Field(..., description='前世关系描述')
depth: str = Field(..., description='缘分深浅', example='三生石上旧精魂')
relationship: str = Field(..., description='还债关系', example='互不相欠 · 共同成长')
class MatchItemSchema(BaseModel):
"""正缘特征验证模型"""
label: str = Field(..., description='验证项', example='外貌特征')
match: int = Field(..., description='契合度百分比', ge=0, le=100, example=90)
desc: str = Field(..., description='描述')
class GuideSchema(BaseModel):
"""潜意识与相处指南模型"""
real_needs: str = Field(..., description='TA的真实需求')
red_zone: str = Field(..., description='绝对雷区')
tips: List[str] = Field(..., description='如何拿捏TA')
class MonthlyFortuneSchema(BaseModel):
"""未来12个月感情运势模型"""
heats: List[int] = Field(..., description='12个月的热度值0-3')
heat_labels: List[str] = Field(..., description='热度标签')
highlights: str = Field(..., description='高光时刻提示')
warnings: str = Field(..., description='预警时刻提示')
class TimelineItemSchema(BaseModel):
"""未来十年关键节点项模型"""
year: str = Field(..., description='年份', example='2025')
title: str = Field(..., description='阶段标题', example='升温期')
desc: str = Field(..., description='描述')
highlight: bool = Field(..., description='是否高亮显示')
class MasterAdviceSchema(BaseModel):
"""大师寄语与化解之道模型"""
message: str = Field(..., description='寄语')
tips: List[str] = Field(..., description='开运建议')
class UnlockedContentSchema(BaseModel):
"""解锁后的深度内容模型"""
past_life: PastLifeSchema = Field(..., description='前世羁绊')
match_items: List[MatchItemSchema] = Field(..., description='正缘特征验证')
guide: GuideSchema = Field(..., description='潜意识与相处指南')
monthly_fortune: MonthlyFortuneSchema = Field(..., description='未来12个月感情运势')
timeline: List[TimelineItemSchema] = Field(..., description='未来十年关键节点')
master_advice: MasterAdviceSchema = Field(..., description='大师寄语与化解之道')
class UnlockStatsSchema(BaseModel):
"""解锁统计信息模型"""
unlock_count: int = Field(..., description='已解锁人数', example=12392)
accuracy: str = Field(..., description='准确率', example='98%')
class PersonResponseSchema(BaseModel):
"""人员响应信息模型"""
name: str = Field(..., description='姓名')
gender: str = Field(..., description='性别')
birth_date: str = Field(..., description='生辰八字')
class AffinityCalculateResponseSchema(BaseModel):
"""缘分合盘测算响应模型"""
relationship: str = Field(..., description='缘分类型')
relationship_label: str = Field(..., description='缘分类型标签')
person1: PersonResponseSchema = Field(..., description='甲方信息')
person2: PersonResponseSchema = Field(..., description='乙方信息')
score: int = Field(..., description='默契指数总分', ge=0, le=100)
score_badge: str = Field(..., description='婚配等级')
six_dimension: SixDimensionSchema = Field(..., description='六维契合度评分')
radar_desc: str = Field(..., description='六维雷达图描述')
analysis_cards: List[AnalysisCardSchema] = Field(..., description='分析卡片列表')
unlocked: Optional[UnlockedContentSchema] = Field(None, description='解锁后的深度内容')
is_unlocked: bool = Field(..., description='是否已解锁深度报告')
unlock_price: float = Field(..., description='解锁价格(元)')
unlock_stats: UnlockStatsSchema = Field(..., description='解锁统计信息')
class YifanAffinityCreateSchema(BaseModel):
"""
缘分合盘新增模型
"""
is_deleted: int = Field(default=0, description='是否删除(0否 1是)')
task_status: int = Field(default=1, description='任务状态(1已创建 2测算中 5测算成功 3测算超时 0任务失败)')
user_id: int | None = Field(default=None, description='用户ID')
relationship: str = Field(default=..., description='缘分类型')
person1_name: str = Field(default=..., description='甲方姓名')
person1_gender: str = Field(default=..., description='甲方性别')
person1_birth_date: str = Field(default=..., description='甲方生辰显示格式')
person1_birth_date_api: datetime.datetime = Field(default=..., description='甲方生辰API格式')
person2_name: str = Field(default=..., description='乙方姓名')
person2_gender: str = Field(default=..., description='乙方性别')
person2_birth_date: str = Field(default=..., description='乙方生辰显示格式')
person2_birth_date_api: datetime.datetime = Field(default=..., description='乙方生辰API格式')
score: int | None = Field(default=None, description='默契指数总分')
score_badge: str | None = Field(default=None, description='婚配等级')
six_dimension: str | None = Field(default=None, description='六维契合度评分JSON')
radar_desc: str | None = Field(default=None, description='六维雷达图描述')
analysis_cards: str | None = Field(default=None, description='分析卡片列表JSON')
is_unlocked: int = Field(default=0, description='是否已解锁深度报告')
unlock_price: float | None = Field(default=9.9, description='解锁价格')
unlocked_content: str | None = Field(default=None, description='解锁后的深度内容JSON')
ai_source_data: str | None = Field(default=None, description='AI原始数据')
error_message: str | None = Field(default=None, description='错误信息')
remark: str | None = Field(default=None, description='备注')
class YifanAffinityUpdateSchema(BaseModel):
"""
缘分合盘更新模型
"""
is_deleted: int | None = Field(default=None, description='是否删除(0否 1是)')
task_status: int | None = Field(default=None, description='任务状态(1已创建 2测算中 5测算成功 3测算超时 0任务失败)')
user_id: int | None = Field(default=None, description='用户ID')
relationship: str | None = Field(default=None, description='缘分类型')
person1_name: str | None = Field(default=None, description='甲方姓名')
person1_gender: str | None = Field(default=None, description='甲方性别')
person1_birth_date: str | None = Field(default=None, description='甲方生辰显示格式')
person1_birth_date_api: datetime.datetime | None = Field(default=None, description='甲方生辰API格式')
person2_name: str | None = Field(default=None, description='乙方姓名')
person2_gender: str | None = Field(default=None, description='乙方性别')
person2_birth_date: str | None = Field(default=None, description='乙方生辰显示格式')
person2_birth_date_api: datetime.datetime | None = Field(default=None, description='乙方生辰API格式')
score: int | None = Field(default=None, description='默契指数总分(0-100)')
score_badge: str | None = Field(default=None, description='婚配等级')
six_dimension: str | None = Field(default=None, description='六维契合度评分JSON')
radar_desc: str | None = Field(default=None, description='六维雷达图描述')
analysis_cards: str | None = Field(default=None, description='分析卡片列表JSON')
is_unlocked: int | None = Field(default=None, description='是否已解锁深度报告')
unlock_price: float | None = Field(default=None, description='解锁价格')
unlocked_content: str | None = Field(default=None, description='解锁后的深度内容JSON')
ai_source_data: str | None = Field(default=None, description='AI原始数据')
error_message: str | None = Field(default=None, description='错误信息')
remark: str | None = Field(default=None, description='备注')
class YifanAffinityOutSchema(YifanAffinityCreateSchema, BaseSchema, UserBySchema):
"""
缘分合盘响应模型
"""
model_config = ConfigDict(from_attributes=True)
class YifanAffinityQueryParam:
"""缘分合盘查询参数"""
def __init__(
self,
relationship: str | None = Query(None, description="缘分类型"),
person1_name: str | None = Query(None, description="甲方姓名"),
person2_name: str | None = Query(None, description="乙方姓名"),
score_min: int | None = Query(None, description="最低分数"),
score_max: int | None = Query(None, description="最高分数"),
score_badge: str | None = Query(None, description="婚配等级"),
is_unlocked: int | None = Query(None, description="是否已解锁深度报告"),
created_id: int | None = Query(None, description="创建人ID"),
updated_id: int | None = Query(None, description="更新人ID"),
is_deleted: int | None = Query(None, description="是否删除(0否 1是)"),
task_status: int | None = Query(None, description="任务状态"),
user_id: int | None = Query(None, description="用户ID"),
created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]),
updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]),
) -> None:
# 精确查询字段
self.created_id = created_id
self.updated_id = updated_id
self.is_deleted = is_deleted
self.task_status = task_status
self.user_id = user_id
self.is_unlocked = is_unlocked
# 模糊查询字段
self.relationship = ("like", relationship)
self.person1_name = ("like", person1_name)
self.person2_name = ("like", person2_name)
self.score_badge = ("like", score_badge)
# 范围查询字段
if score_min is not None and score_max is not None:
self.score = ("between", (score_min, score_max))
elif score_min is not None:
self.score = (">=", score_min)
elif score_max is not None:
self.score = ("<=", score_max)
# 时间范围查询
if created_time and len(created_time) == 2:
self.created_time = ("between", (created_time[0], created_time[1]))
if updated_time and len(updated_time) == 2:
self.updated_time = ("between", (updated_time[0], updated_time[1]))

View File

@@ -0,0 +1,377 @@
# -*- coding: utf-8 -*-
import json
import asyncio
from typing import Dict, Any
from app.api.v1.module_system.auth.schema import AuthSchema
from app.core.exceptions import CustomException
from app.core.logger import log
from app.api.v1.module_yifan.yifan_affinity.crud import YifanAffinityCRUD
from app.api.v1.module_yifan.yifan_affinity.schema import (
YifanAffinityCreateSchema, YifanAffinityUpdateSchema, YifanAffinityOutSchema, YifanAffinityQueryParam,
AffinityCalculateRequestSchema, AffinityCalculateResponseSchema
)
class YifanAffinityService:
"""缘分合盘服务"""
@classmethod
async def calculate_affinity_service(cls, auth: AuthSchema, data: AffinityCalculateRequestSchema) -> Dict[str, Any]:
"""
缘分合盘测算服务(异步模式)
1. 校验数据创建报告记录status=1 已创建)
2. 启动后台任务执行AI测算
3. 立即返回 report_id + status
"""
user_id = auth.user.id if auth.user else 0
# 创建缘分合盘记录
create_data = YifanAffinityCreateSchema(
user_id=user_id,
relationship=data.relationship,
person1_name=data.person1.name,
person1_gender=data.person1.gender,
person1_birth_date=data.person1.birth_date,
person1_birth_date_api=data.person1.birth_date_api,
person2_name=data.person2.name,
person2_gender=data.person2.gender,
person2_birth_date=data.person2.birth_date,
person2_birth_date_api=data.person2.birth_date_api,
task_status=1,
is_deleted=0
)
try:
affinity_obj = await YifanAffinityCRUD(auth).create_yifan_affinity_crud(data=create_data)
affinity_id = affinity_obj.id
log.info(f"[缘分合盘] 报告创建成功ID: {affinity_id}")
except Exception as e:
log.error(f"[缘分合盘] 报告创建失败: {str(e)}")
raise CustomException(msg=f"保存缘分合盘报告失败: {str(e)}")
# 启动后台任务执行AI测算
asyncio.create_task(
cls._process_affinity_calculation(
affinity_id=affinity_id,
data=data,
user_id=user_id,
)
)
return {"affinity_id": affinity_id, "status": 1}
@classmethod
async def _process_affinity_calculation(
cls,
affinity_id: int,
data: AffinityCalculateRequestSchema,
user_id: int,
) -> None:
"""后台任务执行缘分合盘AI测算"""
log_prefix = f"[缘分合盘-{affinity_id}]"
try:
# 更新状态为测算中
await cls._update_affinity_status(affinity_id, task_status=2)
log.info(f"{log_prefix} 开始AI测算")
# 构建AI输入文本
relationship_labels = {
"couple": "情侣",
"married": "夫妻",
"crush": "暗恋",
"partner": "合伙",
"friend": "知己",
"family": "亲缘"
}
relationship_label = relationship_labels.get(data.relationship, data.relationship)
ai_input_text = f"""缘分合盘测算请求:
缘分类型:{relationship_label}
甲方信息:
- 姓名:{data.person1.name}
- 性别:{'' if data.person1.gender == 'male' else ''}
- 生辰:{data.person1.birth_date}
乙方信息:
- 姓名:{data.person2.name}
- 性别:{'' if data.person2.gender == 'male' else ''}
- 生辰:{data.person2.birth_date}
请根据以上信息进行缘分合盘测算,返回详细的分析结果。"""
# 调用AI服务进行测算
ai_response = await cls._call_ai_service(ai_input_text)
log.info(f"ai_response is {ai_response}")
if not ai_response:
raise Exception("AI服务返回空结果")
# 解析AI响应并构建结果数据
result_data = cls._parse_ai_response(ai_response, data, relationship_label)
log.info(f"result_data is {result_data}")
# 更新数据库记录
update_data = YifanAffinityUpdateSchema(
score=result_data.get('score'),
score_badge=result_data.get('score_badge'),
six_dimension=json.dumps(result_data.get('six_dimension'), ensure_ascii=False),
radar_desc=result_data.get('radar_desc'),
analysis_cards=json.dumps(result_data.get('analysis_cards'), ensure_ascii=False),
unlocked_content=json.dumps(result_data.get('unlocked'), ensure_ascii=False),
ai_source_data=ai_response,
task_status=5 # 测算成功
)
await cls._update_affinity_record(affinity_id, update_data)
log.info(f"{log_prefix} AI测算完成并保存成功")
except asyncio.TimeoutError:
log.error(f"{log_prefix} AI测算超时")
await cls._update_affinity_status(affinity_id, task_status=3, error_message="AI测算超时")
except Exception as e:
log.error(f"{log_prefix} AI测算失败: {str(e)}")
await cls._update_affinity_status(affinity_id, task_status=0, error_message=str(e)[:500])
@classmethod
async def _call_ai_service(cls, text: str) -> str:
"""
调用AI服务进行缘分合盘测算
Args:
text: 输入文本
Returns:
AI响应结果
"""
from app.api.v1.module_application.ai.service import AIModelTestService
log.info(f"[缘分合盘] 调用AI服务输入长度: {len(text)}")
try:
# 调用实际的AI服务使用personal_naming模型类型
ai_response = await AIModelTestService.test_naming(
model_type="yuanfen_hepan",
text=text
)
log.info(f"[缘分合盘] AI服务响应成功响应长度: {len(ai_response)}")
return ai_response
except Exception as e:
log.error(f"[缘分合盘] AI服务调用失败: {str(e)}")
# 如果AI服务调用失败返回模拟数据作为降级方案
log.warning("[缘分合盘] 使用模拟数据作为降级方案")
mock_response = {
"score": 85,
"score_badge": "上等婚",
"six_dimension": {
"personality": 88,
"career": 82,
"wealth": 90,
"health": 85,
"emotion": 87,
"family": 83
},
"radar_desc": "你们在财运和性格方面非常契合,情感交流也很顺畅。",
"analysis_cards": [
{
"title": "性格契合度",
"content": "你们的性格互补,能够相互理解和支持。",
"score": 88
},
{
"title": "事业发展",
"content": "在事业上你们有共同目标,能够相互促进。",
"score": 82
}
]
}
return json.dumps(mock_response, ensure_ascii=False)
@classmethod
def _parse_ai_response(cls, ai_response: str, request_data: AffinityCalculateRequestSchema, relationship_label: str) -> Dict[str, Any]:
"""解析AI响应数据"""
try:
ai_data = json.loads(ai_response)
# 构建完整的响应数据
result_data = {
"relationship": request_data.relationship,
"relationship_label": relationship_label,
"person1": {
"name": request_data.person1.name,
"gender": request_data.person1.gender,
"birth_date": request_data.person1.birth_date
},
"person2": {
"name": request_data.person2.name,
"gender": request_data.person2.gender,
"birth_date": request_data.person2.birth_date
},
"score": ai_data.get("score", 0),
"score_badge": ai_data.get("score_badge", "中等婚"),
"six_dimension": ai_data.get("six_dimension", {}),
"radar_desc": ai_data.get("radar_desc", ""),
"analysis_cards": ai_data.get("analysis_cards", []),
"unlocked": ai_data.get("unlocked", {}),
"is_unlocked": False,
"unlock_price": 9.9,
"unlock_stats": {
"unlock_count": 12392,
"accuracy": "98%"
}
}
return result_data
except json.JSONDecodeError as e:
log.error(f"AI响应JSON解析失败: {str(e)}")
raise Exception(f"AI响应格式错误: {str(e)}")
@classmethod
async def _update_affinity_status(cls, affinity_id: int, task_status: int, error_message: str = None):
"""更新缘分合盘状态"""
from app.core.database import async_db_session
async with async_db_session() as session:
try:
# 创建一个临时的auth对象用于更新操作
temp_auth = AuthSchema(user=None, db=session)
crud = YifanAffinityCRUD(temp_auth)
crud.session = session
update_data = YifanAffinityUpdateSchema(
task_status=task_status,
error_message=error_message
)
await crud.update_yifan_affinity_crud(affinity_id, update_data)
await session.commit() # 提交事务
log.info(f"[缘分合盘] 状态更新成功ID: {affinity_id}, 状态: {task_status}")
except Exception as e:
await session.rollback() # 回滚事务
log.error(f"[缘分合盘] 状态更新失败ID: {affinity_id}, 错误: {str(e)}")
raise
@classmethod
async def _update_affinity_record(cls, affinity_id: int, update_data: YifanAffinityUpdateSchema):
"""更新缘分合盘记录"""
from app.core.database import async_db_session
async with async_db_session() as session:
try:
# 创建一个临时的auth对象用于更新操作
temp_auth = AuthSchema(user=None, db=session)
crud = YifanAffinityCRUD(temp_auth)
crud.session = session
await crud.update_yifan_affinity_crud(affinity_id, update_data)
await session.commit() # 提交事务
log.info(f"[缘分合盘] 记录更新成功ID: {affinity_id}")
except Exception as e:
await session.rollback() # 回滚事务
log.error(f"[缘分合盘] 记录更新失败ID: {affinity_id}, 错误: {str(e)}")
raise
@classmethod
async def get_affinity_result_service(cls, auth: AuthSchema, affinity_id: int) -> AffinityCalculateResponseSchema:
"""获取缘分合盘结果"""
affinity_obj = await YifanAffinityCRUD(auth).get_yifan_affinity_crud(affinity_id)
if not affinity_obj:
raise CustomException(msg="缘分合盘记录不存在")
if affinity_obj.task_status != 5:
raise CustomException(msg="缘分合盘测算尚未完成")
# 构建响应数据
response_data = {
"relationship": affinity_obj.relationship,
"relationship_label": cls._get_relationship_label(affinity_obj.relationship),
"person1": {
"name": affinity_obj.person1_name,
"gender": affinity_obj.person1_gender,
"birth_date": affinity_obj.person1_birth_date
},
"person2": {
"name": affinity_obj.person2_name,
"gender": affinity_obj.person2_gender,
"birth_date": affinity_obj.person2_birth_date
},
"score": affinity_obj.score or 0,
"score_badge": affinity_obj.score_badge or "中等婚",
"six_dimension": json.loads(affinity_obj.six_dimension) if affinity_obj.six_dimension else {},
"radar_desc": affinity_obj.radar_desc or "",
"analysis_cards": json.loads(affinity_obj.analysis_cards) if affinity_obj.analysis_cards else [],
"unlocked": json.loads(affinity_obj.unlocked_content) if affinity_obj.unlocked_content and affinity_obj.is_unlocked else None,
"is_unlocked": bool(affinity_obj.is_unlocked),
"unlock_price": float(affinity_obj.unlock_price or 9.9),
"unlock_stats": {
"unlock_count": 12392,
"accuracy": "98%"
}
}
return AffinityCalculateResponseSchema(**response_data)
@classmethod
def _get_relationship_label(cls, relationship: str) -> str:
"""获取缘分类型标签"""
labels = {
"couple": "情侣",
"married": "夫妻",
"crush": "暗恋",
"partner": "合伙",
"friend": "知己",
"family": "亲缘"
}
return labels.get(relationship, relationship)
@classmethod
async def create_yifan_affinity_service(cls, auth: AuthSchema, data: YifanAffinityCreateSchema) -> dict:
"""创建缘分合盘记录"""
result_dict = await YifanAffinityCRUD(auth).create_yifan_affinity_crud(data=data)
return YifanAffinityOutSchema.model_validate(result_dict).model_dump()
@classmethod
async def get_yifan_affinity_service(cls, auth: AuthSchema, affinity_id: int) -> dict:
"""获取单个缘分合盘记录"""
result_dict = await YifanAffinityCRUD(auth).get_yifan_affinity_crud(affinity_id=affinity_id)
if not result_dict:
raise CustomException(msg="缘分合盘记录不存在")
return YifanAffinityOutSchema.model_validate(result_dict).model_dump()
@classmethod
async def update_yifan_affinity_service(cls, auth: AuthSchema, affinity_id: int, data: YifanAffinityUpdateSchema) -> dict:
"""更新缘分合盘记录"""
result_dict = await YifanAffinityCRUD(auth).update_yifan_affinity_crud(affinity_id=affinity_id, data=data)
return YifanAffinityOutSchema.model_validate(result_dict).model_dump()
@classmethod
async def delete_yifan_affinity_service(cls, auth: AuthSchema, affinity_id: int) -> bool:
"""删除缘分合盘记录"""
return await YifanAffinityCRUD(auth).delete_yifan_affinity_crud(affinity_id=affinity_id)
@classmethod
async def list_yifan_affinity_service(cls, auth: AuthSchema, search: YifanAffinityQueryParam | None = None, order_by: list[dict] | None = None) -> list[dict]:
"""列表查询"""
search_dict = search.__dict__ if search else None
obj_list = await YifanAffinityCRUD(auth).list_yifan_affinity_crud(search=search_dict, order_by=order_by)
return [YifanAffinityOutSchema.model_validate(obj).model_dump() for obj in obj_list]
@classmethod
async def page_yifan_affinity_service(cls, auth: AuthSchema, page_no: int, page_size: int, search: YifanAffinityQueryParam | None = None, order_by: list[dict] | None = None) -> dict:
"""分页查询"""
search_dict = search.__dict__ if search else None
result_dict = await YifanAffinityCRUD(auth).page_yifan_affinity_crud(page_no=page_no, page_size=page_size, search=search_dict, order_by=order_by)
# 基础CRUD返回的是items键我们需要转换为data键以保持API一致性
if "items" in result_dict:
result_dict["data"] = result_dict.pop("items")
return result_dict