Build lightweight AI agent admin

This commit is contained in:
Codex
2026-06-08 18:14:59 +08:00
commit e164840f43
2530 changed files with 435693 additions and 0 deletions
+241
View File
@@ -0,0 +1,241 @@
from typing import List
from fastapi import APIRouter, Depends, Query
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.base_schema import ResponseModel
from core.region.service import RegionService
from core.region.model import Province, City, Area, Street, Village
from core.region.schema import (
ProvinceResponse, CityResponse, AreaResponse,
StreetResponse, VillageResponse
)
router = APIRouter(prefix="/regions", tags=["行政区划管理"])
@router.get("/tree", summary="获取行政区划树形数据")
async def get_region_tree(
level: int = Query(default=3, ge=1, le=5, description="级联层级:1=省,2=省市,3=省市区,4=省市区街道,5=省市区街道村"),
db: AsyncSession = Depends(get_db)
):
"""
获取行政区划树形结构数据,用于级联选择器
- level: 1=仅省份,2=省市两级,3=省市区三级,4=省市区街道四级,5=省市区街道村五级
"""
tree_data = await RegionService.get_tree(db, level=level)
return tree_data
@router.get("/provinces", response_model=List[ProvinceResponse], summary="获取省份列表")
async def get_provinces(db: AsyncSession = Depends(get_db)):
"""获取所有省份列表"""
provinces = await RegionService.get_provinces(db)
return provinces
@router.post("/provinces", response_model=ProvinceResponse, summary="创建省份")
async def create_province(data: dict, db: AsyncSession = Depends(get_db)):
"""创建省份"""
province = await RegionService.create_province(db, data)
return province
@router.put("/provinces/{province_id}", response_model=ProvinceResponse, summary="更新省份")
async def update_province(province_id: str, data: dict, db: AsyncSession = Depends(get_db)):
"""更新省份"""
province = await RegionService.update_province(db, province_id, data)
if not province:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="省份不存在")
return province
@router.delete("/provinces/{province_id}", response_model=ResponseModel, summary="删除省份")
async def delete_province(province_id: str, db: AsyncSession = Depends(get_db)):
"""删除省份"""
success = await RegionService.delete_province(db, province_id)
if not success:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="省份不存在")
return ResponseModel(message="删除成功")
@router.get("/cities", response_model=List[CityResponse], summary="获取城市列表")
async def get_cities(
province_code: str = Query(None, description="省份代码"),
db: AsyncSession = Depends(get_db)
):
"""根据省份代码获取城市列表"""
if province_code:
cities = await RegionService.get_cities_by_province(db, province_code)
else:
stmt = select(City).order_by(City.sort, City.code)
result = await db.execute(stmt)
cities = result.scalars().all()
return cities
@router.post("/cities", response_model=CityResponse, summary="创建城市")
async def create_city(data: dict, db: AsyncSession = Depends(get_db)):
"""创建城市"""
city = await RegionService.create_city(db, data)
return city
@router.put("/cities/{city_id}", response_model=CityResponse, summary="更新城市")
async def update_city(city_id: str, data: dict, db: AsyncSession = Depends(get_db)):
"""更新城市"""
city = await RegionService.update_city(db, city_id, data)
if not city:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="城市不存在")
return city
@router.delete("/cities/{city_id}", response_model=ResponseModel, summary="删除城市")
async def delete_city(city_id: str, db: AsyncSession = Depends(get_db)):
"""删除城市"""
success = await RegionService.delete_city(db, city_id)
if not success:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="城市不存在")
return ResponseModel(message="删除成功")
@router.get("/areas", response_model=List[AreaResponse], summary="获取区县列表")
async def get_areas(
city_code: str = Query(None, description="城市代码"),
db: AsyncSession = Depends(get_db)
):
"""根据城市代码获取区县列表"""
if city_code:
areas = await RegionService.get_areas_by_city(db, city_code)
else:
stmt = select(Area).order_by(Area.sort, Area.code)
result = await db.execute(stmt)
areas = result.scalars().all()
return areas
@router.post("/areas", response_model=AreaResponse, summary="创建区县")
async def create_area(data: dict, db: AsyncSession = Depends(get_db)):
"""创建区县"""
area = await RegionService.create_area(db, data)
return area
@router.put("/areas/{area_id}", response_model=AreaResponse, summary="更新区县")
async def update_area(area_id: str, data: dict, db: AsyncSession = Depends(get_db)):
"""更新区县"""
area = await RegionService.update_area(db, area_id, data)
if not area:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="区县不存在")
return area
@router.delete("/areas/{area_id}", response_model=ResponseModel, summary="删除区县")
async def delete_area(area_id: str, db: AsyncSession = Depends(get_db)):
"""删除区县"""
success = await RegionService.delete_area(db, area_id)
if not success:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="区县不存在")
return ResponseModel(message="删除成功")
@router.get("/streets", response_model=List[StreetResponse], summary="获取街道列表")
async def get_streets(
area_code: str = Query(None, description="区县代码"),
db: AsyncSession = Depends(get_db)
):
"""根据区县代码获取街道列表"""
if area_code:
streets = await RegionService.get_streets_by_area(db, area_code)
else:
stmt = select(Street).order_by(Street.sort, Street.code).limit(1000)
result = await db.execute(stmt)
streets = result.scalars().all()
return streets
@router.post("/streets", response_model=StreetResponse, summary="创建街道")
async def create_street(data: dict, db: AsyncSession = Depends(get_db)):
"""创建街道"""
street = await RegionService.create_street(db, data)
return street
@router.put("/streets/{street_id}", response_model=StreetResponse, summary="更新街道")
async def update_street(street_id: str, data: dict, db: AsyncSession = Depends(get_db)):
"""更新街道"""
street = await RegionService.update_street(db, street_id, data)
if not street:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="街道不存在")
return street
@router.delete("/streets/{street_id}", response_model=ResponseModel, summary="删除街道")
async def delete_street(street_id: str, db: AsyncSession = Depends(get_db)):
"""删除街道"""
success = await RegionService.delete_street(db, street_id)
if not success:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="街道不存在")
return ResponseModel(message="删除成功")
@router.get("/villages", response_model=List[VillageResponse], summary="获取村庄列表")
async def get_villages(
street_code: str = Query(None, description="街道代码"),
db: AsyncSession = Depends(get_db)
):
"""根据街道代码获取村庄列表"""
if street_code:
villages = await RegionService.get_villages_by_street(db, street_code)
else:
stmt = select(Village).order_by(Village.sort, Village.code).limit(1000)
result = await db.execute(stmt)
villages = result.scalars().all()
return villages
@router.post("/villages", response_model=VillageResponse, summary="创建村庄")
async def create_village(data: dict, db: AsyncSession = Depends(get_db)):
"""创建村庄"""
village = await RegionService.create_village(db, data)
return village
@router.put("/villages/{village_id}", response_model=VillageResponse, summary="更新村庄")
async def update_village(village_id: str, data: dict, db: AsyncSession = Depends(get_db)):
"""更新村庄"""
village = await RegionService.update_village(db, village_id, data)
if not village:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="村庄不存在")
return village
@router.delete("/villages/{village_id}", response_model=ResponseModel, summary="删除村庄")
async def delete_village(village_id: str, db: AsyncSession = Depends(get_db)):
"""删除村庄"""
success = await RegionService.delete_village(db, village_id)
if not success:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="村庄不存在")
return ResponseModel(message="删除成功")
@router.get("/name/{code}", response_model=ResponseModel, summary="根据代码获取名称")
async def get_region_name(
code: str,
db: AsyncSession = Depends(get_db)
):
"""根据区域代码获取区域名称"""
name = await RegionService.get_region_name(db, code)
return ResponseModel(data={"name": name})
+77
View File
@@ -0,0 +1,77 @@
from sqlalchemy import Column, String, Integer, ForeignKey
from sqlalchemy.orm import relationship
from app.base_model import BaseModel
class Province(BaseModel):
"""省份模型"""
__tablename__ = "core_province"
code = Column(String(20), unique=True, nullable=False, index=True, comment="省份代码")
name = Column(String(100), nullable=False, comment="省份名称")
sort = Column(Integer, default=0, comment="排序")
# 关联关系
cities = relationship("City", back_populates="province", cascade="all, delete-orphan")
class City(BaseModel):
"""城市模型"""
__tablename__ = "core_city"
code = Column(String(20), unique=True, nullable=False, index=True, comment="城市代码")
name = Column(String(100), nullable=False, comment="城市名称")
province_code = Column(String(20), ForeignKey("core_province.code", ondelete="CASCADE"), nullable=False, comment="省份代码")
sort = Column(Integer, default=0, comment="排序")
# 关联关系
province = relationship("Province", back_populates="cities")
areas = relationship("Area", back_populates="city", cascade="all, delete-orphan")
class Area(BaseModel):
"""区县模型"""
__tablename__ = "core_area"
code = Column(String(20), unique=True, nullable=False, index=True, comment="区县代码")
name = Column(String(100), nullable=False, comment="区县名称")
province_code = Column(String(20), nullable=False, index=True, comment="省份代码")
city_code = Column(String(20), ForeignKey("core_city.code", ondelete="CASCADE"), nullable=False, comment="城市代码")
sort = Column(Integer, default=0, comment="排序")
# 关联关系
city = relationship("City", back_populates="areas")
streets = relationship("Street", back_populates="area", cascade="all, delete-orphan")
class Street(BaseModel):
"""街道/乡镇模型"""
__tablename__ = "core_street"
code = Column(String(20), unique=True, nullable=False, index=True, comment="街道代码")
name = Column(String(100), nullable=False, comment="街道名称")
province_code = Column(String(20), nullable=False, index=True, comment="省份代码")
city_code = Column(String(20), nullable=False, index=True, comment="城市代码")
area_code = Column(String(20), ForeignKey("core_area.code", ondelete="CASCADE"), nullable=False, comment="区县代码")
sort = Column(Integer, default=0, comment="排序")
# 关联关系
area = relationship("Area", back_populates="streets")
villages = relationship("Village", back_populates="street", cascade="all, delete-orphan")
class Village(BaseModel):
"""村/社区模型"""
__tablename__ = "core_village"
code = Column(String(20), unique=True, nullable=False, index=True, comment="村庄代码")
name = Column(String(100), nullable=False, comment="村庄名称")
province_code = Column(String(20), nullable=False, index=True, comment="省份代码")
city_code = Column(String(20), nullable=False, index=True, comment="城市代码")
area_code = Column(String(20), nullable=False, index=True, comment="区县代码")
street_code = Column(String(20), ForeignKey("core_street.code", ondelete="CASCADE"), nullable=False, comment="街道代码")
sort = Column(Integer, default=0, comment="排序")
# 关联关系
street = relationship("Street", back_populates="villages")
+67
View File
@@ -0,0 +1,67 @@
from typing import Optional, List
from pydantic import BaseModel, ConfigDict
class RegionBase(BaseModel):
"""区域基础 Schema"""
code: str
name: str
class ProvinceResponse(RegionBase):
"""省份响应 Schema"""
id: str
sort: int = 0
model_config = ConfigDict(from_attributes=True)
class CityResponse(RegionBase):
"""城市响应 Schema"""
id: str
province_code: str
sort: int = 0
model_config = ConfigDict(from_attributes=True)
class AreaResponse(RegionBase):
"""区县响应 Schema"""
id: str
province_code: str
city_code: str
sort: int = 0
model_config = ConfigDict(from_attributes=True)
class StreetResponse(RegionBase):
"""街道响应 Schema"""
id: str
province_code: str
city_code: str
area_code: str
sort: int = 0
model_config = ConfigDict(from_attributes=True)
class VillageResponse(RegionBase):
"""村庄响应 Schema"""
id: str
province_code: str
city_code: str
area_code: str
street_code: str
sort: int = 0
model_config = ConfigDict(from_attributes=True)
class RegionTreeNode(BaseModel):
"""区域树节点 Schema(用于级联选择器)"""
code: str
name: str
children: Optional[List['RegionTreeNode']] = None
model_config = ConfigDict(from_attributes=True)
+376
View File
@@ -0,0 +1,376 @@
from typing import List, Dict, Any, Optional
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from core.region.model import Province, City, Area, Street, Village
from core.region.schema import RegionTreeNode
class RegionService:
"""省市区街道村五级行政区划服务类"""
# ========== 省份 CRUD ==========
@classmethod
async def create_province(cls, db: AsyncSession, data: Dict[str, Any]) -> Province:
"""创建省份"""
province = Province(**data)
db.add(province)
await db.commit()
await db.refresh(province)
return province
@classmethod
async def update_province(cls, db: AsyncSession, province_id: str, data: Dict[str, Any]) -> Optional[Province]:
"""更新省份"""
stmt = select(Province).where(Province.id == province_id)
result = await db.execute(stmt)
province = result.scalar_one_or_none()
if province:
for key, value in data.items():
setattr(province, key, value)
await db.commit()
await db.refresh(province)
return province
@classmethod
async def delete_province(cls, db: AsyncSession, province_id: str) -> bool:
"""删除省份"""
stmt = select(Province).where(Province.id == province_id)
result = await db.execute(stmt)
province = result.scalar_one_or_none()
if province:
await db.delete(province)
await db.commit()
return True
return False
# ========== 城市 CRUD ==========
@classmethod
async def create_city(cls, db: AsyncSession, data: Dict[str, Any]) -> City:
"""创建城市"""
city = City(**data)
db.add(city)
await db.commit()
await db.refresh(city)
return city
@classmethod
async def update_city(cls, db: AsyncSession, city_id: str, data: Dict[str, Any]) -> Optional[City]:
"""更新城市"""
stmt = select(City).where(City.id == city_id)
result = await db.execute(stmt)
city = result.scalar_one_or_none()
if city:
for key, value in data.items():
setattr(city, key, value)
await db.commit()
await db.refresh(city)
return city
@classmethod
async def delete_city(cls, db: AsyncSession, city_id: str) -> bool:
"""删除城市"""
stmt = select(City).where(City.id == city_id)
result = await db.execute(stmt)
city = result.scalar_one_or_none()
if city:
await db.delete(city)
await db.commit()
return True
return False
# ========== 区县 CRUD ==========
@classmethod
async def create_area(cls, db: AsyncSession, data: Dict[str, Any]) -> Area:
"""创建区县"""
area = Area(**data)
db.add(area)
await db.commit()
await db.refresh(area)
return area
@classmethod
async def update_area(cls, db: AsyncSession, area_id: str, data: Dict[str, Any]) -> Optional[Area]:
"""更新区县"""
stmt = select(Area).where(Area.id == area_id)
result = await db.execute(stmt)
area = result.scalar_one_or_none()
if area:
for key, value in data.items():
setattr(area, key, value)
await db.commit()
await db.refresh(area)
return area
@classmethod
async def delete_area(cls, db: AsyncSession, area_id: str) -> bool:
"""删除区县"""
stmt = select(Area).where(Area.id == area_id)
result = await db.execute(stmt)
area = result.scalar_one_or_none()
if area:
await db.delete(area)
await db.commit()
return True
return False
# ========== 街道 CRUD ==========
@classmethod
async def create_street(cls, db: AsyncSession, data: Dict[str, Any]) -> Street:
"""创建街道"""
street = Street(**data)
db.add(street)
await db.commit()
await db.refresh(street)
return street
@classmethod
async def update_street(cls, db: AsyncSession, street_id: str, data: Dict[str, Any]) -> Optional[Street]:
"""更新街道"""
stmt = select(Street).where(Street.id == street_id)
result = await db.execute(stmt)
street = result.scalar_one_or_none()
if street:
for key, value in data.items():
setattr(street, key, value)
await db.commit()
await db.refresh(street)
return street
@classmethod
async def delete_street(cls, db: AsyncSession, street_id: str) -> bool:
"""删除街道"""
stmt = select(Street).where(Street.id == street_id)
result = await db.execute(stmt)
street = result.scalar_one_or_none()
if street:
await db.delete(street)
await db.commit()
return True
return False
# ========== 村庄 CRUD ==========
@classmethod
async def create_village(cls, db: AsyncSession, data: Dict[str, Any]) -> Village:
"""创建村庄"""
village = Village(**data)
db.add(village)
await db.commit()
await db.refresh(village)
return village
@classmethod
async def update_village(cls, db: AsyncSession, village_id: str, data: Dict[str, Any]) -> Optional[Village]:
"""更新村庄"""
stmt = select(Village).where(Village.id == village_id)
result = await db.execute(stmt)
village = result.scalar_one_or_none()
if village:
for key, value in data.items():
setattr(village, key, value)
await db.commit()
await db.refresh(village)
return village
@classmethod
async def delete_village(cls, db: AsyncSession, village_id: str) -> bool:
"""删除村庄"""
stmt = select(Village).where(Village.id == village_id)
result = await db.execute(stmt)
village = result.scalar_one_or_none()
if village:
await db.delete(village)
await db.commit()
return True
return False
@classmethod
async def get_tree(cls, db: AsyncSession, level: int = 3) -> List[Dict[str, Any]]:
"""
获取行政区划树形结构数据
Args:
db: 数据库会话
level: 级联层级 1=省 2=省市 3=省市区 4=省市区街道 5=省市区街道村
Returns:
树形结构数据列表
"""
# 查询所有省份
stmt = select(Province).order_by(Province.sort, Province.code)
result = await db.execute(stmt)
provinces = result.scalars().all()
tree_data = []
for province in provinces:
province_node = {
"code": province.code,
"name": province.name,
}
if level >= 2:
# 查询该省份下的所有城市
city_stmt = select(City).where(
City.province_code == province.code
).order_by(City.sort, City.code)
city_result = await db.execute(city_stmt)
cities = city_result.scalars().all()
city_nodes = []
for city in cities:
city_node = {
"code": city.code,
"name": city.name,
}
if level >= 3:
# 查询该城市下的所有区县
area_stmt = select(Area).where(
Area.city_code == city.code
).order_by(Area.sort, Area.code)
area_result = await db.execute(area_stmt)
areas = area_result.scalars().all()
area_nodes = []
for area in areas:
area_node = {
"code": area.code,
"name": area.name,
}
if level >= 4:
# 查询该区县下的所有街道
street_stmt = select(Street).where(
Street.area_code == area.code
).order_by(Street.sort, Street.code)
street_result = await db.execute(street_stmt)
streets = street_result.scalars().all()
street_nodes = []
for street in streets:
street_node = {
"code": street.code,
"name": street.name,
}
if level >= 5:
# 查询该街道下的所有村庄
village_stmt = select(Village).where(
Village.street_code == street.code
).order_by(Village.sort, Village.code)
village_result = await db.execute(village_stmt)
villages = village_result.scalars().all()
village_nodes = [
{
"code": village.code,
"name": village.name,
}
for village in villages
]
if village_nodes:
street_node["children"] = village_nodes
street_nodes.append(street_node)
if street_nodes:
area_node["children"] = street_nodes
area_nodes.append(area_node)
if area_nodes:
city_node["children"] = area_nodes
city_nodes.append(city_node)
if city_nodes:
province_node["children"] = city_nodes
tree_data.append(province_node)
return tree_data
@classmethod
async def get_provinces(cls, db: AsyncSession) -> List[Province]:
"""获取所有省份"""
stmt = select(Province).order_by(Province.sort, Province.code)
result = await db.execute(stmt)
return result.scalars().all()
@classmethod
async def get_cities_by_province(cls, db: AsyncSession, province_code: str) -> List[City]:
"""根据省份代码获取城市列表"""
stmt = select(City).where(
City.province_code == province_code
).order_by(City.sort, City.code)
result = await db.execute(stmt)
return result.scalars().all()
@classmethod
async def get_areas_by_city(cls, db: AsyncSession, city_code: str) -> List[Area]:
"""根据城市代码获取区县列表"""
stmt = select(Area).where(
Area.city_code == city_code
).order_by(Area.sort, Area.code)
result = await db.execute(stmt)
return result.scalars().all()
@classmethod
async def get_streets_by_area(cls, db: AsyncSession, area_code: str) -> List[Street]:
"""根据区县代码获取街道列表"""
stmt = select(Street).where(
Street.area_code == area_code
).order_by(Street.sort, Street.code)
result = await db.execute(stmt)
return result.scalars().all()
@classmethod
async def get_villages_by_street(cls, db: AsyncSession, street_code: str) -> List[Village]:
"""根据街道代码获取村庄列表"""
stmt = select(Village).where(
Village.street_code == street_code
).order_by(Village.sort, Village.code)
result = await db.execute(stmt)
return result.scalars().all()
@classmethod
async def get_region_name(cls, db: AsyncSession, code: str) -> str:
"""根据区域代码获取名称"""
# 尝试从省份查找
stmt = select(Province).where(Province.code == code)
result = await db.execute(stmt)
province = result.scalar_one_or_none()
if province:
return province.name
# 尝试从城市查找
stmt = select(City).where(City.code == code)
result = await db.execute(stmt)
city = result.scalar_one_or_none()
if city:
return city.name
# 尝试从区县查找
stmt = select(Area).where(Area.code == code)
result = await db.execute(stmt)
area = result.scalar_one_or_none()
if area:
return area.name
# 尝试从街道查找
stmt = select(Street).where(Street.code == code)
result = await db.execute(stmt)
street = result.scalar_one_or_none()
if street:
return street.name
# 尝试从村庄查找
stmt = select(Village).where(Village.code == code)
result = await db.execute(stmt)
village = result.scalar_one_or_none()
if village:
return village.name
return ""