feat: 菜单管理添加排序功能

- Permission 模型新增 sort 字段
- 后端 API 按 sort 升序排列树形菜单
- 前端菜单管理页面增加排序列
- 初始数据添加排序号
- 修复 GET /permissions/{id} 详情接口 bug

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jayhgq 2026-07-20 08:33:51 +00:00
parent 0545d68018
commit 9586c7467a
6 changed files with 1334 additions and 1275 deletions

View File

@ -44,7 +44,9 @@ async def create_permission(
name=permission_data.name, name=permission_data.name,
code=permission_data.code, code=permission_data.code,
parentid=permission_data.parentid, parentid=permission_data.parentid,
route=permission_data.route,
description=permission_data.description, description=permission_data.description,
sort=permission_data.sort,
) )
db.add(db_permission) db.add(db_permission)
@ -112,6 +114,7 @@ async def get_permission_tree(
"parentid": perm.parentid, "parentid": perm.parentid,
"route": perm.route, "route": perm.route,
"description": perm.description, "description": perm.description,
"sort": perm.sort or 0,
"createtime": perm.createtime, "createtime": perm.createtime,
"updatetime": perm.updatetime, "updatetime": perm.updatetime,
"children": [] "children": []
@ -128,6 +131,16 @@ async def get_permission_tree(
# 子节点,添加到父节点的 children # 子节点,添加到父节点的 children
perm_dict[parentid]["children"].append(perm_data) perm_dict[parentid]["children"].append(perm_data)
# 递归排序:按 sort 字段升序排列
def sort_tree(nodes):
nodes.sort(key=lambda n: n.get("sort", 0))
for node in nodes:
if node.get("children"):
sort_tree(node["children"])
return nodes
tree = sort_tree(tree)
# 记录查询日志 # 记录查询日志
await create_operation_log( await create_operation_log(
db, current_user.id, current_user.username, "search", "permission", db, current_user.id, current_user.username, "search", "permission",
@ -187,6 +200,7 @@ async def get_menu(
"parentid": perm.parentid, "parentid": perm.parentid,
"route": perm.route, "route": perm.route,
"description": perm.description, "description": perm.description,
"sort": perm.sort or 0,
"createtime": perm.createtime, "createtime": perm.createtime,
"updatetime": perm.updatetime, "updatetime": perm.updatetime,
"children": [] "children": []
@ -203,6 +217,16 @@ async def get_menu(
# 子节点,添加到父节点的 children # 子节点,添加到父节点的 children
perm_dict[parentid]["children"].append(perm_data) perm_dict[parentid]["children"].append(perm_data)
# 递归排序:按 sort 字段升序排列
def sort_tree(nodes):
nodes.sort(key=lambda n: n.get("sort", 0))
for node in nodes:
if node.get("children"):
sort_tree(node["children"])
return nodes
tree = sort_tree(tree)
# 记录查询日志 # 记录查询日志
await create_operation_log( await create_operation_log(
db, current_user.id, current_user.username, "search", "permission", db, current_user.id, current_user.username, "search", "permission",
@ -222,7 +246,7 @@ async def get_permission(
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
): ):
"""获取单个权限""" """获取单个权限"""
permission = await permission_crud.get(db, id=permission_id) permission = await permission_crud.get(db, id=permission_id, schema_to_select=PermissionResponse, return_as_model=True)
if not permission: if not permission:
raise HTTPException(status_code=404, detail="权限不存在") raise HTTPException(status_code=404, detail="权限不存在")
@ -271,7 +295,7 @@ async def update_permission(
raise HTTPException(status_code=404, detail="权限不存在") raise HTTPException(status_code=404, detail="权限不存在")
# 记录操作日志 # 记录操作日志
changed = {} changed = {}
for field in ["name", "code", "parentid", "description"]: for field in ["name", "code", "parentid", "route", "sort", "description"]:
old_val = getattr(old_permission, field, None) old_val = getattr(old_permission, field, None)
new_val = update_data.get(field) new_val = update_data.get(field)
if old_val != new_val: if old_val != new_val:
@ -452,6 +476,7 @@ async def get_current_user_permissions(
"parentid": perm.parentid, "parentid": perm.parentid,
"route": perm.route, "route": perm.route,
"description": perm.description, "description": perm.description,
"sort": perm.sort or 0,
"createtime": perm.createtime, "createtime": perm.createtime,
"updatetime": perm.updatetime, "updatetime": perm.updatetime,
"children": [] "children": []
@ -468,6 +493,16 @@ async def get_current_user_permissions(
# 子节点,添加到父节点的 children # 子节点,添加到父节点的 children
perm_dict[parentid]["children"].append(perm_data) perm_dict[parentid]["children"].append(perm_data)
# 递归排序:按 sort 字段升序排列
def sort_tree(nodes):
nodes.sort(key=lambda n: n.get("sort", 0))
for node in nodes:
if node.get("children"):
sort_tree(node["children"])
return nodes
menu_tree = sort_tree(menu_tree)
# 记录查询日志 # 记录查询日志
await create_operation_log( await create_operation_log(
db, current_user.id, current_user.username, "search", "permission", db, current_user.id, current_user.username, "search", "permission",

View File

@ -1,452 +1,451 @@
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, text from sqlalchemy import select, text
from models import Role, User, Permission, SystemSetting from models import Role, User, Permission, SystemSetting
from utils.password import get_password_hash from utils.password import get_password_hash
async def init_permissions(db: AsyncSession): async def init_permissions(db: AsyncSession):
"""初始化权限数据(树形结构)""" """初始化权限数据(树形结构)"""
# 定义需要初始化的权限(树形结构) # 定义需要初始化的权限(树形结构)
# parentid: 0 = 顶级模块, >0 = 子权限 # parentid: 0 = 顶级模块, >0 = 子权限
permission_tree = [ permission_tree = [
# ======================================== # ========================================
# 顶级模块 (DB ID: 1,2,3) # 顶级模块 (DB ID: 1,2,3)
# ======================================== # ========================================
{"name": "权限管理", "code": "perm", "parentid": 0, "route": "/admin/permissions", "description": "权限管理模块"}, {"name": "权限管理", "code": "perm", "parentid": 0, "route": "/admin/permissions", "description": "权限管理模块", "sort": 1},
{"name": "系统设置", "code": "system", "parentid": 0, "route": "/admin/settings", "description": "系统设置模块"}, {"name": "系统设置", "code": "system", "parentid": 0, "route": "/admin/settings", "description": "系统设置模块", "sort": 2},
{"name": "日志管理", "code": "log", "parentid": 0, "route": "/admin/logs", "description": "日志管理模块"}, {"name": "日志管理", "code": "log", "parentid": 0, "route": "/admin/logs", "description": "日志管理模块", "sort": 3},
# ======================================== # ========================================
# 权限管理 -> 二级子模块 (parentid=1, DB ID: 4,5,6) # 权限管理 -> 二级子模块 (parentid=1, DB ID: 4,5,6)
# ======================================== # ========================================
{"name": "用户管理", "code": "perm:user", "parentid": 1, "route": "/admin/users", "description": "用户管理子模块"}, {"name": "用户管理", "code": "perm:user", "parentid": 1, "route": "/admin/users", "description": "用户管理子模块", "sort": 1},
{"name": "角色管理", "code": "perm:role", "parentid": 1, "route": "/admin/roles", "description": "角色管理子模块"}, {"name": "角色管理", "code": "perm:role", "parentid": 1, "route": "/admin/roles", "description": "角色管理子模块", "sort": 2},
{"name": "菜单管理", "code": "perm:menu", "parentid": 1, "route": "/admin/menu", "description": "菜单管理子模块"}, {"name": "菜单管理", "code": "perm:menu", "parentid": 1, "route": "/admin/menu", "description": "菜单管理子模块", "sort": 3},
# ======================================== # ========================================
# 用户管理操作 (parentid=4, DB ID: 7-14) # 用户管理操作 (parentid=4, DB ID: 7-14)
# ======================================== # ========================================
{"name": "查看列表", "code": "perm:user:list", "parentid": 4, "description": "查看用户列表"}, {"name": "查看列表", "code": "perm:user:list", "parentid": 4, "description": "查看用户列表", "sort": 1},
{"name": "查看详情", "code": "perm:user:detail", "parentid": 4, "description": "查看用户详情"}, {"name": "查看详情", "code": "perm:user:detail", "parentid": 4, "description": "查看用户详情", "sort": 2},
{"name": "搜索", "code": "perm:user:search", "parentid": 4, "description": "搜索用户"}, {"name": "搜索", "code": "perm:user:search", "parentid": 4, "description": "搜索用户", "sort": 3},
{"name": "创建", "code": "perm:user:create", "parentid": 4, "description": "创建新用户"}, {"name": "创建", "code": "perm:user:create", "parentid": 4, "description": "创建新用户", "sort": 4},
{"name": "编辑", "code": "perm:user:edit", "parentid": 4, "description": "编辑用户信息"}, {"name": "编辑", "code": "perm:user:edit", "parentid": 4, "description": "编辑用户信息", "sort": 5},
{"name": "删除", "code": "perm:user:delete", "parentid": 4, "description": "删除用户"}, {"name": "删除", "code": "perm:user:delete", "parentid": 4, "description": "删除用户", "sort": 6},
{"name": "导出", "code": "perm:user:export", "parentid": 4, "description": "导出用户数据"}, {"name": "导出", "code": "perm:user:export", "parentid": 4, "description": "导出用户数据", "sort": 7},
{"name": "批量操作", "code": "perm:user:batch", "parentid": 4, "description": "批量操作用户"}, {"name": "批量操作", "code": "perm:user:batch", "parentid": 4, "description": "批量操作用户", "sort": 8},
# ======================================== # ========================================
# 角色管理操作 (parentid=5, DB ID: 15-22) # 角色管理操作 (parentid=5, DB ID: 15-22)
# ======================================== # ========================================
{"name": "查看列表", "code": "perm:role:list", "parentid": 5, "description": "查看角色列表"}, {"name": "查看列表", "code": "perm:role:list", "parentid": 5, "description": "查看角色列表", "sort": 1},
{"name": "查看详情", "code": "perm:role:detail", "parentid": 5, "description": "查看角色详情"}, {"name": "查看详情", "code": "perm:role:detail", "parentid": 5, "description": "查看角色详情", "sort": 2},
{"name": "搜索", "code": "perm:role:search", "parentid": 5, "description": "搜索角色"}, {"name": "搜索", "code": "perm:role:search", "parentid": 5, "description": "搜索角色", "sort": 3},
{"name": "创建", "code": "perm:role:create", "parentid": 5, "description": "创建新角色"}, {"name": "创建", "code": "perm:role:create", "parentid": 5, "description": "创建新角色", "sort": 4},
{"name": "编辑", "code": "perm:role:edit", "parentid": 5, "description": "编辑角色信息"}, {"name": "编辑", "code": "perm:role:edit", "parentid": 5, "description": "编辑角色信息", "sort": 5},
{"name": "删除", "code": "perm:role:delete", "parentid": 5, "description": "删除角色"}, {"name": "删除", "code": "perm:role:delete", "parentid": 5, "description": "删除角色", "sort": 6},
{"name": "导出", "code": "perm:role:export", "parentid": 5, "description": "导出角色数据"}, {"name": "导出", "code": "perm:role:export", "parentid": 5, "description": "导出角色数据", "sort": 7},
{"name": "分配权限", "code": "perm:role:assign", "parentid": 5, "description": "为角色分配权限"}, {"name": "分配权限", "code": "perm:role:assign", "parentid": 5, "description": "为角色分配权限", "sort": 8},
# ======================================== # ========================================
# 菜单管理操作 (parentid=6, DB ID: 23-29) # 菜单管理操作 (parentid=6, DB ID: 23-29)
# ======================================== # ========================================
{"name": "查看列表", "code": "perm:menu:list", "parentid": 6, "description": "查看菜单列表"}, {"name": "查看列表", "code": "perm:menu:list", "parentid": 6, "description": "查看菜单列表", "sort": 1},
{"name": "查看详情", "code": "perm:menu:detail", "parentid": 6, "description": "查看菜单详情"}, {"name": "查看详情", "code": "perm:menu:detail", "parentid": 6, "description": "查看菜单详情", "sort": 2},
{"name": "搜索", "code": "perm:menu:search", "parentid": 6, "description": "搜索菜单"}, {"name": "搜索", "code": "perm:menu:search", "parentid": 6, "description": "搜索菜单", "sort": 3},
{"name": "创建", "code": "perm:menu:create", "parentid": 6, "description": "创建新菜单"}, {"name": "创建", "code": "perm:menu:create", "parentid": 6, "description": "创建新菜单", "sort": 4},
{"name": "编辑", "code": "perm:menu:edit", "parentid": 6, "description": "编辑菜单信息"}, {"name": "编辑", "code": "perm:menu:edit", "parentid": 6, "description": "编辑菜单信息", "sort": 5},
{"name": "删除", "code": "perm:menu:delete", "parentid": 6, "description": "删除菜单"}, {"name": "删除", "code": "perm:menu:delete", "parentid": 6, "description": "删除菜单", "sort": 6},
{"name": "导出", "code": "perm:menu:export", "parentid": 6, "description": "导出菜单数据"}, {"name": "导出", "code": "perm:menu:export", "parentid": 6, "description": "导出菜单数据", "sort": 7},
# ======================================== # ========================================
# 系统设置 -> 子权限 (parentid=2, DB ID: 30) # 系统设置 -> 子权限 (parentid=2, DB ID: 30)
# ======================================== # ========================================
{"name": "系统设置", "code": "system:view", "parentid": 2, "description": "查看和修改系统设置"}, {"name": "系统设置", "code": "system:view", "parentid": 2, "description": "查看和修改系统设置", "sort": 1},
# ======================================== # ========================================
# 日志管理 -> 子模块 (parentid=3, DB ID: 31,32) # 日志管理 -> 子模块 (parentid=3, DB ID: 31,32,37)
# ======================================== # ========================================
{"name": "登录日志", "code": "log:login", "parentid": 3, "route": "/admin/login-logs", "description": "登录日志查看"}, {"name": "登录日志", "code": "log:login", "parentid": 3, "route": "/admin/login-logs", "description": "登录日志查看", "sort": 1},
{"name": "操作日志", "code": "log:operation", "parentid": 3, "route": "/admin/operation-logs", "description": "操作日志查看"}, {"name": "操作日志", "code": "log:operation", "parentid": 3, "route": "/admin/operation-logs", "description": "操作日志查看", "sort": 2},
{"name": "清理日志", "code": "log:clean", "parentid": 3, "description": "清理旧日志", "sort": 3},
# ========================================
# 登录日志操作 (parentid=31, DB ID: 33,34) # ========================================
# ======================================== # 登录日志操作 (parentid=31, DB ID: 33,34)
{"name": "查看列表", "code": "log:login:list", "parentid": 31, "description": "查看登录日志列表"}, # ========================================
{"name": "搜索", "code": "log:login:search", "parentid": 31, "description": "搜索登录日志"}, {"name": "查看列表", "code": "log:login:list", "parentid": 31, "description": "查看登录日志列表", "sort": 1},
{"name": "搜索", "code": "log:login:search", "parentid": 31, "description": "搜索登录日志", "sort": 2},
# ========================================
# 操作日志操作 (parentid=32, DB ID: 35,36) # ========================================
# ======================================== # 操作日志操作 (parentid=32, DB ID: 35,36)
{"name": "查看列表", "code": "log:operation:list", "parentid": 32, "description": "查看操作日志列表"}, # ========================================
{"name": "搜索", "code": "log:operation:search", "parentid": 32, "description": "搜索操作日志"}, {"name": "查看列表", "code": "log:operation:list", "parentid": 32, "description": "查看操作日志列表", "sort": 1},
{"name": "搜索", "code": "log:operation:search", "parentid": 32, "description": "搜索操作日志", "sort": 2},
# ======================================== ]
# 日志清理 (parentid=3, DB ID: 37)
# ======================================== # 检查现有权限
{"name": "清理日志", "code": "log:clean", "parentid": 3, "description": "清理旧日志"}, result = await db.execute(select(Permission))
] existing_permissions = result.scalars().all()
existing_permission_codes = {perm.code: perm for perm in existing_permissions}
# 检查现有权限
result = await db.execute(select(Permission)) # 创建或更新权限
existing_permissions = result.scalars().all() created_count = 0
existing_permission_codes = {perm.code: perm for perm in existing_permissions} updated_count = 0
# 创建或更新权限 for perm_data in permission_tree:
created_count = 0 perm_code = perm_data["code"]
updated_count = 0 if perm_code not in existing_permission_codes:
# 权限不存在,需要创建
for perm_data in permission_tree: db.add(Permission(**perm_data))
perm_code = perm_data["code"] created_count += 1
if perm_code not in existing_permission_codes: else:
# 权限不存在,需要创建 # 权限存在,检查是否需要更新
db.add(Permission(**perm_data)) existing_perm = existing_permission_codes[perm_code]
created_count += 1 update_needed = False
else:
# 权限存在,检查是否需要更新 if existing_perm.name != perm_data["name"]:
existing_perm = existing_permission_codes[perm_code] existing_perm.name = perm_data["name"]
update_needed = False update_needed = True
if existing_perm.parentid != perm_data["parentid"]:
if existing_perm.name != perm_data["name"]: existing_perm.parentid = perm_data["parentid"]
existing_perm.name = perm_data["name"] update_needed = True
update_needed = True if existing_perm.route != perm_data.get("route"):
if existing_perm.parentid != perm_data["parentid"]: existing_perm.route = perm_data.get("route")
existing_perm.parentid = perm_data["parentid"] update_needed = True
update_needed = True if existing_perm.description != perm_data["description"]:
if existing_perm.route != perm_data.get("route"): existing_perm.description = perm_data["description"]
existing_perm.route = perm_data.get("route") update_needed = True
update_needed = True if existing_perm.sort != perm_data.get("sort", 0):
if existing_perm.description != perm_data["description"]: existing_perm.sort = perm_data.get("sort", 0)
existing_perm.description = perm_data["description"] update_needed = True
update_needed = True
if update_needed:
if update_needed: updated_count += 1
updated_count += 1
if created_count > 0 or updated_count > 0:
if created_count > 0 or updated_count > 0: await db.commit()
await db.commit() print(f"创建了 {created_count} 个权限,更新了 {updated_count} 个权限")
print(f"创建了 {created_count} 个权限,更新了 {updated_count} 个权限") else:
else: print("权限数据已存在且无需更新,跳过初始化")
print("权限数据已存在且无需更新,跳过初始化")
print("权限数据初始化/更新完成")
print("权限数据初始化/更新完成")
async def init_role_permissions(db: AsyncSession):
async def init_role_permissions(db: AsyncSession): """初始化角色权限分配"""
"""初始化角色权限分配""" from sqlalchemy.orm import selectinload
from sqlalchemy.orm import selectinload
# 获取管理员角色(使用预加载避免懒加载问题)
# 获取管理员角色(使用预加载避免懒加载问题) result = await db.execute(
result = await db.execute( select(Role)
select(Role) .filter(Role.name == "管理员")
.filter(Role.name == "管理员") .options(selectinload(Role.permissions))
.options(selectinload(Role.permissions)) )
) admin_role = result.scalar_one_or_none()
admin_role = result.scalar_one_or_none() if not admin_role:
if not admin_role: print("管理员角色不存在,请先初始化角色数据")
print("管理员角色不存在,请先初始化角色数据") return
return
# 获取所有权限
# 获取所有权限 result = await db.execute(select(Permission))
result = await db.execute(select(Permission)) all_permissions = result.scalars().all()
all_permissions = result.scalars().all()
if not all_permissions:
if not all_permissions: print("没有找到任何权限,请先初始化权限数据")
print("没有找到任何权限,请先初始化权限数据") return
return
# 获取管理员角色当前的权限(使用预加载的数据)
# 获取管理员角色当前的权限(使用预加载的数据) admin_permission_codes = set()
admin_permission_codes = set() if admin_role.permissions:
if admin_role.permissions: admin_permission_codes = {perm.code for perm in admin_role.permissions}
admin_permission_codes = {perm.code for perm in admin_role.permissions}
# 检查是否需要添加权限
# 检查是否需要添加权限 permissions_to_add = []
permissions_to_add = [] for perm in all_permissions:
for perm in all_permissions: if perm.code not in admin_permission_codes:
if perm.code not in admin_permission_codes: permissions_to_add.append(perm)
permissions_to_add.append(perm)
if permissions_to_add:
if permissions_to_add: # 直接设置权限(替换而非追加,确保权限完整)
# 直接设置权限(替换而非追加,确保权限完整) admin_role.permissions = all_permissions
admin_role.permissions = all_permissions await db.commit()
await db.commit() print(f"为管理员角色分配了 {len(permissions_to_add)} 个权限")
print(f"为管理员角色分配了 {len(permissions_to_add)} 个权限") else:
else: print("管理员角色权限已完整,无需更新")
print("管理员角色权限已完整,无需更新")
print("角色权限初始化/更新完成")
print("角色权限初始化/更新完成")
async def init_roles(db: AsyncSession):
async def init_roles(db: AsyncSession): """初始化角色数据"""
"""初始化角色数据""" # 定义需要初始化的角色
# 定义需要初始化的角色 required_roles = [
required_roles = [ {"name": "管理员", "creator": "system"},
{"name": "管理员", "creator": "system"}, {"name": "普通用户", "creator": "system"},
{"name": "普通用户", "creator": "system"}, {"name": "访客", "creator": "system"},
{"name": "访客", "creator": "system"}, {"name": "会员", "creator": "system"},
{"name": "会员", "creator": "system"}, ]
]
# 检查现有角色
# 检查现有角色 result = await db.execute(select(Role))
result = await db.execute(select(Role)) existing_roles = result.scalars().all()
existing_roles = result.scalars().all() existing_role_names = {role.name for role in existing_roles}
existing_role_names = {role.name for role in existing_roles}
# 检查需要创建或更新的角色
# 检查需要创建或更新的角色 roles_to_add = []
roles_to_add = [] roles_to_update = []
roles_to_update = []
for role_data in required_roles:
for role_data in required_roles: role_name = role_data["name"]
role_name = role_data["name"] if role_name not in existing_role_names:
if role_name not in existing_role_names: # 角色不存在,需要创建
# 角色不存在,需要创建 roles_to_add.append(Role(**role_data))
roles_to_add.append(Role(**role_data)) else:
else: # 角色存在,检查是否需要更新
# 角色存在,检查是否需要更新 existing_role = next(
existing_role = next( role for role in existing_roles if role.name == role_name
role for role in existing_roles if role.name == role_name )
) if existing_role.creator != role_data["creator"]:
if existing_role.creator != role_data["creator"]: # 需要更新
# 需要更新 existing_role.creator = role_data["creator"]
existing_role.creator = role_data["creator"] roles_to_update.append(existing_role)
roles_to_update.append(existing_role)
# 执行创建和更新操作
# 执行创建和更新操作 if roles_to_add:
if roles_to_add: for role in roles_to_add:
for role in roles_to_add: db.add(role)
db.add(role) await db.commit()
await db.commit() print(f"创建了 {len(roles_to_add)} 个角色")
print(f"创建了 {len(roles_to_add)} 个角色")
if roles_to_update:
if roles_to_update: await db.commit()
await db.commit() print(f"更新了 {len(roles_to_update)} 个角色")
print(f"更新了 {len(roles_to_update)} 个角色")
if not roles_to_add and not roles_to_update:
if not roles_to_add and not roles_to_update: print("角色数据已存在且无需更新,跳过初始化")
print("角色数据已存在且无需更新,跳过初始化")
print("角色数据初始化/更新完成")
print("角色数据初始化/更新完成")
async def init_admin_user(db: AsyncSession):
async def init_admin_user(db: AsyncSession): """初始化管理员用户"""
"""初始化管理员用户""" # 获取管理员角色
# 获取管理员角色 result = await db.execute(select(Role).filter(Role.name == "管理员"))
result = await db.execute(select(Role).filter(Role.name == "管理员")) admin_role = result.scalar_one_or_none()
admin_role = result.scalar_one_or_none() if not admin_role:
if not admin_role: print("管理员角色不存在,请先初始化角色数据")
print("管理员角色不存在,请先初始化角色数据") return
return
# 定义需要的管理员用户数据
# 定义需要的管理员用户数据 admin_data = {
admin_data = { "username": "admin",
"username": "admin", "nickname": "系统管理员",
"nickname": "系统管理员", "email": "admin@example.com",
"email": "admin@example.com", "phone": "13800138000",
"phone": "13800138000", "password_hash": get_password_hash("admin"),
"password_hash": get_password_hash("admin"), "role_id": admin_role.id,
"role_id": admin_role.id, "avatar": "",
"avatar": "", "wx_openid": "",
"wx_openid": "", }
}
# 检查管理员用户是否存在
# 检查管理员用户是否存在 result = await db.execute(select(User).filter(User.username == "admin"))
result = await db.execute(select(User).filter(User.username == "admin")) existing_admin = result.scalar_one_or_none()
existing_admin = result.scalar_one_or_none()
if not existing_admin:
if not existing_admin: # 管理员不存在,创建
# 管理员不存在,创建 admin_user = User(**admin_data)
admin_user = User(**admin_data) db.add(admin_user)
db.add(admin_user) await db.commit()
await db.commit() print("创建了管理员用户")
print("创建了管理员用户") else:
else: # 管理员存在,检查是否需要更新
# 管理员存在,检查是否需要更新 update_needed = False
update_needed = False
# 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置)
# 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置) if existing_admin.nickname != admin_data["nickname"]:
if existing_admin.nickname != admin_data["nickname"]: existing_admin.nickname = admin_data["nickname"]
existing_admin.nickname = admin_data["nickname"] update_needed = True
update_needed = True if existing_admin.email != admin_data["email"]:
if existing_admin.email != admin_data["email"]: existing_admin.email = admin_data["email"]
existing_admin.email = admin_data["email"] update_needed = True
update_needed = True if existing_admin.phone != admin_data["phone"]:
if existing_admin.phone != admin_data["phone"]: existing_admin.phone = admin_data["phone"]
existing_admin.phone = admin_data["phone"] update_needed = True
update_needed = True if existing_admin.role_id != admin_data["role_id"]:
if existing_admin.role_id != admin_data["role_id"]: existing_admin.role_id = admin_data["role_id"]
existing_admin.role_id = admin_data["role_id"] update_needed = True
update_needed = True if existing_admin.avatar != admin_data["avatar"]:
if existing_admin.avatar != admin_data["avatar"]: existing_admin.avatar = admin_data["avatar"]
existing_admin.avatar = admin_data["avatar"] update_needed = True
update_needed = True if existing_admin.wx_openid != admin_data["wx_openid"]:
if existing_admin.wx_openid != admin_data["wx_openid"]: existing_admin.wx_openid = admin_data["wx_openid"]
existing_admin.wx_openid = admin_data["wx_openid"] update_needed = True
update_needed = True
if update_needed:
if update_needed: await db.commit()
await db.commit() print("更新了管理员用户")
print("更新了管理员用户") else:
else: print("管理员用户已存在且无需更新,跳过初始化")
print("管理员用户已存在且无需更新,跳过初始化")
print("管理员用户初始化/更新完成")
print("管理员用户初始化/更新完成")
async def init_test_user(db: AsyncSession):
async def init_test_user(db: AsyncSession): """初始化测试用户"""
"""初始化测试用户""" # 获取普通用户角色
# 获取普通用户角色 result = await db.execute(select(Role).filter(Role.name == "普通用户"))
result = await db.execute(select(Role).filter(Role.name == "普通用户")) user_role = result.scalar_one_or_none()
user_role = result.scalar_one_or_none() if not user_role:
if not user_role: print("普通用户角色不存在,请先初始化角色数据")
print("普通用户角色不存在,请先初始化角色数据") return
return
# 定义需要的测试用户数据
# 定义需要的测试用户数据 test_data = {
test_data = { "username": "test",
"username": "test", "nickname": "测试用户",
"nickname": "测试用户", "email": "test@example.com",
"email": "test@example.com", "phone": "13800138001",
"phone": "13800138001", "password_hash": get_password_hash("test"),
"password_hash": get_password_hash("test"), "role_id": user_role.id,
"role_id": user_role.id, "avatar": "",
"avatar": "", "wx_openid": "",
"wx_openid": "", }
}
# 检查测试用户是否存在
# 检查测试用户是否存在 result = await db.execute(select(User).filter(User.username == "test"))
result = await db.execute(select(User).filter(User.username == "test")) existing_test_user = result.scalar_one_or_none()
existing_test_user = result.scalar_one_or_none()
if not existing_test_user:
if not existing_test_user: # 测试用户不存在,创建
# 测试用户不存在,创建 test_user = User(**test_data)
test_user = User(**test_data) db.add(test_user)
db.add(test_user) await db.commit()
await db.commit() print("创建了测试用户")
print("创建了测试用户") else:
else: # 测试用户存在,检查是否需要更新
# 测试用户存在,检查是否需要更新 update_needed = False
update_needed = False
# 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置)
# 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置) if existing_test_user.nickname != test_data["nickname"]:
if existing_test_user.nickname != test_data["nickname"]: existing_test_user.nickname = test_data["nickname"]
existing_test_user.nickname = test_data["nickname"] update_needed = True
update_needed = True if existing_test_user.email != test_data["email"]:
if existing_test_user.email != test_data["email"]: existing_test_user.email = test_data["email"]
existing_test_user.email = test_data["email"] update_needed = True
update_needed = True if existing_test_user.phone != test_data["phone"]:
if existing_test_user.phone != test_data["phone"]: existing_test_user.phone = test_data["phone"]
existing_test_user.phone = test_data["phone"] update_needed = True
update_needed = True if existing_test_user.role_id != test_data["role_id"]:
if existing_test_user.role_id != test_data["role_id"]: existing_test_user.role_id = test_data["role_id"]
existing_test_user.role_id = test_data["role_id"] update_needed = True
update_needed = True if existing_test_user.avatar != test_data["avatar"]:
if existing_test_user.avatar != test_data["avatar"]: existing_test_user.avatar = test_data["avatar"]
existing_test_user.avatar = test_data["avatar"] update_needed = True
update_needed = True if existing_test_user.wx_openid != test_data["wx_openid"]:
if existing_test_user.wx_openid != test_data["wx_openid"]: existing_test_user.wx_openid = test_data["wx_openid"]
existing_test_user.wx_openid = test_data["wx_openid"] update_needed = True
update_needed = True
if update_needed:
if update_needed: await db.commit()
await db.commit() print("更新了测试用户")
print("更新了测试用户") else:
else: print("测试用户已存在且无需更新,跳过初始化")
print("测试用户已存在且无需更新,跳过初始化")
print("测试用户初始化/更新完成")
print("测试用户初始化/更新完成")
async def init_system_settings(db: AsyncSession):
async def init_system_settings(db: AsyncSession): """初始化系统设置数据"""
"""初始化系统设置数据""" # 定义需要初始化的系统设置
# 定义需要初始化的系统设置 system_settings = [
system_settings = [ {
{ "key": "系统标题",
"key": "系统标题", "value": "图书管理系统",
"value": "图书管理系统", "type": "string",
"type": "string", "description": "系统显示标题"
"description": "系统显示标题" },
}, {
{ "key": "页脚信息",
"key": "页脚信息", "value": "Copyright © 2026 图书管理系统 版权所有 | 技术支持:强蕊科技",
"value": "Copyright © 2026 图书管理系统 版权所有 | 技术支持:强蕊科技", "type": "string",
"type": "string", "description": "页脚信息"
"description": "页脚信息" },
}, {
{ "key": "ICP信息",
"key": "ICP信息", "value": "京ICP备2023000000号",
"value": "京ICP备2023000000号", "type": "string",
"type": "string", "description": "ICP备案信息"
"description": "ICP备案信息" },
}, {
{ "key": "启用手机短信注册",
"key": "启用手机短信注册", "value": "false",
"value": "false", "type": "boolean",
"type": "boolean", "description": "是否允许通过手机短信验证码注册"
"description": "是否允许通过手机短信验证码注册" },
}, {
{ "key": "启用邮箱注册",
"key": "启用邮箱注册", "value": "false",
"value": "false", "type": "boolean",
"type": "boolean", "description": "是否允许通过邮箱验证链接注册"
"description": "是否允许通过邮箱验证链接注册" },
}, {
{ "key": "系统Logo",
"key": "系统Logo", "value": "",
"value": "", "type": "image",
"type": "image", "description": "系统Logo图片"
"description": "系统Logo图片" }
} ]
]
# 检查现有系统设置
# 检查现有系统设置 result = await db.execute(select(SystemSetting))
result = await db.execute(select(SystemSetting)) existing_settings = result.scalars().all()
existing_settings = result.scalars().all() existing_setting_keys = {setting.key: setting for setting in existing_settings}
existing_setting_keys = {setting.key: setting for setting in existing_settings}
# 创建或更新系统设置
# 创建或更新系统设置 created_count = 0
created_count = 0 updated_count = 0
updated_count = 0
for setting_data in system_settings:
for setting_data in system_settings: setting_key = setting_data["key"]
setting_key = setting_data["key"] if setting_key not in existing_setting_keys:
if setting_key not in existing_setting_keys: # 设置不存在,需要创建
# 设置不存在,需要创建 db.add(SystemSetting(**setting_data))
db.add(SystemSetting(**setting_data)) created_count += 1
created_count += 1 else:
else: # 设置存在,检查是否需要更新
# 设置存在,检查是否需要更新 existing_setting = existing_setting_keys[setting_key]
existing_setting = existing_setting_keys[setting_key] update_needed = False
update_needed = False
if existing_setting.value != setting_data["value"]:
if existing_setting.value != setting_data["value"]: existing_setting.value = setting_data["value"]
existing_setting.value = setting_data["value"] update_needed = True
update_needed = True if existing_setting.type != setting_data["type"]:
if existing_setting.type != setting_data["type"]: existing_setting.type = setting_data["type"]
existing_setting.type = setting_data["type"] update_needed = True
update_needed = True if existing_setting.description != setting_data.get("description"):
if existing_setting.description != setting_data.get("description"): existing_setting.description = setting_data.get("description")
existing_setting.description = setting_data.get("description") update_needed = True
update_needed = True
if update_needed:
if update_needed: updated_count += 1
updated_count += 1
if created_count > 0 or updated_count > 0:
if created_count > 0 or updated_count > 0: await db.commit()
await db.commit() print(f"创建了 {created_count} 个系统设置,更新了 {updated_count} 个系统设置")
print(f"创建了 {created_count} 个系统设置,更新了 {updated_count} 个系统设置") else:
else: print("系统设置数据已存在且无需更新,跳过初始化")
print("系统设置数据已存在且无需更新,跳过初始化")
print("系统设置数据初始化/更新完成")
print("系统设置数据初始化/更新完成")
async def init_all_data(db: AsyncSession):
async def init_all_data(db: AsyncSession): """初始化所有数据"""
"""初始化所有数据""" print("开始初始化数据库数据...")
print("开始初始化数据库数据...") await init_roles(db)
await init_roles(db) await init_permissions(db)
await init_permissions(db) await init_role_permissions(db)
await init_role_permissions(db) await init_admin_user(db)
await init_admin_user(db) await init_test_user(db)
await init_test_user(db) await init_system_settings(db)
await init_system_settings(db) print("数据库数据初始化完成")
print("数据库数据初始化完成")

View File

@ -1,172 +1,182 @@
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, FileResponse from fastapi.responses import HTMLResponse, FileResponse
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from apps.urls import api_router from apps.urls import api_router
from apps.settings.urls import UPLOAD_DIR from apps.settings.urls import UPLOAD_DIR
from database import engine, get_db from database import engine, get_db
from models import Base from models import Base
from init_data import init_all_data from init_data import init_all_data
from pathlib import Path from pathlib import Path
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import JSONResponse from starlette.responses import JSONResponse
from sqlalchemy import text
@asynccontextmanager
async def lifespan(app: FastAPI): @asynccontextmanager
# 启动时初始化数据库 async def lifespan(app: FastAPI):
async with engine.begin() as conn: # 启动时初始化数据库
# 创建所有表 async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all) # 创建所有表
await conn.run_sync(Base.metadata.create_all)
# 初始化数据 # 为已有表添加 sort 列(兼容 PostgreSQL 和 SQLite
async for db in get_db(): try:
await init_all_data(db) await conn.execute(text("ALTER TABLE permission ADD COLUMN IF NOT EXISTS sort INTEGER DEFAULT 0"))
break except Exception:
# SQLite 不支持 IF NOT EXISTS
yield try:
# 关闭时的清理工作 await conn.execute(text("ALTER TABLE permission ADD COLUMN sort INTEGER DEFAULT 0"))
await engine.dispose() except Exception:
pass # 列已存在则忽略
# 实例化FastAPI # 初始化数据
app = FastAPI( async for db in get_db():
title="图书系统授权服务API", await init_all_data(db)
description="""图书系统授权服务API是一套用于用户认证和授权的服务提供用户注册、登录、权限校验等功能。 break
同时实现了基于角色的访问控制RBAC支持自定义角色和权限还增加了日志记录功能方便监控和调试""",
version="1.0.0", yield
lifespan=lifespan, # 关闭时的清理工作
docs_url=None, await engine.dispose()
redoc_url="/redoc",
)
# 实例化FastAPI
# 开发环境前后端分离时的跨域(生产环境请收窄 allow_origins app = FastAPI(
app.add_middleware( title="图书系统授权服务API",
CORSMiddleware, description="""图书系统授权服务API是一套用于用户认证和授权的服务提供用户注册、登录、权限校验等功能。
allow_origins=[ 同时实现了基于角色的访问控制RBAC支持自定义角色和权限还增加了日志记录功能方便监控和调试""",
"http://localhost:5173", version="1.0.0",
"http://127.0.0.1:5173", lifespan=lifespan,
"http://localhost:3000", docs_url=None,
"http://127.0.0.1:3000", redoc_url="/redoc",
], )
allow_credentials=True,
allow_methods=["*"], # 开发环境前后端分离时的跨域(生产环境请收窄 allow_origins
allow_headers=["*"], app.add_middleware(
expose_headers=["X-Captcha-ID"], CORSMiddleware,
) allow_origins=[
"http://localhost:5173",
# 挂载swagger-ui静态文件目录 "http://127.0.0.1:5173",
swagger_ui_path = Path(__file__).parent.parent / "swagger-ui" "http://localhost:3000",
app.mount("/static", StaticFiles(directory=str(swagger_ui_path)), name="static") "http://127.0.0.1:3000",
],
# 上传图片通过自定义端点提供(和 settings/urls.py 中的 UPLOAD_DIR 保持一致) allow_credentials=True,
@app.get("/uploads/{filename}") allow_methods=["*"],
async def serve_upload(filename: str): allow_headers=["*"],
"""提供上传的图片文件""" expose_headers=["X-Captcha-ID"],
file_path = UPLOAD_DIR / filename )
if not file_path.exists():
raise HTTPException(status_code=404, detail="文件不存在") # 挂载swagger-ui静态文件目录
return FileResponse(str(file_path)) swagger_ui_path = Path(__file__).parent.parent / "swagger-ui"
app.mount("/static", StaticFiles(directory=str(swagger_ui_path)), name="static")
# 自定义Swagger UI页面 # 上传图片通过自定义端点提供(和 settings/urls.py 中的 UPLOAD_DIR 保持一致)
@app.get("/docs", include_in_schema=False) @app.get("/uploads/{filename}")
async def custom_swagger_ui_html(): async def serve_upload(filename: str):
html_content = """ """提供上传的图片文件"""
<!DOCTYPE html> file_path = UPLOAD_DIR / filename
<html lang="en"> if not file_path.exists():
<head> raise HTTPException(status_code=404, detail="文件不存在")
<meta charset="UTF-8"> return FileResponse(str(file_path))
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Book System API - Swagger UI</title>
<link rel="stylesheet" type="text/css" href="/static/swagger-ui.css"> # 自定义Swagger UI页面
<link rel="icon" type="image/png" href="/static/favicon-32x32.png" sizes="32x32"/> @app.get("/docs", include_in_schema=False)
<style> async def custom_swagger_ui_html():
html { html_content = """
box-sizing: border-box; <!DOCTYPE html>
overflow: -moz-scrollbars-vertical; <html lang="en">
overflow-y: scroll; <head>
} <meta charset="UTF-8">
*, *:before, *:after { <meta name="viewport" content="width=device-width, initial-scale=1.0">
box-sizing: inherit; <title>Book System API - Swagger UI</title>
} <link rel="stylesheet" type="text/css" href="/static/swagger-ui.css">
body { <link rel="icon" type="image/png" href="/static/favicon-32x32.png" sizes="32x32"/>
margin: 0; <style>
background: #fafafa; html {
} box-sizing: border-box;
</style> overflow: -moz-scrollbars-vertical;
</head> overflow-y: scroll;
<body> }
<div id="swagger-ui"></div> *, *:before, *:after {
<script src="/static/swagger-ui-bundle.js"></script> box-sizing: inherit;
<script> }
window.onload = function() { body {
const ui = SwaggerUIBundle({ margin: 0;
url: "/openapi.json", background: #fafafa;
dom_id: '#swagger-ui', }
deepLinking: true, </style>
presets: [ </head>
SwaggerUIBundle.presets.apis, <body>
SwaggerUIBundle.SwaggerUIStandalonePreset <div id="swagger-ui"></div>
], <script src="/static/swagger-ui-bundle.js"></script>
layout: "BaseLayout", <script>
persistAuthorization: true, window.onload = function() {
docExpansion: "list" const ui = SwaggerUIBundle({
}); url: "/openapi.json",
window.ui = ui; dom_id: '#swagger-ui',
}; deepLinking: true,
</script> presets: [
</body> SwaggerUIBundle.presets.apis,
</html> SwaggerUIBundle.SwaggerUIStandalonePreset
""" ],
return HTMLResponse(content=html_content) layout: "BaseLayout",
persistAuthorization: true,
docExpansion: "list"
# 包含路由 });
app.include_router(api_router) window.ui = ui;
};
</script>
# 声明装饰器方法和路径 </body>
@app.get("/") </html>
# 声明装饰器函数 """
async def home(): return HTMLResponse(content=html_content)
return {"message": "Hello World!!!"}
# 包含路由
# 自定义验证错误处理器 app.include_router(api_router)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
errors = [] # 声明装饰器方法和路径
for err in exc.errors(): @app.get("/")
field = err.get("loc", ["unknown"])[-1] # 声明装饰器函数
msg = err.get("msg", "验证失败") async def home():
return {"message": "Hello World!!!"}
if "min_length" in msg.lower():
if field == "username":
msg = "用户名长度至少为3个字符" # 自定义验证错误处理器
elif field == "password": @app.exception_handler(RequestValidationError)
msg = "密码长度至少为6个字符" async def validation_exception_handler(request: Request, exc: RequestValidationError):
elif "max_length" in msg.lower(): errors = []
if field == "username": for err in exc.errors():
msg = "用户名长度不能超过50个字符" field = err.get("loc", ["unknown"])[-1]
elif field == "password": msg = err.get("msg", "验证失败")
msg = "密码长度不能超过50个字符"
if "min_length" in msg.lower():
errors.append({"field": field, "message": msg}) if field == "username":
msg = "用户名长度至少为3个字符"
return JSONResponse( elif field == "password":
status_code=422, msg = "密码长度至少为6个字符"
content={"code": 422, "message": "验证失败", "errors": errors} elif "max_length" in msg.lower():
) if field == "username":
msg = "用户名长度不能超过50个字符"
elif field == "password":
# 如果使用命令行启动使用uvicorn 文件名:app --reload启动即可下面命令就不用写 msg = "密码长度不能超过50个字符"
# 如果写下面的命令就不需要命令行启动了直接用IDE运行即可
if __name__ == "__main__": errors.append({"field": field, "message": msg})
import uvicorn
import os return JSONResponse(
status_code=422,
name = f"{os.path.splitext(os.path.basename(os.path.abspath(__file__)))[0]}:app" content={"code": 422, "message": "验证失败", "errors": errors}
uvicorn.run(name, host="0.0.0.0", port=8000, reload=True, reload_dirs=["_"]) )
# 如果使用命令行启动使用uvicorn 文件名:app --reload启动即可下面命令就不用写
# 如果写下面的命令就不需要命令行启动了直接用IDE运行即可
if __name__ == "__main__":
import uvicorn
import os
name = f"{os.path.splitext(os.path.basename(os.path.abspath(__file__)))[0]}:app"
uvicorn.run(name, host="0.0.0.0", port=8000, reload=True, reload_dirs=["_"])

View File

@ -1,140 +1,141 @@
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, func, Table, Index, Text from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, func, Table, Index, Text
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime from datetime import datetime
Base = declarative_base() Base = declarative_base()
class Role(Base): class Role(Base):
"""角色数据库模型""" """角色数据库模型"""
__tablename__ = "role" __tablename__ = "role"
id = Column(Integer, primary_key=True, index=True, comment="角色ID") id = Column(Integer, primary_key=True, index=True, comment="角色ID")
name = Column(String(50), nullable=False, unique=True, comment="角色名称") name = Column(String(50), nullable=False, unique=True, comment="角色名称")
createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间") createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
updatetime = Column( updatetime = Column(
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间" DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
) )
creator = Column(String(50), nullable=True, comment="创建人") creator = Column(String(50), nullable=True, comment="创建人")
# 关联到User # 关联到User
users = relationship("User", back_populates="role_rel") users = relationship("User", back_populates="role_rel")
# 关联到Permission多对多 # 关联到Permission多对多
permissions = relationship( permissions = relationship(
"Permission", "Permission",
secondary="role_permission", secondary="role_permission",
back_populates="roles" back_populates="roles"
) )
class User(Base): class User(Base):
"""用户数据库模型""" """用户数据库模型"""
__tablename__ = "users" __tablename__ = "users"
id = Column(Integer, primary_key=True, index=True) id = Column(Integer, primary_key=True, index=True)
username = Column(String(50), unique=True, nullable=False, comment="登录名") username = Column(String(50), unique=True, nullable=False, comment="登录名")
nickname = Column(String(50), nullable=False, comment="昵称") nickname = Column(String(50), nullable=False, comment="昵称")
email = Column(String(50), nullable=True, comment="电子邮箱") email = Column(String(50), nullable=True, comment="电子邮箱")
phone = Column(String(11), nullable=True, comment="手机号码") phone = Column(String(11), nullable=True, comment="手机号码")
wx_openid = Column(String(100), nullable=True, comment="微信OpenID") wx_openid = Column(String(100), nullable=True, comment="微信OpenID")
password_hash = Column(String(128), nullable=False, comment="密码哈希值") password_hash = Column(String(128), nullable=False, comment="密码哈希值")
avatar = Column(String(255), nullable=True, comment="头像路径") avatar = Column(String(255), nullable=True, comment="头像路径")
role_id = Column(Integer, ForeignKey("role.id"), default=2, comment="角色ID") role_id = Column(Integer, ForeignKey("role.id"), default=2, comment="角色ID")
createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间") createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
updatetime = Column( updatetime = Column(
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间" DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
) )
lastlogintime = Column(DateTime, comment="最后登录时间") lastlogintime = Column(DateTime, comment="最后登录时间")
# 关联到Role # 关联到Role
role_rel = relationship("Role", back_populates="users") role_rel = relationship("Role", back_populates="users")
class Permission(Base): class Permission(Base):
"""权限数据库模型""" """权限数据库模型"""
__tablename__ = "permission" __tablename__ = "permission"
id = Column(Integer, primary_key=True, index=True, comment="权限ID") id = Column(Integer, primary_key=True, index=True, comment="权限ID")
name = Column(String(100), nullable=False, comment="权限名称") name = Column(String(100), nullable=False, comment="权限名称")
code = Column(String(100), nullable=False, unique=True, comment="权限编码") code = Column(String(100), nullable=False, unique=True, comment="权限编码")
parentid = Column(Integer, default=0, comment="父权限ID0表示顶级模块") parentid = Column(Integer, default=0, comment="父权限ID0表示顶级模块")
route = Column(String(255), nullable=True, comment="前端路由路径") route = Column(String(255), nullable=True, comment="前端路由路径")
description = Column(String(255), nullable=True, comment="权限描述") description = Column(String(255), nullable=True, comment="权限描述")
createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间") sort = Column(Integer, default=0, comment="排序号(升序排列)")
updatetime = Column( createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间" updatetime = Column(
) DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
)
# 关联到Role多对多
roles = relationship( # 关联到Role多对多
"Role", roles = relationship(
secondary="role_permission", "Role",
back_populates="permissions" secondary="role_permission",
) back_populates="permissions"
)
# 角色权限关联表(多对多)
role_permission = Table( # 角色权限关联表(多对多)
"role_permission", role_permission = Table(
Base.metadata, "role_permission",
Column("role_id", Integer, ForeignKey("role.id"), primary_key=True, comment="角色ID"), Base.metadata,
Column("permission_id", Integer, ForeignKey("permission.id"), primary_key=True, comment="权限ID") Column("role_id", Integer, ForeignKey("role.id"), primary_key=True, comment="角色ID"),
) Column("permission_id", Integer, ForeignKey("permission.id"), primary_key=True, comment="权限ID")
)
class SystemSetting(Base):
"""系统设置数据库模型""" class SystemSetting(Base):
"""系统设置数据库模型"""
__tablename__ = "system_setting"
id = Column(Integer, primary_key=True, index=True, comment="设置ID") __tablename__ = "system_setting"
key = Column(String(100), nullable=False, unique=True, comment="设置键名") id = Column(Integer, primary_key=True, index=True, comment="设置ID")
value = Column(String(2000), nullable=False, comment="设置值") key = Column(String(100), nullable=False, unique=True, comment="设置键名")
type = Column(String(20), default="string", comment="类型string/image/select/boolean") value = Column(String(2000), nullable=False, comment="设置值")
options = Column(String(500), nullable=True, comment="下拉选项(用|分隔)") type = Column(String(20), default="string", comment="类型string/image/select/boolean")
description = Column(String(255), nullable=True, comment="设置描述") options = Column(String(500), nullable=True, comment="下拉选项(用|分隔)")
createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间") description = Column(String(255), nullable=True, comment="设置描述")
updatetime = Column( createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间" updatetime = Column(
) DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
)
class LoginLog(Base):
"""登录日志""" class LoginLog(Base):
"""登录日志"""
__tablename__ = "login_log"
id = Column(Integer, primary_key=True, index=True, comment="日志ID") __tablename__ = "login_log"
username = Column(String(50), nullable=False, comment="用户名") id = Column(Integer, primary_key=True, index=True, comment="日志ID")
login_time = Column(DateTime, default=datetime.utcnow, comment="登录时间") username = Column(String(50), nullable=False, comment="用户名")
ip_address = Column(String(50), nullable=True, comment="IP地址") login_time = Column(DateTime, default=datetime.utcnow, comment="登录时间")
user_agent = Column(String(500), nullable=True, comment="User-Agent") ip_address = Column(String(50), nullable=True, comment="IP地址")
status = Column(String(20), nullable=False, comment="状态: success/failure") user_agent = Column(String(500), nullable=True, comment="User-Agent")
failure_reason = Column(String(255), nullable=True, comment="失败原因") status = Column(String(20), nullable=False, comment="状态: success/failure")
failure_reason = Column(String(255), nullable=True, comment="失败原因")
Index("ix_login_log_login_time", LoginLog.login_time)
Index("ix_login_log_username", LoginLog.username) Index("ix_login_log_login_time", LoginLog.login_time)
Index("ix_login_log_status", LoginLog.status) Index("ix_login_log_username", LoginLog.username)
Index("ix_login_log_status", LoginLog.status)
class OperationLog(Base):
"""操作日志""" class OperationLog(Base):
"""操作日志"""
__tablename__ = "operation_log"
id = Column(Integer, primary_key=True, index=True, comment="日志ID") __tablename__ = "operation_log"
user_id = Column(Integer, nullable=False, comment="用户ID") id = Column(Integer, primary_key=True, index=True, comment="日志ID")
username = Column(String(50), nullable=False, comment="用户名") user_id = Column(Integer, nullable=False, comment="用户ID")
action_type = Column(String(20), nullable=False, comment="操作类型: create/update/delete") username = Column(String(50), nullable=False, comment="用户名")
target_type = Column(String(50), nullable=False, comment="目标类型: user/role/permission/setting") action_type = Column(String(20), nullable=False, comment="操作类型: create/update/delete")
target_id = Column(Integer, nullable=True, comment="目标ID") target_type = Column(String(50), nullable=False, comment="目标类型: user/role/permission/setting")
target_name = Column(String(100), nullable=True, comment="目标名称") target_id = Column(Integer, nullable=True, comment="目标ID")
detail = Column(Text, nullable=True, comment="变更详情(JSON)") target_name = Column(String(100), nullable=True, comment="目标名称")
ip_address = Column(String(50), nullable=True, comment="IP地址") detail = Column(Text, nullable=True, comment="变更详情(JSON)")
user_agent = Column(String(500), nullable=True, comment="User-Agent") ip_address = Column(String(50), nullable=True, comment="IP地址")
createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间") user_agent = Column(String(500), nullable=True, comment="User-Agent")
createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
Index("ix_operation_log_createtime", OperationLog.createtime)
Index("ix_operation_log_username", OperationLog.username) Index("ix_operation_log_createtime", OperationLog.createtime)
Index("ix_operation_log_action_type", OperationLog.action_type) Index("ix_operation_log_username", OperationLog.username)
Index("ix_operation_log_action_type", OperationLog.action_type)
Index("ix_operation_log_target_type", OperationLog.target_type) Index("ix_operation_log_target_type", OperationLog.target_type)

View File

@ -1,335 +1,337 @@
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from typing import Optional from typing import Optional
from datetime import datetime from datetime import datetime
class UserBase(BaseModel): class UserBase(BaseModel):
"""用户基础模型""" """用户基础模型"""
username: str = Field( username: str = Field(
..., ...,
min_length=3, min_length=3,
max_length=50, max_length=50,
description="登录名", description="登录名",
json_schema_extra={ json_schema_extra={
"error_messages": { "error_messages": {
"min_length": "用户名长度至少为3个字符", "min_length": "用户名长度至少为3个字符",
"max_length": "用户名长度不能超过50个字符" "max_length": "用户名长度不能超过50个字符"
} }
} }
) )
nickname: str = Field(..., max_length=50, description="昵称") nickname: str = Field(..., max_length=50, description="昵称")
email: Optional[str] = Field(None, max_length=50, description="电子邮箱") email: Optional[str] = Field(None, max_length=50, description="电子邮箱")
phone: Optional[str] = Field(None, max_length=11, description="手机号码") phone: Optional[str] = Field(None, max_length=11, description="手机号码")
wx_openid: Optional[str] = Field(None, max_length=100, description="微信OpenID") wx_openid: Optional[str] = Field(None, max_length=100, description="微信OpenID")
avatar: Optional[str] = Field(None, max_length=255, description="头像路径") avatar: Optional[str] = Field(None, max_length=255, description="头像路径")
role_id: Optional[int] = Field(2, description="角色ID") role_id: Optional[int] = Field(2, description="角色ID")
model_config = {"extra": "ignore"} model_config = {"extra": "ignore"}
class UserCreateRequest(UserBase): class UserCreateRequest(UserBase):
"""用户创建请求模型""" """用户创建请求模型"""
password: str = Field( password: str = Field(
..., ...,
min_length=6, min_length=6,
max_length=50, max_length=50,
description="密码", description="密码",
json_schema_extra={ json_schema_extra={
"error_messages": { "error_messages": {
"min_length": "密码长度至少为6个字符", "min_length": "密码长度至少为6个字符",
"max_length": "密码长度不能超过50个字符" "max_length": "密码长度不能超过50个字符"
} }
} }
) )
class UserCreate(UserBase): class UserCreate(UserBase):
"""用户创建模型""" """用户创建模型"""
password_hash: str = Field( password_hash: str = Field(
..., min_length=6, max_length=128, description="密码哈希值" ..., min_length=6, max_length=128, description="密码哈希值"
) )
class UserUpdate(BaseModel): class UserUpdate(BaseModel):
"""用户更新模型""" """用户更新模型"""
username: str = Field(..., min_length=3, max_length=50, description="登录名") username: str = Field(..., min_length=3, max_length=50, description="登录名")
nickname: str = Field(..., max_length=50, description="昵称") nickname: str = Field(..., max_length=50, description="昵称")
email: Optional[str] = Field(None, max_length=50, description="电子邮箱") email: Optional[str] = Field(None, max_length=50, description="电子邮箱")
phone: Optional[str] = Field(None, max_length=11, description="手机号码") phone: Optional[str] = Field(None, max_length=11, description="手机号码")
wx_openid: Optional[str] = Field(None, max_length=100, description="微信OpenID") wx_openid: Optional[str] = Field(None, max_length=100, description="微信OpenID")
avatar: Optional[str] = Field(None, max_length=255, description="头像路径") avatar: Optional[str] = Field(None, max_length=255, description="头像路径")
role_id: Optional[int] = Field(2, description="角色ID") role_id: Optional[int] = Field(2, description="角色ID")
password: Optional[str] = Field(None, max_length=128, description="密码") password: Optional[str] = Field(None, max_length=128, description="密码")
class UserLogin(BaseModel): class UserLogin(BaseModel):
"""用户登录模型""" """用户登录模型"""
username: str = Field(..., description="用户名/邮箱/手机号") username: str = Field(..., description="用户名/邮箱/手机号")
password: str = Field(..., description="密码") password: str = Field(..., description="密码")
captcha_id: str = Field(..., description="验证码ID") captcha_id: str = Field(..., description="验证码ID")
captcha_code: str = Field(..., description="验证码") captcha_code: str = Field(..., description="验证码")
class UserResponse(BaseModel): class UserResponse(BaseModel):
"""用户响应模型""" """用户响应模型"""
id: int = Field(..., description="用户ID") id: int = Field(..., description="用户ID")
username: str = Field(..., description="登录名") username: str = Field(..., description="登录名")
nickname: str = Field(..., description="昵称") nickname: str = Field(..., description="昵称")
email: Optional[str] = Field(None, description="电子邮箱") email: Optional[str] = Field(None, description="电子邮箱")
phone: Optional[str] = Field(None, description="手机号码") phone: Optional[str] = Field(None, description="手机号码")
wx_openid: Optional[str] = Field(None, description="微信OpenID") wx_openid: Optional[str] = Field(None, description="微信OpenID")
avatar: Optional[str] = Field(None, description="头像路径") avatar: Optional[str] = Field(None, description="头像路径")
role_id: int = Field(..., description="角色ID") role_id: int = Field(..., description="角色ID")
createtime: datetime = Field(..., description="创建时间") createtime: datetime = Field(..., description="创建时间")
updatetime: datetime = Field(..., description="更新时间") updatetime: datetime = Field(..., description="更新时间")
lastlogintime: Optional[datetime] = Field(None, description="最后登录时间") lastlogintime: Optional[datetime] = Field(None, description="最后登录时间")
class Config: class Config:
from_attributes = True from_attributes = True
class Token(BaseModel): class Token(BaseModel):
"""令牌模型""" """令牌模型"""
access_token: str = Field(..., description="访问令牌") access_token: str = Field(..., description="访问令牌")
token_type: str = Field(..., description="令牌类型") token_type: str = Field(..., description="令牌类型")
class TokenData(BaseModel): class TokenData(BaseModel):
"""令牌数据模型""" """令牌数据模型"""
user_id: Optional[int] = None user_id: Optional[int] = None
email: Optional[str] = None email: Optional[str] = None
class RoleBase(BaseModel): class RoleBase(BaseModel):
"""角色基础模型""" """角色基础模型"""
name: str = Field(..., max_length=50, description="角色名称") name: str = Field(..., max_length=50, description="角色名称")
class RoleCreate(RoleBase): class RoleCreate(RoleBase):
"""角色创建模型""" """角色创建模型"""
creator: Optional[str] = Field(None, max_length=50, description="创建人") creator: Optional[str] = Field(None, max_length=50, description="创建人")
class RoleResponse(RoleBase): class RoleResponse(RoleBase):
"""角色响应模型""" """角色响应模型"""
id: int = Field(..., description="角色ID") id: int = Field(..., description="角色ID")
createtime: datetime = Field(..., description="创建时间") createtime: datetime = Field(..., description="创建时间")
updatetime: datetime = Field(..., description="更新时间") updatetime: datetime = Field(..., description="更新时间")
creator: Optional[str] = Field(None, description="创建人") creator: Optional[str] = Field(None, description="创建人")
class Config: class Config:
from_attributes = True from_attributes = True
# 权限相关模型 # 权限相关模型
class PermissionBase(BaseModel): class PermissionBase(BaseModel):
"""权限基础模型""" """权限基础模型"""
name: str = Field(..., max_length=100, description="权限名称") name: str = Field(..., max_length=100, description="权限名称")
code: str = Field(..., max_length=100, description="权限编码") code: str = Field(..., max_length=100, description="权限编码")
parentid: int = Field(0, description="父权限ID0表示顶级模块") parentid: int = Field(0, description="父权限ID0表示顶级模块")
route: Optional[str] = Field(None, max_length=255, description="前端路由路径") route: Optional[str] = Field(None, max_length=255, description="前端路由路径")
description: Optional[str] = Field(None, max_length=255, description="权限描述") description: Optional[str] = Field(None, max_length=255, description="权限描述")
sort: int = Field(0, description="排序号(升序排列)")
class PermissionCreate(PermissionBase):
"""权限创建模型""" class PermissionCreate(PermissionBase):
pass """权限创建模型"""
pass
class PermissionResponse(PermissionBase):
"""权限响应模型""" class PermissionResponse(PermissionBase):
"""权限响应模型"""
id: int = Field(..., description="权限ID")
createtime: datetime = Field(..., description="创建时间") id: int = Field(..., description="权限ID")
updatetime: datetime = Field(..., description="更新时间") createtime: datetime = Field(..., description="创建时间")
updatetime: datetime = Field(..., description="更新时间")
class Config:
from_attributes = True class Config:
from_attributes = True
class PermissionTreeResponse(BaseModel):
"""权限树响应模型""" class PermissionTreeResponse(BaseModel):
"""权限树响应模型"""
id: int = Field(..., description="权限ID")
name: str = Field(..., description="权限名称") id: int = Field(..., description="权限ID")
code: str = Field(..., description="权限编码") name: str = Field(..., description="权限名称")
parentid: int = Field(..., description="父权限ID") code: str = Field(..., description="权限编码")
route: Optional[str] = Field(None, description="前端路由路径") parentid: int = Field(..., description="父权限ID")
createtime: Optional[datetime] = Field(None, description="创建时间") route: Optional[str] = Field(None, description="前端路由路径")
updatetime: Optional[datetime] = Field(None, description="更新时间") createtime: Optional[datetime] = Field(None, description="创建时间")
description: Optional[str] = Field(None, description="权限描述") updatetime: Optional[datetime] = Field(None, description="更新时间")
children: list = Field([], description="子权限列表") description: Optional[str] = Field(None, description="权限描述")
sort: int = Field(0, description="排序号")
children: list = Field([], description="子权限列表")
# 角色权限关联相关模型
class RolePermissionAssign(BaseModel):
"""角色权限分配模型""" # 角色权限关联相关模型
class RolePermissionAssign(BaseModel):
role_id: int = Field(..., description="角色ID") """角色权限分配模型"""
permission_ids: list[int] = Field(..., description="权限ID列表")
role_id: int = Field(..., description="角色ID")
permission_ids: list[int] = Field(..., description="权限ID列表")
class RolePermissionResponse(BaseModel):
"""角色权限响应模型"""
class RolePermissionResponse(BaseModel):
role_id: int = Field(..., description="角色ID") """角色权限响应模型"""
role_name: str = Field(..., description="角色名称")
permissions: list[PermissionResponse] = Field(..., description="权限列表") role_id: int = Field(..., description="角色ID")
menu: list = Field([], description="菜单树形结构") role_name: str = Field(..., description="角色名称")
permissions: list[PermissionResponse] = Field(..., description="权限列表")
menu: list = Field([], description="菜单树形结构")
# 系统设置相关模型
class SystemSettingBase(BaseModel):
"""系统设置基础模型""" # 系统设置相关模型
class SystemSettingBase(BaseModel):
key: str = Field(..., max_length=100, description="设置键名") """系统设置基础模型"""
value: str = Field(..., max_length=2000, description="设置值")
type: Optional[str] = Field("string", max_length=20, description="类型string/image/select/boolean") key: str = Field(..., max_length=100, description="设置键名")
options: Optional[str] = Field(None, max_length=500, description="下拉选项(用|分隔)") value: str = Field(..., max_length=2000, description="设置值")
description: Optional[str] = Field(None, max_length=255, description="设置描述") type: Optional[str] = Field("string", max_length=20, description="类型string/image/select/boolean")
options: Optional[str] = Field(None, max_length=500, description="下拉选项(用|分隔)")
model_config = {"extra": "ignore"} description: Optional[str] = Field(None, max_length=255, description="设置描述")
model_config = {"extra": "ignore"}
class SystemSettingCreate(SystemSettingBase):
"""系统设置创建模型"""
pass class SystemSettingCreate(SystemSettingBase):
"""系统设置创建模型"""
pass
class SystemSettingUpdate(SystemSettingBase):
"""系统设置更新模型"""
key: Optional[str] = Field(None, max_length=100, description="设置键名") class SystemSettingUpdate(SystemSettingBase):
"""系统设置更新模型"""
key: Optional[str] = Field(None, max_length=100, description="设置键名")
class SystemSettingResponse(BaseModel):
"""系统设置响应模型"""
class SystemSettingResponse(BaseModel):
id: int = Field(..., description="设置ID") """系统设置响应模型"""
key: str = Field(..., description="设置键名")
value: str = Field(..., description="设置值") id: int = Field(..., description="设置ID")
type: str = Field(..., description="类型") key: str = Field(..., description="设置键名")
options: Optional[str] = Field(None, description="下拉选项") value: str = Field(..., description="设置值")
description: Optional[str] = Field(None, description="设置描述") type: str = Field(..., description="类型")
createtime: datetime = Field(..., description="创建时间") options: Optional[str] = Field(None, description="下拉选项")
updatetime: datetime = Field(..., description="更新时间") description: Optional[str] = Field(None, description="设置描述")
createtime: datetime = Field(..., description="创建时间")
class Config: updatetime: datetime = Field(..., description="更新时间")
from_attributes = True
class Config:
from_attributes = True
class LoginLogResponse(BaseModel):
"""登录日志响应模型"""
class LoginLogResponse(BaseModel):
id: int = Field(..., description="日志ID") """登录日志响应模型"""
username: str = Field(..., description="用户名")
login_time: datetime = Field(..., description="登录时间") id: int = Field(..., description="日志ID")
ip_address: Optional[str] = Field(None, description="IP地址") username: str = Field(..., description="用户名")
user_agent: Optional[str] = Field(None, description="User-Agent") login_time: datetime = Field(..., description="登录时间")
status: str = Field(..., description="状态: success/failure") ip_address: Optional[str] = Field(None, description="IP地址")
failure_reason: Optional[str] = Field(None, description="失败原因") user_agent: Optional[str] = Field(None, description="User-Agent")
status: str = Field(..., description="状态: success/failure")
class Config: failure_reason: Optional[str] = Field(None, description="失败原因")
from_attributes = True
class Config:
from_attributes = True
class LoginLogSearch(BaseModel):
"""登录日志搜索模型"""
class LoginLogSearch(BaseModel):
username: Optional[str] = Field(None, description="用户名(模糊搜索)") """登录日志搜索模型"""
status: Optional[str] = Field(None, description="状态")
start_time: Optional[datetime] = Field(None, description="开始时间") username: Optional[str] = Field(None, description="用户名(模糊搜索)")
end_time: Optional[datetime] = Field(None, description="结束时间") status: Optional[str] = Field(None, description="状态")
start_time: Optional[datetime] = Field(None, description="开始时间")
end_time: Optional[datetime] = Field(None, description="结束时间")
class LoginLogCleanRequest(BaseModel):
"""登录日志清理请求模型"""
class LoginLogCleanRequest(BaseModel):
before_date: datetime = Field(..., description="清理此日期之前的日志") """登录日志清理请求模型"""
before_date: datetime = Field(..., description="清理此日期之前的日志")
class OperationLogResponse(BaseModel):
"""操作日志响应模型"""
class OperationLogResponse(BaseModel):
id: int = Field(..., description="日志ID") """操作日志响应模型"""
user_id: int = Field(..., description="用户ID")
username: str = Field(..., description="用户名") id: int = Field(..., description="日志ID")
action_type: str = Field(..., description="操作类型: create/update/delete") user_id: int = Field(..., description="用户ID")
target_type: str = Field(..., description="目标类型: user/role/permission/setting") username: str = Field(..., description="用户名")
target_id: Optional[int] = Field(None, description="目标ID") action_type: str = Field(..., description="操作类型: create/update/delete")
target_name: Optional[str] = Field(None, description="目标名称") target_type: str = Field(..., description="目标类型: user/role/permission/setting")
detail: Optional[str] = Field(None, description="变更详情(JSON)") target_id: Optional[int] = Field(None, description="目标ID")
ip_address: Optional[str] = Field(None, description="IP地址") target_name: Optional[str] = Field(None, description="目标名称")
user_agent: Optional[str] = Field(None, description="User-Agent") detail: Optional[str] = Field(None, description="变更详情(JSON)")
createtime: datetime = Field(..., description="创建时间") ip_address: Optional[str] = Field(None, description="IP地址")
user_agent: Optional[str] = Field(None, description="User-Agent")
class Config: createtime: datetime = Field(..., description="创建时间")
from_attributes = True
class Config:
from_attributes = True
class OperationLogSearch(BaseModel):
"""操作日志搜索模型"""
class OperationLogSearch(BaseModel):
username: Optional[str] = Field(None, description="用户名(模糊搜索)") """操作日志搜索模型"""
action_type: Optional[str] = Field(None, description="操作类型")
target_type: Optional[str] = Field(None, description="目标类型") username: Optional[str] = Field(None, description="用户名(模糊搜索)")
start_time: Optional[datetime] = Field(None, description="开始时间") action_type: Optional[str] = Field(None, description="操作类型")
end_time: Optional[datetime] = Field(None, description="结束时间") target_type: Optional[str] = Field(None, description="目标类型")
start_time: Optional[datetime] = Field(None, description="开始时间")
end_time: Optional[datetime] = Field(None, description="结束时间")
# ========================================
# 用户注册相关模型
# ======================================== # ========================================
# 用户注册相关模型
# ========================================
class PhoneSendCodeRequest(BaseModel):
"""发送短信验证码请求"""
phone: str = Field(..., pattern=r"^\d{11}$", description="手机号11位数字") class PhoneSendCodeRequest(BaseModel):
"""发送短信验证码请求"""
phone: str = Field(..., pattern=r"^\d{11}$", description="手机号11位数字")
class PhoneSendCodeResponse(BaseModel):
"""发送短信验证码响应"""
message: str = "验证码已发送" class PhoneSendCodeResponse(BaseModel):
expire_seconds: int = 300 """发送短信验证码响应"""
message: str = "验证码已发送"
expire_seconds: int = 300
class PhoneRegisterRequest(BaseModel):
"""手机号注册请求"""
username: str = Field(..., min_length=3, max_length=50, description="用户名") class PhoneRegisterRequest(BaseModel):
phone: str = Field(..., pattern=r"^\d{11}$", description="手机号") """手机号注册请求"""
code: str = Field(..., min_length=6, max_length=6, description="短信验证码") username: str = Field(..., min_length=3, max_length=50, description="用户名")
password: str = Field(..., min_length=6, max_length=50, description="密码") phone: str = Field(..., pattern=r"^\d{11}$", description="手机号")
nickname: str = Field(..., max_length=50, description="昵称") code: str = Field(..., min_length=6, max_length=6, description="短信验证码")
password: str = Field(..., min_length=6, max_length=50, description="密码")
nickname: str = Field(..., max_length=50, description="昵称")
class EmailRegisterInitRequest(BaseModel):
"""邮箱注册初始化请求"""
username: str = Field(..., min_length=3, max_length=50, description="用户名") class EmailRegisterInitRequest(BaseModel):
email: str = Field(..., max_length=50, description="电子邮箱") """邮箱注册初始化请求"""
password: str = Field(..., min_length=6, max_length=50, description="密码") username: str = Field(..., min_length=3, max_length=50, description="用户名")
nickname: str = Field(..., max_length=50, description="昵称") email: str = Field(..., max_length=50, description="电子邮箱")
password: str = Field(..., min_length=6, max_length=50, description="密码")
nickname: str = Field(..., max_length=50, description="昵称")
class EmailRegisterInitResponse(BaseModel):
"""邮箱注册初始化响应"""
message: str = "验证邮件已发送,请查收并点击链接完成注册" class EmailRegisterInitResponse(BaseModel):
expire_minutes: int = 30 """邮箱注册初始化响应"""
message: str = "验证邮件已发送,请查收并点击链接完成注册"
expire_minutes: int = 30
class RegisterSuccessResponse(BaseModel):
"""注册成功响应"""
id: int class RegisterSuccessResponse(BaseModel):
username: str """注册成功响应"""
nickname: str id: int
username: str
nickname: str
message: str = "注册成功" message: str = "注册成功"

View File

@ -1,176 +1,188 @@
import type { CreateCrudOptions } from "@fast-crud/fast-crud"; import type { CreateCrudOptions } from "@fast-crud/fast-crud";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { http } from "@/modules/admin/api/http"; import { http } from "@/modules/admin/api/http";
import { toSearchParams } from "@/modules/admin/utils/list-query"; import { toSearchParams } from "@/modules/admin/utils/list-query";
export type PermissionRow = { export type PermissionRow = {
id: number; id: number;
name: string; name: string;
code: string; code: string;
parentid: number; parentid: number;
route?: string | null; route?: string | null;
description?: string | null; description?: string | null;
createtime: string; sort: number;
updatetime: string; createtime: string;
children?: PermissionRow[]; updatetime: string;
}; children?: PermissionRow[];
};
export const createPermissionCrudOptions: CreateCrudOptions<PermissionRow> = () => {
return { export const createPermissionCrudOptions: CreateCrudOptions<PermissionRow> = () => {
crudOptions: { return {
pagination: { show: false }, crudOptions: {
search: { pagination: { show: false },
show: true, search: {
options: { show: true,
layout: 'inline', options: {
}, layout: 'inline',
expand: false, },
}, expand: false,
tree: { },
enable: true, tree: {
childrenKey: "children", enable: true,
parentKey: "parentid", childrenKey: "children",
hasChildField: "children", parentKey: "parentid",
}, hasChildField: "children",
request: { },
transformQuery: ({ form }) => request: {
toSearchParams(form as Record<string, unknown>, ["name", "code"]), transformQuery: ({ form }) =>
pageRequest: async (_query) => { toSearchParams(form as Record<string, unknown>, ["name", "code"]),
const { data } = await http.get<PermissionRow[]>("/permissions/tree"); pageRequest: async (_query) => {
return data; const { data } = await http.get<PermissionRow[]>("/permissions/tree");
}, return data;
transformRes: ({ res }) => { },
const records = Array.isArray(res) ? res : []; transformRes: ({ res }) => {
return { const records = Array.isArray(res) ? res : [];
currentPage: 1, return {
pageSize: Math.max(records.length, 1), currentPage: 1,
total: records.length, pageSize: Math.max(records.length, 1),
records, total: records.length,
}; records,
}, };
addRequest: async ({ form }) => { },
await http.post("/permissions/", form); addRequest: async ({ form }) => {
ElMessage.success({ message: "添加成功", duration: 5000 }); await http.post("/permissions/", form);
}, ElMessage.success({ message: "添加成功", duration: 5000 });
editRequest: async ({ form, row }) => { },
if (!row) return; editRequest: async ({ form, row }) => {
await http.put(`/permissions/${row.id}`, form); if (!row) return;
ElMessage.success({ message: "修改成功", duration: 5000 }); await http.put(`/permissions/${row.id}`, form);
}, ElMessage.success({ message: "修改成功", duration: 5000 });
delRequest: async ({ row }) => { },
if (!row) return; delRequest: async ({ row }) => {
await http.delete(`/permissions/${row.id}`); if (!row) return;
ElMessage.success({ message: "删除成功", duration: 5000 }); await http.delete(`/permissions/${row.id}`);
}, ElMessage.success({ message: "删除成功", duration: 5000 });
onSuccess: ({ action }: { action: string }) => { },
if (action === "add") { onSuccess: ({ action }: { action: string }) => {
ElMessage.success({ message: "添加成功", duration: 5000 }); if (action === "add") {
} else if (action === "edit") { ElMessage.success({ message: "添加成功", duration: 5000 });
ElMessage.success({ message: "修改成功", duration: 5000 }); } else if (action === "edit") {
} ElMessage.success({ message: "修改成功", duration: 5000 });
}, }
onError: (error: any) => { },
let message = "操作失败"; onError: (error: any) => {
if (error.response?.data) { let message = "操作失败";
const data = error.response.data; if (error.response?.data) {
if (data.message) { const data = error.response.data;
message = data.message; if (data.message) {
} else if (data.detail) { message = data.message;
message = typeof data.detail === "string" ? data.detail : "操作失败"; } else if (data.detail) {
} message = typeof data.detail === "string" ? data.detail : "操作失败";
} }
ElMessage.error({ message, duration: 5000 }); }
}, ElMessage.error({ message, duration: 5000 });
}, },
table: { },
remove: { table: {
showSuccessNotification: false, remove: {
}, showSuccessNotification: false,
}, },
rowHandle: { },
buttons: { rowHandle: {
edit: { text: "编辑" }, buttons: {
remove: { text: "删除" }, edit: { text: "编辑" },
}, remove: { text: "删除" },
}, },
columns: { },
id: { columns: {
title: "ID", id: {
key: "id", title: "ID",
type: "number", key: "id",
column: { width: 70 }, type: "number",
form: { show: false }, column: { width: 70 },
}, form: { show: false },
name: { },
title: "名称", name: {
key: "name", title: "名称",
type: "text", key: "name",
search: { show: true }, type: "text",
form: { search: { show: true },
rules: [{ required: true, message: "必填" }], form: {
}, rules: [{ required: true, message: "必填" }],
}, },
code: { },
title: "编码", code: {
key: "code", title: "编码",
type: "text", key: "code",
search: { show: true }, type: "text",
form: { search: { show: true },
rules: [{ required: true, message: "必填" }], form: {
}, rules: [{ required: true, message: "必填" }],
}, },
parentid: { },
title: "父级ID", parentid: {
key: "parentid", title: "父级ID",
type: "number", key: "parentid",
form: { type: "number",
value: 0, form: {
rules: [{ required: true, message: "必填" }], value: 0,
}, rules: [{ required: true, message: "必填" }],
}, },
description: { },
title: "描述", description: {
key: "description", title: "描述",
type: "textarea", key: "description",
column: { show: false }, type: "textarea",
}, column: { show: false },
route: { },
title: "路由", route: {
key: "route", title: "路由",
type: "text", key: "route",
column: { width: 200 }, type: "text",
form: { column: { width: 200 },
placeholder: "如:/admin/users", form: {
}, placeholder: "如:/admin/users",
}, },
createtime: { },
title: "创建时间", sort: {
key: "createtime", title: "排序",
type: "text", key: "sort",
form: { show: false }, type: "number",
column: { width: 170 }, column: { width: 80, align: "center" },
valueBuilder({ value, row, key }) { form: {
if (!row || value == null) return; value: 0,
(row as Record<string, unknown>)[key as string] = dayjs(value).format( rules: [{ required: true, message: "必填" }],
"YYYY-MM-DD HH:mm:ss", helper: "数字越小,排序越靠前",
); },
}, },
}, createtime: {
updatetime: { title: "创建时间",
title: "更新时间", key: "createtime",
key: "updatetime", type: "text",
type: "text", form: { show: false },
form: { show: false }, column: { width: 170 },
column: { width: 170 }, valueBuilder({ value, row, key }) {
valueBuilder({ value, row, key }) { if (!row || value == null) return;
if (!row || value == null) return; (row as Record<string, unknown>)[key as string] = dayjs(value).format(
(row as Record<string, unknown>)[key as string] = dayjs(value).format( "YYYY-MM-DD HH:mm:ss",
"YYYY-MM-DD HH:mm:ss", );
); },
}, },
}, updatetime: {
}, title: "更新时间",
}, key: "updatetime",
}; type: "text",
}; form: { show: false },
column: { width: 170 },
valueBuilder({ value, row, key }) {
if (!row || value == null) return;
(row as Record<string, unknown>)[key as string] = dayjs(value).format(
"YYYY-MM-DD HH:mm:ss",
);
},
},
},
},
};
};