This commit is contained in:
2025-11-18 16:46:04 +08:00
commit 1bd91df9a1
24 changed files with 1954 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
from datetime import datetime, timezone, timedelta
from pydantic import BaseModel, Field, computed_field
from typing import List
from uuid import UUID
from utils.time_tool import TimestampModel
CHINA_TZ = timezone(timedelta(hours=8))
class Base(BaseModel):
"""
基础地址信息模型
包含地址相关的通用字段,供创建与输出模型复用
"""
firstname: str = Field(..., description='')
lastname: str = Field(..., description='')
full_name: str = Field(..., description='全名')
birthday: str = Field(..., description='生日')
street_address: str = Field(..., description='街道地址')
city: str = Field(..., description='城市')
phone: str = Field(..., description='电话')
zip_code: str = Field(..., description='邮编')
state_fullname: str = Field(..., description='州全称')
status: bool = Field(False, description='状态')
class Create(Base):
"""
创建请求模型
"""
pass
class Update(BaseModel):
"""
更新请求模型,支持部分更新
"""
firstname: str | None = Field(None, description='')
lastname: str | None = Field(None, description='')
full_name: str | None = Field(None, description='全名')
birthday: str | None = Field(None, description='生日')
street_address: str | None = Field(None, description='街道地址')
city: str | None = Field(None, description='城市')
phone: str | None = Field(None, description='电话')
zip_code: str | None = Field(None, description='邮编')
state_fullname: str | None = Field(None, description='州全称')
status: bool | None = Field(None, description='状态')
class Out(TimestampModel, Base):
"""
输出模型
"""
code: int = Field(200, description='状态码')
message: str = Field('成功', description='提示信息')
id: UUID = Field(..., description='ID')
create_time: datetime = Field(..., description='创建时间')
update_time: datetime = Field(..., description='更新时间')
@computed_field
@property
def create_time_cn(self) -> str:
return self.create_time.astimezone(CHINA_TZ).strftime("%Y-%m-%d %H:%M:%S")
@computed_field
@property
def update_time_cn(self) -> str:
return self.update_time.astimezone(CHINA_TZ).strftime("%Y-%m-%d %H:%M:%S")
class Config:
from_attributes = True
class OutList(BaseModel):
"""
列表输出模型
"""
code: int = Field(200, description='状态码')
message: str = Field('成功', description='提示信息')
count: int = Field(0, description='总数')
num: int = Field(0, description='当前数量')
items: List[Out] = Field([], description='列表数据')

View File

