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 @@
# -*- coding: utf-8 -*-

View File

@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.common.response import SuccessResponse
from app.core.dependencies import db_getter, get_current_user
from app.api.v1.module_system.auth.schema import AuthSchema
from app.api.v1.module_common.activity.crud import ActivityCRUD
# 定义路由前缀
ActivityRouter = APIRouter(prefix="/activity", tags=["活动记录"])
@ActivityRouter.get("/display", summary="获取活动展示列表", description="获取前端展示格式的用户活动记录(无需登录)")
async def get_activity_display(
limit: int = 10,
db: AsyncSession = Depends(db_getter)
) -> SuccessResponse:
"""
获取活动展示列表(公开接口,无需登录)
返回格式: ["张** 预约了 宝宝起名 服务", "李** 完成了 个人改名 测算", ...]
"""
# 创建一个不需要用户信息的 auth 对象
auth = AuthSchema(db=db, check_data_scope=False)
crud = ActivityCRUD(auth)
data = await crud.get_display_list(limit=limit)
return SuccessResponse(data=data)
@ActivityRouter.post("/create", summary="创建活动记录", description="创建活动记录(需要登录)")
async def create_activity(
user_name: str,
action: str,
service_name: str,
service_type: str = None,
sort_order: int = 0,
auth: AuthSchema = Depends(get_current_user)
) -> SuccessResponse:
"""
创建活动记录(需要登录)
"""
crud = ActivityCRUD(auth)
data = {
"user_name": user_name,
"action": action,
"service_name": service_name,
"service_type": service_type,
"sort_order": sort_order
}
result = await crud.create(data)
return SuccessResponse(data={"id": result.id}, msg="创建成功")

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from typing import List
from app.core.base_crud import CRUDBase
from app.api.v1.module_common.activity.model import ActivityModel
from app.api.v1.module_common.activity.schema import ActivityCreate, ActivityUpdate
from app.api.v1.module_system.auth.schema import AuthSchema
class ActivityCRUD(CRUDBase[ActivityModel, ActivityCreate, ActivityUpdate]):
"""活动记录CRUD"""
def __init__(self, auth: AuthSchema) -> None:
super().__init__(ActivityModel, auth)
async def get_display_list(self, limit: int = 10) -> List[str]:
"""
获取前端展示格式的活动列表
参数:
- limit: 返回数量限制
返回:
- List[str]: 格式化的活动文本列表
"""
activities = await self.list(
search={"status": "0"},
order_by=[{"sort_order": "asc"}, {"created_time": "desc"}]
)
result = []
for activity in activities[:limit]:
text = f"{activity.user_name} {activity.action}{activity.service_name} {activity.service_type or ''}".strip()
result.append(text)
return result

View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from sqlalchemy import String, Integer
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base_model import ModelMixin
class ActivityModel(ModelMixin):
"""用户活动记录表"""
__tablename__ = "biz_activity"
user_name: Mapped[str] = mapped_column(String(50), nullable=False, comment="用户名(脱敏)")
action: Mapped[str] = mapped_column(String(50), nullable=False, comment="操作类型(预约/完成/购买/查看)")
service_name: Mapped[str] = mapped_column(String(100), nullable=False, comment="服务名称")
service_type: Mapped[str] = mapped_column(String(50), nullable=True, comment="服务类型")
sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False, comment="排序顺序")

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
class ActivityBase(BaseModel):
"""活动记录基础模型"""
user_name: str = Field(..., max_length=50, description="用户名(脱敏)")
action: str = Field(..., max_length=50, description="操作类型")
service_name: str = Field(..., max_length=100, description="服务名称")
service_type: Optional[str] = Field(None, max_length=50, description="服务类型")
sort_order: int = Field(default=0, description="排序顺序")
class ActivityCreate(ActivityBase):
"""创建活动记录"""
pass
class ActivityUpdate(BaseModel):
"""更新活动记录"""
user_name: Optional[str] = Field(None, max_length=50)
action: Optional[str] = Field(None, max_length=50)
service_name: Optional[str] = Field(None, max_length=100)
service_type: Optional[str] = Field(None, max_length=50)
sort_order: Optional[int] = None
class ActivityOut(ActivityBase):
"""活动记录输出模型"""
id: int
created_time: datetime
class Config:
from_attributes = True
class ActivityDisplay(BaseModel):
"""前端展示格式"""
text: str = Field(..., description="展示文本")

