454 lines
13 KiB
Markdown
454 lines
13 KiB
Markdown
# 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<ActionResult<ApiResponse<LoginResponse>>> Login(LoginRequest request);
|
||
|
||
// POST /api/auth/refresh - 刷新 Token
|
||
[HttpPost("refresh")]
|
||
public async Task<ActionResult<ApiResponse<LoginResponse>>> RefreshToken(RefreshTokenRequest request);
|
||
|
||
// POST /api/auth/logout - 退出登录
|
||
[HttpPost("logout")]
|
||
public async Task<ActionResult<ApiResponse>> Logout();
|
||
}
|
||
```
|
||
|
||
### 2. UserController
|
||
|
||
负责用户信息和用户管理的 API 端点。
|
||
|
||
```csharp
|
||
[ApiController]
|
||
[Route("api/user")]
|
||
public class UserController : ControllerBase
|
||
{
|
||
// GET /api/user/info - 获取当前用户信息
|
||
[HttpGet("info")]
|
||
[Authorize]
|
||
public async Task<ActionResult<ApiResponse<UserInfo>>> GetUserInfo();
|
||
|
||
// GET /api/user/list - 获取用户列表(分页)
|
||
[HttpGet("list")]
|
||
[Authorize(Roles = "R_SUPER,R_ADMIN")]
|
||
public async Task<ActionResult<ApiResponse<PaginatedResponse<UserListItem>>>> GetUserList([FromQuery] UserSearchParams query);
|
||
|
||
// POST /api/user - 创建用户
|
||
[HttpPost]
|
||
[Authorize(Roles = "R_SUPER,R_ADMIN")]
|
||
public async Task<ActionResult<ApiResponse<UserListItem>>> CreateUser(CreateUserRequest request);
|
||
|
||
// PUT /api/user/{id} - 更新用户
|
||
[HttpPut("{id}")]
|
||
[Authorize(Roles = "R_SUPER,R_ADMIN")]
|
||
public async Task<ActionResult<ApiResponse<UserListItem>>> UpdateUser(int id, UpdateUserRequest request);
|
||
|
||
// DELETE /api/user/{id} - 删除用户
|
||
[HttpDelete("{id}")]
|
||
[Authorize(Roles = "R_SUPER")]
|
||
public async Task<ActionResult<ApiResponse>> 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<ActionResult<ApiResponse<PaginatedResponse<RoleListItem>>>> GetRoleList([FromQuery] RoleSearchParams query);
|
||
|
||
// POST /api/role - 创建角色
|
||
[HttpPost]
|
||
[Authorize(Roles = "R_SUPER")]
|
||
public async Task<ActionResult<ApiResponse<RoleListItem>>> CreateRole(CreateRoleRequest request);
|
||
|
||
// PUT /api/role/{id} - 更新角色
|
||
[HttpPut("{id}")]
|
||
[Authorize(Roles = "R_SUPER")]
|
||
public async Task<ActionResult<ApiResponse<RoleListItem>>> UpdateRole(int id, UpdateRoleRequest request);
|
||
|
||
// DELETE /api/role/{id} - 删除角色
|
||
[HttpDelete("{id}")]
|
||
[Authorize(Roles = "R_SUPER")]
|
||
public async Task<ActionResult<ApiResponse>> DeleteRole(int id);
|
||
|
||
// PUT /api/role/{id}/menus - 分配菜单权限
|
||
[HttpPut("{id}/menus")]
|
||
[Authorize(Roles = "R_SUPER")]
|
||
public async Task<ActionResult<ApiResponse>> 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<ActionResult<ApiResponse<List<MenuTreeItem>>>> GetUserMenus();
|
||
|
||
// GET /api/menu/list - 获取所有菜单(管理用)
|
||
[HttpGet("menu/list")]
|
||
[Authorize(Roles = "R_SUPER")]
|
||
public async Task<ActionResult<ApiResponse<List<MenuTreeItem>>>> GetAllMenus();
|
||
|
||
// POST /api/menu - 创建菜单
|
||
[HttpPost("menu")]
|
||
[Authorize(Roles = "R_SUPER")]
|
||
public async Task<ActionResult<ApiResponse<MenuTreeItem>>> CreateMenu(CreateMenuRequest request);
|
||
|
||
// PUT /api/menu/{id} - 更新菜单
|
||
[HttpPut("menu/{id}")]
|
||
[Authorize(Roles = "R_SUPER")]
|
||
public async Task<ActionResult<ApiResponse<MenuTreeItem>>> UpdateMenu(int id, UpdateMenuRequest request);
|
||
|
||
// DELETE /api/menu/{id} - 删除菜单
|
||
[HttpDelete("menu/{id}")]
|
||
[Authorize(Roles = "R_SUPER")]
|
||
public async Task<ActionResult<ApiResponse>> 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<UserRole> 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<UserRole> UserRoles { get; set; }
|
||
public ICollection<RoleMenu> 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<Menu> Children { get; set; }
|
||
public ICollection<RoleMenu> 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<T>
|
||
{
|
||
public int Code { get; set; } = 200;
|
||
public string Msg { get; set; } = "success";
|
||
public T? Data { get; set; }
|
||
}
|
||
|
||
public class ApiResponse : ApiResponse<object> { }
|
||
|
||
// 登录请求
|
||
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<string> Roles { get; set; }
|
||
public List<string> Buttons { get; set; }
|
||
}
|
||
|
||
// 分页响应
|
||
public class PaginatedResponse<T>
|
||
{
|
||
public List<T> 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<MenuTreeItem>? 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<string>? 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 格式和内容
|
||
- 验证分页逻辑
|
||
- 验证搜索过滤逻辑
|
||
- 验证权限控制逻辑
|