diff --git a/backend/auth/apps/permissions/urls.py b/backend/auth/apps/permissions/urls.py
index f9fec4e..30acde6 100644
--- a/backend/auth/apps/permissions/urls.py
+++ b/backend/auth/apps/permissions/urls.py
@@ -44,7 +44,9 @@ async def create_permission(
name=permission_data.name,
code=permission_data.code,
parentid=permission_data.parentid,
+ route=permission_data.route,
description=permission_data.description,
+ sort=permission_data.sort,
)
db.add(db_permission)
@@ -112,6 +114,7 @@ async def get_permission_tree(
"parentid": perm.parentid,
"route": perm.route,
"description": perm.description,
+ "sort": perm.sort or 0,
"createtime": perm.createtime,
"updatetime": perm.updatetime,
"children": []
@@ -128,6 +131,16 @@ async def get_permission_tree(
# 子节点,添加到父节点的 children
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(
db, current_user.id, current_user.username, "search", "permission",
@@ -187,6 +200,7 @@ async def get_menu(
"parentid": perm.parentid,
"route": perm.route,
"description": perm.description,
+ "sort": perm.sort or 0,
"createtime": perm.createtime,
"updatetime": perm.updatetime,
"children": []
@@ -203,6 +217,16 @@ async def get_menu(
# 子节点,添加到父节点的 children
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(
db, current_user.id, current_user.username, "search", "permission",
@@ -222,7 +246,7 @@ async def get_permission(
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:
raise HTTPException(status_code=404, detail="权限不存在")
@@ -271,7 +295,7 @@ async def update_permission(
raise HTTPException(status_code=404, detail="权限不存在")
# 记录操作日志
changed = {}
- for field in ["name", "code", "parentid", "description"]:
+ for field in ["name", "code", "parentid", "route", "sort", "description"]:
old_val = getattr(old_permission, field, None)
new_val = update_data.get(field)
if old_val != new_val:
@@ -452,6 +476,7 @@ async def get_current_user_permissions(
"parentid": perm.parentid,
"route": perm.route,
"description": perm.description,
+ "sort": perm.sort or 0,
"createtime": perm.createtime,
"updatetime": perm.updatetime,
"children": []
@@ -468,6 +493,16 @@ async def get_current_user_permissions(
# 子节点,添加到父节点的 children
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(
db, current_user.id, current_user.username, "search", "permission",
diff --git a/backend/auth/init_data.py b/backend/auth/init_data.py
index a6db11e..85a0096 100644
--- a/backend/auth/init_data.py
+++ b/backend/auth/init_data.py
@@ -1,452 +1,451 @@
-from sqlalchemy.ext.asyncio import AsyncSession
-from sqlalchemy import select, text
-from models import Role, User, Permission, SystemSetting
-from utils.password import get_password_hash
-
-
-async def init_permissions(db: AsyncSession):
- """初始化权限数据(树形结构)"""
- # 定义需要初始化的权限(树形结构)
- # parentid: 0 = 顶级模块, >0 = 子权限
- permission_tree = [
- # ========================================
- # 顶级模块 (DB ID: 1,2,3)
- # ========================================
- {"name": "权限管理", "code": "perm", "parentid": 0, "route": "/admin/permissions", "description": "权限管理模块"},
- {"name": "系统设置", "code": "system", "parentid": 0, "route": "/admin/settings", "description": "系统设置模块"},
- {"name": "日志管理", "code": "log", "parentid": 0, "route": "/admin/logs", "description": "日志管理模块"},
-
- # ========================================
- # 权限管理 -> 二级子模块 (parentid=1, DB ID: 4,5,6)
- # ========================================
- {"name": "用户管理", "code": "perm:user", "parentid": 1, "route": "/admin/users", "description": "用户管理子模块"},
- {"name": "角色管理", "code": "perm:role", "parentid": 1, "route": "/admin/roles", "description": "角色管理子模块"},
- {"name": "菜单管理", "code": "perm:menu", "parentid": 1, "route": "/admin/menu", "description": "菜单管理子模块"},
-
- # ========================================
- # 用户管理操作 (parentid=4, DB ID: 7-14)
- # ========================================
- {"name": "查看列表", "code": "perm:user:list", "parentid": 4, "description": "查看用户列表"},
- {"name": "查看详情", "code": "perm:user:detail", "parentid": 4, "description": "查看用户详情"},
- {"name": "搜索", "code": "perm:user:search", "parentid": 4, "description": "搜索用户"},
- {"name": "创建", "code": "perm:user:create", "parentid": 4, "description": "创建新用户"},
- {"name": "编辑", "code": "perm:user:edit", "parentid": 4, "description": "编辑用户信息"},
- {"name": "删除", "code": "perm:user:delete", "parentid": 4, "description": "删除用户"},
- {"name": "导出", "code": "perm:user:export", "parentid": 4, "description": "导出用户数据"},
- {"name": "批量操作", "code": "perm:user:batch", "parentid": 4, "description": "批量操作用户"},
-
- # ========================================
- # 角色管理操作 (parentid=5, DB ID: 15-22)
- # ========================================
- {"name": "查看列表", "code": "perm:role:list", "parentid": 5, "description": "查看角色列表"},
- {"name": "查看详情", "code": "perm:role:detail", "parentid": 5, "description": "查看角色详情"},
- {"name": "搜索", "code": "perm:role:search", "parentid": 5, "description": "搜索角色"},
- {"name": "创建", "code": "perm:role:create", "parentid": 5, "description": "创建新角色"},
- {"name": "编辑", "code": "perm:role:edit", "parentid": 5, "description": "编辑角色信息"},
- {"name": "删除", "code": "perm:role:delete", "parentid": 5, "description": "删除角色"},
- {"name": "导出", "code": "perm:role:export", "parentid": 5, "description": "导出角色数据"},
- {"name": "分配权限", "code": "perm:role:assign", "parentid": 5, "description": "为角色分配权限"},
-
- # ========================================
- # 菜单管理操作 (parentid=6, DB ID: 23-29)
- # ========================================
- {"name": "查看列表", "code": "perm:menu:list", "parentid": 6, "description": "查看菜单列表"},
- {"name": "查看详情", "code": "perm:menu:detail", "parentid": 6, "description": "查看菜单详情"},
- {"name": "搜索", "code": "perm:menu:search", "parentid": 6, "description": "搜索菜单"},
- {"name": "创建", "code": "perm:menu:create", "parentid": 6, "description": "创建新菜单"},
- {"name": "编辑", "code": "perm:menu:edit", "parentid": 6, "description": "编辑菜单信息"},
- {"name": "删除", "code": "perm:menu:delete", "parentid": 6, "description": "删除菜单"},
- {"name": "导出", "code": "perm:menu:export", "parentid": 6, "description": "导出菜单数据"},
-
- # ========================================
- # 系统设置 -> 子权限 (parentid=2, DB ID: 30)
- # ========================================
- {"name": "系统设置", "code": "system:view", "parentid": 2, "description": "查看和修改系统设置"},
-
- # ========================================
- # 日志管理 -> 子模块 (parentid=3, DB ID: 31,32)
- # ========================================
- {"name": "登录日志", "code": "log:login", "parentid": 3, "route": "/admin/login-logs", "description": "登录日志查看"},
- {"name": "操作日志", "code": "log:operation", "parentid": 3, "route": "/admin/operation-logs", "description": "操作日志查看"},
-
- # ========================================
- # 登录日志操作 (parentid=31, DB ID: 33,34)
- # ========================================
- {"name": "查看列表", "code": "log:login:list", "parentid": 31, "description": "查看登录日志列表"},
- {"name": "搜索", "code": "log:login:search", "parentid": 31, "description": "搜索登录日志"},
-
- # ========================================
- # 操作日志操作 (parentid=32, DB ID: 35,36)
- # ========================================
- {"name": "查看列表", "code": "log:operation:list", "parentid": 32, "description": "查看操作日志列表"},
- {"name": "搜索", "code": "log:operation:search", "parentid": 32, "description": "搜索操作日志"},
-
- # ========================================
- # 日志清理 (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}
-
- # 创建或更新权限
- created_count = 0
- updated_count = 0
-
- for perm_data in permission_tree:
- perm_code = perm_data["code"]
- if perm_code not in existing_permission_codes:
- # 权限不存在,需要创建
- db.add(Permission(**perm_data))
- created_count += 1
- else:
- # 权限存在,检查是否需要更新
- existing_perm = existing_permission_codes[perm_code]
- update_needed = False
-
- if existing_perm.name != perm_data["name"]:
- existing_perm.name = perm_data["name"]
- update_needed = True
- if existing_perm.parentid != perm_data["parentid"]:
- existing_perm.parentid = perm_data["parentid"]
- update_needed = True
- if existing_perm.route != perm_data.get("route"):
- existing_perm.route = perm_data.get("route")
- update_needed = True
- if existing_perm.description != perm_data["description"]:
- existing_perm.description = perm_data["description"]
- update_needed = True
-
- if update_needed:
- updated_count += 1
-
- if created_count > 0 or updated_count > 0:
- await db.commit()
- print(f"创建了 {created_count} 个权限,更新了 {updated_count} 个权限")
- else:
- print("权限数据已存在且无需更新,跳过初始化")
-
- print("权限数据初始化/更新完成")
-
-
-async def init_role_permissions(db: AsyncSession):
- """初始化角色权限分配"""
- from sqlalchemy.orm import selectinload
-
- # 获取管理员角色(使用预加载避免懒加载问题)
- result = await db.execute(
- select(Role)
- .filter(Role.name == "管理员")
- .options(selectinload(Role.permissions))
- )
- admin_role = result.scalar_one_or_none()
- if not admin_role:
- print("管理员角色不存在,请先初始化角色数据")
- return
-
- # 获取所有权限
- result = await db.execute(select(Permission))
- all_permissions = result.scalars().all()
-
- if not all_permissions:
- print("没有找到任何权限,请先初始化权限数据")
- return
-
- # 获取管理员角色当前的权限(使用预加载的数据)
- admin_permission_codes = set()
- if admin_role.permissions:
- admin_permission_codes = {perm.code for perm in admin_role.permissions}
-
- # 检查是否需要添加权限
- permissions_to_add = []
- for perm in all_permissions:
- if perm.code not in admin_permission_codes:
- permissions_to_add.append(perm)
-
- if permissions_to_add:
- # 直接设置权限(替换而非追加,确保权限完整)
- admin_role.permissions = all_permissions
- await db.commit()
- print(f"为管理员角色分配了 {len(permissions_to_add)} 个权限")
- else:
- print("管理员角色权限已完整,无需更新")
-
- print("角色权限初始化/更新完成")
-
-
-async def init_roles(db: AsyncSession):
- """初始化角色数据"""
- # 定义需要初始化的角色
- required_roles = [
- {"name": "管理员", "creator": "system"},
- {"name": "普通用户", "creator": "system"},
- {"name": "访客", "creator": "system"},
- {"name": "会员", "creator": "system"},
- ]
-
- # 检查现有角色
- result = await db.execute(select(Role))
- existing_roles = result.scalars().all()
- existing_role_names = {role.name for role in existing_roles}
-
- # 检查需要创建或更新的角色
- roles_to_add = []
- roles_to_update = []
-
- for role_data in required_roles:
- role_name = role_data["name"]
- if role_name not in existing_role_names:
- # 角色不存在,需要创建
- roles_to_add.append(Role(**role_data))
- else:
- # 角色存在,检查是否需要更新
- existing_role = next(
- role for role in existing_roles if role.name == role_name
- )
- if existing_role.creator != role_data["creator"]:
- # 需要更新
- existing_role.creator = role_data["creator"]
- roles_to_update.append(existing_role)
-
- # 执行创建和更新操作
- if roles_to_add:
- for role in roles_to_add:
- db.add(role)
- await db.commit()
- print(f"创建了 {len(roles_to_add)} 个角色")
-
- if roles_to_update:
- await db.commit()
- print(f"更新了 {len(roles_to_update)} 个角色")
-
- if not roles_to_add and not roles_to_update:
- print("角色数据已存在且无需更新,跳过初始化")
-
- print("角色数据初始化/更新完成")
-
-
-async def init_admin_user(db: AsyncSession):
- """初始化管理员用户"""
- # 获取管理员角色
- result = await db.execute(select(Role).filter(Role.name == "管理员"))
- admin_role = result.scalar_one_or_none()
- if not admin_role:
- print("管理员角色不存在,请先初始化角色数据")
- return
-
- # 定义需要的管理员用户数据
- admin_data = {
- "username": "admin",
- "nickname": "系统管理员",
- "email": "admin@example.com",
- "phone": "13800138000",
- "password_hash": get_password_hash("admin"),
- "role_id": admin_role.id,
- "avatar": "",
- "wx_openid": "",
- }
-
- # 检查管理员用户是否存在
- result = await db.execute(select(User).filter(User.username == "admin"))
- existing_admin = result.scalar_one_or_none()
-
- if not existing_admin:
- # 管理员不存在,创建
- admin_user = User(**admin_data)
- db.add(admin_user)
- await db.commit()
- print("创建了管理员用户")
- else:
- # 管理员存在,检查是否需要更新
- update_needed = False
-
- # 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置)
- if existing_admin.nickname != admin_data["nickname"]:
- existing_admin.nickname = admin_data["nickname"]
- update_needed = True
- if existing_admin.email != admin_data["email"]:
- existing_admin.email = admin_data["email"]
- update_needed = True
- if existing_admin.phone != admin_data["phone"]:
- existing_admin.phone = admin_data["phone"]
- update_needed = True
- if existing_admin.role_id != admin_data["role_id"]:
- existing_admin.role_id = admin_data["role_id"]
- update_needed = True
- if existing_admin.avatar != admin_data["avatar"]:
- existing_admin.avatar = admin_data["avatar"]
- update_needed = True
- if existing_admin.wx_openid != admin_data["wx_openid"]:
- existing_admin.wx_openid = admin_data["wx_openid"]
- update_needed = True
-
- if update_needed:
- await db.commit()
- print("更新了管理员用户")
- else:
- print("管理员用户已存在且无需更新,跳过初始化")
-
- print("管理员用户初始化/更新完成")
-
-
-async def init_test_user(db: AsyncSession):
- """初始化测试用户"""
- # 获取普通用户角色
- result = await db.execute(select(Role).filter(Role.name == "普通用户"))
- user_role = result.scalar_one_or_none()
- if not user_role:
- print("普通用户角色不存在,请先初始化角色数据")
- return
-
- # 定义需要的测试用户数据
- test_data = {
- "username": "test",
- "nickname": "测试用户",
- "email": "test@example.com",
- "phone": "13800138001",
- "password_hash": get_password_hash("test"),
- "role_id": user_role.id,
- "avatar": "",
- "wx_openid": "",
- }
-
- # 检查测试用户是否存在
- result = await db.execute(select(User).filter(User.username == "test"))
- existing_test_user = result.scalar_one_or_none()
-
- if not existing_test_user:
- # 测试用户不存在,创建
- test_user = User(**test_data)
- db.add(test_user)
- await db.commit()
- print("创建了测试用户")
- else:
- # 测试用户存在,检查是否需要更新
- update_needed = False
-
- # 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置)
- if existing_test_user.nickname != test_data["nickname"]:
- existing_test_user.nickname = test_data["nickname"]
- update_needed = True
- if existing_test_user.email != test_data["email"]:
- existing_test_user.email = test_data["email"]
- update_needed = True
- if existing_test_user.phone != test_data["phone"]:
- existing_test_user.phone = test_data["phone"]
- update_needed = True
- if existing_test_user.role_id != test_data["role_id"]:
- existing_test_user.role_id = test_data["role_id"]
- update_needed = True
- if existing_test_user.avatar != test_data["avatar"]:
- existing_test_user.avatar = test_data["avatar"]
- update_needed = True
- if existing_test_user.wx_openid != test_data["wx_openid"]:
- existing_test_user.wx_openid = test_data["wx_openid"]
- update_needed = True
-
- if update_needed:
- await db.commit()
- print("更新了测试用户")
- else:
- print("测试用户已存在且无需更新,跳过初始化")
-
- print("测试用户初始化/更新完成")
-
-
-async def init_system_settings(db: AsyncSession):
- """初始化系统设置数据"""
- # 定义需要初始化的系统设置
- system_settings = [
- {
- "key": "系统标题",
- "value": "图书管理系统",
- "type": "string",
- "description": "系统显示标题"
- },
- {
- "key": "页脚信息",
- "value": "Copyright © 2026 图书管理系统 版权所有 | 技术支持:强蕊科技",
- "type": "string",
- "description": "页脚信息"
- },
- {
- "key": "ICP信息",
- "value": "京ICP备2023000000号",
- "type": "string",
- "description": "ICP备案信息"
- },
- {
- "key": "启用手机短信注册",
- "value": "false",
- "type": "boolean",
- "description": "是否允许通过手机短信验证码注册"
- },
- {
- "key": "启用邮箱注册",
- "value": "false",
- "type": "boolean",
- "description": "是否允许通过邮箱验证链接注册"
- },
- {
- "key": "系统Logo",
- "value": "",
- "type": "image",
- "description": "系统Logo图片"
- }
- ]
-
- # 检查现有系统设置
- result = await db.execute(select(SystemSetting))
- existing_settings = result.scalars().all()
- existing_setting_keys = {setting.key: setting for setting in existing_settings}
-
- # 创建或更新系统设置
- created_count = 0
- updated_count = 0
-
- for setting_data in system_settings:
- setting_key = setting_data["key"]
- if setting_key not in existing_setting_keys:
- # 设置不存在,需要创建
- db.add(SystemSetting(**setting_data))
- created_count += 1
- else:
- # 设置存在,检查是否需要更新
- existing_setting = existing_setting_keys[setting_key]
- update_needed = False
-
- if existing_setting.value != setting_data["value"]:
- existing_setting.value = setting_data["value"]
- update_needed = True
- if existing_setting.type != setting_data["type"]:
- existing_setting.type = setting_data["type"]
- update_needed = True
- if existing_setting.description != setting_data.get("description"):
- existing_setting.description = setting_data.get("description")
- update_needed = True
-
- if update_needed:
- updated_count += 1
-
- if created_count > 0 or updated_count > 0:
- await db.commit()
- print(f"创建了 {created_count} 个系统设置,更新了 {updated_count} 个系统设置")
- else:
- print("系统设置数据已存在且无需更新,跳过初始化")
-
- print("系统设置数据初始化/更新完成")
-
-
-async def init_all_data(db: AsyncSession):
- """初始化所有数据"""
- print("开始初始化数据库数据...")
- await init_roles(db)
- await init_permissions(db)
- await init_role_permissions(db)
- await init_admin_user(db)
- await init_test_user(db)
- await init_system_settings(db)
- print("数据库数据初始化完成")
+from sqlalchemy.ext.asyncio import AsyncSession
+from sqlalchemy import select, text
+from models import Role, User, Permission, SystemSetting
+from utils.password import get_password_hash
+
+
+async def init_permissions(db: AsyncSession):
+ """初始化权限数据(树形结构)"""
+ # 定义需要初始化的权限(树形结构)
+ # parentid: 0 = 顶级模块, >0 = 子权限
+ permission_tree = [
+ # ========================================
+ # 顶级模块 (DB ID: 1,2,3)
+ # ========================================
+ {"name": "权限管理", "code": "perm", "parentid": 0, "route": "/admin/permissions", "description": "权限管理模块", "sort": 1},
+ {"name": "系统设置", "code": "system", "parentid": 0, "route": "/admin/settings", "description": "系统设置模块", "sort": 2},
+ {"name": "日志管理", "code": "log", "parentid": 0, "route": "/admin/logs", "description": "日志管理模块", "sort": 3},
+
+ # ========================================
+ # 权限管理 -> 二级子模块 (parentid=1, DB ID: 4,5,6)
+ # ========================================
+ {"name": "用户管理", "code": "perm:user", "parentid": 1, "route": "/admin/users", "description": "用户管理子模块", "sort": 1},
+ {"name": "角色管理", "code": "perm:role", "parentid": 1, "route": "/admin/roles", "description": "角色管理子模块", "sort": 2},
+ {"name": "菜单管理", "code": "perm:menu", "parentid": 1, "route": "/admin/menu", "description": "菜单管理子模块", "sort": 3},
+
+ # ========================================
+ # 用户管理操作 (parentid=4, DB ID: 7-14)
+ # ========================================
+ {"name": "查看列表", "code": "perm:user:list", "parentid": 4, "description": "查看用户列表", "sort": 1},
+ {"name": "查看详情", "code": "perm:user:detail", "parentid": 4, "description": "查看用户详情", "sort": 2},
+ {"name": "搜索", "code": "perm:user:search", "parentid": 4, "description": "搜索用户", "sort": 3},
+ {"name": "创建", "code": "perm:user:create", "parentid": 4, "description": "创建新用户", "sort": 4},
+ {"name": "编辑", "code": "perm:user:edit", "parentid": 4, "description": "编辑用户信息", "sort": 5},
+ {"name": "删除", "code": "perm:user:delete", "parentid": 4, "description": "删除用户", "sort": 6},
+ {"name": "导出", "code": "perm:user:export", "parentid": 4, "description": "导出用户数据", "sort": 7},
+ {"name": "批量操作", "code": "perm:user:batch", "parentid": 4, "description": "批量操作用户", "sort": 8},
+
+ # ========================================
+ # 角色管理操作 (parentid=5, DB ID: 15-22)
+ # ========================================
+ {"name": "查看列表", "code": "perm:role:list", "parentid": 5, "description": "查看角色列表", "sort": 1},
+ {"name": "查看详情", "code": "perm:role:detail", "parentid": 5, "description": "查看角色详情", "sort": 2},
+ {"name": "搜索", "code": "perm:role:search", "parentid": 5, "description": "搜索角色", "sort": 3},
+ {"name": "创建", "code": "perm:role:create", "parentid": 5, "description": "创建新角色", "sort": 4},
+ {"name": "编辑", "code": "perm:role:edit", "parentid": 5, "description": "编辑角色信息", "sort": 5},
+ {"name": "删除", "code": "perm:role:delete", "parentid": 5, "description": "删除角色", "sort": 6},
+ {"name": "导出", "code": "perm:role:export", "parentid": 5, "description": "导出角色数据", "sort": 7},
+ {"name": "分配权限", "code": "perm:role:assign", "parentid": 5, "description": "为角色分配权限", "sort": 8},
+
+ # ========================================
+ # 菜单管理操作 (parentid=6, DB ID: 23-29)
+ # ========================================
+ {"name": "查看列表", "code": "perm:menu:list", "parentid": 6, "description": "查看菜单列表", "sort": 1},
+ {"name": "查看详情", "code": "perm:menu:detail", "parentid": 6, "description": "查看菜单详情", "sort": 2},
+ {"name": "搜索", "code": "perm:menu:search", "parentid": 6, "description": "搜索菜单", "sort": 3},
+ {"name": "创建", "code": "perm:menu:create", "parentid": 6, "description": "创建新菜单", "sort": 4},
+ {"name": "编辑", "code": "perm:menu:edit", "parentid": 6, "description": "编辑菜单信息", "sort": 5},
+ {"name": "删除", "code": "perm:menu:delete", "parentid": 6, "description": "删除菜单", "sort": 6},
+ {"name": "导出", "code": "perm:menu:export", "parentid": 6, "description": "导出菜单数据", "sort": 7},
+
+ # ========================================
+ # 系统设置 -> 子权限 (parentid=2, DB ID: 30)
+ # ========================================
+ {"name": "系统设置", "code": "system:view", "parentid": 2, "description": "查看和修改系统设置", "sort": 1},
+
+ # ========================================
+ # 日志管理 -> 子模块 (parentid=3, DB ID: 31,32,37)
+ # ========================================
+ {"name": "登录日志", "code": "log:login", "parentid": 3, "route": "/admin/login-logs", "description": "登录日志查看", "sort": 1},
+ {"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)
+ # ========================================
+ {"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)
+ # ========================================
+ {"name": "查看列表", "code": "log:operation:list", "parentid": 32, "description": "查看操作日志列表", "sort": 1},
+ {"name": "搜索", "code": "log:operation:search", "parentid": 32, "description": "搜索操作日志", "sort": 2},
+ ]
+
+ # 检查现有权限
+ result = await db.execute(select(Permission))
+ existing_permissions = result.scalars().all()
+ existing_permission_codes = {perm.code: perm for perm in existing_permissions}
+
+ # 创建或更新权限
+ created_count = 0
+ updated_count = 0
+
+ for perm_data in permission_tree:
+ perm_code = perm_data["code"]
+ if perm_code not in existing_permission_codes:
+ # 权限不存在,需要创建
+ db.add(Permission(**perm_data))
+ created_count += 1
+ else:
+ # 权限存在,检查是否需要更新
+ existing_perm = existing_permission_codes[perm_code]
+ update_needed = False
+
+ if existing_perm.name != perm_data["name"]:
+ existing_perm.name = perm_data["name"]
+ update_needed = True
+ if existing_perm.parentid != perm_data["parentid"]:
+ existing_perm.parentid = perm_data["parentid"]
+ update_needed = True
+ if existing_perm.route != perm_data.get("route"):
+ existing_perm.route = perm_data.get("route")
+ update_needed = True
+ if existing_perm.description != perm_data["description"]:
+ existing_perm.description = perm_data["description"]
+ update_needed = True
+ if existing_perm.sort != perm_data.get("sort", 0):
+ existing_perm.sort = perm_data.get("sort", 0)
+ update_needed = True
+
+ if update_needed:
+ updated_count += 1
+
+ if created_count > 0 or updated_count > 0:
+ await db.commit()
+ print(f"创建了 {created_count} 个权限,更新了 {updated_count} 个权限")
+ else:
+ print("权限数据已存在且无需更新,跳过初始化")
+
+ print("权限数据初始化/更新完成")
+
+
+async def init_role_permissions(db: AsyncSession):
+ """初始化角色权限分配"""
+ from sqlalchemy.orm import selectinload
+
+ # 获取管理员角色(使用预加载避免懒加载问题)
+ result = await db.execute(
+ select(Role)
+ .filter(Role.name == "管理员")
+ .options(selectinload(Role.permissions))
+ )
+ admin_role = result.scalar_one_or_none()
+ if not admin_role:
+ print("管理员角色不存在,请先初始化角色数据")
+ return
+
+ # 获取所有权限
+ result = await db.execute(select(Permission))
+ all_permissions = result.scalars().all()
+
+ if not all_permissions:
+ print("没有找到任何权限,请先初始化权限数据")
+ return
+
+ # 获取管理员角色当前的权限(使用预加载的数据)
+ admin_permission_codes = set()
+ if admin_role.permissions:
+ admin_permission_codes = {perm.code for perm in admin_role.permissions}
+
+ # 检查是否需要添加权限
+ permissions_to_add = []
+ for perm in all_permissions:
+ if perm.code not in admin_permission_codes:
+ permissions_to_add.append(perm)
+
+ if permissions_to_add:
+ # 直接设置权限(替换而非追加,确保权限完整)
+ admin_role.permissions = all_permissions
+ await db.commit()
+ print(f"为管理员角色分配了 {len(permissions_to_add)} 个权限")
+ else:
+ print("管理员角色权限已完整,无需更新")
+
+ print("角色权限初始化/更新完成")
+
+
+async def init_roles(db: AsyncSession):
+ """初始化角色数据"""
+ # 定义需要初始化的角色
+ required_roles = [
+ {"name": "管理员", "creator": "system"},
+ {"name": "普通用户", "creator": "system"},
+ {"name": "访客", "creator": "system"},
+ {"name": "会员", "creator": "system"},
+ ]
+
+ # 检查现有角色
+ result = await db.execute(select(Role))
+ existing_roles = result.scalars().all()
+ existing_role_names = {role.name for role in existing_roles}
+
+ # 检查需要创建或更新的角色
+ roles_to_add = []
+ roles_to_update = []
+
+ for role_data in required_roles:
+ role_name = role_data["name"]
+ if role_name not in existing_role_names:
+ # 角色不存在,需要创建
+ roles_to_add.append(Role(**role_data))
+ else:
+ # 角色存在,检查是否需要更新
+ existing_role = next(
+ role for role in existing_roles if role.name == role_name
+ )
+ if existing_role.creator != role_data["creator"]:
+ # 需要更新
+ existing_role.creator = role_data["creator"]
+ roles_to_update.append(existing_role)
+
+ # 执行创建和更新操作
+ if roles_to_add:
+ for role in roles_to_add:
+ db.add(role)
+ await db.commit()
+ print(f"创建了 {len(roles_to_add)} 个角色")
+
+ if roles_to_update:
+ await db.commit()
+ print(f"更新了 {len(roles_to_update)} 个角色")
+
+ if not roles_to_add and not roles_to_update:
+ print("角色数据已存在且无需更新,跳过初始化")
+
+ print("角色数据初始化/更新完成")
+
+
+async def init_admin_user(db: AsyncSession):
+ """初始化管理员用户"""
+ # 获取管理员角色
+ result = await db.execute(select(Role).filter(Role.name == "管理员"))
+ admin_role = result.scalar_one_or_none()
+ if not admin_role:
+ print("管理员角色不存在,请先初始化角色数据")
+ return
+
+ # 定义需要的管理员用户数据
+ admin_data = {
+ "username": "admin",
+ "nickname": "系统管理员",
+ "email": "admin@example.com",
+ "phone": "13800138000",
+ "password_hash": get_password_hash("admin"),
+ "role_id": admin_role.id,
+ "avatar": "",
+ "wx_openid": "",
+ }
+
+ # 检查管理员用户是否存在
+ result = await db.execute(select(User).filter(User.username == "admin"))
+ existing_admin = result.scalar_one_or_none()
+
+ if not existing_admin:
+ # 管理员不存在,创建
+ admin_user = User(**admin_data)
+ db.add(admin_user)
+ await db.commit()
+ print("创建了管理员用户")
+ else:
+ # 管理员存在,检查是否需要更新
+ update_needed = False
+
+ # 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置)
+ if existing_admin.nickname != admin_data["nickname"]:
+ existing_admin.nickname = admin_data["nickname"]
+ update_needed = True
+ if existing_admin.email != admin_data["email"]:
+ existing_admin.email = admin_data["email"]
+ update_needed = True
+ if existing_admin.phone != admin_data["phone"]:
+ existing_admin.phone = admin_data["phone"]
+ update_needed = True
+ if existing_admin.role_id != admin_data["role_id"]:
+ existing_admin.role_id = admin_data["role_id"]
+ update_needed = True
+ if existing_admin.avatar != admin_data["avatar"]:
+ existing_admin.avatar = admin_data["avatar"]
+ update_needed = True
+ if existing_admin.wx_openid != admin_data["wx_openid"]:
+ existing_admin.wx_openid = admin_data["wx_openid"]
+ update_needed = True
+
+ if update_needed:
+ await db.commit()
+ print("更新了管理员用户")
+ else:
+ print("管理员用户已存在且无需更新,跳过初始化")
+
+ print("管理员用户初始化/更新完成")
+
+
+async def init_test_user(db: AsyncSession):
+ """初始化测试用户"""
+ # 获取普通用户角色
+ result = await db.execute(select(Role).filter(Role.name == "普通用户"))
+ user_role = result.scalar_one_or_none()
+ if not user_role:
+ print("普通用户角色不存在,请先初始化角色数据")
+ return
+
+ # 定义需要的测试用户数据
+ test_data = {
+ "username": "test",
+ "nickname": "测试用户",
+ "email": "test@example.com",
+ "phone": "13800138001",
+ "password_hash": get_password_hash("test"),
+ "role_id": user_role.id,
+ "avatar": "",
+ "wx_openid": "",
+ }
+
+ # 检查测试用户是否存在
+ result = await db.execute(select(User).filter(User.username == "test"))
+ existing_test_user = result.scalar_one_or_none()
+
+ if not existing_test_user:
+ # 测试用户不存在,创建
+ test_user = User(**test_data)
+ db.add(test_user)
+ await db.commit()
+ print("创建了测试用户")
+ else:
+ # 测试用户存在,检查是否需要更新
+ update_needed = False
+
+ # 检查字段是否需要更新(不包括密码,因为密码只在首次创建时设置)
+ if existing_test_user.nickname != test_data["nickname"]:
+ existing_test_user.nickname = test_data["nickname"]
+ update_needed = True
+ if existing_test_user.email != test_data["email"]:
+ existing_test_user.email = test_data["email"]
+ update_needed = True
+ if existing_test_user.phone != test_data["phone"]:
+ existing_test_user.phone = test_data["phone"]
+ update_needed = True
+ if existing_test_user.role_id != test_data["role_id"]:
+ existing_test_user.role_id = test_data["role_id"]
+ update_needed = True
+ if existing_test_user.avatar != test_data["avatar"]:
+ existing_test_user.avatar = test_data["avatar"]
+ update_needed = True
+ if existing_test_user.wx_openid != test_data["wx_openid"]:
+ existing_test_user.wx_openid = test_data["wx_openid"]
+ update_needed = True
+
+ if update_needed:
+ await db.commit()
+ print("更新了测试用户")
+ else:
+ print("测试用户已存在且无需更新,跳过初始化")
+
+ print("测试用户初始化/更新完成")
+
+
+async def init_system_settings(db: AsyncSession):
+ """初始化系统设置数据"""
+ # 定义需要初始化的系统设置
+ system_settings = [
+ {
+ "key": "系统标题",
+ "value": "图书管理系统",
+ "type": "string",
+ "description": "系统显示标题"
+ },
+ {
+ "key": "页脚信息",
+ "value": "Copyright © 2026 图书管理系统 版权所有 | 技术支持:强蕊科技",
+ "type": "string",
+ "description": "页脚信息"
+ },
+ {
+ "key": "ICP信息",
+ "value": "京ICP备2023000000号",
+ "type": "string",
+ "description": "ICP备案信息"
+ },
+ {
+ "key": "启用手机短信注册",
+ "value": "false",
+ "type": "boolean",
+ "description": "是否允许通过手机短信验证码注册"
+ },
+ {
+ "key": "启用邮箱注册",
+ "value": "false",
+ "type": "boolean",
+ "description": "是否允许通过邮箱验证链接注册"
+ },
+ {
+ "key": "系统Logo",
+ "value": "",
+ "type": "image",
+ "description": "系统Logo图片"
+ }
+ ]
+
+ # 检查现有系统设置
+ result = await db.execute(select(SystemSetting))
+ existing_settings = result.scalars().all()
+ existing_setting_keys = {setting.key: setting for setting in existing_settings}
+
+ # 创建或更新系统设置
+ created_count = 0
+ updated_count = 0
+
+ for setting_data in system_settings:
+ setting_key = setting_data["key"]
+ if setting_key not in existing_setting_keys:
+ # 设置不存在,需要创建
+ db.add(SystemSetting(**setting_data))
+ created_count += 1
+ else:
+ # 设置存在,检查是否需要更新
+ existing_setting = existing_setting_keys[setting_key]
+ update_needed = False
+
+ if existing_setting.value != setting_data["value"]:
+ existing_setting.value = setting_data["value"]
+ update_needed = True
+ if existing_setting.type != setting_data["type"]:
+ existing_setting.type = setting_data["type"]
+ update_needed = True
+ if existing_setting.description != setting_data.get("description"):
+ existing_setting.description = setting_data.get("description")
+ update_needed = True
+
+ if update_needed:
+ updated_count += 1
+
+ if created_count > 0 or updated_count > 0:
+ await db.commit()
+ print(f"创建了 {created_count} 个系统设置,更新了 {updated_count} 个系统设置")
+ else:
+ print("系统设置数据已存在且无需更新,跳过初始化")
+
+ print("系统设置数据初始化/更新完成")
+
+
+async def init_all_data(db: AsyncSession):
+ """初始化所有数据"""
+ print("开始初始化数据库数据...")
+ await init_roles(db)
+ await init_permissions(db)
+ await init_role_permissions(db)
+ await init_admin_user(db)
+ await init_test_user(db)
+ await init_system_settings(db)
+ print("数据库数据初始化完成")
diff --git a/backend/auth/main.py b/backend/auth/main.py
index 4180034..e6f03d7 100644
--- a/backend/auth/main.py
+++ b/backend/auth/main.py
@@ -1,172 +1,182 @@
-from fastapi import FastAPI, HTTPException
-from fastapi.middleware.cors import CORSMiddleware
-from fastapi.staticfiles import StaticFiles
-from fastapi.responses import HTMLResponse, FileResponse
-from fastapi.exceptions import RequestValidationError
-from contextlib import asynccontextmanager
-from apps.urls import api_router
-from apps.settings.urls import UPLOAD_DIR
-from database import engine, get_db
-from models import Base
-from init_data import init_all_data
-from pathlib import Path
-from starlette.requests import Request
-from starlette.responses import JSONResponse
-
-
-@asynccontextmanager
-async def lifespan(app: FastAPI):
- # 启动时初始化数据库
- async with engine.begin() as conn:
- # 创建所有表
- await conn.run_sync(Base.metadata.create_all)
-
- # 初始化数据
- async for db in get_db():
- await init_all_data(db)
- break
-
- yield
- # 关闭时的清理工作
- await engine.dispose()
-
-
-# 实例化FastAPI
-app = FastAPI(
- title="图书系统授权服务API",
- description="""图书系统授权服务API是一套用于用户认证和授权的服务,提供用户注册、登录、权限校验等功能。
- 同时实现了基于角色的访问控制(RBAC),支持自定义角色和权限。还增加了日志记录功能,方便监控和调试。""",
- version="1.0.0",
- lifespan=lifespan,
- docs_url=None,
- redoc_url="/redoc",
-)
-
-# 开发环境前后端分离时的跨域(生产环境请收窄 allow_origins)
-app.add_middleware(
- CORSMiddleware,
- allow_origins=[
- "http://localhost:5173",
- "http://127.0.0.1:5173",
- "http://localhost:3000",
- "http://127.0.0.1:3000",
- ],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
- expose_headers=["X-Captcha-ID"],
-)
-
-# 挂载swagger-ui静态文件目录
-swagger_ui_path = Path(__file__).parent.parent / "swagger-ui"
-app.mount("/static", StaticFiles(directory=str(swagger_ui_path)), name="static")
-
-# 上传图片通过自定义端点提供(和 settings/urls.py 中的 UPLOAD_DIR 保持一致)
-@app.get("/uploads/{filename}")
-async def serve_upload(filename: str):
- """提供上传的图片文件"""
- file_path = UPLOAD_DIR / filename
- if not file_path.exists():
- raise HTTPException(status_code=404, detail="文件不存在")
- return FileResponse(str(file_path))
-
-
-# 自定义Swagger UI页面
-@app.get("/docs", include_in_schema=False)
-async def custom_swagger_ui_html():
- html_content = """
-
-
-
-
-
- Book System API - Swagger UI
-
-
-
-
-
-
-
-
-
-
- """
- return HTMLResponse(content=html_content)
-
-
-# 包含路由
-app.include_router(api_router)
-
-
-# 声明装饰器方法和路径
-@app.get("/")
-# 声明装饰器函数
-async def home():
- return {"message": "Hello World!!!"}
-
-
-# 自定义验证错误处理器
-@app.exception_handler(RequestValidationError)
-async def validation_exception_handler(request: Request, exc: RequestValidationError):
- errors = []
- for err in exc.errors():
- field = err.get("loc", ["unknown"])[-1]
- msg = err.get("msg", "验证失败")
-
- if "min_length" in msg.lower():
- if field == "username":
- msg = "用户名长度至少为3个字符"
- elif field == "password":
- msg = "密码长度至少为6个字符"
- elif "max_length" in msg.lower():
- if field == "username":
- msg = "用户名长度不能超过50个字符"
- elif field == "password":
- msg = "密码长度不能超过50个字符"
-
- errors.append({"field": field, "message": msg})
-
- return JSONResponse(
- status_code=422,
- content={"code": 422, "message": "验证失败", "errors": errors}
- )
-
-
-# 如果使用命令行启动,使用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=["_"])
+from fastapi import FastAPI, HTTPException
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.staticfiles import StaticFiles
+from fastapi.responses import HTMLResponse, FileResponse
+from fastapi.exceptions import RequestValidationError
+from contextlib import asynccontextmanager
+from apps.urls import api_router
+from apps.settings.urls import UPLOAD_DIR
+from database import engine, get_db
+from models import Base
+from init_data import init_all_data
+from pathlib import Path
+from starlette.requests import Request
+from starlette.responses import JSONResponse
+from sqlalchemy import text
+
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ # 启动时初始化数据库
+ async with engine.begin() as conn:
+ # 创建所有表
+ await conn.run_sync(Base.metadata.create_all)
+ # 为已有表添加 sort 列(兼容 PostgreSQL 和 SQLite)
+ try:
+ await conn.execute(text("ALTER TABLE permission ADD COLUMN IF NOT EXISTS sort INTEGER DEFAULT 0"))
+ except Exception:
+ # SQLite 不支持 IF NOT EXISTS
+ try:
+ await conn.execute(text("ALTER TABLE permission ADD COLUMN sort INTEGER DEFAULT 0"))
+ except Exception:
+ pass # 列已存在则忽略
+
+ # 初始化数据
+ async for db in get_db():
+ await init_all_data(db)
+ break
+
+ yield
+ # 关闭时的清理工作
+ await engine.dispose()
+
+
+# 实例化FastAPI
+app = FastAPI(
+ title="图书系统授权服务API",
+ description="""图书系统授权服务API是一套用于用户认证和授权的服务,提供用户注册、登录、权限校验等功能。
+ 同时实现了基于角色的访问控制(RBAC),支持自定义角色和权限。还增加了日志记录功能,方便监控和调试。""",
+ version="1.0.0",
+ lifespan=lifespan,
+ docs_url=None,
+ redoc_url="/redoc",
+)
+
+# 开发环境前后端分离时的跨域(生产环境请收窄 allow_origins)
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=[
+ "http://localhost:5173",
+ "http://127.0.0.1:5173",
+ "http://localhost:3000",
+ "http://127.0.0.1:3000",
+ ],
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+ expose_headers=["X-Captcha-ID"],
+)
+
+# 挂载swagger-ui静态文件目录
+swagger_ui_path = Path(__file__).parent.parent / "swagger-ui"
+app.mount("/static", StaticFiles(directory=str(swagger_ui_path)), name="static")
+
+# 上传图片通过自定义端点提供(和 settings/urls.py 中的 UPLOAD_DIR 保持一致)
+@app.get("/uploads/{filename}")
+async def serve_upload(filename: str):
+ """提供上传的图片文件"""
+ file_path = UPLOAD_DIR / filename
+ if not file_path.exists():
+ raise HTTPException(status_code=404, detail="文件不存在")
+ return FileResponse(str(file_path))
+
+
+# 自定义Swagger UI页面
+@app.get("/docs", include_in_schema=False)
+async def custom_swagger_ui_html():
+ html_content = """
+
+
+
+
+
+ Book System API - Swagger UI
+
+
+
+
+
+
+
+
+
+
+ """
+ return HTMLResponse(content=html_content)
+
+
+# 包含路由
+app.include_router(api_router)
+
+
+# 声明装饰器方法和路径
+@app.get("/")
+# 声明装饰器函数
+async def home():
+ return {"message": "Hello World!!!"}
+
+
+# 自定义验证错误处理器
+@app.exception_handler(RequestValidationError)
+async def validation_exception_handler(request: Request, exc: RequestValidationError):
+ errors = []
+ for err in exc.errors():
+ field = err.get("loc", ["unknown"])[-1]
+ msg = err.get("msg", "验证失败")
+
+ if "min_length" in msg.lower():
+ if field == "username":
+ msg = "用户名长度至少为3个字符"
+ elif field == "password":
+ msg = "密码长度至少为6个字符"
+ elif "max_length" in msg.lower():
+ if field == "username":
+ msg = "用户名长度不能超过50个字符"
+ elif field == "password":
+ msg = "密码长度不能超过50个字符"
+
+ errors.append({"field": field, "message": msg})
+
+ return JSONResponse(
+ status_code=422,
+ content={"code": 422, "message": "验证失败", "errors": errors}
+ )
+
+
+# 如果使用命令行启动,使用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=["_"])
diff --git a/backend/auth/models.py b/backend/auth/models.py
index ccae4e4..60c5049 100644
--- a/backend/auth/models.py
+++ b/backend/auth/models.py
@@ -1,140 +1,141 @@
-from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, func, Table, Index, Text
-from sqlalchemy.orm import relationship
-from sqlalchemy.ext.declarative import declarative_base
-from datetime import datetime
-
-Base = declarative_base()
-
-
-class Role(Base):
- """角色数据库模型"""
-
- __tablename__ = "role"
- id = Column(Integer, primary_key=True, index=True, comment="角色ID")
- name = Column(String(50), nullable=False, unique=True, comment="角色名称")
- createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
- updatetime = Column(
- DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
- )
- creator = Column(String(50), nullable=True, comment="创建人")
-
- # 关联到User
- users = relationship("User", back_populates="role_rel")
- # 关联到Permission(多对多)
- permissions = relationship(
- "Permission",
- secondary="role_permission",
- back_populates="roles"
- )
-
-
-class User(Base):
- """用户数据库模型"""
-
- __tablename__ = "users"
- id = Column(Integer, primary_key=True, index=True)
- username = Column(String(50), unique=True, nullable=False, comment="登录名")
- nickname = Column(String(50), nullable=False, comment="昵称")
- email = Column(String(50), nullable=True, comment="电子邮箱")
- phone = Column(String(11), nullable=True, comment="手机号码")
- wx_openid = Column(String(100), nullable=True, comment="微信OpenID")
- password_hash = Column(String(128), nullable=False, comment="密码哈希值")
- avatar = Column(String(255), nullable=True, comment="头像路径")
- role_id = Column(Integer, ForeignKey("role.id"), default=2, comment="角色ID")
- createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
- updatetime = Column(
- DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
- )
- lastlogintime = Column(DateTime, comment="最后登录时间")
-
- # 关联到Role
- role_rel = relationship("Role", back_populates="users")
-
-
-class Permission(Base):
- """权限数据库模型"""
-
- __tablename__ = "permission"
- id = Column(Integer, primary_key=True, index=True, comment="权限ID")
- name = Column(String(100), nullable=False, comment="权限名称")
- code = Column(String(100), nullable=False, unique=True, comment="权限编码")
- parentid = Column(Integer, default=0, comment="父权限ID(0表示顶级模块)")
- route = Column(String(255), nullable=True, comment="前端路由路径")
- description = Column(String(255), nullable=True, comment="权限描述")
- createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
- updatetime = Column(
- DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
- )
-
- # 关联到Role(多对多)
- roles = relationship(
- "Role",
- secondary="role_permission",
- back_populates="permissions"
- )
-
-
-# 角色权限关联表(多对多)
-role_permission = Table(
- "role_permission",
- Base.metadata,
- 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):
- """系统设置数据库模型"""
-
- __tablename__ = "system_setting"
- id = Column(Integer, primary_key=True, index=True, comment="设置ID")
- key = Column(String(100), nullable=False, unique=True, comment="设置键名")
- value = Column(String(2000), nullable=False, comment="设置值")
- type = Column(String(20), default="string", comment="类型:string/image/select/boolean")
- options = Column(String(500), nullable=True, comment="下拉选项(用|分隔)")
- description = Column(String(255), nullable=True, comment="设置描述")
- createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
- updatetime = Column(
- DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
- )
-
-
-class LoginLog(Base):
- """登录日志"""
-
- __tablename__ = "login_log"
- id = Column(Integer, primary_key=True, index=True, comment="日志ID")
- username = Column(String(50), nullable=False, comment="用户名")
- login_time = Column(DateTime, default=datetime.utcnow, comment="登录时间")
- ip_address = Column(String(50), nullable=True, comment="IP地址")
- user_agent = Column(String(500), nullable=True, comment="User-Agent")
- 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_status", LoginLog.status)
-
-
-class OperationLog(Base):
- """操作日志"""
-
- __tablename__ = "operation_log"
- id = Column(Integer, primary_key=True, index=True, comment="日志ID")
- user_id = Column(Integer, nullable=False, comment="用户ID")
- username = Column(String(50), nullable=False, comment="用户名")
- action_type = Column(String(20), nullable=False, comment="操作类型: create/update/delete")
- target_type = Column(String(50), nullable=False, comment="目标类型: user/role/permission/setting")
- target_id = Column(Integer, nullable=True, comment="目标ID")
- target_name = Column(String(100), nullable=True, comment="目标名称")
- detail = Column(Text, nullable=True, comment="变更详情(JSON)")
- ip_address = Column(String(50), nullable=True, comment="IP地址")
- 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_action_type", OperationLog.action_type)
+from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, func, Table, Index, Text
+from sqlalchemy.orm import relationship
+from sqlalchemy.ext.declarative import declarative_base
+from datetime import datetime
+
+Base = declarative_base()
+
+
+class Role(Base):
+ """角色数据库模型"""
+
+ __tablename__ = "role"
+ id = Column(Integer, primary_key=True, index=True, comment="角色ID")
+ name = Column(String(50), nullable=False, unique=True, comment="角色名称")
+ createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
+ updatetime = Column(
+ DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
+ )
+ creator = Column(String(50), nullable=True, comment="创建人")
+
+ # 关联到User
+ users = relationship("User", back_populates="role_rel")
+ # 关联到Permission(多对多)
+ permissions = relationship(
+ "Permission",
+ secondary="role_permission",
+ back_populates="roles"
+ )
+
+
+class User(Base):
+ """用户数据库模型"""
+
+ __tablename__ = "users"
+ id = Column(Integer, primary_key=True, index=True)
+ username = Column(String(50), unique=True, nullable=False, comment="登录名")
+ nickname = Column(String(50), nullable=False, comment="昵称")
+ email = Column(String(50), nullable=True, comment="电子邮箱")
+ phone = Column(String(11), nullable=True, comment="手机号码")
+ wx_openid = Column(String(100), nullable=True, comment="微信OpenID")
+ password_hash = Column(String(128), nullable=False, comment="密码哈希值")
+ avatar = Column(String(255), nullable=True, comment="头像路径")
+ role_id = Column(Integer, ForeignKey("role.id"), default=2, comment="角色ID")
+ createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
+ updatetime = Column(
+ DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
+ )
+ lastlogintime = Column(DateTime, comment="最后登录时间")
+
+ # 关联到Role
+ role_rel = relationship("Role", back_populates="users")
+
+
+class Permission(Base):
+ """权限数据库模型"""
+
+ __tablename__ = "permission"
+ id = Column(Integer, primary_key=True, index=True, comment="权限ID")
+ name = Column(String(100), nullable=False, comment="权限名称")
+ code = Column(String(100), nullable=False, unique=True, comment="权限编码")
+ parentid = Column(Integer, default=0, comment="父权限ID(0表示顶级模块)")
+ route = Column(String(255), nullable=True, comment="前端路由路径")
+ description = Column(String(255), nullable=True, comment="权限描述")
+ sort = Column(Integer, default=0, comment="排序号(升序排列)")
+ createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
+ updatetime = Column(
+ DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
+ )
+
+ # 关联到Role(多对多)
+ roles = relationship(
+ "Role",
+ secondary="role_permission",
+ back_populates="permissions"
+ )
+
+
+# 角色权限关联表(多对多)
+role_permission = Table(
+ "role_permission",
+ Base.metadata,
+ 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):
+ """系统设置数据库模型"""
+
+ __tablename__ = "system_setting"
+ id = Column(Integer, primary_key=True, index=True, comment="设置ID")
+ key = Column(String(100), nullable=False, unique=True, comment="设置键名")
+ value = Column(String(2000), nullable=False, comment="设置值")
+ type = Column(String(20), default="string", comment="类型:string/image/select/boolean")
+ options = Column(String(500), nullable=True, comment="下拉选项(用|分隔)")
+ description = Column(String(255), nullable=True, comment="设置描述")
+ createtime = Column(DateTime, default=datetime.utcnow, comment="创建时间")
+ updatetime = Column(
+ DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, comment="更新时间"
+ )
+
+
+class LoginLog(Base):
+ """登录日志"""
+
+ __tablename__ = "login_log"
+ id = Column(Integer, primary_key=True, index=True, comment="日志ID")
+ username = Column(String(50), nullable=False, comment="用户名")
+ login_time = Column(DateTime, default=datetime.utcnow, comment="登录时间")
+ ip_address = Column(String(50), nullable=True, comment="IP地址")
+ user_agent = Column(String(500), nullable=True, comment="User-Agent")
+ 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_status", LoginLog.status)
+
+
+class OperationLog(Base):
+ """操作日志"""
+
+ __tablename__ = "operation_log"
+ id = Column(Integer, primary_key=True, index=True, comment="日志ID")
+ user_id = Column(Integer, nullable=False, comment="用户ID")
+ username = Column(String(50), nullable=False, comment="用户名")
+ action_type = Column(String(20), nullable=False, comment="操作类型: create/update/delete")
+ target_type = Column(String(50), nullable=False, comment="目标类型: user/role/permission/setting")
+ target_id = Column(Integer, nullable=True, comment="目标ID")
+ target_name = Column(String(100), nullable=True, comment="目标名称")
+ detail = Column(Text, nullable=True, comment="变更详情(JSON)")
+ ip_address = Column(String(50), nullable=True, comment="IP地址")
+ 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_action_type", OperationLog.action_type)
Index("ix_operation_log_target_type", OperationLog.target_type)
\ No newline at end of file
diff --git a/backend/auth/schemas.py b/backend/auth/schemas.py
index f94a2f7..a6ca708 100644
--- a/backend/auth/schemas.py
+++ b/backend/auth/schemas.py
@@ -1,335 +1,337 @@
-from pydantic import BaseModel, Field
-from typing import Optional
-from datetime import datetime
-
-
-class UserBase(BaseModel):
- """用户基础模型"""
-
- username: str = Field(
- ...,
- min_length=3,
- max_length=50,
- description="登录名",
- json_schema_extra={
- "error_messages": {
- "min_length": "用户名长度至少为3个字符",
- "max_length": "用户名长度不能超过50个字符"
- }
- }
- )
- nickname: str = Field(..., max_length=50, description="昵称")
- email: Optional[str] = Field(None, max_length=50, description="电子邮箱")
- phone: Optional[str] = Field(None, max_length=11, description="手机号码")
- wx_openid: Optional[str] = Field(None, max_length=100, description="微信OpenID")
- avatar: Optional[str] = Field(None, max_length=255, description="头像路径")
- role_id: Optional[int] = Field(2, description="角色ID")
-
- model_config = {"extra": "ignore"}
-
-
-class UserCreateRequest(UserBase):
- """用户创建请求模型"""
-
- password: str = Field(
- ...,
- min_length=6,
- max_length=50,
- description="密码",
- json_schema_extra={
- "error_messages": {
- "min_length": "密码长度至少为6个字符",
- "max_length": "密码长度不能超过50个字符"
- }
- }
- )
-
-
-class UserCreate(UserBase):
- """用户创建模型"""
-
- password_hash: str = Field(
- ..., min_length=6, max_length=128, description="密码哈希值"
- )
-
-
-class UserUpdate(BaseModel):
- """用户更新模型"""
-
- username: str = Field(..., min_length=3, max_length=50, description="登录名")
- nickname: str = Field(..., max_length=50, description="昵称")
- email: Optional[str] = Field(None, max_length=50, description="电子邮箱")
- phone: Optional[str] = Field(None, max_length=11, description="手机号码")
- wx_openid: Optional[str] = Field(None, max_length=100, description="微信OpenID")
- avatar: Optional[str] = Field(None, max_length=255, description="头像路径")
- role_id: Optional[int] = Field(2, description="角色ID")
- password: Optional[str] = Field(None, max_length=128, description="密码")
-
-
-class UserLogin(BaseModel):
- """用户登录模型"""
-
- username: str = Field(..., description="用户名/邮箱/手机号")
- password: str = Field(..., description="密码")
- captcha_id: str = Field(..., description="验证码ID")
- captcha_code: str = Field(..., description="验证码")
-
-
-class UserResponse(BaseModel):
- """用户响应模型"""
-
- id: int = Field(..., description="用户ID")
- username: str = Field(..., description="登录名")
- nickname: str = Field(..., description="昵称")
- email: Optional[str] = Field(None, description="电子邮箱")
- phone: Optional[str] = Field(None, description="手机号码")
- wx_openid: Optional[str] = Field(None, description="微信OpenID")
- avatar: Optional[str] = Field(None, description="头像路径")
- role_id: int = Field(..., description="角色ID")
- createtime: datetime = Field(..., description="创建时间")
- updatetime: datetime = Field(..., description="更新时间")
- lastlogintime: Optional[datetime] = Field(None, description="最后登录时间")
-
- class Config:
- from_attributes = True
-
-
-class Token(BaseModel):
- """令牌模型"""
-
- access_token: str = Field(..., description="访问令牌")
- token_type: str = Field(..., description="令牌类型")
-
-
-class TokenData(BaseModel):
- """令牌数据模型"""
-
- user_id: Optional[int] = None
- email: Optional[str] = None
-
-
-class RoleBase(BaseModel):
- """角色基础模型"""
-
- name: str = Field(..., max_length=50, description="角色名称")
-
-
-class RoleCreate(RoleBase):
- """角色创建模型"""
-
- creator: Optional[str] = Field(None, max_length=50, description="创建人")
-
-
-class RoleResponse(RoleBase):
- """角色响应模型"""
-
- id: int = Field(..., description="角色ID")
- createtime: datetime = Field(..., description="创建时间")
- updatetime: datetime = Field(..., description="更新时间")
- creator: Optional[str] = Field(None, description="创建人")
-
- class Config:
- from_attributes = True
-
-
-# 权限相关模型
-class PermissionBase(BaseModel):
- """权限基础模型"""
-
- name: str = Field(..., max_length=100, description="权限名称")
- code: str = Field(..., max_length=100, description="权限编码")
- parentid: int = Field(0, description="父权限ID(0表示顶级模块)")
- route: Optional[str] = Field(None, max_length=255, description="前端路由路径")
- description: Optional[str] = Field(None, max_length=255, description="权限描述")
-
-
-class PermissionCreate(PermissionBase):
- """权限创建模型"""
- pass
-
-
-class PermissionResponse(PermissionBase):
- """权限响应模型"""
-
- id: int = Field(..., description="权限ID")
- createtime: datetime = Field(..., description="创建时间")
- updatetime: datetime = Field(..., description="更新时间")
-
- class Config:
- from_attributes = True
-
-
-class PermissionTreeResponse(BaseModel):
- """权限树响应模型"""
-
- id: int = Field(..., description="权限ID")
- name: str = Field(..., description="权限名称")
- code: str = Field(..., description="权限编码")
- parentid: int = Field(..., description="父权限ID")
- route: Optional[str] = Field(None, description="前端路由路径")
- createtime: Optional[datetime] = Field(None, description="创建时间")
- updatetime: Optional[datetime] = Field(None, description="更新时间")
- description: Optional[str] = Field(None, description="权限描述")
- children: list = Field([], description="子权限列表")
-
-
-# 角色权限关联相关模型
-class RolePermissionAssign(BaseModel):
- """角色权限分配模型"""
-
- role_id: int = Field(..., description="角色ID")
- permission_ids: list[int] = Field(..., description="权限ID列表")
-
-
-class RolePermissionResponse(BaseModel):
- """角色权限响应模型"""
-
- role_id: int = Field(..., description="角色ID")
- role_name: str = Field(..., description="角色名称")
- permissions: list[PermissionResponse] = Field(..., description="权限列表")
- menu: list = Field([], description="菜单树形结构")
-
-
-# 系统设置相关模型
-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")
- options: Optional[str] = Field(None, max_length=500, description="下拉选项(用|分隔)")
- description: Optional[str] = Field(None, max_length=255, description="设置描述")
-
- model_config = {"extra": "ignore"}
-
-
-class SystemSettingCreate(SystemSettingBase):
- """系统设置创建模型"""
- pass
-
-
-class SystemSettingUpdate(SystemSettingBase):
- """系统设置更新模型"""
- key: Optional[str] = Field(None, max_length=100, description="设置键名")
-
-
-class SystemSettingResponse(BaseModel):
- """系统设置响应模型"""
-
- id: int = Field(..., description="设置ID")
- key: str = Field(..., description="设置键名")
- value: str = Field(..., description="设置值")
- type: str = Field(..., description="类型")
- options: Optional[str] = Field(None, description="下拉选项")
- description: Optional[str] = Field(None, description="设置描述")
- createtime: datetime = Field(..., description="创建时间")
- updatetime: datetime = Field(..., description="更新时间")
-
- class Config:
- from_attributes = True
-
-
-class LoginLogResponse(BaseModel):
- """登录日志响应模型"""
-
- id: int = Field(..., description="日志ID")
- username: str = Field(..., description="用户名")
- login_time: datetime = Field(..., description="登录时间")
- ip_address: Optional[str] = Field(None, description="IP地址")
- user_agent: Optional[str] = Field(None, description="User-Agent")
- status: str = Field(..., description="状态: success/failure")
- failure_reason: Optional[str] = Field(None, description="失败原因")
-
- class Config:
- from_attributes = True
-
-
-class LoginLogSearch(BaseModel):
- """登录日志搜索模型"""
-
- username: Optional[str] = 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):
- """登录日志清理请求模型"""
-
- before_date: datetime = Field(..., description="清理此日期之前的日志")
-
-
-class OperationLogResponse(BaseModel):
- """操作日志响应模型"""
-
- id: int = Field(..., description="日志ID")
- user_id: int = Field(..., description="用户ID")
- username: str = Field(..., description="用户名")
- action_type: str = Field(..., description="操作类型: create/update/delete")
- target_type: str = Field(..., description="目标类型: user/role/permission/setting")
- target_id: Optional[int] = Field(None, description="目标ID")
- target_name: Optional[str] = Field(None, description="目标名称")
- detail: Optional[str] = Field(None, description="变更详情(JSON)")
- ip_address: Optional[str] = Field(None, description="IP地址")
- user_agent: Optional[str] = Field(None, description="User-Agent")
- createtime: datetime = Field(..., description="创建时间")
-
- class Config:
- from_attributes = True
-
-
-class OperationLogSearch(BaseModel):
- """操作日志搜索模型"""
-
- username: Optional[str] = Field(None, description="用户名(模糊搜索)")
- action_type: Optional[str] = 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 PhoneSendCodeResponse(BaseModel):
- """发送短信验证码响应"""
- message: str = "验证码已发送"
- expire_seconds: int = 300
-
-
-class PhoneRegisterRequest(BaseModel):
- """手机号注册请求"""
- username: str = Field(..., min_length=3, max_length=50, description="用户名")
- phone: str = Field(..., pattern=r"^\d{11}$", 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="用户名")
- 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 = "验证邮件已发送,请查收并点击链接完成注册"
- expire_minutes: int = 30
-
-
-class RegisterSuccessResponse(BaseModel):
- """注册成功响应"""
- id: int
- username: str
- nickname: str
+from pydantic import BaseModel, Field
+from typing import Optional
+from datetime import datetime
+
+
+class UserBase(BaseModel):
+ """用户基础模型"""
+
+ username: str = Field(
+ ...,
+ min_length=3,
+ max_length=50,
+ description="登录名",
+ json_schema_extra={
+ "error_messages": {
+ "min_length": "用户名长度至少为3个字符",
+ "max_length": "用户名长度不能超过50个字符"
+ }
+ }
+ )
+ nickname: str = Field(..., max_length=50, description="昵称")
+ email: Optional[str] = Field(None, max_length=50, description="电子邮箱")
+ phone: Optional[str] = Field(None, max_length=11, description="手机号码")
+ wx_openid: Optional[str] = Field(None, max_length=100, description="微信OpenID")
+ avatar: Optional[str] = Field(None, max_length=255, description="头像路径")
+ role_id: Optional[int] = Field(2, description="角色ID")
+
+ model_config = {"extra": "ignore"}
+
+
+class UserCreateRequest(UserBase):
+ """用户创建请求模型"""
+
+ password: str = Field(
+ ...,
+ min_length=6,
+ max_length=50,
+ description="密码",
+ json_schema_extra={
+ "error_messages": {
+ "min_length": "密码长度至少为6个字符",
+ "max_length": "密码长度不能超过50个字符"
+ }
+ }
+ )
+
+
+class UserCreate(UserBase):
+ """用户创建模型"""
+
+ password_hash: str = Field(
+ ..., min_length=6, max_length=128, description="密码哈希值"
+ )
+
+
+class UserUpdate(BaseModel):
+ """用户更新模型"""
+
+ username: str = Field(..., min_length=3, max_length=50, description="登录名")
+ nickname: str = Field(..., max_length=50, description="昵称")
+ email: Optional[str] = Field(None, max_length=50, description="电子邮箱")
+ phone: Optional[str] = Field(None, max_length=11, description="手机号码")
+ wx_openid: Optional[str] = Field(None, max_length=100, description="微信OpenID")
+ avatar: Optional[str] = Field(None, max_length=255, description="头像路径")
+ role_id: Optional[int] = Field(2, description="角色ID")
+ password: Optional[str] = Field(None, max_length=128, description="密码")
+
+
+class UserLogin(BaseModel):
+ """用户登录模型"""
+
+ username: str = Field(..., description="用户名/邮箱/手机号")
+ password: str = Field(..., description="密码")
+ captcha_id: str = Field(..., description="验证码ID")
+ captcha_code: str = Field(..., description="验证码")
+
+
+class UserResponse(BaseModel):
+ """用户响应模型"""
+
+ id: int = Field(..., description="用户ID")
+ username: str = Field(..., description="登录名")
+ nickname: str = Field(..., description="昵称")
+ email: Optional[str] = Field(None, description="电子邮箱")
+ phone: Optional[str] = Field(None, description="手机号码")
+ wx_openid: Optional[str] = Field(None, description="微信OpenID")
+ avatar: Optional[str] = Field(None, description="头像路径")
+ role_id: int = Field(..., description="角色ID")
+ createtime: datetime = Field(..., description="创建时间")
+ updatetime: datetime = Field(..., description="更新时间")
+ lastlogintime: Optional[datetime] = Field(None, description="最后登录时间")
+
+ class Config:
+ from_attributes = True
+
+
+class Token(BaseModel):
+ """令牌模型"""
+
+ access_token: str = Field(..., description="访问令牌")
+ token_type: str = Field(..., description="令牌类型")
+
+
+class TokenData(BaseModel):
+ """令牌数据模型"""
+
+ user_id: Optional[int] = None
+ email: Optional[str] = None
+
+
+class RoleBase(BaseModel):
+ """角色基础模型"""
+
+ name: str = Field(..., max_length=50, description="角色名称")
+
+
+class RoleCreate(RoleBase):
+ """角色创建模型"""
+
+ creator: Optional[str] = Field(None, max_length=50, description="创建人")
+
+
+class RoleResponse(RoleBase):
+ """角色响应模型"""
+
+ id: int = Field(..., description="角色ID")
+ createtime: datetime = Field(..., description="创建时间")
+ updatetime: datetime = Field(..., description="更新时间")
+ creator: Optional[str] = Field(None, description="创建人")
+
+ class Config:
+ from_attributes = True
+
+
+# 权限相关模型
+class PermissionBase(BaseModel):
+ """权限基础模型"""
+
+ name: str = Field(..., max_length=100, description="权限名称")
+ code: str = Field(..., max_length=100, description="权限编码")
+ parentid: int = Field(0, description="父权限ID(0表示顶级模块)")
+ route: 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):
+ """权限创建模型"""
+ pass
+
+
+class PermissionResponse(PermissionBase):
+ """权限响应模型"""
+
+ id: int = Field(..., description="权限ID")
+ createtime: datetime = Field(..., description="创建时间")
+ updatetime: datetime = Field(..., description="更新时间")
+
+ class Config:
+ from_attributes = True
+
+
+class PermissionTreeResponse(BaseModel):
+ """权限树响应模型"""
+
+ id: int = Field(..., description="权限ID")
+ name: str = Field(..., description="权限名称")
+ code: str = Field(..., description="权限编码")
+ parentid: int = Field(..., description="父权限ID")
+ route: Optional[str] = Field(None, description="前端路由路径")
+ createtime: Optional[datetime] = Field(None, description="创建时间")
+ updatetime: Optional[datetime] = Field(None, description="更新时间")
+ description: Optional[str] = Field(None, description="权限描述")
+ sort: int = Field(0, description="排序号")
+ children: list = Field([], description="子权限列表")
+
+
+# 角色权限关联相关模型
+class RolePermissionAssign(BaseModel):
+ """角色权限分配模型"""
+
+ role_id: int = Field(..., description="角色ID")
+ permission_ids: list[int] = Field(..., description="权限ID列表")
+
+
+class RolePermissionResponse(BaseModel):
+ """角色权限响应模型"""
+
+ role_id: int = Field(..., description="角色ID")
+ role_name: str = Field(..., description="角色名称")
+ permissions: list[PermissionResponse] = Field(..., description="权限列表")
+ menu: list = Field([], description="菜单树形结构")
+
+
+# 系统设置相关模型
+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")
+ options: Optional[str] = Field(None, max_length=500, description="下拉选项(用|分隔)")
+ description: Optional[str] = Field(None, max_length=255, description="设置描述")
+
+ model_config = {"extra": "ignore"}
+
+
+class SystemSettingCreate(SystemSettingBase):
+ """系统设置创建模型"""
+ pass
+
+
+class SystemSettingUpdate(SystemSettingBase):
+ """系统设置更新模型"""
+ key: Optional[str] = Field(None, max_length=100, description="设置键名")
+
+
+class SystemSettingResponse(BaseModel):
+ """系统设置响应模型"""
+
+ id: int = Field(..., description="设置ID")
+ key: str = Field(..., description="设置键名")
+ value: str = Field(..., description="设置值")
+ type: str = Field(..., description="类型")
+ options: Optional[str] = Field(None, description="下拉选项")
+ description: Optional[str] = Field(None, description="设置描述")
+ createtime: datetime = Field(..., description="创建时间")
+ updatetime: datetime = Field(..., description="更新时间")
+
+ class Config:
+ from_attributes = True
+
+
+class LoginLogResponse(BaseModel):
+ """登录日志响应模型"""
+
+ id: int = Field(..., description="日志ID")
+ username: str = Field(..., description="用户名")
+ login_time: datetime = Field(..., description="登录时间")
+ ip_address: Optional[str] = Field(None, description="IP地址")
+ user_agent: Optional[str] = Field(None, description="User-Agent")
+ status: str = Field(..., description="状态: success/failure")
+ failure_reason: Optional[str] = Field(None, description="失败原因")
+
+ class Config:
+ from_attributes = True
+
+
+class LoginLogSearch(BaseModel):
+ """登录日志搜索模型"""
+
+ username: Optional[str] = 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):
+ """登录日志清理请求模型"""
+
+ before_date: datetime = Field(..., description="清理此日期之前的日志")
+
+
+class OperationLogResponse(BaseModel):
+ """操作日志响应模型"""
+
+ id: int = Field(..., description="日志ID")
+ user_id: int = Field(..., description="用户ID")
+ username: str = Field(..., description="用户名")
+ action_type: str = Field(..., description="操作类型: create/update/delete")
+ target_type: str = Field(..., description="目标类型: user/role/permission/setting")
+ target_id: Optional[int] = Field(None, description="目标ID")
+ target_name: Optional[str] = Field(None, description="目标名称")
+ detail: Optional[str] = Field(None, description="变更详情(JSON)")
+ ip_address: Optional[str] = Field(None, description="IP地址")
+ user_agent: Optional[str] = Field(None, description="User-Agent")
+ createtime: datetime = Field(..., description="创建时间")
+
+ class Config:
+ from_attributes = True
+
+
+class OperationLogSearch(BaseModel):
+ """操作日志搜索模型"""
+
+ username: Optional[str] = Field(None, description="用户名(模糊搜索)")
+ action_type: Optional[str] = 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 PhoneSendCodeResponse(BaseModel):
+ """发送短信验证码响应"""
+ message: str = "验证码已发送"
+ expire_seconds: int = 300
+
+
+class PhoneRegisterRequest(BaseModel):
+ """手机号注册请求"""
+ username: str = Field(..., min_length=3, max_length=50, description="用户名")
+ phone: str = Field(..., pattern=r"^\d{11}$", 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="用户名")
+ 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 = "验证邮件已发送,请查收并点击链接完成注册"
+ expire_minutes: int = 30
+
+
+class RegisterSuccessResponse(BaseModel):
+ """注册成功响应"""
+ id: int
+ username: str
+ nickname: str
message: str = "注册成功"
\ No newline at end of file
diff --git a/frontend/src/modules/admin/views/permissions/permission-crud.ts b/frontend/src/modules/admin/views/permissions/permission-crud.ts
index 66603d1..b3fe497 100644
--- a/frontend/src/modules/admin/views/permissions/permission-crud.ts
+++ b/frontend/src/modules/admin/views/permissions/permission-crud.ts
@@ -1,176 +1,188 @@
-import type { CreateCrudOptions } from "@fast-crud/fast-crud";
-import dayjs from "dayjs";
-import { ElMessage } from "element-plus";
-import { http } from "@/modules/admin/api/http";
-import { toSearchParams } from "@/modules/admin/utils/list-query";
-
-export type PermissionRow = {
- id: number;
- name: string;
- code: string;
- parentid: number;
- route?: string | null;
- description?: string | null;
- createtime: string;
- updatetime: string;
- children?: PermissionRow[];
-};
-
-export const createPermissionCrudOptions: CreateCrudOptions = () => {
- return {
- crudOptions: {
- pagination: { show: false },
- search: {
- show: true,
- options: {
- layout: 'inline',
- },
- expand: false,
- },
- tree: {
- enable: true,
- childrenKey: "children",
- parentKey: "parentid",
- hasChildField: "children",
- },
- request: {
- transformQuery: ({ form }) =>
- toSearchParams(form as Record, ["name", "code"]),
- pageRequest: async (_query) => {
- const { data } = await http.get("/permissions/tree");
- return data;
- },
- transformRes: ({ res }) => {
- const records = Array.isArray(res) ? res : [];
- return {
- currentPage: 1,
- pageSize: Math.max(records.length, 1),
- total: records.length,
- records,
- };
- },
- addRequest: async ({ form }) => {
- await http.post("/permissions/", form);
- ElMessage.success({ message: "添加成功", duration: 5000 });
- },
- editRequest: async ({ form, row }) => {
- if (!row) return;
- await http.put(`/permissions/${row.id}`, form);
- ElMessage.success({ message: "修改成功", duration: 5000 });
- },
- delRequest: async ({ row }) => {
- if (!row) return;
- await http.delete(`/permissions/${row.id}`);
- ElMessage.success({ message: "删除成功", duration: 5000 });
- },
- onSuccess: ({ action }: { action: string }) => {
- if (action === "add") {
- ElMessage.success({ message: "添加成功", duration: 5000 });
- } else if (action === "edit") {
- ElMessage.success({ message: "修改成功", duration: 5000 });
- }
- },
- onError: (error: any) => {
- let message = "操作失败";
- if (error.response?.data) {
- const data = error.response.data;
- if (data.message) {
- message = data.message;
- } else if (data.detail) {
- message = typeof data.detail === "string" ? data.detail : "操作失败";
- }
- }
- ElMessage.error({ message, duration: 5000 });
- },
- },
- table: {
- remove: {
- showSuccessNotification: false,
- },
- },
- rowHandle: {
- buttons: {
- edit: { text: "编辑" },
- remove: { text: "删除" },
- },
- },
- columns: {
- id: {
- title: "ID",
- key: "id",
- type: "number",
- column: { width: 70 },
- form: { show: false },
- },
- name: {
- title: "名称",
- key: "name",
- type: "text",
- search: { show: true },
- form: {
- rules: [{ required: true, message: "必填" }],
- },
- },
- code: {
- title: "编码",
- key: "code",
- type: "text",
- search: { show: true },
- form: {
- rules: [{ required: true, message: "必填" }],
- },
- },
- parentid: {
- title: "父级ID",
- key: "parentid",
- type: "number",
- form: {
- value: 0,
- rules: [{ required: true, message: "必填" }],
- },
- },
- description: {
- title: "描述",
- key: "description",
- type: "textarea",
- column: { show: false },
- },
- route: {
- title: "路由",
- key: "route",
- type: "text",
- column: { width: 200 },
- form: {
- placeholder: "如:/admin/users",
- },
- },
- createtime: {
- title: "创建时间",
- key: "createtime",
- type: "text",
- form: { show: false },
- column: { width: 170 },
- valueBuilder({ value, row, key }) {
- if (!row || value == null) return;
- (row as Record)[key as string] = dayjs(value).format(
- "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)[key as string] = dayjs(value).format(
- "YYYY-MM-DD HH:mm:ss",
- );
- },
- },
- },
- },
- };
-};
+import type { CreateCrudOptions } from "@fast-crud/fast-crud";
+import dayjs from "dayjs";
+import { ElMessage } from "element-plus";
+import { http } from "@/modules/admin/api/http";
+import { toSearchParams } from "@/modules/admin/utils/list-query";
+
+export type PermissionRow = {
+ id: number;
+ name: string;
+ code: string;
+ parentid: number;
+ route?: string | null;
+ description?: string | null;
+ sort: number;
+ createtime: string;
+ updatetime: string;
+ children?: PermissionRow[];
+};
+
+export const createPermissionCrudOptions: CreateCrudOptions = () => {
+ return {
+ crudOptions: {
+ pagination: { show: false },
+ search: {
+ show: true,
+ options: {
+ layout: 'inline',
+ },
+ expand: false,
+ },
+ tree: {
+ enable: true,
+ childrenKey: "children",
+ parentKey: "parentid",
+ hasChildField: "children",
+ },
+ request: {
+ transformQuery: ({ form }) =>
+ toSearchParams(form as Record, ["name", "code"]),
+ pageRequest: async (_query) => {
+ const { data } = await http.get("/permissions/tree");
+ return data;
+ },
+ transformRes: ({ res }) => {
+ const records = Array.isArray(res) ? res : [];
+ return {
+ currentPage: 1,
+ pageSize: Math.max(records.length, 1),
+ total: records.length,
+ records,
+ };
+ },
+ addRequest: async ({ form }) => {
+ await http.post("/permissions/", form);
+ ElMessage.success({ message: "添加成功", duration: 5000 });
+ },
+ editRequest: async ({ form, row }) => {
+ if (!row) return;
+ await http.put(`/permissions/${row.id}`, form);
+ ElMessage.success({ message: "修改成功", duration: 5000 });
+ },
+ delRequest: async ({ row }) => {
+ if (!row) return;
+ await http.delete(`/permissions/${row.id}`);
+ ElMessage.success({ message: "删除成功", duration: 5000 });
+ },
+ onSuccess: ({ action }: { action: string }) => {
+ if (action === "add") {
+ ElMessage.success({ message: "添加成功", duration: 5000 });
+ } else if (action === "edit") {
+ ElMessage.success({ message: "修改成功", duration: 5000 });
+ }
+ },
+ onError: (error: any) => {
+ let message = "操作失败";
+ if (error.response?.data) {
+ const data = error.response.data;
+ if (data.message) {
+ message = data.message;
+ } else if (data.detail) {
+ message = typeof data.detail === "string" ? data.detail : "操作失败";
+ }
+ }
+ ElMessage.error({ message, duration: 5000 });
+ },
+ },
+ table: {
+ remove: {
+ showSuccessNotification: false,
+ },
+ },
+ rowHandle: {
+ buttons: {
+ edit: { text: "编辑" },
+ remove: { text: "删除" },
+ },
+ },
+ columns: {
+ id: {
+ title: "ID",
+ key: "id",
+ type: "number",
+ column: { width: 70 },
+ form: { show: false },
+ },
+ name: {
+ title: "名称",
+ key: "name",
+ type: "text",
+ search: { show: true },
+ form: {
+ rules: [{ required: true, message: "必填" }],
+ },
+ },
+ code: {
+ title: "编码",
+ key: "code",
+ type: "text",
+ search: { show: true },
+ form: {
+ rules: [{ required: true, message: "必填" }],
+ },
+ },
+ parentid: {
+ title: "父级ID",
+ key: "parentid",
+ type: "number",
+ form: {
+ value: 0,
+ rules: [{ required: true, message: "必填" }],
+ },
+ },
+ description: {
+ title: "描述",
+ key: "description",
+ type: "textarea",
+ column: { show: false },
+ },
+ route: {
+ title: "路由",
+ key: "route",
+ type: "text",
+ column: { width: 200 },
+ form: {
+ placeholder: "如:/admin/users",
+ },
+ },
+ sort: {
+ title: "排序",
+ key: "sort",
+ type: "number",
+ column: { width: 80, align: "center" },
+ form: {
+ value: 0,
+ rules: [{ required: true, message: "必填" }],
+ helper: "数字越小,排序越靠前",
+ },
+ },
+ createtime: {
+ title: "创建时间",
+ key: "createtime",
+ type: "text",
+ form: { show: false },
+ column: { width: 170 },
+ valueBuilder({ value, row, key }) {
+ if (!row || value == null) return;
+ (row as Record)[key as string] = dayjs(value).format(
+ "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)[key as string] = dayjs(value).format(
+ "YYYY-MM-DD HH:mm:ss",
+ );
+ },
+ },
+ },
+ },
+ };
+};