View File

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

View File

@@ -0,0 +1,49 @@
'''
Author: caoziyuan ziyuan.cao@zhuying.com
Date: 2025-12-23 14:08:15
LastEditors: caoziyuan ziyuan.cao@zhuying.com
LastEditTime: 2025-12-23 14:24:55
FilePath: \naming-backend\app\api\v1\module_common\calendar\controller.py
Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
'''
# -*- coding: utf-8 -*-
from datetime import date
from fastapi import APIRouter, Query
from app.common.response import SuccessResponse
from app.api.v1.module_common.calendar.service import CalendarService
# 定义路由
CalendarRouter = APIRouter(prefix="/calendar", tags=["万年历"])
@CalendarRouter.get("/today", summary="获取今日万年历", description="获取今天的农历信息、宜忌等(无需登录)")
async def get_today_calendar() -> SuccessResponse:
"""
获取今日万年历信息
返回格式:
{
"lunar": "腊月 十二",
"year": "甲辰年",
"solar": "2024.01.22",
"yi": ["出行", "开市", "交易", "裁衣"],
"ji": ["动土", "安葬", "破土", "作灶"]
}
"""
data = CalendarService.get_calendar_info()
return SuccessResponse(data=data)
@CalendarRouter.get("/date", summary="获取指定日期万年历", description="获取指定日期的农历信息、宜忌等(无需登录)")
async def get_date_calendar(
year: int = Query(..., description="年份2024"),
month: int = Query(..., ge=1, le=12, description="月份1-12"),
day: int = Query(..., ge=1, le=31, description="日期1-31")
) -> SuccessResponse:
"""
获取指定日期的万年历信息
"""
data = CalendarService.get_calendar_info(year, month, day)
return SuccessResponse(data=data)

View File

@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
from typing import List
from pydantic import BaseModel, Field
class CalendarOut(BaseModel):
"""万年历返回数据结构"""
lunar: str = Field(..., description="农历日期,如:腊月 十二")
year: str = Field(..., description="农历年份,如:甲辰年")
solar: str = Field(..., description="公历日期2024.01.22")
yi: List[str] = Field(default=[], description="宜做的事情")
ji: List[str] = Field(default=[], description="忌做的事情")

View File

