0.0.1
This commit is contained in:
9
app/apis/country/__init__.py
Normal file
9
app/apis/country/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from fastapi import APIRouter
|
||||
from .info.view import app as info_app
|
||||
from .food.view import app as food_app
|
||||
from .shop.view import app as shop_app
|
||||
|
||||
app = APIRouter()
|
||||
app.include_router(info_app, prefix='/info', tags=['信息'])
|
||||
app.include_router(food_app, prefix='/food', tags=['食物'])
|
||||
app.include_router(shop_app, prefix='/shop', tags=['商店'])
|
||||
66
app/apis/country/food/schema.py
Normal file
66
app/apis/country/food/schema.py
Normal file
@@ -0,0 +1,66 @@
|
||||
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):
|
||||
"""
|
||||
基础食物信息模型
|
||||
|
||||
仅包含食物名称
|
||||
"""
|
||||
name: str = Field(..., description='食物名称')
|
||||
|
||||
|
||||
class Create(Base):
|
||||
"""
|
||||
创建请求模型
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Update(BaseModel):
|
||||
"""
|
||||
更新请求模型,支持部分更新
|
||||
"""
|
||||
name: str | 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='列表数据')
|
||||
122
app/apis/country/food/view.py
Normal file
122
app/apis/country/food/view.py
Normal file
@@ -0,0 +1,122 @@
|
||||
|
||||
from fastapi import APIRouter, Query, Body, HTTPException
|
||||
from uuid import UUID
|
||||
from .schema import Create, Update, Out, OutList
|
||||
from ..models import Food
|
||||
from utils.decorators import handle_exceptions_unified
|
||||
from utils.time_tool import parse_time
|
||||
from utils.out_base import CommonOut
|
||||
|
||||
app = APIRouter()
|
||||
|
||||
|
||||
# 创建食物
|
||||
@app.post("", response_model=Out, description='创建食物', summary='创建食物')
|
||||
@handle_exceptions_unified()
|
||||
async def post(item: Create = Body(..., description='创建数据')):
|
||||
"""
|
||||
创建食物记录
|
||||
"""
|
||||
res = await Food.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'),
|
||||
name: str | None = Query(None, description='食物名称'),
|
||||
order_by: str | None = Query('create_time', description='排序字段',
|
||||
regex='^(-)?(id|name|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 = Food.all()
|
||||
if id:
|
||||
query = query.filter(id=id)
|
||||
if name:
|
||||
query = query.filter(name=name)
|
||||
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 Food.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 Food.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)
|
||||
84
app/apis/country/info/schema.py
Normal file
84
app/apis/country/info/schema.py
Normal 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='列表数据')
|
||||
171
app/apis/country/info/view.py
Normal file
171
app/apis/country/info/view.py
Normal 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
|
||||
110
app/apis/country/models.py
Normal file
110
app/apis/country/models.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import uuid
|
||||
from tortoise import fields
|
||||
from tortoise.models import Model
|
||||
|
||||
|
||||
class Shop(Model):
|
||||
"""
|
||||
店铺模型
|
||||
|
||||
字段:
|
||||
id (UUIDField): 主键,默认使用 UUID 生成
|
||||
province (CharField): 省份,最大长度 255
|
||||
city (CharField): 城市,最大长度 255
|
||||
street (CharField): 街道,最大长度 255
|
||||
shop_name (CharField): 店铺名称,最大长度 255
|
||||
shop_number (CharField): 店铺号码,最大长度 255, nullable 为 True
|
||||
"""
|
||||
id = fields.UUIDField(pk=True, default=uuid.uuid4, description="ID")
|
||||
province = fields.CharField(max_length=255, index=True, description="省份")
|
||||
city = fields.CharField(max_length=255, index=True, description="城市")
|
||||
street = fields.CharField(max_length=255, index=True, description="街道")
|
||||
shop_name = fields.CharField(max_length=255, index=True, description="店铺名称")
|
||||
shop_number = fields.CharField(max_length=255, null=True, description="店铺号码")
|
||||
create_time = fields.DatetimeField(auto_now_add=True, index=True, description='创建时间')
|
||||
update_time = fields.DatetimeField(auto_now=True, description='更新时间')
|
||||
|
||||
|
||||
class Meta:
|
||||
table = "shop"
|
||||
table_description = "店铺表"
|
||||
ordering = ["create_time"]
|
||||
indexes = [
|
||||
("province", "city", "street"),
|
||||
]
|
||||
def __repr__(self):
|
||||
return f"<Shop(id={self.id}, province={self.province}, city={self.city}, street={self.street}, shop_name={self.shop_name})>"
|
||||
|
||||
__str__ = __repr__
|
||||
|
||||
class Food(Model):
|
||||
"""
|
||||
食物模型
|
||||
|
||||
字段:
|
||||
id (UUIDField): 主键,默认使用 UUID 生成
|
||||
name (CharField): 食物名称,最大长度 255
|
||||
"""
|
||||
id = fields.UUIDField(pk=True, default=uuid.uuid4, description="ID")
|
||||
name = fields.CharField(max_length=255, index=True, description="食物名称")
|
||||
create_time = fields.DatetimeField(auto_now_add=True, index=True, description='创建时间')
|
||||
update_time = fields.DatetimeField(auto_now=True, description='更新时间')
|
||||
|
||||
|
||||
class Meta:
|
||||
table = "food"
|
||||
table_description = "食物表"
|
||||
ordering = ["create_time"]
|
||||
indexes = [
|
||||
("name",),
|
||||
]
|
||||
def __repr__(self):
|
||||
return f"<Food(id={self.id}, name={self.name})>"
|
||||
|
||||
__str__ = __repr__
|
||||
|
||||
|
||||
class Info(Model):
|
||||
"""
|
||||
信息模型
|
||||
|
||||
字段:
|
||||
id (UUIDField): 主键,默认使用 UUID 生成
|
||||
firstname (CharField): 名,最大长度 255
|
||||
lastname (CharField): 姓,最大长度 255
|
||||
full_name (CharField): 全名,最大长度 255
|
||||
birthday (CharField): 生日(原始字符串),最大长度 32
|
||||
street_address (CharField): 街道地址,最大长度 255
|
||||
city (CharField): 城市,最大长度 255
|
||||
phone (CharField): 电话,最大长度 64
|
||||
zip_code (CharField): 邮编,最大长度 20
|
||||
state_fullname (CharField): 州全称,最大长度 255
|
||||
"""
|
||||
id = fields.UUIDField(pk=True, default=uuid.uuid4, description="ID")
|
||||
firstname = fields.CharField(max_length=255, index=True, description="名")
|
||||
lastname = fields.CharField(max_length=255, index=True, description="姓")
|
||||
full_name = fields.CharField(max_length=255, index=True, description="全名")
|
||||
birthday = fields.CharField(max_length=32, description="生日")
|
||||
street_address = fields.CharField(max_length=255, index=True, description="街道地址")
|
||||
city = fields.CharField(max_length=255, index=True, description="城市")
|
||||
phone = fields.CharField(max_length=64, description="电话")
|
||||
zip_code = fields.CharField(max_length=20, index=True, description="邮编")
|
||||
state_fullname = fields.CharField(max_length=255, index=True, description="州全称")
|
||||
status = fields.BooleanField(default=False, description="状态")
|
||||
create_time = fields.DatetimeField(auto_now_add=True, index=True, description='创建时间')
|
||||
update_time = fields.DatetimeField(auto_now=True, description='更新时间')
|
||||
|
||||
|
||||
class Meta:
|
||||
table = "info"
|
||||
table_description = "信息表"
|
||||
ordering = ["create_time"]
|
||||
indexes = [
|
||||
("city", "zip_code", "state_fullname"),
|
||||
("firstname", "lastname"),
|
||||
]
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Info(id={self.id}, firstname={self.firstname}, lastname={self.lastname}, full_name={self.full_name}, birthday={self.birthday}, street_address={self.street_address}, city={self.city}, phone={self.phone}, zip_code={self.zip_code}, state_fullname={self.state_fullname})>"
|
||||
|
||||
__str__ = __repr__
|
||||
74
app/apis/country/shop/schema.py
Normal file
74
app/apis/country/shop/schema.py
Normal file
@@ -0,0 +1,74 @@
|
||||
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):
|
||||
"""
|
||||
基础店铺信息模型
|
||||
|
||||
包含店铺相关的通用字段,供创建与输出模型复用
|
||||
"""
|
||||
province: str = Field(..., description='省份')
|
||||
city: str = Field(..., description='城市')
|
||||
street: str = Field(..., description='街道')
|
||||
shop_name: str = Field(..., description='店铺名称')
|
||||
shop_number: str | None = Field(None, description='店铺号码')
|
||||
|
||||
|
||||
class Create(Base):
|
||||
"""
|
||||
创建请求模型
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Update(BaseModel):
|
||||
"""
|
||||
更新请求模型,支持部分更新
|
||||
"""
|
||||
province: str | None = Field(None, description='省份')
|
||||
city: str | None = Field(None, description='城市')
|
||||
street: str | None = Field(None, description='街道')
|
||||
shop_name: str | None = Field(None, description='店铺名称')
|
||||
shop_number: str | 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='列表数据')
|
||||
134
app/apis/country/shop/view.py
Normal file
134
app/apis/country/shop/view.py
Normal file
@@ -0,0 +1,134 @@
|
||||
|
||||
from fastapi import APIRouter, Query, Body, HTTPException
|
||||
from uuid import UUID
|
||||
from .schema import Create, Update, Out, OutList
|
||||
from ..models import Shop
|
||||
from utils.decorators import handle_exceptions_unified
|
||||
from utils.time_tool import parse_time
|
||||
from utils.out_base import CommonOut
|
||||
|
||||
app = APIRouter()
|
||||
|
||||
|
||||
# 创建店铺
|
||||
@app.post("", response_model=Out, description='创建店铺', summary='创建店铺')
|
||||
@handle_exceptions_unified()
|
||||
async def post(item: Create = Body(..., description='创建数据')):
|
||||
"""
|
||||
创建店铺记录
|
||||
"""
|
||||
res = await Shop.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'),
|
||||
province: str | None = Query(None, description='省份'),
|
||||
city: str | None = Query(None, description='城市'),
|
||||
street: str | None = Query(None, description='街道'),
|
||||
shop_name: str | None = Query(None, description='店铺名称'),
|
||||
shop_number: str | None = Query(None, description='店铺号码'),
|
||||
order_by: str | None = Query('create_time', description='排序字段',
|
||||
regex='^(-)?(id|province|city|street|shop_name|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 = Shop.all()
|
||||
if id:
|
||||
query = query.filter(id=id)
|
||||
if province:
|
||||
query = query.filter(province=province)
|
||||
if city:
|
||||
query = query.filter(city=city)
|
||||
if street:
|
||||
query = query.filter(street=street)
|
||||
if shop_name:
|
||||
query = query.filter(shop_name=shop_name)
|
||||
if shop_number:
|
||||
query = query.filter(shop_number=shop_number)
|
||||
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 Shop.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 Shop.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)
|
||||
Reference in New Issue
Block a user