@@ -0,0 +1,171 @@
from fastapi import APIRouter, Query, Body, HTTPException
import random
from uuid import UUID
from .schema import Create, Update, Out, OutList
from ..models import Info
from utils.decorators import handle_exceptions_unified
from utils.time_tool import parse_time
from utils.out_base import CommonOut
from tortoise.transactions import in_transaction
app = APIRouter()
# 创建信息
@app.post("", response_model=Out, description='创建信息', summary='创建信息')
@handle_exceptions_unified()
async def post(item: Create = Body(..., description='创建数据')):
"""
创建信息记录
"""
res = await Info.create(**item.model_dump())
if not res:
raise HTTPException(status_code=400, detail='创建失败')
return res
# 查询信息
@app.get("", response_model=OutList, description='获取信息', summary='获取信息')
@handle_exceptions_unified()
async def gets(
id: UUID | None = Query(None, description='主键ID'),
firstname: str | None = Query(None, description=''),
lastname: str | None = Query(None, description=''),
full_name: str | None = Query(None, description='全名'),
birthday: str | None = Query(None, description='生日'),
street_address: str | None = Query(None, description='街道地址'),
city: str | None = Query(None, description='城市'),
phone: str | None = Query(None, description='电话'),
zip_code: str | None = Query(None, description='邮编'),
state_fullname: str | None = Query(None, description='州全称'),
status: bool | None = Query(None, description='状态'),
order_by: str | None = Query('create_time', description='排序字段',
regex='^(-)?(id|firstname|lastname|city|zip_code|create_time|update_time)$'),
res_count: bool = Query(False, description='是否返回总数'),
create_time_start: str | int | None = Query(
None, description='创建时间开始 (支持 YYYY-MM-DD / YYYY-MM-DD HH:mm:ss / 13位时间戳)'),
create_time_end: str | int | None = Query(
None, description='创建时间结束 (支持 YYYY-MM-DD / YYYY-MM-DD HH:mm:ss / 13位时间戳)'),
update_time_start: str | int | None = Query(
None, description='更新时间开始 (支持 YYYY-MM-DD / YYYY-MM-DD HH:mm:ss / 13位时间戳)'),
update_time_end: str | int | None = Query(
None, description='更新时间结束 (支持 YYYY-MM-DD / YYYY-MM-DD HH:mm:ss / 13位时间戳)'),
page: int = Query(1, ge=1, description='页码'),
limit: int = Query(10, ge=1, le=1000, description='每页数量'),
):
"""
获取信息列表
"""
query = Info.all()
if id:
query = query.filter(id=id)
if firstname:
query = query.filter(firstname=firstname)
if lastname:
query = query.filter(lastname=lastname)
if full_name:
query = query.filter(full_name=full_name)
if birthday:
query = query.filter(birthday=birthday)
if street_address:
query = query.filter(street_address=street_address)
if city:
query = query.filter(city=city)
if phone:
query = query.filter(phone=phone)
if zip_code:
query = query.filter(zip_code=zip_code)
if state_fullname:
query = query.filter(state_fullname=state_fullname)
if status is not None:
query = query.filter(status=status)
if create_time_start:
query = query.filter(create_time__gte=parse_time(create_time_start))
if create_time_end:
query = query.filter(create_time__lte=parse_time(
create_time_end, is_end=True))
if update_time_start:
query = query.filter(update_time__gte=parse_time(update_time_start))
if update_time_end:
query = query.filter(update_time__lte=parse_time(
update_time_end, is_end=True))
if order_by:
query = query.order_by(order_by)
if res_count:
count = await query.count()
else:
count = -1
offset = (page - 1) * limit # 计算偏移量
query = query.limit(limit).offset(offset) # 应用分页
res = await query
if not res:
raise HTTPException(status_code=404, detail='信息不存在')
num = len(res)
return OutList(count=count, num=num, items=res)
# 更新信息
@app.put("", response_model=Out, description='更新信息', summary='更新信息')
@handle_exceptions_unified()
async def put(id: UUID = Query(..., description='主键ID'),
item: Update = Body(..., description='更新数据'),
):
"""
部分更新信息,只更新传入的非空字段
"""
# 检查信息是否存在
secret = await Info.get_or_none(id=id)
if not secret:
raise HTTPException(status_code=404, detail='信息不存在')
# 获取要更新的字段排除None值的字段
update_data = item.model_dump(exclude_unset=True)
# 如果没有要更新的字段
if not update_data:
raise HTTPException(status_code=400, detail='没有要更新的字段')
# 更新信息字段
await secret.update_from_dict(update_data)
await secret.save()
return secret
# 删除信息
@app.delete("", response_model=CommonOut, description='删除信息', summary='删除信息')
@handle_exceptions_unified()
async def delete(id: UUID = Query(..., description='主键ID'),
):
"""删除信息"""
secret = await Info.get_or_none(id=id)
if not secret:
raise HTTPException(status_code=404, detail='信息不存在')
await secret.delete()
# Tortoise ORM 单个实例的 delete() 方法返回 None而不是删除的记录数
# 删除成功时手动返回 1如果有异常会被装饰器捕获
return CommonOut(count=1)
# 随机获取一条状态修改为True的记录
@app.put("/one", response_model=Out, description='随机获取一条状态修改为True的记录', summary='随机获取一条状态修改为True的记录')
@handle_exceptions_unified()
async def random_update_status():
"""
随机获取一条状态为 False 的记录并在事务中更新为 True
"""
async with in_transaction() as conn:
q = Info.filter(status=False).using_db(conn)
current_running_count = await q.count()
if current_running_count == 0:
raise HTTPException(status_code=404, detail='没有状态为False的记录')
pick_index = random.choice(range(current_running_count))
item = await q.order_by('create_time').offset(pick_index).first()
updated = await Info.filter(id=item.id, status=False).using_db(conn).update(status=True)
if updated == 0:
raise HTTPException(status_code=400, detail='并发冲突,未更新')
return item