@@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
import json
from datetime import datetime
from typing import Optional, List
from functools import lru_cache
from openai import OpenAI
from cnlunar import Lunar
from app.config.setting import settings
class CalendarService:
"""万年历服务 - 使用 cnlunar 获取农历信息DeepSeek 生成宜忌"""
# 内存缓存,存储日期对应的宜忌数据
_yi_ji_cache: dict = {}
@classmethod
def get_calendar_info(cls, year: Optional[int] = None, month: Optional[int] = None, day: Optional[int] = None) -> dict:
"""
获取万年历信息
参数:
- year: 年份,默认今年
- month: 月份,默认本月
- day: 日期,默认今天
返回:
- dict: 万年历信息
"""
# 获取日期
if year and month and day:
solar_date = datetime(year, month, day)
else:
solar_date = datetime.now()
# 使用 cnlunar 获取农历信息
lunar = Lunar(solar_date)
# 获取农历月份和日期
lunar_month = lunar.lunarMonthCn # 如:腊月
lunar_day = lunar.lunarDayCn # 如:十二
# 获取天干地支年份
gan_zhi_year = lunar.year8Char # 如:甲辰
# 使用 DeepSeek 生成宜忌(带缓存)
yi_list, ji_list = cls._get_yi_ji_cached(solar_date, lunar)
return {
"lunar": f"{lunar_month} {lunar_day}",
"year": f"{gan_zhi_year}",
"solar": solar_date.strftime("%Y.%m.%d"),
"yi": yi_list,
"ji": ji_list,
}
@classmethod
def _get_yi_ji_cached(cls, solar_date: datetime, lunar: Lunar) -> tuple[List[str], List[str]]:
"""
获取宜忌数据(带缓存)
同一天的数据只调用一次 API后续直接返回缓存
"""
# 用日期字符串作为缓存 key
cache_key = solar_date.strftime("%Y-%m-%d")
# 如果缓存中有数据,直接返回
if cache_key in cls._yi_ji_cache:
return cls._yi_ji_cache[cache_key]
# 缓存中没有,调用 API 获取
yi_list, ji_list = cls._get_yi_ji_from_deepseek(solar_date, lunar)
# 存入缓存
cls._yi_ji_cache[cache_key] = (yi_list, ji_list)
# 限制缓存大小,最多保留 30 天的数据
if len(cls._yi_ji_cache) > 30:
# 删除最早的一条
oldest_key = next(iter(cls._yi_ji_cache))
del cls._yi_ji_cache[oldest_key]
return yi_list, ji_list
@classmethod
def _get_yi_ji_from_deepseek(cls, solar_date: datetime, lunar: Lunar) -> tuple[List[str], List[str]]:
"""
调用 DeepSeek API 生成宜忌数据
参数:
- solar_date: 公历日期
- lunar: 农历对象
返回:
- tuple: (宜列表, 忌列表)
"""
try:
# 从配置文件读取 API 配置
client = OpenAI(
api_key=settings.DEEPSEEK_API_KEY,
base_url=settings.DEEPSEEK_BASE_URL
)
# 构建提示词
prompt = f"""请根据以下日期信息,生成中国传统黄历的宜忌数据。
日期信息:
- 公历:{solar_date.strftime("%Y年%m月%d")}
- 农历:{lunar.lunarMonthCn}{lunar.lunarDayCn}
- 天干地支:{lunar.year8Char}{lunar.month8Char}{lunar.day8Char}
请严格按照以下 JSON 格式返回,不要有其他内容:
{{"yi": ["宜做的事1", "宜做的事2", "宜做的事3", "宜做的事4"], "ji": ["忌做的事1", "忌做的事2", "忌做的事3", "忌做的事4"]}}
要求:
1. 宜和忌各返回4-6个项目
2. 使用传统黄历术语,如:祭祀、祈福、嫁娶、出行、开市、动土、安葬等
3. 只返回 JSON不要有任何解释"""
response = client.chat.completions.create(
model=settings.DEEPSEEK_MODEL,
messages=[
{"role": "system", "content": "你是一个精通中国传统黄历的专家,请根据日期生成准确的宜忌信息。"},
{"role": "user", "content": prompt}
],
temperature=0.7,
max_tokens=200
)
# 解析返回的 JSON
result_text = response.choices[0].message.content.strip()
result = json.loads(result_text)
return result.get("yi", []), result.get("ji", [])
except Exception as e:
# 如果 API 调用失败,返回默认值
print(f"DeepSeek API 调用失败: {e}")
return ["祭祀", "祈福", "出行", "开市"], ["动土", "安葬", "破土", "作灶"]

View File

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

View File

