# Design Document: Admin System Backend ## Overview 本设计文档描述了为 adminSystem 前端提供后端支持的 API 设计。后端将扩展现有的 C# ASP.NET Core 项目,添加用户认证、用户管理、角色管理和动态菜单功能。 ## Architecture ```mermaid graph TB subgraph Frontend["adminSystem Frontend"] Login[登录页面] Dashboard[控制台] UserMgmt[用户管理] RoleMgmt[角色管理] MenuMgmt[菜单管理] AMT[AMT Scanner 功能] end subgraph Backend["C# Backend API"] AuthController[AuthController] UserController[UserController] RoleController[RoleController] MenuController[MenuController] AuthService[AuthService] UserService[UserService] RoleService[RoleService] MenuService[MenuService] JwtMiddleware[JWT 中间件] end subgraph Database["MySQL Database"] Users[(Users)] Roles[(Roles)] UserRoles[(UserRoles)] Menus[(Menus)] RoleMenus[(RoleMenus)] end Login --> AuthController Dashboard --> MenuController UserMgmt --> UserController RoleMgmt --> RoleController MenuMgmt --> MenuController AMT --> |现有 API| Backend AuthController --> AuthService UserController --> UserService RoleController --> RoleService MenuController --> MenuService AuthService --> Users UserService --> Users UserService --> UserRoles RoleService --> Roles RoleService --> RoleMenus MenuService --> Menus MenuService --> RoleMenus ``` ## Components and Interfaces ### 1. AuthController 负责用户认证相关的 API 端点。 ```csharp [ApiController] [Route("api/auth")] public class AuthController : ControllerBase { // POST /api/auth/login - 用户登录 [HttpPost("login")] public async Task>> Login(LoginRequest request); // POST /api/auth/refresh - 刷新 Token [HttpPost("refresh")] public async Task>> RefreshToken(RefreshTokenRequest request); // POST /api/auth/logout - 退出登录 [HttpPost("logout")] public async Task> Logout(); } ``` ### 2. UserController 负责用户信息和用户管理的 API 端点。 ```csharp [ApiController] [Route("api/user")] public class UserController : ControllerBase { // GET /api/user/info - 获取当前用户信息 [HttpGet("info")] [Authorize] public async Task>> GetUserInfo(); // GET /api/user/list - 获取用户列表(分页) [HttpGet("list")] [Authorize(Roles = "R_SUPER,R_ADMIN")] public async Task>>> GetUserList([FromQuery] UserSearchParams query); // POST /api/user - 创建用户 [HttpPost] [Authorize(Roles = "R_SUPER,R_ADMIN")] public async Task>> CreateUser(CreateUserRequest request); // PUT /api/user/{id} - 更新用户 [HttpPut("{id}")] [Authorize(Roles = "R_SUPER,R_ADMIN")] public async Task>> UpdateUser(int id, UpdateUserRequest request); // DELETE /api/user/{id} - 删除用户 [HttpDelete("{id}")] [Authorize(Roles = "R_SUPER")] public async Task> DeleteUser(int id); } ``` ### 3. RoleController 负责角色管理的 API 端点。 ```csharp [ApiController] [Route("api/role")] public class RoleController : ControllerBase { // GET /api/role/list - 获取角色列表(分页) [HttpGet("list")] [Authorize(Roles = "R_SUPER")] public async Task>>> GetRoleList([FromQuery] RoleSearchParams query); // POST /api/role - 创建角色 [HttpPost] [Authorize(Roles = "R_SUPER")] public async Task>> CreateRole(CreateRoleRequest request); // PUT /api/role/{id} - 更新角色 [HttpPut("{id}")] [Authorize(Roles = "R_SUPER")] public async Task>> UpdateRole(int id, UpdateRoleRequest request); // DELETE /api/role/{id} - 删除角色 [HttpDelete("{id}")] [Authorize(Roles = "R_SUPER")] public async Task> DeleteRole(int id); // PUT /api/role/{id}/menus - 分配菜单权限 [HttpPut("{id}/menus")] [Authorize(Roles = "R_SUPER")] public async Task> AssignMenus(int id, AssignMenusRequest request); } ``` ### 4. MenuController 负责动态菜单的 API 端点。 ```csharp [ApiController] [Route("api")] public class MenuController : ControllerBase { // GET /api/v3/system/menus/simple - 获取用户菜单(adminSystem 使用的接口) [HttpGet("v3/system/menus/simple")] [Authorize] public async Task>>> GetUserMenus(); // GET /api/menu/list - 获取所有菜单(管理用) [HttpGet("menu/list")] [Authorize(Roles = "R_SUPER")] public async Task>>> GetAllMenus(); // POST /api/menu - 创建菜单 [HttpPost("menu")] [Authorize(Roles = "R_SUPER")] public async Task>> CreateMenu(CreateMenuRequest request); // PUT /api/menu/{id} - 更新菜单 [HttpPut("menu/{id}")] [Authorize(Roles = "R_SUPER")] public async Task>> UpdateMenu(int id, UpdateMenuRequest request); // DELETE /api/menu/{id} - 删除菜单 [HttpDelete("menu/{id}")] [Authorize(Roles = "R_SUPER")] public async Task> DeleteMenu(int id); } ``` ## Data Models ### 数据库实体 ```csharp // 用户表 public class User { public int Id { get; set; } public string UserName { get; set; } public string PasswordHash { get; set; } public string? NickName { get; set; } public string? Email { get; set; } public string? Phone { get; set; } public string? Avatar { get; set; } public string Gender { get; set; } = "0"; // 0-未知, 1-男, 2-女 public string Status { get; set; } = "1"; // 1-启用, 2-禁用 public DateTime CreatedAt { get; set; } public DateTime? UpdatedAt { get; set; } public string? CreatedBy { get; set; } public string? UpdatedBy { get; set; } public bool IsDeleted { get; set; } = false; public ICollection UserRoles { get; set; } } // 角色表 public class Role { public int Id { get; set; } public string RoleName { get; set; } public string RoleCode { get; set; } // R_SUPER, R_ADMIN, R_USER public string? Description { get; set; } public bool Enabled { get; set; } = true; public DateTime CreatedAt { get; set; } public ICollection UserRoles { get; set; } public ICollection RoleMenus { get; set; } } // 用户-角色关联表 public class UserRole { public int UserId { get; set; } public User User { get; set; } public int RoleId { get; set; } public Role Role { get; set; } } // 菜单表 public class Menu { public int Id { get; set; } public int? ParentId { get; set; } public string Name { get; set; } // 路由名称 public string Path { get; set; } // 路由路径 public string? Component { get; set; } // 组件路径 public string? Title { get; set; } // 菜单标题 (i18n key) public string? Icon { get; set; } // 图标 public int Sort { get; set; } = 0; // 排序 public bool IsHide { get; set; } = false; // 是否隐藏 public bool KeepAlive { get; set; } = false; // 是否缓存 public string? Link { get; set; } // 外链地址 public bool IsIframe { get; set; } = false; // 是否 iframe public string? Roles { get; set; } // 角色限制 (JSON 数组) public DateTime CreatedAt { get; set; } public Menu? Parent { get; set; } public ICollection Children { get; set; } public ICollection RoleMenus { get; set; } } // 角色-菜单关联表 public class RoleMenu { public int RoleId { get; set; } public Role Role { get; set; } public int MenuId { get; set; } public Menu Menu { get; set; } } ``` ### API 请求/响应模型 ```csharp // 统一响应格式 public class ApiResponse { public int Code { get; set; } = 200; public string Msg { get; set; } = "success"; public T? Data { get; set; } } public class ApiResponse : ApiResponse { } // 登录请求 public class LoginRequest { public string UserName { get; set; } public string Password { get; set; } } // 登录响应 public class LoginResponse { public string Token { get; set; } public string RefreshToken { get; set; } } // 用户信息响应 public class UserInfo { public int UserId { get; set; } public string UserName { get; set; } public string? Email { get; set; } public string? Avatar { get; set; } public List Roles { get; set; } public List Buttons { get; set; } } // 分页响应 public class PaginatedResponse { public List Records { get; set; } public int Current { get; set; } public int Size { get; set; } public int Total { get; set; } } // 菜单树项 public class MenuTreeItem { public string Path { get; set; } public string Name { get; set; } public string? Component { get; set; } public MenuMeta Meta { get; set; } public List? Children { get; set; } } public class MenuMeta { public string Title { get; set; } public string? Icon { get; set; } public bool? IsHide { get; set; } public bool? KeepAlive { get; set; } public string? Link { get; set; } public bool? IsIframe { get; set; } public List? Roles { get; set; } } ``` ## Correctness Properties *A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* ### Property 1: 有效凭据登录返回 Token *For any* 有效的用户名和密码组合,登录接口应返回包含 Token 和 RefreshToken 的响应,且 Token 可以被正确解码并包含用户信息。 **Validates: Requirements 1.1, 1.5** ### Property 2: 无效凭据登录返回 401 *For any* 无效的用户名或密码,登录接口应返回 401 状态码。 **Validates: Requirements 1.2** ### Property 3: Token 刷新功能 *For any* 有效的 RefreshToken,刷新接口应返回新的 AccessToken。 **Validates: Requirements 1.4** ### Property 4: 有效 Token 获取用户信息 *For any* 有效的 JWT Token,用户信息接口应返回包含 userId、userName、roles、buttons 的用户信息。 **Validates: Requirements 2.1, 2.2, 2.3** ### Property 5: 无效 Token 返回 401 *For any* 无效或过期的 Token,受保护的 API 应返回 401 状态码。 **Validates: Requirements 2.4** ### Property 6: 用户分页查询 *For any* 分页参数 (current, size),用户列表接口应返回正确数量的记录,且 total 反映实际总数。 **Validates: Requirements 3.1** ### Property 7: 用户名唯一性验证 *For any* 已存在的用户名,创建用户接口应返回错误。 **Validates: Requirements 3.2** ### Property 8: 用户搜索过滤 *For any* 搜索条件,返回的用户列表应只包含符合条件的用户。 **Validates: Requirements 3.3** ### Property 9: 角色编码唯一性验证 *For any* 已存在的角色编码,创建角色接口应返回错误。 **Validates: Requirements 4.2** ### Property 10: 菜单角色过滤 *For any* 用户角色,菜单接口应只返回该角色有权限访问的菜单项。 **Validates: Requirements 5.1, 5.4** ### Property 11: 统一响应格式 *For any* API 请求,响应应包含 code、msg、data 字段。 **Validates: Requirements 7.1** ## Error Handling | 错误场景 | HTTP 状态码 | 响应 Code | 错误消息 | |---------|------------|----------|---------| | 用户名或密码错误 | 401 | 401 | 用户名或密码错误 | | Token 无效或过期 | 401 | 401 | 未授权访问 | | 权限不足 | 403 | 403 | 权限不足 | | 资源不存在 | 404 | 404 | 资源不存在 | | 参数验证失败 | 400 | 400 | 具体验证错误信息 | | 用户名已存在 | 400 | 400 | 用户名已存在 | | 角色编码已存在 | 400 | 400 | 角色编码已存在 | | 服务器内部错误 | 500 | 500 | 服务器内部错误 | ## Testing Strategy ### 单元测试 - 测试 AuthService 的密码哈希和验证逻辑 - 测试 JWT Token 的生成和解析 - 测试用户、角色、菜单的 CRUD 操作 - 测试权限过滤逻辑 ### 集成测试 - 测试完整的登录流程 - 测试 Token 刷新流程 - 测试用户管理 API - 测试角色管理 API - 测试菜单获取和过滤 ### 属性测试 使用 FsCheck 或类似库进行属性测试: - 验证 Token 格式和内容 - 验证分页逻辑 - 验证搜索过滤逻辑 - 验证权限控制逻辑