@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
from pathlib import Path
from fastapi import APIRouter, BackgroundTasks, Body, Depends, UploadFile, Request
from fastapi.responses import JSONResponse, FileResponse
from app.core.dependencies import AuthPermission
from app.core.logger import log
from app.common.response import SuccessResponse, UploadFileResponse
from app.core.router_class import OperationLogRoute
from app.utils.upload_util import UploadUtil
from .service import FileService
FileRouter = APIRouter(route_class=OperationLogRoute, prefix="/file", tags=["文件管理"])
@FileRouter.post("/upload", summary="上传文件", description="上传文件",dependencies=[Depends(AuthPermission(["module_common:file:upload"]))])
async def upload_controller(
file: UploadFile,
request: Request,
) -> JSONResponse:
"""
上传文件
参数:
- file (UploadFile): 上传的文件
- request (Request): 请求对象
返回:
- JSONResponse: 包含上传文件详情的JSON响应
"""
result_dict = await FileService.upload_service(base_url=str(request.base_url), file=file)
log.info(f"上传文件成功 {result_dict}")
return SuccessResponse(data=result_dict, msg="上传文件成功")
@FileRouter.post("/download", summary="下载文件", description="下载文件", dependencies=[Depends(AuthPermission(["module_common:file:download"]))])
async def download_controller(
background_tasks: BackgroundTasks,
file_path: str = Body(..., description="文件路径"),
delete: bool = Body(False, description="是否删除文件"),
) -> FileResponse:
"""
下载文件
参数:
- background_tasks (BackgroundTasks): 后台任务对象
- file_path (str): 文件路径
- delete (bool): 是否删除文件
返回:
- FileResponse: 包含下载文件的响应
"""
result = await FileService.download_service(file_path=file_path)
if delete:
background_tasks.add_task(UploadUtil.delete_file, Path(file_path))
log.info(f"下载文件成功")
return UploadFileResponse(file_path=result.file_path, filename=result.file_name)

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from pydantic.alias_generators import to_camel
class ImportFieldModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
base_column: str | None = Field(description='数据库字段名', default=None)
excel_column: str | None = Field(description='excel字段名', default=None)
default_value: str | None = Field(description='默认值', default=None)
is_required: bool | None = Field(description='是否必传', default=None)
selected: bool | None = Field(description='是否勾选', default=None)
@model_validator(mode='before')
@classmethod
def _normalize(cls, data):
if isinstance(data, dict):
for key in ('base_column', 'excel_column', 'default_value'):
val = data.get(key)
if isinstance(val, str):
val = val.strip()
if val == '':
val = None
data[key] = val
# is_required 兼容转换
val = data.get('is_required')
if isinstance(val, str):
lowered = val.strip().lower()
if lowered in {'true', '1', 'y', 'yes'}:
data['is_required'] = True
elif lowered in {'false', '0', 'n', 'no'}:
data['is_required'] = False
return data
@model_validator(mode='after')
def _validate(self):
if self.selected and not (self.base_column and self.base_column.strip()):
raise ValueError('选中字段必须提供数据库字段名')
if self.is_required and not (self.excel_column and self.excel_column.strip()):
raise ValueError('必传字段必须提供excel字段名')
return self
class ImportModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
table_name: str | None = Field(description='表名', default=None)
sheet_name: str | None = Field(description='Sheet名', default=None)
filed_info: list[ImportFieldModel] | None = Field(description='字段关联表', default=None)
file_name: str | None = Field(description='文件名', default=None)
@model_validator(mode='after')
def _validate(self):
# excel_column 不重复(忽略 None
if self.filed_info:
seen = set()
for f in self.filed_info:
if f.excel_column:
key = f.excel_column.strip()
if key in seen:
raise ValueError('excel字段名存在重复')
seen.add(key)
return self

View File

@@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
from typing import Dict
from fastapi import UploadFile
from app.core.exceptions import CustomException
from app.core.base_schema import UploadResponseSchema, DownloadFileSchema
from app.utils.upload_util import UploadUtil
class FileService:
"""
文件管理服务层
"""
@classmethod
async def upload_service(cls, base_url: str, file: UploadFile, upload_type: str = 'local') -> Dict:
"""
上传文件。
参数:
- base_url (str): 基础访问 URL。
- file (UploadFile): 上传文件对象。
- upload_type (str): 上传类型,'local''oss',默认 'local'
返回:
- Dict: 上传响应字典。
异常:
- CustomException: 当未选择文件或上传类型错误时抛出。
"""
if not file:
raise CustomException(msg="请选择要上传的文件")
if upload_type == 'local':
filename, filepath, file_url = await UploadUtil.upload_file(file=file, base_url=base_url)
else:
raise CustomException(msg="上传类型错误")
return UploadResponseSchema(
file_path=f'{filepath}',
file_name=filename,
origin_name=file.filename,
file_url=f'{file_url}',
).model_dump()
@classmethod
async def download_service(cls, file_path: str) -> DownloadFileSchema:
"""
下载文件。
参数:
- file_path (str): 文件路径。
返回:
- DownloadFileSchema: 下载文件响应对象。
异常:
- CustomException: 当未选择文件或文件不存在时抛出。
"""
if not file_path:
raise CustomException(msg="请选择要下载的文件")
if not UploadUtil.check_file_exists(file_path):
raise CustomException(msg="文件不存在")
file_name = UploadUtil.download_file(file_path)
return DownloadFileSchema(
file_path=file_path,
file_name=str(file_name),
)

View File

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

View File

@@ -0,0 +1,125 @@
'''
Author: caoziyuan ziyuan.cao@zhuying.com
Date: 2025-12-23 15:20:07
LastEditors: caoziyuan ziyuan.cao@zhuying.com
LastEditTime: 2025-12-23 15:41:59
FilePath: \naming-backend\app\api\v1\module_common\goodname\controller.py
Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
'''
# -*- coding: utf-8 -*-
from typing import List
from fastapi import APIRouter, Query, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.common.response import SuccessResponse
from app.core.dependencies import db_getter, get_current_user
from app.api.v1.module_system.auth.schema import AuthSchema
from app.api.v1.module_common.goodname.crud import NameRecordCRUD
from app.api.v1.module_common.goodname.schema import NameRecordCreate, NameRecordUpdate, NameRecordOut
# 定义路由
GoodNameRouter = APIRouter(prefix="/goodname", tags=["佳名赏析"])
@GoodNameRouter.get("/list", summary="获取佳名列表", description="获取佳名赏析列表(无需登录)")
async def get_good_name_list(
limit: int = Query(default=3, ge=1, le=50, description="返回数量"),
db: AsyncSession = Depends(db_getter)
) -> SuccessResponse:
"""
获取佳名赏析列表
返回格式:
[
{"name": "清芷", "source": "楚辞", "desc": "岸芷汀兰,郁郁青青"},
{"name": "云帆", "source": "行路难", "desc": "长风破浪会有时,直挂云帆济沧海"},
...
]
"""
auth = AuthSchema(db=db, check_data_scope=False)
crud = NameRecordCRUD(auth)
data = await crud.get_good_names_for_display(limit)
return SuccessResponse(data=data)
@GoodNameRouter.get("/random", summary="随机获取佳名", description="随机获取佳名赏析(无需登录)")
async def get_random_good_names(
count: int = Query(default=3, ge=1, le=10, description="返回数量"),
db: AsyncSession = Depends(db_getter)
) -> SuccessResponse:
"""
随机获取佳名
"""
auth = AuthSchema(db=db, check_data_scope=False)
crud = NameRecordCRUD(auth)
data = await crud.get_random_names(count)
return SuccessResponse(data=data)
@GoodNameRouter.post("/create", summary="创建起名记录", description="创建起名记录(需要登录)")
async def create_name_record(
data: NameRecordCreate,
auth: AuthSchema = Depends(get_current_user)
) -> SuccessResponse:
"""
创建起名记录
"""
crud = NameRecordCRUD(auth)
result = await crud.create(data)
return SuccessResponse(data={"id": result.id}, msg="创建成功")
@GoodNameRouter.get("/my", summary="获取我的起名记录", description="获取当前用户的起名记录(需要登录)")
async def get_my_name_records(
page: int = Query(default=1, ge=1, description="页码"),
size: int = Query(default=10, ge=1, le=100, description="每页数量"),
auth: AuthSchema = Depends(get_current_user)
) -> SuccessResponse:
"""
获取我的起名记录
"""
crud = NameRecordCRUD(auth)
offset = (page - 1) * size
result = await crud.page(
offset=offset,
limit=size,
order_by=[{"created_time": "desc"}],
search={"created_id": auth.user.id},
out_schema=NameRecordOut
)
return SuccessResponse(data=result)
@GoodNameRouter.put("/favorite/{record_id}", summary="收藏/取消收藏", description="收藏或取消收藏起名记录(需要登录)")
async def toggle_favorite(
record_id: int,
auth: AuthSchema = Depends(get_current_user)
) -> SuccessResponse:
"""
收藏/取消收藏起名记录
"""
crud = NameRecordCRUD(auth)
record = await crud.get(id=record_id)
if not record:
return SuccessResponse(msg="记录不存在", success=False)
# 切换收藏状态
new_status = 0 if record.is_favorite == 1 else 1
await crud.update(record_id, {"is_favorite": new_status})
msg = "收藏成功" if new_status == 1 else "已取消收藏"
return SuccessResponse(msg=msg)
@GoodNameRouter.delete("/delete/{record_id}", summary="删除起名记录", description="删除起名记录(需要登录)")
async def delete_name_record(
record_id: int,
auth: AuthSchema = Depends(get_current_user)
) -> SuccessResponse:
"""
删除起名记录
"""
crud = NameRecordCRUD(auth)
await crud.delete([record_id])
return SuccessResponse(msg="删除成功")

View File

@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
from typing import List
from app.core.base_crud import CRUDBase
from app.api.v1.module_common.goodname.model import UserNameRecordModel
from app.api.v1.module_common.goodname.schema import NameRecordCreate, NameRecordUpdate
from app.api.v1.module_system.auth.schema import AuthSchema
class NameRecordCRUD(CRUDBase[UserNameRecordModel, NameRecordCreate, NameRecordUpdate]):
"""起名记录 CRUD"""
def __init__(self, auth: AuthSchema) -> None:
super().__init__(UserNameRecordModel, auth)
async def get_good_names_for_display(self, limit: int = 10) -> List[dict]:
"""
获取佳名赏析列表(用于前端展示)
参数:
- limit: 返回数量
返回:
- List[dict]: 格式化的佳名列表
"""
records = await self.list(
search={"status": "0"},
order_by=[{"created_time": "desc"}]
)
result = []
for record in records[:limit]:
result.append({
"name": record.name,
"source": record.source or "",
"desc": record.source_text or record.meaning or ""
})
return result
async def get_random_names(self, count: int = 3) -> List[dict]:
"""
随机获取佳名
参数:
- count: 返回数量
返回:
- List[dict]: 随机佳名列表
"""
import random
records = await self.list(
search={"status": "0"},
order_by=[{"id": "asc"}]
)
if not records:
return []
# 随机选择
selected = random.sample(list(records), min(count, len(records)))
result = []
for record in selected:
result.append({
"name": record.name,
"source": record.source or "",
"desc": record.source_text or record.meaning or ""
})
return result

View File

@@ -0,0 +1,57 @@
'''
Author: caoziyuan ziyuan.cao@zhuying.com
Date: 2025-12-23 15:22:53
LastEditors: caoziyuan ziyuan.cao@zhuying.com
LastEditTime: 2025-12-23 15:41:35
FilePath: \naming-backend\app\api\v1\module_common\goodname\model.py
Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
'''
# -*- coding: utf-8 -*-
from sqlalchemy import String, Integer, Text, SmallInteger
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base_model import ModelMixin, UserMixin
class UserNameRecordModel(ModelMixin, UserMixin):
"""用户起名记录表"""
__tablename__ = "biz_user_name_record"
# ========== 基本信息 ==========
name: Mapped[str] = mapped_column(String(50), nullable=False, comment="起的名字")
surname: Mapped[str | None] = mapped_column(String(20), nullable=True, comment="姓氏")
full_name: Mapped[str | None] = mapped_column(String(70), nullable=True, comment="完整姓名")
# ========== 名字来源 ==========
source: Mapped[str | None] = mapped_column(String(100), nullable=True, comment="出处(如:诗经、楚辞)")
source_text: Mapped[str | None] = mapped_column(Text, nullable=True, comment="原文/出处原句")
meaning: Mapped[str | None] = mapped_column(Text, nullable=True, comment="名字含义/寓意")
# ========== 起名对象信息 ==========
gender: Mapped[int | None] = mapped_column(SmallInteger, default=0, nullable=True, comment="性别(0:未知 1:男 2:女)")
birth_year: Mapped[int | None] = mapped_column(Integer, nullable=True, comment="出生年份")
birth_month: Mapped[int | None] = mapped_column(Integer, nullable=True, comment="出生月份")
birth_day: Mapped[int | None] = mapped_column(Integer, nullable=True, comment="出生日期")
birth_hour: Mapped[int | None] = mapped_column(Integer, nullable=True, comment="出生时辰(0-23)")
lunar_birth: Mapped[str | None] = mapped_column(String(50), nullable=True, comment="农历生日")
# ========== 五行八字 ==========
wuxing: Mapped[str | None] = mapped_column(String(20), nullable=True, comment="五行属性(如:金木水火土)")
bazi: Mapped[str | None] = mapped_column(String(50), nullable=True, comment="八字")
wuxing_lack: Mapped[str | None] = mapped_column(String(20), nullable=True, comment="五行缺失")
# ========== 起名类型 ==========
name_type: Mapped[int | None] = mapped_column(SmallInteger, default=1, nullable=True, comment="起名类型(1:宝宝起名 2:成人改名 3:公司起名 4:店铺起名)")
# ========== 评分相关 ==========
score: Mapped[int | None] = mapped_column(Integer, nullable=True, comment="名字评分(0-100)")
score_detail: Mapped[str | None] = mapped_column(Text, nullable=True, comment="评分详情(JSON)")
# ========== 用户操作 ==========
is_favorite: Mapped[int] = mapped_column(SmallInteger, default=0, nullable=False, comment="是否收藏(0:否 1:是)")
is_used: Mapped[int] = mapped_column(SmallInteger, default=0, nullable=False, comment="是否已使用(0:否 1:是)")
# ========== 来源渠道 ==========
channel: Mapped[str | None] = mapped_column(String(50), nullable=True, comment="来源渠道小程序、APP、网页")
ip_address: Mapped[str | None] = mapped_column(String(50), nullable=True, comment="用户IP地址")

View File

@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
from typing import Optional
from datetime import datetime
from pydantic import BaseModel, Field
class GoodNameItem(BaseModel):
"""佳名赏析项(简化版,用于列表展示)"""
name: str = Field(..., description="名字")
source: str = Field(..., description="出处")
desc: str = Field(..., description="原文/释义")
class NameRecordCreate(BaseModel):
"""创建起名记录"""
name: str = Field(..., max_length=50, description="起的名字")
surname: Optional[str] = Field(None, max_length=20, description="姓氏")
full_name: Optional[str] = Field(None, max_length=70, description="完整姓名")
source: Optional[str] = Field(None, max_length=100, description="出处")
source_text: Optional[str] = Field(None, description="原文/出处原句")
meaning: Optional[str] = Field(None, description="名字含义/寓意")
gender: Optional[int] = Field(0, description="性别(0:未知 1:男 2:女)")
birth_year: Optional[int] = Field(None, description="出生年份")
birth_month: Optional[int] = Field(None, ge=1, le=12, description="出生月份")
birth_day: Optional[int] = Field(None, ge=1, le=31, description="出生日期")
birth_hour: Optional[int] = Field(None, ge=0, le=23, description="出生时辰")
lunar_birth: Optional[str] = Field(None, max_length=50, description="农历生日")
wuxing: Optional[str] = Field(None, max_length=20, description="五行属性")
bazi: Optional[str] = Field(None, max_length=50, description="八字")
wuxing_lack: Optional[str] = Field(None, max_length=20, description="五行缺失")
name_type: Optional[int] = Field(1, description="起名类型(1:宝宝起名 2:成人改名 3:公司起名 4:店铺起名)")
score: Optional[int] = Field(None, ge=0, le=100, description="名字评分")
score_detail: Optional[str] = Field(None, description="评分详情(JSON)")
channel: Optional[str] = Field(None, max_length=50, description="来源渠道")
class NameRecordUpdate(BaseModel):
"""更新起名记录"""
name: Optional[str] = Field(None, max_length=50)
surname: Optional[str] = Field(None, max_length=20)
full_name: Optional[str] = Field(None, max_length=70)
source: Optional[str] = Field(None, max_length=100)
source_text: Optional[str] = None
meaning: Optional[str] = None
is_favorite: Optional[int] = Field(None, description="是否收藏(0:否 1:是)")
is_used: Optional[int] = Field(None, description="是否已使用(0:否 1:是)")
class NameRecordOut(BaseModel):
"""起名记录输出"""
id: int
name: str
surname: Optional[str] = None
full_name: Optional[str] = None
source: Optional[str] = None
source_text: Optional[str] = None
meaning: Optional[str] = None
gender: Optional[int] = None
birth_year: Optional[int] = None
birth_month: Optional[int] = None
birth_day: Optional[int] = None
lunar_birth: Optional[str] = None
wuxing: Optional[str] = None
bazi: Optional[str] = None
wuxing_lack: Optional[str] = None
name_type: Optional[int] = None
score: Optional[int] = None
is_favorite: int = 0
is_used: int = 0
created_time: datetime
class Config:
from_attributes = True

View File

@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
from typing import List
class GoodNameService:
"""佳名赏析服务"""
# 佳名数据
GOOD_NAMES = [
{"name": "清芷", "source": "楚辞", "desc": "岸芷汀兰,郁郁青青"},
{"name": "云帆", "source": "行路难", "desc": "长风破浪会有时,直挂云帆济沧海"},
{"name": "望舒", "source": "离骚", "desc": "前望舒使先驱兮,后飞廉使奔属"},
{"name": "思齐", "source": "论语", "desc": "见贤思齐焉,见不贤而内自省也"},
{"name": "若华", "source": "楚辞", "desc": "桂树丛生兮山之幽,偃蹇连蜷兮枝相缭"},
{"name": "嘉言", "source": "尚书", "desc": "嘉言罔攸伏,野无遗贤"},
{"name": "明哲", "source": "诗经", "desc": "既明且哲,以保其身"},
{"name": "子衿", "source": "诗经", "desc": "青青子衿,悠悠我心"},
{"name": "静姝", "source": "诗经", "desc": "静女其姝,俟我于城隅"},
{"name": "修远", "source": "离骚", "desc": "路漫漫其修远兮,吾将上下而求索"},
{"name": "星汉", "source": "观沧海", "desc": "日月之行,若出其中;星汉灿烂,若出其里"},
{"name": "霁月", "source": "世说新语", "desc": "光风霁月,坦荡胸怀"},
]
@classmethod
def get_good_names(cls, limit: int = 10) -> List[dict]:
"""
获取佳名列表
参数:
- limit: 返回数量默认10条
返回:
- List[dict]: 佳名列表
"""
return cls.GOOD_NAMES[:limit]
@classmethod
def get_random_names(cls, count: int = 3) -> List[dict]:
"""
随机获取佳名
参数:
- count: 返回数量默认3条
返回:
- List[dict]: 随机佳名列表
"""
import random
return random.sample(cls.GOOD_NAMES, min(count, len(cls.GOOD_NAMES)))

View File

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

View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from fastapi import APIRouter
from fastapi.responses import JSONResponse
HealthRouter = APIRouter(prefix="", tags=["健康检查"])
@HealthRouter.get("/health", summary="健康检查", description="检查系统健康状态")
async def health_check() -> JSONResponse:
"""
健康检查接口
返回:
- JSONResponse: 包含健康状态的JSON响应
"""
return JSONResponse(content={"msg": "Healthy"}, status_code=200)