feat: 添加系统配置、审计中心、通知中心、文件管理模块
This commit is contained in:
@@ -0,0 +1,707 @@
|
|||||||
|
# E2E测试用例设计文档
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
本项目是一个基于Spring Boot 3.5.9 + WebFlux的响应式管理系统API,采用PostgreSQL数据库,支持用户、角色、字典等核心业务功能。
|
||||||
|
|
||||||
|
## 测试目标
|
||||||
|
|
||||||
|
1. 验证关键用户场景的端到端流程
|
||||||
|
2. 确保API接口的正确性和稳定性
|
||||||
|
3. 验证认证授权机制
|
||||||
|
4. 测试数据一致性和完整性
|
||||||
|
5. 验证错误处理和边界条件
|
||||||
|
|
||||||
|
## 测试环境
|
||||||
|
|
||||||
|
- **测试框架**: Python + Playwright
|
||||||
|
- **API基础URL**: http://localhost:8080
|
||||||
|
- **测试数据库**: PostgreSQL (Test环境)
|
||||||
|
- **测试数据**: 自动化准备和清理
|
||||||
|
|
||||||
|
## 测试用例设计
|
||||||
|
|
||||||
|
### 1. 认证授权测试
|
||||||
|
|
||||||
|
#### 1.1 用户登录流程
|
||||||
|
**用例ID**: TC-AUTH-001
|
||||||
|
**优先级**: P0
|
||||||
|
**前置条件**: 系统已启动,数据库中存在测试用户
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送POST请求到 `/api/auth/login`
|
||||||
|
2. 提供正确的用户名和密码
|
||||||
|
3. 验证返回的access_token和refresh_token
|
||||||
|
4. 验证token格式正确性
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回包含accessToken和refreshToken
|
||||||
|
- token格式符合JWT标准
|
||||||
|
|
||||||
|
**测试数据**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin123"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.2 Token刷新流程
|
||||||
|
**用例ID**: TC-AUTH-002
|
||||||
|
**优先级**: P0
|
||||||
|
**前置条件**: 已获得有效的refresh_token
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送POST请求到 `/api/auth/refresh`
|
||||||
|
2. 提供有效的refresh_token
|
||||||
|
3. 验证返回新的access_token
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回新的accessToken
|
||||||
|
- refreshToken保持不变或更新
|
||||||
|
|
||||||
|
#### 1.3 用户登出流程
|
||||||
|
**用例ID**: TC-AUTH-003
|
||||||
|
**优先级**: P1
|
||||||
|
**前置条件**: 已登录,持有有效token
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送POST请求到 `/api/auth/logout`
|
||||||
|
2. 在Header中携带Authorization: Bearer {token}
|
||||||
|
3. 验证登出成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回成功消息
|
||||||
|
- token被加入黑名单
|
||||||
|
|
||||||
|
#### 1.4 无效登录测试
|
||||||
|
**用例ID**: TC-AUTH-004
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 使用错误的用户名或密码登录
|
||||||
|
2. 验证错误响应
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 401
|
||||||
|
- 返回认证失败消息
|
||||||
|
|
||||||
|
### 2. 用户管理测试
|
||||||
|
|
||||||
|
#### 2.1 创建用户
|
||||||
|
**用例ID**: TC-USER-001
|
||||||
|
**优先级**: P0
|
||||||
|
**前置条件**: 已登录,具有ADMIN权限
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送POST请求到 `/api/users`
|
||||||
|
2. 提供用户信息
|
||||||
|
3. 验证用户创建成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 201
|
||||||
|
- 返回创建的用户信息
|
||||||
|
- 用户ID已生成
|
||||||
|
- 密码已加密
|
||||||
|
|
||||||
|
**测试数据**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "testuser",
|
||||||
|
"password": "password123",
|
||||||
|
"email": "test@example.com",
|
||||||
|
"roleId": 2,
|
||||||
|
"status": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2 查询单个用户
|
||||||
|
**用例ID**: TC-USER-002
|
||||||
|
**优先级**: P0
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送GET请求到 `/api/users/{id}`
|
||||||
|
2. 验证返回正确的用户信息
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回完整的用户信息
|
||||||
|
|
||||||
|
#### 2.3 查询所有用户
|
||||||
|
**用例ID**: TC-USER-003
|
||||||
|
**优先级**: P0
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送GET请求到 `/api/users`
|
||||||
|
2. 验证返回用户列表
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回用户数组
|
||||||
|
- 包含分页信息(如已实现)
|
||||||
|
|
||||||
|
#### 2.4 更新用户
|
||||||
|
**用例ID**: TC-USER-004
|
||||||
|
**优先级**: P0
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送PUT请求到 `/api/users/{id}`
|
||||||
|
2. 提供更新的用户信息
|
||||||
|
3. 验证更新成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回更新后的用户信息
|
||||||
|
|
||||||
|
#### 2.5 删除用户
|
||||||
|
**用例ID**: TC-USER-005
|
||||||
|
**优先级**: P0
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送DELETE请求到 `/api/users/{id}`
|
||||||
|
2. 验证删除成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 204
|
||||||
|
- 用户已被物理删除
|
||||||
|
|
||||||
|
#### 2.6 逻辑删除用户
|
||||||
|
**用例ID**: TC-USER-006
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送DELETE请求到 `/api/users/{id}/logical`
|
||||||
|
2. 验证逻辑删除成功
|
||||||
|
3. 查询已删除用户(带includeDeleted=true)
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 用户被标记为已删除
|
||||||
|
- 可以通过includeDeleted参数查询到
|
||||||
|
|
||||||
|
#### 2.7 恢复已删除用户
|
||||||
|
**用例ID**: TC-USER-007
|
||||||
|
**优先级**: P1
|
||||||
|
**前置条件**: 用户已被逻辑删除
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送POST请求到 `/api/users/{id}/restore`
|
||||||
|
2. 验证恢复成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 用户状态恢复正常
|
||||||
|
|
||||||
|
#### 2.8 用户名唯一性检查
|
||||||
|
**用例ID**: TC-USER-008
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送GET请求到 `/api/users/check/username?username=existing`
|
||||||
|
2. 验证返回true
|
||||||
|
3. 使用不存在的用户名验证返回false
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回正确的布尔值
|
||||||
|
|
||||||
|
#### 2.9 邮箱唯一性检查
|
||||||
|
**用例ID**: TC-USER-009
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送GET请求到 `/api/users/check/email?email=existing@example.com`
|
||||||
|
2. 验证返回true
|
||||||
|
3. 使用不存在的邮箱验证返回false
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回正确的布尔值
|
||||||
|
|
||||||
|
### 3. 角色管理测试
|
||||||
|
|
||||||
|
#### 3.1 创建角色
|
||||||
|
**用例ID**: TC-ROLE-001
|
||||||
|
**优先级**: P0
|
||||||
|
**前置条件**: 已登录,具有ADMIN权限
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送POST请求到 `/api/roles`
|
||||||
|
2. 提供角色信息
|
||||||
|
3. 验证角色创建成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 201
|
||||||
|
- 返回创建的角色信息
|
||||||
|
|
||||||
|
**测试数据**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "TEST_ROLE",
|
||||||
|
"description": "测试角色",
|
||||||
|
"permissions": "READ,WRITE"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2 查询角色
|
||||||
|
**用例ID**: TC-ROLE-002
|
||||||
|
**优先级**: P0
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送GET请求到 `/api/roles/{id}`
|
||||||
|
2. 验证返回正确的角色信息
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回完整的角色信息
|
||||||
|
|
||||||
|
#### 3.3 按名称查询角色
|
||||||
|
**用例ID**: TC-ROLE-003
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送GET请求到 `/api/roles/name/{name}`
|
||||||
|
2. 验证返回正确的角色信息
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回指定名称的角色
|
||||||
|
|
||||||
|
#### 3.4 查询所有角色
|
||||||
|
**用例ID**: TC-ROLE-004
|
||||||
|
**优先级**: P0
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送GET请求到 `/api/roles`
|
||||||
|
2. 验证返回角色列表
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回角色数组
|
||||||
|
|
||||||
|
#### 3.5 更新角色
|
||||||
|
**用例ID**: TC-ROLE-005
|
||||||
|
**优先级**: P0
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送PUT请求到 `/api/roles/{id}`
|
||||||
|
2. 提供更新的角色信息
|
||||||
|
3. 验证更新成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回更新后的角色信息
|
||||||
|
|
||||||
|
#### 3.6 删除角色
|
||||||
|
**用例ID**: TC-ROLE-006
|
||||||
|
**优先级**: P0
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送DELETE请求到 `/api/roles/{id}`
|
||||||
|
2. 验证删除成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 204
|
||||||
|
|
||||||
|
#### 3.7 逻辑删除角色
|
||||||
|
**用例ID**: TC-ROLE-007
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送DELETE请求到 `/api/roles/{id}/logical`
|
||||||
|
2. 验证逻辑删除成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 角色被标记为已删除
|
||||||
|
|
||||||
|
#### 3.8 恢复已删除角色
|
||||||
|
**用例ID**: TC-ROLE-008
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送POST请求到 `/api/roles/{id}/restore`
|
||||||
|
2. 验证恢复成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 角色状态恢复正常
|
||||||
|
|
||||||
|
#### 3.9 角色名唯一性检查
|
||||||
|
**用例ID**: TC-ROLE-009
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送GET请求到 `/api/roles/check/name?name=existing`
|
||||||
|
2. 验证返回true
|
||||||
|
3. 使用不存在的角色名验证返回false
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回正确的布尔值
|
||||||
|
|
||||||
|
### 4. 字典管理测试
|
||||||
|
|
||||||
|
#### 4.1 创建字典
|
||||||
|
**用例ID**: TC-DICT-001
|
||||||
|
**优先级**: P0
|
||||||
|
**前置条件**: 已登录,具有ADMIN权限
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送POST请求到 `/api/dictionaries`
|
||||||
|
2. 提供字典信息
|
||||||
|
3. 验证字典创建成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 201
|
||||||
|
- 返回创建的字典信息
|
||||||
|
|
||||||
|
**测试数据**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "USER_STATUS",
|
||||||
|
"code": "ACTIVE",
|
||||||
|
"name": "激活",
|
||||||
|
"value": "1",
|
||||||
|
"remark": "用户激活状态",
|
||||||
|
"sort": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.2 查询字典
|
||||||
|
**用例ID**: TC-DICT-002
|
||||||
|
**优先级**: P0
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送GET请求到 `/api/dictionaries/{id}`
|
||||||
|
2. 验证返回正确的字典信息
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回完整的字典信息
|
||||||
|
|
||||||
|
#### 4.3 按类型查询字典
|
||||||
|
**用例ID**: TC-DICT-003
|
||||||
|
**优先级**: P0
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送GET请求到 `/api/dictionaries/type/{type}`
|
||||||
|
2. 验证返回指定类型的字典列表
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回指定类型的字典数组
|
||||||
|
- 按sort字段排序
|
||||||
|
|
||||||
|
#### 4.4 查询所有字典
|
||||||
|
**用例ID**: TC-DICT-004
|
||||||
|
**优先级**: P0
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送GET请求到 `/api/dictionaries`
|
||||||
|
2. 验证返回字典列表
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回字典数组
|
||||||
|
|
||||||
|
#### 4.5 更新字典
|
||||||
|
**用例ID**: TC-DICT-005
|
||||||
|
**优先级**: P0
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送PUT请求到 `/api/dictionaries/{id}`
|
||||||
|
2. 提供更新的字典信息
|
||||||
|
3. 验证更新成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回更新后的字典信息
|
||||||
|
|
||||||
|
#### 4.6 删除字典
|
||||||
|
**用例ID**: TC-DICT-006
|
||||||
|
**优先级**: P0
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送DELETE请求到 `/api/dictionaries/{id}`
|
||||||
|
2. 验证删除成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 204
|
||||||
|
|
||||||
|
#### 4.7 字典类型和编码唯一性检查
|
||||||
|
**用例ID**: TC-DICT-007
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送GET请求到 `/api/dictionaries/check/exists?type=TYPE&code=CODE`
|
||||||
|
2. 验证返回true或false
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回正确的布尔值
|
||||||
|
|
||||||
|
### 5. OAuth2客户端管理测试
|
||||||
|
|
||||||
|
#### 5.1 创建OAuth2客户端
|
||||||
|
**用例ID**: TC-OAUTH2-001
|
||||||
|
**优先级**: P1
|
||||||
|
**前置条件**: 已登录,具有ADMIN权限
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送POST请求到 `/api/oauth2/clients`
|
||||||
|
2. 提供客户端信息
|
||||||
|
3. 验证客户端创建成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 201
|
||||||
|
- 返回创建的客户端信息
|
||||||
|
- clientSecret已加密
|
||||||
|
|
||||||
|
**测试数据**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"clientId": "test-client",
|
||||||
|
"clientSecret": "secret123",
|
||||||
|
"clientName": "Test Client",
|
||||||
|
"webServerRedirectUri": "http://localhost:8080/callback",
|
||||||
|
"scope": "read,write",
|
||||||
|
"authorizedGrantTypes": "authorization_code,refresh_token",
|
||||||
|
"accessTokenValiditySeconds": 7200,
|
||||||
|
"refreshTokenValiditySeconds": 2592000,
|
||||||
|
"autoApprove": false,
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.2 查询OAuth2客户端
|
||||||
|
**用例ID**: TC-OAUTH2-002
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送GET请求到 `/api/oauth2/clients/{id}`
|
||||||
|
2. 验证返回正确的客户端信息
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回完整的客户端信息
|
||||||
|
|
||||||
|
#### 5.3 按clientId查询OAuth2凭证
|
||||||
|
**用例ID**: TC-OAUTH2-003
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送GET请求到 `/api/oauth2/clients/client-id/{clientId}`
|
||||||
|
2. 验证返回正确的客户端信息
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回指定clientId的客户端
|
||||||
|
|
||||||
|
#### 5.4 查询所有OAuth2客户端
|
||||||
|
**用例ID**: TC-OAUTH2-004
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送GET请求到 `/api/oauth2/clients`
|
||||||
|
2. 验证返回客户端列表
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回客户端数组
|
||||||
|
|
||||||
|
#### 5.5 更新OAuth2客户端
|
||||||
|
**用例ID**: TC-OAUTH2-005
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送PUT请求到 `/api/oauth2/clients/{id}`
|
||||||
|
2. 提供更新的客户端信息
|
||||||
|
3. 验证更新成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 返回更新后的客户端信息
|
||||||
|
|
||||||
|
#### 5.6 删除OAuth2客户端
|
||||||
|
**用例ID**: TC-OAUTH2-006
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送DELETE请求到 `/api/oauth2/clients/{id}`
|
||||||
|
2. 验证删除成功
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 204
|
||||||
|
|
||||||
|
### 6. 权限验证测试
|
||||||
|
|
||||||
|
#### 6.1 无token访问受保护资源
|
||||||
|
**用例ID**: TC-PERM-001
|
||||||
|
**优先级**: P0
|
||||||
|
**测试步骤**:
|
||||||
|
1. 不携带token访问需要认证的API
|
||||||
|
2. 验证返回401
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 401
|
||||||
|
- 返回认证失败消息
|
||||||
|
|
||||||
|
#### 6.2 使用过期token访问
|
||||||
|
**用例ID**: TC-PERM-002
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 使用已过期的token访问API
|
||||||
|
2. 验证返回401
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 401
|
||||||
|
- 返回token无效消息
|
||||||
|
|
||||||
|
#### 6.3 使用已登出的token访问
|
||||||
|
**用例ID**: TC-PERM-003
|
||||||
|
**优先级**: P1
|
||||||
|
**前置条件**: token已被登出
|
||||||
|
**测试步骤**:
|
||||||
|
1. 使用已加入黑名单的token访问API
|
||||||
|
2. 验证返回401
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 401
|
||||||
|
- 返回token无效消息
|
||||||
|
|
||||||
|
#### 6.4 无权限访问资源
|
||||||
|
**用例ID**: TC-PERM-004
|
||||||
|
**优先级**: P1
|
||||||
|
**前置条件**: 用户没有访问资源的权限
|
||||||
|
**测试步骤**:
|
||||||
|
1. 使用普通用户token访问需要ADMIN权限的资源
|
||||||
|
2. 验证返回403
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 403
|
||||||
|
- 返回权限不足消息
|
||||||
|
|
||||||
|
### 7. 边界条件和异常测试
|
||||||
|
|
||||||
|
#### 7.1 创建重复用户名
|
||||||
|
**用例ID**: TC-EDGE-001
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 创建用户A
|
||||||
|
2. 使用相同的用户名创建用户B
|
||||||
|
3. 验证返回错误
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 400或409
|
||||||
|
- 返回用户名已存在错误
|
||||||
|
|
||||||
|
#### 7.2 创建重复邮箱
|
||||||
|
**用例ID**: TC-EDGE-002
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 创建用户A
|
||||||
|
2. 使用相同的邮箱创建用户B
|
||||||
|
3. 验证返回错误
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 400或409
|
||||||
|
- 返回邮箱已存在错误
|
||||||
|
|
||||||
|
#### 7.3 查询不存在的资源
|
||||||
|
**用例ID**: TC-EDGE-003
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 查询不存在的用户ID
|
||||||
|
2. 验证返回404
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 404
|
||||||
|
- 返回资源未找到消息
|
||||||
|
|
||||||
|
#### 7.4 无效的请求参数
|
||||||
|
**用例ID**: TC-EDGE-004
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送缺少必填字段的请求
|
||||||
|
2. 验证返回400
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 400
|
||||||
|
- 返回参数验证错误
|
||||||
|
|
||||||
|
#### 7.5 超长字段输入
|
||||||
|
**用例ID**: TC-EDGE-005
|
||||||
|
**优先级**: P2
|
||||||
|
**测试步骤**:
|
||||||
|
1. 发送超长用户名或邮箱
|
||||||
|
2. 验证返回400
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 400
|
||||||
|
- 返回字段长度超限错误
|
||||||
|
|
||||||
|
### 8. 性能测试
|
||||||
|
|
||||||
|
#### 8.1 并发登录测试
|
||||||
|
**用例ID**: TC-PERF-001
|
||||||
|
**优先级**: P2
|
||||||
|
**测试步骤**:
|
||||||
|
1. 模拟100个并发登录请求
|
||||||
|
2. 验证所有请求都能正常响应
|
||||||
|
3. 记录响应时间
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- 所有请求成功
|
||||||
|
- 平均响应时间 < 500ms
|
||||||
|
- 无错误发生
|
||||||
|
|
||||||
|
#### 8.2 批量查询性能测试
|
||||||
|
**用例ID**: TC-PERF-002
|
||||||
|
**优先级**: P2
|
||||||
|
**测试步骤**:
|
||||||
|
1. 创建1000个测试用户
|
||||||
|
2. 查询所有用户
|
||||||
|
3. 记录响应时间
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- HTTP状态码: 200
|
||||||
|
- 响应时间 < 1000ms
|
||||||
|
- 返回完整数据
|
||||||
|
|
||||||
|
### 9. 数据一致性测试
|
||||||
|
|
||||||
|
#### 9.1 缓存一致性测试
|
||||||
|
**用例ID**: TC-CONSIST-001
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 查询用户A(首次查询,从数据库)
|
||||||
|
2. 更新用户A
|
||||||
|
3. 再次查询用户A(应从缓存获取)
|
||||||
|
4. 验证返回更新后的数据
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- 第二次查询返回更新后的数据
|
||||||
|
- 缓存被正确失效
|
||||||
|
|
||||||
|
#### 9.2 审计日志测试
|
||||||
|
**用例ID**: TC-CONSIST-002
|
||||||
|
**优先级**: P1
|
||||||
|
**测试步骤**:
|
||||||
|
1. 创建用户
|
||||||
|
2. 更新用户
|
||||||
|
3. 删除用户
|
||||||
|
4. 查询审计日志
|
||||||
|
5. 验证所有操作都被记录
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
- 所有操作都被记录
|
||||||
|
- 审计日志包含完整的变更信息
|
||||||
|
|
||||||
|
## 测试执行计划
|
||||||
|
|
||||||
|
### 测试优先级
|
||||||
|
- P0: 核心功能,必须全部通过
|
||||||
|
- P1: 重要功能,应该全部通过
|
||||||
|
- P2: 辅助功能,尽量通过
|
||||||
|
|
||||||
|
### 测试顺序
|
||||||
|
1. 认证授权测试
|
||||||
|
2. 用户管理测试
|
||||||
|
3. 角色管理测试
|
||||||
|
4. 字典管理测试
|
||||||
|
5. OAuth2客户端管理测试
|
||||||
|
6. 权限验证测试
|
||||||
|
7. 边界条件和异常测试
|
||||||
|
8. 性能测试
|
||||||
|
9. 数据一致性测试
|
||||||
|
|
||||||
|
### 测试数据准备
|
||||||
|
- 自动化创建测试用户、角色、字典数据
|
||||||
|
- 测试完成后自动清理
|
||||||
|
- 使用事务回滚确保测试隔离
|
||||||
|
|
||||||
|
## 测试报告
|
||||||
|
|
||||||
|
测试报告应包含以下内容:
|
||||||
|
1. 测试执行摘要
|
||||||
|
2. 通过/失败用例统计
|
||||||
|
3. 失败用例详情
|
||||||
|
4. 性能指标
|
||||||
|
5. 缺陷列表
|
||||||
|
6. 测试覆盖率
|
||||||
|
|
||||||
|
## 缺陷分类
|
||||||
|
|
||||||
|
- 严重: 系统崩溃、数据丢失
|
||||||
|
- 高: 核心功能不可用
|
||||||
|
- 中: 功能部分不可用
|
||||||
|
- 低: 界面、文案问题
|
||||||
@@ -0,0 +1,339 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: false
|
||||||
|
description: 6A工作流
|
||||||
|
---
|
||||||
|
|
||||||
|
# 6A 工作流执行规范
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
6A 工作流是一种系统化的软件开发方法论,通过六个阶段确保项目高质量交付:
|
||||||
|
|
||||||
|
Align(对齐) → Architect(架构) → Atomize(原子化) → Approve(审批) → Automate(自动化) → Assess(评估)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 阶段 1: Align 对齐阶段
|
||||||
|
|
||||||
|
### 🎯 目标
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
模糊需求 → 精确规范
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📋 执行步骤
|
||||||
|
|
||||||
|
1.**项目上下文分析**
|
||||||
|
|
||||||
|
- 分析现有项目结构、技术栈、架构模式、依赖关系
|
||||||
|
- 分析现有代码模式、文档和约定
|
||||||
|
- 理解业务域和数据模型
|
||||||
|
|
||||||
|
2.**需求理解确认**
|
||||||
|
|
||||||
|
- 创建 `.trae/docs/任务名/ALIGNMENT_[任务名].md`
|
||||||
|
- 包含项目和任务特性规范
|
||||||
|
- 包含原始需求、边界确认、需求理解、疑问澄清
|
||||||
|
|
||||||
|
3.**智能决策策略**
|
||||||
|
|
||||||
|
- 自动识别歧义和不确定性
|
||||||
|
- 生成结构化问题清单(按优先级排序)
|
||||||
|
- 优先基于现有项目内容和行业知识进行决策
|
||||||
|
- 有人员倾向或不确定的问题主动中断并询问
|
||||||
|
- 基于回答更新理解和规范
|
||||||
|
|
||||||
|
4.**中断并询问关键决策点**
|
||||||
|
|
||||||
|
- 主动中断询问,迭代执行智能决策策略
|
||||||
|
|
||||||
|
5.**最终共识**
|
||||||
|
|
||||||
|
- 生成 `.trae/docs/任务名/CONSENSUS_[任务名].md` 包含:
|
||||||
|
- 明确的需求描述和验收标准
|
||||||
|
- 技术实现方案、技术约束和集成方案
|
||||||
|
- 任务边界限制和验收标准
|
||||||
|
- 确认所有不确定性已解决
|
||||||
|
|
||||||
|
### ✅ 质量门控
|
||||||
|
|
||||||
|
- [ ] 需求边界清晰无歧义
|
||||||
|
- [ ] 技术方案与现有架构对齐
|
||||||
|
- [ ] 验收标准具体可测试
|
||||||
|
- [ ] 所有关键假设已确认
|
||||||
|
- [ ] 项目特性规范已对齐
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 阶段 2: Architect 架构阶段
|
||||||
|
|
||||||
|
### 🎯 目标
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
共识文档 → 系统架构 → 模块设计 → 接口规范
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📋 执行步骤
|
||||||
|
|
||||||
|
1.**系统分层设计**
|
||||||
|
|
||||||
|
- 基于 CONSENSUS、ALIGNMENT 文档设计架构
|
||||||
|
- 生成 `.trae/docs/任务名/DESIGN_[任务名].md` 包含:
|
||||||
|
- 整体架构图(mermaid 绘制)
|
||||||
|
- 分层设计和核心组件
|
||||||
|
- 模块依赖关系图
|
||||||
|
- 接口契约定义
|
||||||
|
- 数据流向图
|
||||||
|
- 异常处理策略
|
||||||
|
|
||||||
|
2.**设计原则**
|
||||||
|
|
||||||
|
- 严格按照任务范围,避免过度设计
|
||||||
|
- 确保与现有系统架构一致
|
||||||
|
- 复用现有组件和模式
|
||||||
|
|
||||||
|
### ✅ 质量门控
|
||||||
|
|
||||||
|
- [ ] 架构图清晰准确
|
||||||
|
- [ ] 接口定义完整
|
||||||
|
- [ ] 与现有系统无冲突
|
||||||
|
- [ ] 设计可行性验证
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 阶段 3: Atomize 原子化阶段
|
||||||
|
|
||||||
|
### 🎯 目标
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
架构设计 → 拆分任务 → 明确接口 → 依赖关系
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📋 执行步骤
|
||||||
|
|
||||||
|
1.**子任务拆分**
|
||||||
|
|
||||||
|
- 基于 DESIGN 文档生成 `.trae/docs/任务名/TASK_[任务名].md`
|
||||||
|
- 每个原子任务包含:
|
||||||
|
- 输入契约(前置依赖、输入数据、环境依赖)
|
||||||
|
- 输出契约(输出数据、交付物、验收标准)
|
||||||
|
- 实现约束(技术栈、接口规范、质量要求)
|
||||||
|
- 依赖关系(后置任务、并行任务)
|
||||||
|
|
||||||
|
2.**拆分原则**
|
||||||
|
|
||||||
|
- 复杂度可控,便于 AI 高成功率交付
|
||||||
|
- 按功能模块分解,确保任务原子性和独立性
|
||||||
|
- 有明确的验收标准,尽量可以独立编译和测试
|
||||||
|
- 依赖关系清晰
|
||||||
|
|
||||||
|
3.**生成任务依赖图**
|
||||||
|
|
||||||
|
- 使用 mermaid 绘制任务依赖关系图
|
||||||
|
|
||||||
|
### ✅ 质量门控
|
||||||
|
|
||||||
|
- [ ] 任务覆盖完整需求
|
||||||
|
- [ ] 依赖关系无循环
|
||||||
|
- [ ] 每个任务都可独立验证
|
||||||
|
- [ ] 复杂度评估合理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 阶段 4: Approve 审批阶段
|
||||||
|
|
||||||
|
### 🎯 目标
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
原子任务 → 人工审查 → 迭代修改 → 按文档执行
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📋 执行步骤
|
||||||
|
|
||||||
|
1.**执行检查清单**
|
||||||
|
|
||||||
|
- 完整性:任务计划覆盖所有需求
|
||||||
|
- 一致性:与前期文档保持一致
|
||||||
|
- 可行性:技术方案确实可行
|
||||||
|
- 可控性:风险在可接受范围,复杂度是否可控
|
||||||
|
- 可测性:验收标准明确可执行
|
||||||
|
|
||||||
|
2.**最终确认清单**
|
||||||
|
|
||||||
|
- 明确的实现需求(无歧义)
|
||||||
|
- 明确的子任务定义
|
||||||
|
- 明确的边界和限制
|
||||||
|
- 明确的验收标准
|
||||||
|
- 代码、测试、文档质量标准
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 阶段 5: Automate 自动化执行
|
||||||
|
|
||||||
|
### 🎯 目标
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
按节点执行 → 编写测试 → 实现代码 → 文档同步
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📋 执行步骤
|
||||||
|
|
||||||
|
1.**逐步实施子任务**
|
||||||
|
|
||||||
|
- 创建 `.trae/docs/任务名/ACCEPTANCE_[任务名].md` 记录完成情况
|
||||||
|
|
||||||
|
2.**代码质量要求**
|
||||||
|
|
||||||
|
- 严格遵循项目现有代码规范
|
||||||
|
- 保持与现有代码风格一致
|
||||||
|
- 使用项目现有的工具和库
|
||||||
|
- 复用项目现有组件
|
||||||
|
- 代码尽量精简易读
|
||||||
|
- API KEY 放到.env 文件中并且不要提交 git
|
||||||
|
|
||||||
|
3.**异常处理**
|
||||||
|
|
||||||
|
- 遇到不确定问题立刻中断执行
|
||||||
|
- 在 TASK 文档中记录问题详细信息和位置
|
||||||
|
- 寻求人工澄清后继续
|
||||||
|
|
||||||
|
4.**逐步实施流程**
|
||||||
|
|
||||||
|
按任务依赖顺序执行,对每个子任务执行:
|
||||||
|
|
||||||
|
- 执行前检查(验证输入契约、环境准备、依赖满足)
|
||||||
|
- 实现核心逻辑(按设计文档编写代码)
|
||||||
|
- 编写单元测试(边界条件、异常情况)
|
||||||
|
- 运行验证测试
|
||||||
|
- 更新相关文档
|
||||||
|
- 每完成一个任务立即验证
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 阶段 6: Assess 评估阶段
|
||||||
|
|
||||||
|
### 🎯 目标
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
执行结果 → 质量评估 → 文档更新 → 交付确认
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📋 执行步骤
|
||||||
|
|
||||||
|
1.**验证执行结果**
|
||||||
|
|
||||||
|
- 更新 `.trae/docs/任务名/ACCEPTANCE_[任务名].md`
|
||||||
|
- 整体验收检查:
|
||||||
|
- 所有需求已实现
|
||||||
|
- 验收标准全部满足
|
||||||
|
- 项目编译通过
|
||||||
|
- 所有测试通过
|
||||||
|
- 功能完整性验证
|
||||||
|
- 实现与设计文档一致
|
||||||
|
|
||||||
|
2.**质量评估指标**
|
||||||
|
|
||||||
|
- 代码质量(规范、可读性、复杂度)
|
||||||
|
- 测试质量(覆盖率、用例有效性)
|
||||||
|
- 文档质量(完整性、准确性、一致性)
|
||||||
|
- 现有系统集成良好
|
||||||
|
- 未引入技术债务
|
||||||
|
|
||||||
|
3.**最终交付物**
|
||||||
|
|
||||||
|
- 生成 `.trae/docs/任务名/FINAL_[任务名].md`(项目总结报告)
|
||||||
|
- 生成 `.trae/docs/任务名/TODO_[任务名].md`(待办事宜和缺少的配置等)
|
||||||
|
|
||||||
|
4.**TODO 询问**
|
||||||
|
|
||||||
|
- 询问用户 TODO 的解决方式
|
||||||
|
- 精简明确待办事宜和缺少的配置
|
||||||
|
- 提供有用的操作指引
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术执行规范
|
||||||
|
|
||||||
|
### 🔐 安全规范
|
||||||
|
|
||||||
|
- API 密钥等敏感信息使用.env 文件管理
|
||||||
|
|
||||||
|
### 📝 文档同步
|
||||||
|
|
||||||
|
- 代码变更同时更新相关文档
|
||||||
|
|
||||||
|
### 🧪 测试策略
|
||||||
|
|
||||||
|
- 测试优先:先写测试,后写实现
|
||||||
|
- 边界覆盖:覆盖正常流程、边界条件、异常情况
|
||||||
|
|
||||||
|
### 💡 交互体验优化
|
||||||
|
|
||||||
|
#### 进度反馈
|
||||||
|
|
||||||
|
- 显示当前执行阶段
|
||||||
|
- 提供详细的执行步骤
|
||||||
|
- 标示完成情况
|
||||||
|
- 突出需要关注的问题
|
||||||
|
|
||||||
|
#### 异常处理机制
|
||||||
|
|
||||||
|
##### 中断条件
|
||||||
|
|
||||||
|
- 遇到无法自主决策的问题
|
||||||
|
- 觉得需要询问用户的问题
|
||||||
|
- 技术实现出现阻塞
|
||||||
|
- 文档不一致需要确认修正
|
||||||
|
|
||||||
|
##### 恢复策略
|
||||||
|
|
||||||
|
- 保存当前执行状态
|
||||||
|
- 记录问题详细信息
|
||||||
|
- 询问并等待人工干预
|
||||||
|
- 从中断点任务继续执行
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 附录:文档模板索引
|
||||||
|
|
||||||
|
| 阶段 | 文档名称 | 用途 |
|
||||||
|
|
||||||
|
| --------- | ----------------------- | -------------- |
|
||||||
|
|
||||||
|
| Align | ALIGNMENT\_[任务名].md | 需求理解与确认 |
|
||||||
|
|
||||||
|
| Align | CONSENSUS\_[任务名].md | 最终共识与规范 |
|
||||||
|
|
||||||
|
| Architect | DESIGN\_[任务名].md | 系统架构设计 |
|
||||||
|
|
||||||
|
| Atomize | TASK\_[任务名].md | 原子任务定义 |
|
||||||
|
|
||||||
|
| Automate | ACCEPTANCE\_[任务名].md | 执行过程记录 |
|
||||||
|
|
||||||
|
| Assess | FINAL\_[任务名].md | 项目总结报告 |
|
||||||
|
|
||||||
|
| Assess | TODO\_[任务名].md | 待办事宜清单 |
|
||||||
Vendored
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"sqltools.connections": [
|
||||||
|
{
|
||||||
|
"ssh": "Disabled",
|
||||||
|
"previewLimit": 50,
|
||||||
|
"server": "localhost",
|
||||||
|
"port": 55432,
|
||||||
|
"driver": "PostgreSQL",
|
||||||
|
"name": "local",
|
||||||
|
"database": "postgres",
|
||||||
|
"username": "postgres"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# novalon-manage-system
|
||||||
|
|
||||||
|
企业级后台管理系统
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
novalon-manage-system/
|
||||||
|
├── novalon-manage-api/ # 后端 API 项目
|
||||||
|
│ └── manage-sys/ # 系统管理模块
|
||||||
|
├── novalon-manage-web/ # 前端 Web 项目
|
||||||
|
└── docs/ # 文档
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
- Java 21
|
||||||
|
- Spring Boot 3.4.1
|
||||||
|
- Spring Security
|
||||||
|
- JWT Authentication
|
||||||
|
- PostgreSQL
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
- Vue 3 + TypeScript
|
||||||
|
- Ant Design Vue
|
||||||
|
- Pinia
|
||||||
|
- Vite
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd novalon-manage-api
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd novalon-manage-web
|
||||||
|
pnpm install
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## 功能模块
|
||||||
|
|
||||||
|
- 用户管理
|
||||||
|
- 角色管理
|
||||||
|
- 菜单管理
|
||||||
|
- 权限管理
|
||||||
|
- 操作日志
|
||||||
|
- 系统配置 (规划中)
|
||||||
|
- 审计中心 (规划中)
|
||||||
|
- 通知中心 (规划中)
|
||||||
|
- 文件管理 (规划中)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,435 @@
|
|||||||
|
# 系统配置与审计通知中心实施计划
|
||||||
|
|
||||||
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||||
|
|
||||||
|
**Goal:** 完成系统配置(字典管理、系统参数)、审计中心(登录日志、操作日志、异常追踪)、通知中心(系统公告、消息推送WebSocket)、文件管理(上传/下载/预览)的数据库持久化与功能完善
|
||||||
|
|
||||||
|
**Architecture:** 基于Spring R2DBC响应式架构,遵循现有分层模式(Handler->Service->Repository->Entity),使用H2/PostgreSQL + Caffeine缓存,消息推送采用WebSocket
|
||||||
|
|
||||||
|
**Tech Stack:** Spring WebFlux, Spring Data R2DBC, H2/PostgreSQL, Caffeine, WebSocket, Lombok
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第一阶段:数据层基础设施建设
|
||||||
|
|
||||||
|
### Task 1: 创建数据库表SQL脚本
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/sql/init.sql` (创建以下表)
|
||||||
|
|
||||||
|
**Step 1: 编写SQL脚本**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 字典类型表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_dict_type (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
dict_name VARCHAR(100) NOT NULL COMMENT '字典名称',
|
||||||
|
dict_type VARCHAR(100) NOT NULL UNIQUE COMMENT '字典类型',
|
||||||
|
status VARCHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
|
||||||
|
remark VARCHAR(500) COMMENT '备注',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 字典数据表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_dict_data (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
dict_sort INT DEFAULT 0 COMMENT '字典排序',
|
||||||
|
dict_label VARCHAR(100) NOT NULL COMMENT '字典标签',
|
||||||
|
dict_value VARCHAR(100) NOT NULL COMMENT '字典键值',
|
||||||
|
dict_type VARCHAR(100) NOT NULL COMMENT '字典类型',
|
||||||
|
css_class VARCHAR(100) COMMENT '样式属性',
|
||||||
|
list_class VARCHAR(100) COMMENT '表格回显样式',
|
||||||
|
is_default VARCHAR(1) DEFAULT 'N' COMMENT '是否默认(Y是 N否)',
|
||||||
|
status VARCHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 系统配置表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_config (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
config_name VARCHAR(100) NOT NULL COMMENT '配置名称',
|
||||||
|
config_key VARCHAR(100) NOT NULL UNIQUE COMMENT '配置键名',
|
||||||
|
config_value VARCHAR(500) NOT NULL COMMENT '配置值',
|
||||||
|
config_type VARCHAR(1) DEFAULT 'N' COMMENT '系统内置(Y是 N否)',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 登录日志表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_login_log (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
username VARCHAR(50) COMMENT '用户名',
|
||||||
|
ip VARCHAR(50) COMMENT 'IP地址',
|
||||||
|
location VARCHAR(255) COMMENT '登录位置',
|
||||||
|
browser VARCHAR(50) COMMENT '浏览器类型',
|
||||||
|
os VARCHAR(50) COMMENT '操作系统',
|
||||||
|
status VARCHAR(1) COMMENT '登录状态(0成功 1失败)',
|
||||||
|
message VARCHAR(255) COMMENT '提示消息',
|
||||||
|
login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 异常日志表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_exception_log (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
username VARCHAR(50) COMMENT '用户名',
|
||||||
|
title VARCHAR(100) COMMENT '异常标题',
|
||||||
|
exception_name VARCHAR(100) COMMENT '异常名称',
|
||||||
|
method_name VARCHAR(100) COMMENT '方法名称',
|
||||||
|
method_params TEXT COMMENT '方法参数',
|
||||||
|
exception_msg TEXT COMMENT '异常信息',
|
||||||
|
exception_stack TEXT COMMENT '堆栈信息',
|
||||||
|
ip VARCHAR(50) COMMENT 'IP地址',
|
||||||
|
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 系统公告表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_notice (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
notice_title VARCHAR(100) NOT NULL COMMENT '公告标题',
|
||||||
|
notice_type VARCHAR(1) DEFAULT '1' COMMENT '公告类型(1通知 2公告)',
|
||||||
|
notice_content TEXT COMMENT '公告内容',
|
||||||
|
status VARCHAR(1) DEFAULT '0' COMMENT '公告状态(0正常 1关闭)',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 文件管理表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_file (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
file_name VARCHAR(255) NOT NULL COMMENT '文件名',
|
||||||
|
file_path VARCHAR(500) NOT NULL COMMENT '文件路径',
|
||||||
|
file_size VARCHAR(50) COMMENT '文件大小',
|
||||||
|
file_type VARCHAR(50) COMMENT '文件类型',
|
||||||
|
storage_type VARCHAR(20) DEFAULT 'local' COMMENT '存储类型(local/oss/s3)',
|
||||||
|
create_by VARCHAR(50) COMMENT '创建者',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 用户消息表(消息推送用)
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_user_message (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
user_id BIGINT NOT NULL COMMENT '接收用户ID',
|
||||||
|
title VARCHAR(100) COMMENT '消息标题',
|
||||||
|
content TEXT COMMENT '消息内容',
|
||||||
|
message_type VARCHAR(1) DEFAULT '1' COMMENT '消息类型(1系统 2通知)',
|
||||||
|
is_read VARCHAR(1) DEFAULT '0' COMMENT '是否已读(0未读 1已读)',
|
||||||
|
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: 提交SQL脚本**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add docs/sql/init.sql
|
||||||
|
git commit -m "feat: 添加系统配置和审计模块数据库表脚本"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: 创建字典类型Entity
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/infrastructure/db/entity/SysDictTypeEntity.java`
|
||||||
|
- Modify: `novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/infrastructure/db/entity/BaseEntity.java`
|
||||||
|
|
||||||
|
**Step 1: 在BaseEntity添加公共字段**
|
||||||
|
|
||||||
|
查看现有BaseEntity确保包含:
|
||||||
|
- id, createdAt, updatedAt, deletedAt
|
||||||
|
|
||||||
|
**Step 2: 创建SysDictTypeEntity**
|
||||||
|
|
||||||
|
```java
|
||||||
|
package cn.novalon.manage.sys.infrastructure.db.entity;
|
||||||
|
|
||||||
|
import org.springframework.data.annotation.Id;
|
||||||
|
import org.springframework.data.relational.core.mapping.Column;
|
||||||
|
import org.springframework.data.relational.core.mapping.Table;
|
||||||
|
|
||||||
|
@Table("sys_dict_type")
|
||||||
|
public class SysDictTypeEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column("dict_name")
|
||||||
|
private String dictName;
|
||||||
|
|
||||||
|
@Column("dict_type")
|
||||||
|
private String dictType;
|
||||||
|
|
||||||
|
@Column("status")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Column("remark")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Column("created_at")
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Column("updated_at")
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
@Column("deleted_at")
|
||||||
|
private LocalDateTime deletedAt;
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3: 提交**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add entity/SysDictTypeEntity.java
|
||||||
|
git commit -m "feat: 添加字典类型Entity"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: 创建其他Entity(字典数据、系统配置、登录日志、异常日志、公告、文件、消息)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `infrastructure/db/entity/SysDictDataEntity.java`
|
||||||
|
- Create: `infrastructure/db/entity/SysConfigEntity.java`
|
||||||
|
- Create: `infrastructure/db/entity/SysLoginLogEntity.java`
|
||||||
|
- Create: `infrastructure/db/entity/SysExceptionLogEntity.java`
|
||||||
|
- Create: `infrastructure/db/entity/SysNoticeEntity.java`
|
||||||
|
- Create: `infrastructure/db/entity/SysFileEntity.java`
|
||||||
|
- Create: `infrastructure/db/entity/SysUserMessageEntity.java`
|
||||||
|
|
||||||
|
**每Entity结构:** 继承BaseEntity,添加对应字段的@Column注解和getter/setter
|
||||||
|
|
||||||
|
**示例 - SysConfigEntity:**
|
||||||
|
```java
|
||||||
|
@Table("sys_config")
|
||||||
|
public class SysConfigEntity {
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column("config_name")
|
||||||
|
private String configName;
|
||||||
|
|
||||||
|
@Column("config_key")
|
||||||
|
private String configKey;
|
||||||
|
|
||||||
|
@Column("config_value")
|
||||||
|
private String configValue;
|
||||||
|
|
||||||
|
@Column("config_type")
|
||||||
|
private String configType;
|
||||||
|
|
||||||
|
@Column("created_at")
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Column("updated_at")
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
@Column("deleted_at")
|
||||||
|
private LocalDateTime deletedAt;
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: 创建Repository接口
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `infrastructure/db/repository/SysDictTypeRepository.java`
|
||||||
|
- Create: `infrastructure/db/repository/SysDictDataRepository.java`
|
||||||
|
- Create: `infrastructure/db/repository/SysConfigRepository.java`
|
||||||
|
- Create: `infrastructure/db/repository/SysLoginLogRepository.java`
|
||||||
|
- Create: `infrastructure/db/repository/SysExceptionLogRepository.java`
|
||||||
|
- Create: `infrastructure/db/repository/SysNoticeRepository.java`
|
||||||
|
- Create: `infrastructure/db/repository/SysFileRepository.java`
|
||||||
|
- Create: `infrastructure/db/repository/SysUserMessageRepository.java`
|
||||||
|
|
||||||
|
**每Repository:** 继承 ReactiveCrudRepository<Entity, Long>
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
```java
|
||||||
|
package cn.novalon.manage.sys.infrastructure.db.repository;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.entity.SysDictTypeEntity;
|
||||||
|
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface SysDictTypeRepository extends ReactiveCrudRepository<SysDictTypeEntity, Long> {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第二阶段:领域层(Domain + Converter)
|
||||||
|
|
||||||
|
### Task 5: 更新Domain模型
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `core/domain/SysDictType.java` - 添加dictDataList字段
|
||||||
|
- Modify: `core/domain/SysDictData.java` - 确保字段完整
|
||||||
|
- Modify: `core/domain/SysConfig.java` - 确保字段完整
|
||||||
|
- Modify: `core/domain/SysLoginLog.java` - 确保字段完整
|
||||||
|
- Create: `core/domain/SysExceptionLog.java`
|
||||||
|
- Create: `core/domain/SysUserMessage.java`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: 创建Converter转换器
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `infrastructure/db/converter/SysDictTypeConverter.java`
|
||||||
|
- Create: `infrastructure/db/converter/SysDictDataConverter.java`
|
||||||
|
- Create: `infrastructure/db/converter/SysConfigConverter.java`
|
||||||
|
- Create: `infrastructure/db/converter/SysLoginLogConverter.java`
|
||||||
|
- Create: `infrastructure/db/converter/SysExceptionLogConverter.java`
|
||||||
|
- Create: `infrastructure/db/converter/SysNoticeConverter.java`
|
||||||
|
- Create: `infrastructure/db/converter/SysFileConverter.java`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第三阶段:服务层(Service)
|
||||||
|
|
||||||
|
### Task 7: 创建Service接口和实现
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `core/service/ISysDictTypeService.java` + `impl/SysDictTypeServiceImpl.java`
|
||||||
|
- Create: `core/service/ISysDictDataService.java` + `impl/SysDictDataServiceImpl.java`
|
||||||
|
- Create: `core/service/ISysConfigService.java` + `impl/SysConfigServiceImpl.java`
|
||||||
|
- Create: `core/service/ISysLoginLogService.java` + `impl/SysLoginLogServiceImpl.java`
|
||||||
|
- Create: `core/service/ISysExceptionLogService.java` + `impl/SysExceptionLogServiceImpl.java`
|
||||||
|
- Create: `core/service/ISysNoticeService.java` + `impl/SysNoticeServiceImpl.java`
|
||||||
|
- Create: `core/service/ISysFileService.java` + `impl/SysFileServiceImpl.java`
|
||||||
|
- Create: `core/service/ISysUserMessageService.java` + `impl/SysUserMessageServiceImpl.java`
|
||||||
|
|
||||||
|
**核心方法:**
|
||||||
|
- CRUD操作
|
||||||
|
- 字典: 根据类型查询数据列表
|
||||||
|
- 配置: 根据key查询value
|
||||||
|
- 日志: 分页查询、按条件筛选
|
||||||
|
- 消息: 查询用户未读消息
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第四阶段:Handler层(API接口)
|
||||||
|
|
||||||
|
### Task 8: 重构Handler接入数据库
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `handler/sys/SysDictHandler.java` - 注入Service替换内存Map
|
||||||
|
- Modify: `handler/sys/SysConfigHandler.java` - 注入Service替换内存Map
|
||||||
|
- Modify: `handler/sys/SysLogHandler.java` - 添加登录日志和异常日志查询
|
||||||
|
- Modify: `handler/sys/SysNoticeHandler.java` - 注入Service替换内存Map
|
||||||
|
- Modify: `handler/sys/SysFileHandler.java` - 完善文件预览功能
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第五阶段:消息推送(WebSocket)
|
||||||
|
|
||||||
|
### Task 9: WebSocket配置
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `config/WebSocketConfig.java`
|
||||||
|
- Create: `handler/websocket/WebSocketHandler.java`
|
||||||
|
- Create: `service/impl/WebSocketService.java`
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSocket
|
||||||
|
public class WebSocketConfig implements WebSocketConfigurer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||||
|
registry.addHandler(webSocketHandler(), "/ws")
|
||||||
|
.setAllowedOrigins("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public WebSocketHandler webSocketHandler() {
|
||||||
|
return new WebSocketHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 10: 消息推送Service
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `core/service/IWebSocketService.java`
|
||||||
|
- Create: `core/service/impl/WebSocketServiceImpl.java`
|
||||||
|
|
||||||
|
**功能:**
|
||||||
|
- 发送消息给指定用户
|
||||||
|
- 发送消息给所有在线用户
|
||||||
|
- 用户上线/下线跟踪
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第六阶段:完善文件预览
|
||||||
|
|
||||||
|
### Task 11: 文件预览支持
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `handler/sys/SysFileHandler.java` - 添加预览接口
|
||||||
|
- 创建DTO: `dto/response/FilePreviewResponse.java`
|
||||||
|
|
||||||
|
**预览支持:**
|
||||||
|
- 图片: 返回Base64或直接流
|
||||||
|
- PDF: 返回PDF流
|
||||||
|
- 文本: 返回文本内容
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第七阶段:异常日志自动记录
|
||||||
|
|
||||||
|
### Task 12: 全局异常处理器
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `handler/GlobalExceptionHandler.java`
|
||||||
|
- Create: `config/GlobalExceptionHandlerConfig.java`
|
||||||
|
|
||||||
|
**功能:**
|
||||||
|
- 捕获所有Controller异常
|
||||||
|
- 自动记录到sys_exception_log表
|
||||||
|
- 返回统一格式的错误响应
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第八阶段:测试
|
||||||
|
|
||||||
|
### Task 13: 单元测试
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `test/service/SysDictTypeServiceTest.java`
|
||||||
|
- Create: `test/service/SysConfigServiceTest.java`
|
||||||
|
- Create: `test/service/SysLoginLogServiceTest.java`
|
||||||
|
- Create: `test/handler/SysDictHandlerTest.java`
|
||||||
|
- Create: `test/handler/SysNoticeHandlerTest.java`
|
||||||
|
|
||||||
|
**测试覆盖:**
|
||||||
|
- Service层CRUD操作
|
||||||
|
- Handler层API响应
|
||||||
|
- 异常场景处理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实施顺序
|
||||||
|
|
||||||
|
1. Task 1: SQL脚本
|
||||||
|
2. Task 2-4: Entity + Repository
|
||||||
|
3. Task 5-6: Domain + Converter
|
||||||
|
4. Task 7: Service层
|
||||||
|
5. Task 8: Handler层重构
|
||||||
|
6. Task 9-10: WebSocket消息推送
|
||||||
|
7. Task 11: 文件预览
|
||||||
|
8. Task 12: 异常日志
|
||||||
|
9. Task 13: 测试
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
-- 系统配置与审计通知中心数据库表脚本
|
||||||
|
-- 数据库: H2/PostgreSQL
|
||||||
|
|
||||||
|
-- 字典类型表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_dict_type (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
dict_name VARCHAR(100) NOT NULL COMMENT '字典名称',
|
||||||
|
dict_type VARCHAR(100) NOT NULL UNIQUE COMMENT '字典类型',
|
||||||
|
status VARCHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
|
||||||
|
remark VARCHAR(500) COMMENT '备注',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 字典数据表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_dict_data (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
dict_sort INT DEFAULT 0 COMMENT '字典排序',
|
||||||
|
dict_label VARCHAR(100) NOT NULL COMMENT '字典标签',
|
||||||
|
dict_value VARCHAR(100) NOT NULL COMMENT '字典键值',
|
||||||
|
dict_type VARCHAR(100) NOT NULL COMMENT '字典类型',
|
||||||
|
css_class VARCHAR(100) COMMENT '样式属性',
|
||||||
|
list_class VARCHAR(100) COMMENT '表格回显样式',
|
||||||
|
is_default VARCHAR(1) DEFAULT 'N' COMMENT '是否默认(Y是 N否)',
|
||||||
|
status VARCHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 系统配置表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_config (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
config_name VARCHAR(100) NOT NULL COMMENT '配置名称',
|
||||||
|
config_key VARCHAR(100) NOT NULL UNIQUE COMMENT '配置键名',
|
||||||
|
config_value VARCHAR(500) NOT NULL COMMENT '配置值',
|
||||||
|
config_type VARCHAR(1) DEFAULT 'N' COMMENT '系统内置(Y是 N否)',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 登录日志表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_login_log (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
username VARCHAR(50) COMMENT '用户名',
|
||||||
|
ip VARCHAR(50) COMMENT 'IP地址',
|
||||||
|
location VARCHAR(255) COMMENT '登录位置',
|
||||||
|
browser VARCHAR(50) COMMENT '浏览器类型',
|
||||||
|
os VARCHAR(50) COMMENT '操作系统',
|
||||||
|
status VARCHAR(1) COMMENT '登录状态(0成功 1失败)',
|
||||||
|
message VARCHAR(255) COMMENT '提示消息',
|
||||||
|
login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 异常日志表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_exception_log (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
username VARCHAR(50) COMMENT '用户名',
|
||||||
|
title VARCHAR(100) COMMENT '异常标题',
|
||||||
|
exception_name VARCHAR(100) COMMENT '异常名称',
|
||||||
|
method_name VARCHAR(100) COMMENT '方法名称',
|
||||||
|
method_params TEXT COMMENT '方法参数',
|
||||||
|
exception_msg TEXT COMMENT '异常信息',
|
||||||
|
exception_stack TEXT COMMENT '堆栈信息',
|
||||||
|
ip VARCHAR(50) COMMENT 'IP地址',
|
||||||
|
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 系统公告表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_notice (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
notice_title VARCHAR(100) NOT NULL COMMENT '公告标题',
|
||||||
|
notice_type VARCHAR(1) DEFAULT '1' COMMENT '公告类型(1通知 2公告)',
|
||||||
|
notice_content TEXT COMMENT '公告内容',
|
||||||
|
status VARCHAR(1) DEFAULT '0' COMMENT '公告状态(0正常 1关闭)',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 文件管理表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_file (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
file_name VARCHAR(255) NOT NULL COMMENT '文件名',
|
||||||
|
file_path VARCHAR(500) NOT NULL COMMENT '文件路径',
|
||||||
|
file_size VARCHAR(50) COMMENT '文件大小',
|
||||||
|
file_type VARCHAR(50) COMMENT '文件类型',
|
||||||
|
storage_type VARCHAR(20) DEFAULT 'local' COMMENT '存储类型(local/oss/s3)',
|
||||||
|
create_by VARCHAR(50) COMMENT '创建者',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 用户消息表(消息推送用)
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_user_message (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
user_id BIGINT NOT NULL COMMENT '接收用户ID',
|
||||||
|
title VARCHAR(100) COMMENT '消息标题',
|
||||||
|
content TEXT COMMENT '消息内容',
|
||||||
|
message_type VARCHAR(1) DEFAULT '1' COMMENT '消息类型(1系统 2通知)',
|
||||||
|
is_read VARCHAR(1) DEFAULT '0' COMMENT '是否已读(0未读 1已读)',
|
||||||
|
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 初始化默认系统配置数据
|
||||||
|
INSERT INTO sys_config (config_name, config_key, config_value, config_type) VALUES
|
||||||
|
('系统名称', 'sys.system.name', 'Novalon管理系统', 'Y'),
|
||||||
|
('系统版本', 'sys.system.version', '1.0.0', 'Y'),
|
||||||
|
('文件上传最大大小', 'sys.file.maxSize', '10485760', 'Y'),
|
||||||
|
('文件上传允许类型', 'sys.file.allowedTypes', 'jpg,jpeg,png,pdf,doc,docx,xls,xlsx', 'Y'),
|
||||||
|
('会话超时时间(分钟)', 'sys.session.timeout', '30', 'Y'),
|
||||||
|
('密码最小长度', 'sys.password.minLength', '6', 'Y');
|
||||||
|
|
||||||
|
-- 初始化默认字典类型
|
||||||
|
INSERT INTO sys_dict_type (dict_name, dict_type, status, remark) VALUES
|
||||||
|
('用户性别', 'sys_user_sex', '0', '用户性别列表'),
|
||||||
|
('菜单状态', 'sys_show_hide', '0', '菜单显示状态'),
|
||||||
|
('系统开关', 'sys_normal_disable', '0', '系统开关状态'),
|
||||||
|
('任务状态', 'sys_job_status', '0', '定时任务状态'),
|
||||||
|
('任务分组', 'sys_job_group', '0', '定时任务分组'),
|
||||||
|
('系统是否', 'sys_yes_no', '0', '系统是否列表');
|
||||||
|
|
||||||
|
-- 初始化默认字典数据
|
||||||
|
INSERT INTO sys_dict_data (dict_label, dict_value, dict_type, dict_sort, is_default, status) VALUES
|
||||||
|
('男', '0', 'sys_user_sex', 1, 'Y', '0'),
|
||||||
|
('女', '1', 'sys_user_sex', 2, 'N', '0'),
|
||||||
|
('未知', '2', 'sys_user_sex', 3, 'N', '0'),
|
||||||
|
('显示', '0', 'sys_show_hide', 1, 'Y', '0'),
|
||||||
|
('隐藏', '1', 'sys_show_hide', 2, 'N', '0'),
|
||||||
|
('正常', '0', 'sys_normal_disable', 1, 'Y', '0'),
|
||||||
|
('停用', '1', 'sys_normal_disable', 2, 'N', '0'),
|
||||||
|
('是', 'Y', 'sys_yes_no', 1, 'Y', '0'),
|
||||||
|
('否', 'N', 'sys_yes_no', 2, 'N', '0');
|
||||||
|
|
||||||
|
-- 创建索引
|
||||||
|
CREATE INDEX idx_sys_dict_type_dict_type ON sys_dict_type(dict_type);
|
||||||
|
CREATE INDEX idx_sys_dict_data_dict_type ON sys_dict_data(dict_type);
|
||||||
|
CREATE INDEX idx_sys_config_config_key ON sys_config(config_key);
|
||||||
|
CREATE INDEX idx_sys_login_log_username ON sys_login_log(username);
|
||||||
|
CREATE INDEX idx_sys_login_log_login_time ON sys_login_log(login_time);
|
||||||
|
CREATE INDEX idx_sys_exception_log_create_time ON sys_exception_log(create_time);
|
||||||
|
CREATE INDEX idx_sys_notice_status ON sys_notice(status);
|
||||||
|
CREATE INDEX idx_sys_file_create_by ON sys_file(create_by);
|
||||||
|
CREATE INDEX idx_sys_user_message_user_id ON sys_user_message(user_id);
|
||||||
|
CREATE INDEX idx_sys_user_message_is_read ON sys_user_message(is_read);
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# E2E测试环境配置
|
||||||
|
|
||||||
|
# API配置
|
||||||
|
API_BASE_URL=http://localhost:8080
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
DATABASE_HOST=localhost
|
||||||
|
DATABASE_PORT=5432
|
||||||
|
DATABASE_NAME=manage_system
|
||||||
|
DATABASE_USERNAME=postgres
|
||||||
|
DATABASE_PASSWORD=postgres
|
||||||
|
|
||||||
|
# 测试用户凭证
|
||||||
|
TEST_USERNAME=admin
|
||||||
|
TEST_PASSWORD=admin123
|
||||||
|
|
||||||
|
# 浏览器配置
|
||||||
|
HEADLESS_BROWSER=true
|
||||||
|
BROWSER_TYPE=chromium
|
||||||
|
|
||||||
|
# 超时配置(毫秒)
|
||||||
|
REQUEST_TIMEOUT=30000
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Pytest
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
|
# Allure
|
||||||
|
allure-results/
|
||||||
|
allure-report/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Playwright
|
||||||
|
.playwright/
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# E2E测试项目
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
本项目使用Python + Playwright框架对Novalon管理系统进行端到端测试。
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- **Python**: 3.9+
|
||||||
|
- **Playwright**: 1.40+
|
||||||
|
- **Pytest**: 7.0+
|
||||||
|
- **Allure**: 测试报告
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
e2e_tests/
|
||||||
|
├── __init__.py
|
||||||
|
├── conftest.py # Pytest配置和fixtures
|
||||||
|
├── pytest.ini # Pytest配置文件
|
||||||
|
├── requirements.txt # Python依赖
|
||||||
|
├── config/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ └── settings.py # 配置管理
|
||||||
|
├── pages/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── base_page.py # 基础页面类
|
||||||
|
│ ├── auth_page.py # 认证相关页面
|
||||||
|
│ ├── user_page.py # 用户管理页面
|
||||||
|
│ ├── role_page.py # 角色管理页面
|
||||||
|
│ └── dictionary_page.py # 字典管理页面
|
||||||
|
├── api/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── base_api.py # 基础API类
|
||||||
|
│ ├── auth_api.py # 认证API
|
||||||
|
│ ├── user_api.py # 用户API
|
||||||
|
│ ├── role_api.py # 角色API
|
||||||
|
│ └── dictionary_api.py # 字典API
|
||||||
|
├── tests/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── test_auth.py # 认证测试
|
||||||
|
│ ├── test_user.py # 用户管理测试
|
||||||
|
│ ├── test_role.py # 角色管理测试
|
||||||
|
│ ├── test_dictionary.py # 字典管理测试
|
||||||
|
│ └── test_oauth2.py # OAuth2测试
|
||||||
|
├── utils/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── data_generator.py # 测试数据生成器
|
||||||
|
│ ├── assertions.py # 断言工具
|
||||||
|
│ └── logger.py # 日志工具
|
||||||
|
└── reports/ # 测试报告目录
|
||||||
|
```
|
||||||
|
|
||||||
|
## 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装Python依赖
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# 安装Playwright浏览器
|
||||||
|
playwright install
|
||||||
|
```
|
||||||
|
|
||||||
|
## 运行测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 运行所有测试
|
||||||
|
pytest
|
||||||
|
|
||||||
|
# 运行特定测试文件
|
||||||
|
pytest tests/test_auth.py
|
||||||
|
|
||||||
|
# 运行特定测试用例
|
||||||
|
pytest tests/test_auth.py::test_login_success
|
||||||
|
|
||||||
|
# 生成Allure报告
|
||||||
|
pytest --alluredir=allure-results
|
||||||
|
allure serve allure-results
|
||||||
|
|
||||||
|
# 并发运行测试
|
||||||
|
pytest -n auto
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
在 `config/settings.py` 中配置:
|
||||||
|
- API基础URL
|
||||||
|
- 测试数据库连接
|
||||||
|
- 测试用户凭证
|
||||||
|
- 超时设置
|
||||||
|
|
||||||
|
## 测试数据管理
|
||||||
|
|
||||||
|
测试数据自动准备和清理:
|
||||||
|
- 每个测试用例独立运行
|
||||||
|
- 使用fixture自动创建和清理测试数据
|
||||||
|
- 支持数据回滚
|
||||||
|
|
||||||
|
## 持续集成
|
||||||
|
|
||||||
|
测试可在CI/CD流程中自动运行:
|
||||||
|
- GitHub Actions
|
||||||
|
- GitLab CI
|
||||||
|
- Jenkins
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
"""
|
||||||
|
E2E测试项目 - Novalon管理系统
|
||||||
|
使用Playwright进行端到端测试
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""API模块"""
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
"""
|
||||||
|
认证API
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Dict, Any
|
||||||
|
from httpx import AsyncClient, Response
|
||||||
|
from .base_api import BaseAPI
|
||||||
|
|
||||||
|
|
||||||
|
class AuthAPI(BaseAPI):
|
||||||
|
"""认证API"""
|
||||||
|
|
||||||
|
def __init__(self, client: AsyncClient):
|
||||||
|
super().__init__(client, "/api/auth")
|
||||||
|
|
||||||
|
async def login(self, username: str, password: str) -> Response:
|
||||||
|
"""用户登录"""
|
||||||
|
return await self.post("/login", json={
|
||||||
|
"username": username,
|
||||||
|
"password": password
|
||||||
|
})
|
||||||
|
|
||||||
|
async def refresh_token(self, refresh_token: str) -> Response:
|
||||||
|
"""刷新token"""
|
||||||
|
return await self.post("/refresh", json={
|
||||||
|
"refreshToken": refresh_token
|
||||||
|
})
|
||||||
|
|
||||||
|
async def logout(self, token: str) -> Response:
|
||||||
|
"""用户登出"""
|
||||||
|
return await self.post("/logout", headers={
|
||||||
|
"Authorization": f"Bearer {token}"
|
||||||
|
})
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
"""
|
||||||
|
基础API类
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from httpx import AsyncClient, Response
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAPI:
|
||||||
|
"""基础API类"""
|
||||||
|
|
||||||
|
def __init__(self, client: AsyncClient, base_url: str = ""):
|
||||||
|
self.client = client
|
||||||
|
self.base_url = base_url
|
||||||
|
|
||||||
|
async def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None, **kwargs) -> Response:
|
||||||
|
"""GET请求"""
|
||||||
|
url = f"{self.base_url}{endpoint}"
|
||||||
|
logger.info(f"GET {url} - Params: {params}")
|
||||||
|
response = await self.client.get(url, params=params, **kwargs)
|
||||||
|
logger.info(f"Response: {response.status_code}")
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def post(self, endpoint: str, data: Optional[Dict[str, Any]] = None, json: Optional[Dict[str, Any]] = None, **kwargs) -> Response:
|
||||||
|
"""POST请求"""
|
||||||
|
url = f"{self.base_url}{endpoint}"
|
||||||
|
logger.info(f"POST {url} - Data: {data} - JSON: {json}")
|
||||||
|
response = await self.client.post(url, data=data, json=json, **kwargs)
|
||||||
|
logger.info(f"Response: {response.status_code}")
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def put(self, endpoint: str, data: Optional[Dict[str, Any]] = None, json: Optional[Dict[str, Any]] = None, **kwargs) -> Response:
|
||||||
|
"""PUT请求"""
|
||||||
|
url = f"{self.base_url}{endpoint}"
|
||||||
|
logger.info(f"PUT {url} - Data: {data} - JSON: {json}")
|
||||||
|
response = await self.client.put(url, data=data, json=json, **kwargs)
|
||||||
|
logger.info(f"Response: {response.status_code}")
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def delete(self, endpoint: str, **kwargs) -> Response:
|
||||||
|
"""DELETE请求"""
|
||||||
|
url = f"{self.base_url}{endpoint}"
|
||||||
|
logger.info(f"DELETE {url}")
|
||||||
|
response = await self.client.delete(url, **kwargs)
|
||||||
|
logger.info(f"Response: {response.status_code}")
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def assert_status_code(self, response: Response, expected_status: int):
|
||||||
|
"""断言状态码"""
|
||||||
|
assert response.status_code == expected_status, f"Expected {expected_status}, got {response.status_code}. Response: {response.text}"
|
||||||
|
|
||||||
|
async def assert_response_contains(self, response: Response, key: str, value: Any = None):
|
||||||
|
"""断言响应包含指定字段"""
|
||||||
|
data = response.json()
|
||||||
|
assert key in data, f"Response does not contain key '{key}'"
|
||||||
|
if value is not None:
|
||||||
|
assert data[key] == value, f"Expected {value}, got {data[key]}"
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
"""
|
||||||
|
字典管理API
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Dict, Any
|
||||||
|
from httpx import AsyncClient, Response
|
||||||
|
from .base_api import BaseAPI
|
||||||
|
|
||||||
|
|
||||||
|
class DictionaryAPI(BaseAPI):
|
||||||
|
"""字典管理API"""
|
||||||
|
|
||||||
|
def __init__(self, client: AsyncClient):
|
||||||
|
super().__init__(client, "/api/dictionaries")
|
||||||
|
|
||||||
|
async def create_dictionary(self, dict_data: Dict[str, Any]) -> Response:
|
||||||
|
"""创建字典"""
|
||||||
|
return await self.post("", json=dict_data)
|
||||||
|
|
||||||
|
async def get_dictionary_by_id(self, dict_id: int) -> Response:
|
||||||
|
"""根据ID获取字典"""
|
||||||
|
return await self.get(f"/{dict_id}")
|
||||||
|
|
||||||
|
async def get_dictionaries_by_type(self, dict_type: str) -> Response:
|
||||||
|
"""根据类型获取字典"""
|
||||||
|
return await self.get(f"/type/{dict_type}")
|
||||||
|
|
||||||
|
async def get_all_dictionaries(self) -> Response:
|
||||||
|
"""获取所有字典"""
|
||||||
|
return await self.get("")
|
||||||
|
|
||||||
|
async def update_dictionary(self, dict_id: int, dict_data: Dict[str, Any]) -> Response:
|
||||||
|
"""更新字典"""
|
||||||
|
return await self.put(f"/{dict_id}", json=dict_data)
|
||||||
|
|
||||||
|
async def delete_dictionary(self, dict_id: int) -> Response:
|
||||||
|
"""删除字典"""
|
||||||
|
return await self.delete(f"/{dict_id}")
|
||||||
|
|
||||||
|
async def check_type_and_code_exists(self, dict_type: str, code: str) -> Response:
|
||||||
|
"""检查类型和编码是否存在"""
|
||||||
|
return await self.get("/check/exists", params={"type": dict_type, "code": code})
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
"""
|
||||||
|
角色管理API
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
from httpx import AsyncClient, Response
|
||||||
|
from .base_api import BaseAPI
|
||||||
|
|
||||||
|
|
||||||
|
class RoleAPI(BaseAPI):
|
||||||
|
"""角色管理API"""
|
||||||
|
|
||||||
|
def __init__(self, client: AsyncClient):
|
||||||
|
super().__init__(client, "/api/roles")
|
||||||
|
|
||||||
|
async def create_role(self, role_data: Dict[str, Any]) -> Response:
|
||||||
|
"""创建角色"""
|
||||||
|
return await self.post("", json=role_data)
|
||||||
|
|
||||||
|
async def get_role_by_id(self, role_id: int) -> Response:
|
||||||
|
"""根据ID获取角色"""
|
||||||
|
return await self.get(f"/{role_id}")
|
||||||
|
|
||||||
|
async def get_role_by_name(self, role_name: str) -> Response:
|
||||||
|
"""根据名称获取角色"""
|
||||||
|
return await self.get(f"/name/{role_name}")
|
||||||
|
|
||||||
|
async def get_all_roles(self, include_deleted: bool = False) -> Response:
|
||||||
|
"""获取所有角色"""
|
||||||
|
return await self.get("", params={"includeDeleted": include_deleted})
|
||||||
|
|
||||||
|
async def update_role(self, role_id: int, role_data: Dict[str, Any]) -> Response:
|
||||||
|
"""更新角色"""
|
||||||
|
return await self.put(f"/{role_id}", json=role_data)
|
||||||
|
|
||||||
|
async def delete_role(self, role_id: int) -> Response:
|
||||||
|
"""删除角色"""
|
||||||
|
return await self.delete(f"/{role_id}")
|
||||||
|
|
||||||
|
async def logical_delete_role(self, role_id: int) -> Response:
|
||||||
|
"""逻辑删除角色"""
|
||||||
|
return await self.delete(f"/{role_id}/logical")
|
||||||
|
|
||||||
|
async def logical_delete_roles(self, role_ids: List[int]) -> Response:
|
||||||
|
"""批量逻辑删除角色"""
|
||||||
|
return await self.post("/logical-delete", json=role_ids)
|
||||||
|
|
||||||
|
async def restore_role(self, role_id: int) -> Response:
|
||||||
|
"""恢复角色"""
|
||||||
|
return await self.post(f"/{role_id}/restore")
|
||||||
|
|
||||||
|
async def restore_roles(self, role_ids: List[int]) -> Response:
|
||||||
|
"""批量恢复角色"""
|
||||||
|
return await self.post("/restore", json=role_ids)
|
||||||
|
|
||||||
|
async def check_name_exists(self, role_name: str) -> Response:
|
||||||
|
"""检查角色名是否存在"""
|
||||||
|
return await self.get("/check/name", params={"name": role_name})
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
"""
|
||||||
|
用户管理API
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
from httpx import AsyncClient, Response
|
||||||
|
from .base_api import BaseAPI
|
||||||
|
|
||||||
|
|
||||||
|
class UserAPI(BaseAPI):
|
||||||
|
"""用户管理API"""
|
||||||
|
|
||||||
|
def __init__(self, client: AsyncClient):
|
||||||
|
super().__init__(client, "/api/users")
|
||||||
|
|
||||||
|
async def create_user(self, user_data: Dict[str, Any]) -> Response:
|
||||||
|
"""创建用户"""
|
||||||
|
return await self.post("", json=user_data)
|
||||||
|
|
||||||
|
async def get_user_by_id(self, user_id: int) -> Response:
|
||||||
|
"""根据ID获取用户"""
|
||||||
|
return await self.get(f"/{user_id}")
|
||||||
|
|
||||||
|
async def get_all_users(self, include_deleted: bool = False) -> Response:
|
||||||
|
"""获取所有用户"""
|
||||||
|
return await self.get("", params={"includeDeleted": include_deleted})
|
||||||
|
|
||||||
|
async def update_user(self, user_id: int, user_data: Dict[str, Any]) -> Response:
|
||||||
|
"""更新用户"""
|
||||||
|
return await self.put(f"/{user_id}", json=user_data)
|
||||||
|
|
||||||
|
async def delete_user(self, user_id: int) -> Response:
|
||||||
|
"""删除用户"""
|
||||||
|
return await self.delete(f"/{user_id}")
|
||||||
|
|
||||||
|
async def logical_delete_user(self, user_id: int) -> Response:
|
||||||
|
"""逻辑删除用户"""
|
||||||
|
return await self.delete(f"/{user_id}/logical")
|
||||||
|
|
||||||
|
async def logical_delete_users(self, user_ids: List[int]) -> Response:
|
||||||
|
"""批量逻辑删除用户"""
|
||||||
|
return await self.post("/logical-delete", json=user_ids)
|
||||||
|
|
||||||
|
async def restore_user(self, user_id: int) -> Response:
|
||||||
|
"""恢复用户"""
|
||||||
|
return await self.post(f"/{user_id}/restore")
|
||||||
|
|
||||||
|
async def restore_users(self, user_ids: List[int]) -> Response:
|
||||||
|
"""批量恢复用户"""
|
||||||
|
return await self.post("/restore", json=user_ids)
|
||||||
|
|
||||||
|
async def check_username_exists(self, username: str) -> Response:
|
||||||
|
"""检查用户名是否存在"""
|
||||||
|
return await self.get("/check/username", params={"username": username})
|
||||||
|
|
||||||
|
async def check_email_exists(self, email: str) -> Response:
|
||||||
|
"""检查邮箱是否存在"""
|
||||||
|
return await self.get("/check/email", params={"email": email})
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""配置模块"""
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
"""
|
||||||
|
配置管理模块
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
from pydantic import Field
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
"""应用配置"""
|
||||||
|
|
||||||
|
API_BASE_URL: str = Field(
|
||||||
|
default="http://localhost:8080",
|
||||||
|
description="API基础URL"
|
||||||
|
)
|
||||||
|
|
||||||
|
DATABASE_HOST: str = Field(
|
||||||
|
default="localhost",
|
||||||
|
description="数据库主机"
|
||||||
|
)
|
||||||
|
|
||||||
|
DATABASE_PORT: int = Field(
|
||||||
|
default=55432,
|
||||||
|
description="数据库端口"
|
||||||
|
)
|
||||||
|
|
||||||
|
DATABASE_NAME: str = Field(
|
||||||
|
default="manage_system",
|
||||||
|
description="数据库名称"
|
||||||
|
)
|
||||||
|
|
||||||
|
DATABASE_USERNAME: str = Field(
|
||||||
|
default="postgres",
|
||||||
|
description="数据库用户名"
|
||||||
|
)
|
||||||
|
|
||||||
|
DATABASE_PASSWORD: str = Field(
|
||||||
|
default="postgres",
|
||||||
|
description="数据库密码"
|
||||||
|
)
|
||||||
|
|
||||||
|
TEST_USERNAME: str = Field(
|
||||||
|
default="admin",
|
||||||
|
description="测试用户名"
|
||||||
|
)
|
||||||
|
|
||||||
|
TEST_PASSWORD: str = Field(
|
||||||
|
default="admin123",
|
||||||
|
description="测试用户密码"
|
||||||
|
)
|
||||||
|
|
||||||
|
REQUEST_TIMEOUT: int = Field(
|
||||||
|
default=30000,
|
||||||
|
description="请求超时时间(毫秒)"
|
||||||
|
)
|
||||||
|
|
||||||
|
HEADLESS_BROWSER: bool = Field(
|
||||||
|
default=True,
|
||||||
|
description="无头浏览器模式"
|
||||||
|
)
|
||||||
|
|
||||||
|
BROWSER_TYPE: str = Field(
|
||||||
|
default="chromium",
|
||||||
|
description="浏览器类型"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
env_file = ".env"
|
||||||
|
env_file_encoding = "utf-8"
|
||||||
|
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
"""
|
||||||
|
Pytest配置和fixtures
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import pytest
|
||||||
|
from typing import AsyncGenerator, Generator
|
||||||
|
from playwright.async_api import async_playwright, Browser, BrowserContext, Page
|
||||||
|
from httpx import AsyncClient
|
||||||
|
|
||||||
|
from config.settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def event_loop():
|
||||||
|
"""创建事件循环"""
|
||||||
|
loop = asyncio.get_event_loop_policy().new_event_loop()
|
||||||
|
yield loop
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
async def browser() -> AsyncGenerator[Browser, None]:
|
||||||
|
"""浏览器fixture"""
|
||||||
|
async with async_playwright() as p:
|
||||||
|
browser = await p.launch(
|
||||||
|
headless=settings.HEADLESS_BROWSER,
|
||||||
|
browser_type=settings.BROWSER_TYPE
|
||||||
|
)
|
||||||
|
yield browser
|
||||||
|
await browser.close()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def context(browser: Browser) -> AsyncGenerator[BrowserContext, None]:
|
||||||
|
"""浏览器上下文fixture"""
|
||||||
|
context = await browser.new_context()
|
||||||
|
yield context
|
||||||
|
await context.close()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def page(context: BrowserContext) -> AsyncGenerator[Page, None]:
|
||||||
|
"""页面fixture"""
|
||||||
|
page = await context.new_page()
|
||||||
|
page.set_default_timeout(settings.REQUEST_TIMEOUT)
|
||||||
|
yield page
|
||||||
|
await page.close()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def http_client() -> AsyncGenerator[AsyncClient, None]:
|
||||||
|
"""HTTP客户端fixture"""
|
||||||
|
async with AsyncClient(
|
||||||
|
base_url=settings.API_BASE_URL,
|
||||||
|
timeout=settings.REQUEST_TIMEOUT / 1000
|
||||||
|
) as client:
|
||||||
|
yield client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def auth_token(http_client: AsyncClient) -> str:
|
||||||
|
"""获取认证token"""
|
||||||
|
response = await http_client.post(
|
||||||
|
"/api/auth/login",
|
||||||
|
json={
|
||||||
|
"username": settings.TEST_USERNAME,
|
||||||
|
"password": settings.TEST_PASSWORD
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
return data.get("accessToken")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def authenticated_client(http_client: AsyncClient, auth_token: str) -> AsyncClient:
|
||||||
|
"""已认证的HTTP客户端"""
|
||||||
|
http_client.headers.update({"Authorization": f"Bearer {auth_token}"})
|
||||||
|
return http_client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_user_data():
|
||||||
|
"""测试用户数据"""
|
||||||
|
import time
|
||||||
|
timestamp = int(time.time() * 1000)
|
||||||
|
return {
|
||||||
|
"username": f"testuser_{timestamp}",
|
||||||
|
"password": "password123",
|
||||||
|
"email": f"test_{timestamp}@example.com",
|
||||||
|
"roleId": 2,
|
||||||
|
"status": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_role_data():
|
||||||
|
"""测试角色数据"""
|
||||||
|
import time
|
||||||
|
timestamp = int(time.time() * 1000)
|
||||||
|
return {
|
||||||
|
"name": f"TEST_ROLE_{timestamp}",
|
||||||
|
"description": "测试角色",
|
||||||
|
"permissions": "READ,WRITE"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_dictionary_data():
|
||||||
|
"""测试字典数据"""
|
||||||
|
return {
|
||||||
|
"type": "USER_STATUS",
|
||||||
|
"code": "ACTIVE",
|
||||||
|
"name": "激活",
|
||||||
|
"value": "1",
|
||||||
|
"remark": "用户激活状态",
|
||||||
|
"sort": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def cleanup_user(authenticated_client: AsyncClient):
|
||||||
|
"""清理测试用户"""
|
||||||
|
user_ids = []
|
||||||
|
|
||||||
|
yield user_ids
|
||||||
|
|
||||||
|
for user_id in user_ids:
|
||||||
|
try:
|
||||||
|
await authenticated_client.delete(f"/api/users/{user_id}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def cleanup_role(authenticated_client: AsyncClient):
|
||||||
|
"""清理测试角色"""
|
||||||
|
role_ids = []
|
||||||
|
|
||||||
|
yield role_ids
|
||||||
|
|
||||||
|
for role_id in role_ids:
|
||||||
|
try:
|
||||||
|
await authenticated_client.delete(f"/api/roles/{role_id}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def cleanup_dictionary(authenticated_client: AsyncClient):
|
||||||
|
"""清理测试字典"""
|
||||||
|
dict_ids = []
|
||||||
|
|
||||||
|
yield dict_ids
|
||||||
|
|
||||||
|
for dict_id in dict_ids:
|
||||||
|
try:
|
||||||
|
await authenticated_client.delete(f"/api/dictionaries/{dict_id}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
[pytest]
|
||||||
|
testpaths = tests
|
||||||
|
python_files = test_*.py
|
||||||
|
python_classes = Test*
|
||||||
|
python_functions = test_*
|
||||||
|
addopts =
|
||||||
|
-v
|
||||||
|
--strict-markers
|
||||||
|
--tb=short
|
||||||
|
--cov=.
|
||||||
|
--cov-report=html
|
||||||
|
--cov-report=term-missing
|
||||||
|
--alluredir=allure-results
|
||||||
|
markers =
|
||||||
|
auth: 认证相关测试
|
||||||
|
user: 用户管理测试
|
||||||
|
role: 角色管理测试
|
||||||
|
dictionary: 字典管理测试
|
||||||
|
oauth2: OAuth2相关测试
|
||||||
|
smoke: 冒烟测试
|
||||||
|
regression: 回归测试
|
||||||
|
slow: 慢速测试
|
||||||
|
asyncio_mode = auto
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Python依赖包
|
||||||
|
|
||||||
|
# 测试框架
|
||||||
|
pytest==7.4.3
|
||||||
|
pytest-asyncio==0.21.1
|
||||||
|
pytest-cov==4.1.0
|
||||||
|
pytest-xdist==3.5.0
|
||||||
|
|
||||||
|
# Playwright
|
||||||
|
playwright==1.40.0
|
||||||
|
|
||||||
|
# HTTP客户端
|
||||||
|
httpx==0.25.2
|
||||||
|
requests==2.31.0
|
||||||
|
|
||||||
|
# 数据处理
|
||||||
|
pydantic==2.5.2
|
||||||
|
pydantic-settings==2.1.0
|
||||||
|
faker==20.1.0
|
||||||
|
|
||||||
|
# 配置管理
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
pyyaml==6.0.1
|
||||||
|
|
||||||
|
# 测试报告
|
||||||
|
allure-pytest==2.13.2
|
||||||
|
|
||||||
|
# 工具库
|
||||||
|
loguru==0.7.2
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""测试模块"""
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
"""
|
||||||
|
认证测试用例
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from api.auth_api import AuthAPI
|
||||||
|
from config.settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.auth
|
||||||
|
@pytest.mark.smoke
|
||||||
|
class TestAuth:
|
||||||
|
"""认证测试类"""
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_login_success(self, http_client):
|
||||||
|
"""测试成功登录"""
|
||||||
|
auth_api = AuthAPI(http_client)
|
||||||
|
response = await auth_api.login(settings.TEST_USERNAME, settings.TEST_PASSWORD)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert "accessToken" in data
|
||||||
|
assert "refreshToken" in data
|
||||||
|
assert isinstance(data["accessToken"], str)
|
||||||
|
assert isinstance(data["refreshToken"], str)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_login_invalid_credentials(self, http_client):
|
||||||
|
"""测试无效凭证登录"""
|
||||||
|
auth_api = AuthAPI(http_client)
|
||||||
|
response = await auth_api.login("invalid_user", "invalid_password")
|
||||||
|
|
||||||
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_login_missing_fields(self, http_client):
|
||||||
|
"""测试缺少必填字段"""
|
||||||
|
auth_api = AuthAPI(http_client)
|
||||||
|
response = await http_client.post("/api/auth/login", json={
|
||||||
|
"username": "test"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_refresh_token_success(self, http_client, auth_token):
|
||||||
|
"""测试刷新token成功"""
|
||||||
|
auth_api = AuthAPI(http_client)
|
||||||
|
|
||||||
|
login_response = await auth_api.login(settings.TEST_USERNAME, settings.TEST_PASSWORD)
|
||||||
|
refresh_token = login_response.json().get("refreshToken")
|
||||||
|
|
||||||
|
response = await auth_api.refresh_token(refresh_token)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert "accessToken" in data
|
||||||
|
assert "refreshToken" in data
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_refresh_token_invalid(self, http_client):
|
||||||
|
"""测试无效刷新token"""
|
||||||
|
auth_api = AuthAPI(http_client)
|
||||||
|
response = await auth_api.refresh_token("invalid_refresh_token")
|
||||||
|
|
||||||
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_logout_success(self, http_client, auth_token):
|
||||||
|
"""测试登出成功"""
|
||||||
|
auth_api = AuthAPI(http_client)
|
||||||
|
response = await auth_api.logout(auth_token)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_logout_without_token(self, http_client):
|
||||||
|
"""测试无token登出"""
|
||||||
|
auth_api = AuthAPI(http_client)
|
||||||
|
response = await http_client.post("/api/auth/logout")
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
"""
|
||||||
|
字典管理测试用例
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from api.dictionary_api import DictionaryAPI
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.dictionary
|
||||||
|
@pytest.mark.regression
|
||||||
|
class TestDictionary:
|
||||||
|
"""字典管理测试类"""
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_dictionary_success(self, authenticated_client, test_dictionary_data, cleanup_dictionary):
|
||||||
|
"""测试创建字典成功"""
|
||||||
|
dict_api = DictionaryAPI(authenticated_client)
|
||||||
|
response = await dict_api.create_dictionary(test_dictionary_data)
|
||||||
|
|
||||||
|
assert response.status_code == 201
|
||||||
|
data = response.json()
|
||||||
|
assert "id" in data
|
||||||
|
assert data["type"] == test_dictionary_data["type"]
|
||||||
|
assert data["code"] == test_dictionary_data["code"]
|
||||||
|
assert data["name"] == test_dictionary_data["name"]
|
||||||
|
|
||||||
|
cleanup_dictionary.append(data["id"])
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_dictionary_duplicate_type_code(self, authenticated_client, test_dictionary_data, cleanup_dictionary):
|
||||||
|
"""测试创建重复类型和编码"""
|
||||||
|
dict_api = DictionaryAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await dict_api.create_dictionary(test_dictionary_data)
|
||||||
|
dict_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await dict_api.create_dictionary(test_dictionary_data)
|
||||||
|
|
||||||
|
assert response.status_code in [400, 409]
|
||||||
|
|
||||||
|
cleanup_dictionary.append(dict_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_dictionary_by_id_success(self, authenticated_client, test_dictionary_data, cleanup_dictionary):
|
||||||
|
"""测试根据ID获取字典成功"""
|
||||||
|
dict_api = DictionaryAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await dict_api.create_dictionary(test_dictionary_data)
|
||||||
|
dict_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await dict_api.get_dictionary_by_id(dict_id)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["id"] == dict_id
|
||||||
|
assert data["type"] == test_dictionary_data["type"]
|
||||||
|
|
||||||
|
cleanup_dictionary.append(dict_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_dictionary_by_id_not_found(self, authenticated_client):
|
||||||
|
"""测试获取不存在的字典"""
|
||||||
|
dict_api = DictionaryAPI(authenticated_client)
|
||||||
|
response = await dict_api.get_dictionary_by_id(999999)
|
||||||
|
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_dictionaries_by_type_success(self, authenticated_client, test_dictionary_data, cleanup_dictionary):
|
||||||
|
"""测试根据类型获取字典成功"""
|
||||||
|
dict_api = DictionaryAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await dict_api.create_dictionary(test_dictionary_data)
|
||||||
|
dict_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await dict_api.get_dictionaries_by_type(test_dictionary_data["type"])
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert isinstance(data, list)
|
||||||
|
assert any(d["id"] == dict_id for d in data)
|
||||||
|
|
||||||
|
cleanup_dictionary.append(dict_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_all_dictionaries_success(self, authenticated_client):
|
||||||
|
"""测试获取所有字典成功"""
|
||||||
|
dict_api = DictionaryAPI(authenticated_client)
|
||||||
|
response = await dict_api.get_all_dictionaries()
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert isinstance(data, list)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_update_dictionary_success(self, authenticated_client, test_dictionary_data, cleanup_dictionary):
|
||||||
|
"""测试更新字典成功"""
|
||||||
|
dict_api = DictionaryAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await dict_api.create_dictionary(test_dictionary_data)
|
||||||
|
dict_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
update_data = {"name": "Updated name"}
|
||||||
|
response = await dict_api.update_dictionary(dict_id, update_data)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["name"] == "Updated name"
|
||||||
|
|
||||||
|
cleanup_dictionary.append(dict_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_delete_dictionary_success(self, authenticated_client, test_dictionary_data, cleanup_dictionary):
|
||||||
|
"""测试删除字典成功"""
|
||||||
|
dict_api = DictionaryAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await dict_api.create_dictionary(test_dictionary_data)
|
||||||
|
dict_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await dict_api.delete_dictionary(dict_id)
|
||||||
|
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_check_type_and_code_exists_true(self, authenticated_client, test_dictionary_data, cleanup_dictionary):
|
||||||
|
"""测试检查类型和编码存在-返回true"""
|
||||||
|
dict_api = DictionaryAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await dict_api.create_dictionary(test_dictionary_data)
|
||||||
|
dict_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await dict_api.check_type_and_code_exists(
|
||||||
|
test_dictionary_data["type"],
|
||||||
|
test_dictionary_data["code"]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() is True
|
||||||
|
|
||||||
|
cleanup_dictionary.append(dict_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_check_type_and_code_exists_false(self, authenticated_client):
|
||||||
|
"""测试检查类型和编码存在-返回false"""
|
||||||
|
dict_api = DictionaryAPI(authenticated_client)
|
||||||
|
response = await dict_api.check_type_and_code_exists("NONEXISTENT_TYPE", "NONEXISTENT_CODE")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() is False
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
"""
|
||||||
|
OAuth2客户端管理测试用例
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from httpx import AsyncClient
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.oauth2
|
||||||
|
@pytest.mark.regression
|
||||||
|
class TestOAuth2:
|
||||||
|
"""OAuth2客户端管理测试类"""
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_oauth2_client_data(self):
|
||||||
|
"""测试OAuth2客户端数据"""
|
||||||
|
import time
|
||||||
|
timestamp = int(time.time() * 1000)
|
||||||
|
return {
|
||||||
|
"clientId": f"test-client-{timestamp}",
|
||||||
|
"clientSecret": "secret123",
|
||||||
|
"clientName": "Test Client",
|
||||||
|
"webServerRedirectUri": "http://localhost:8080/callback",
|
||||||
|
"scope": "read,write",
|
||||||
|
"authorizedGrantTypes": "authorization_code,refresh_token",
|
||||||
|
"accessTokenValiditySeconds": 7200,
|
||||||
|
"refreshTokenValiditySeconds": 2592000,
|
||||||
|
"autoApprove": False,
|
||||||
|
"enabled": True
|
||||||
|
}
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def cleanup_oauth2_client(self, authenticated_client: AsyncClient):
|
||||||
|
"""清理测试OAuth2客户端"""
|
||||||
|
client_ids = []
|
||||||
|
|
||||||
|
yield client_ids
|
||||||
|
|
||||||
|
for client_id in client_ids:
|
||||||
|
try:
|
||||||
|
await authenticated_client.delete(f"/api/oauth2/clients/{client_id}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_oauth2_client_success(self, authenticated_client, test_oauth2_client_data, cleanup_oauth2_client):
|
||||||
|
"""测试创建OAuth2客户端成功"""
|
||||||
|
response = await authenticated_client.post("/api/oauth2/clients", json=test_oauth2_client_data)
|
||||||
|
|
||||||
|
assert response.status_code == 201
|
||||||
|
data = response.json()
|
||||||
|
assert "id" in data
|
||||||
|
assert data["clientId"] == test_oauth2_client_data["clientId"]
|
||||||
|
assert data["clientName"] == test_oauth2_client_data["clientName"]
|
||||||
|
assert "clientSecret" not in data or data["clientSecret"] != test_oauth2_client_data["clientSecret"]
|
||||||
|
|
||||||
|
cleanup_oauth2_client.append(data["id"])
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_oauth2_client_by_id_success(self, authenticated_client, test_oauth2_client_data, cleanup_oauth2_client):
|
||||||
|
"""测试根据ID获取OAuth2客户端成功"""
|
||||||
|
create_response = await authenticated_client.post("/api/oauth2/clients", json=test_oauth2_client_data)
|
||||||
|
client_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await authenticated_client.get(f"/api/oauth2/clients/{client_id}")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["id"] == client_id
|
||||||
|
assert data["clientId"] == test_oauth2_client_data["clientId"]
|
||||||
|
|
||||||
|
cleanup_oauth2_client.append(client_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_oauth2_client_by_id_not_found(self, authenticated_client):
|
||||||
|
"""测试获取不存在的OAuth2客户端"""
|
||||||
|
response = await authenticated_client.get("/api/oauth2/clients/999999")
|
||||||
|
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_oauth2_client_by_client_id_success(self, authenticated_client, test_oauth2_client_data, cleanup_oauth2_client):
|
||||||
|
"""测试根据clientId获取OAuth2客户端成功"""
|
||||||
|
create_response = await authenticated_client.post("/api/oauth2/clients", json=test_oauth2_client_data)
|
||||||
|
client_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await authenticated_client.get(f"/api/oauth2/clients/client-id/{test_oauth2_client_data['clientId']}")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["clientId"] == test_oauth2_client_data["clientId"]
|
||||||
|
|
||||||
|
cleanup_oauth2_client.append(client_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_all_oauth2_clients_success(self, authenticated_client):
|
||||||
|
"""测试获取所有OAuth2客户端成功"""
|
||||||
|
response = await authenticated_client.get("/api/oauth2/clients")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert isinstance(data, list)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_update_oauth2_client_success(self, authenticated_client, test_oauth2_client_data, cleanup_oauth2_client):
|
||||||
|
"""测试更新OAuth2客户端成功"""
|
||||||
|
create_response = await authenticated_client.post("/api/oauth2/clients", json=test_oauth2_client_data)
|
||||||
|
client_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
update_data = {"clientName": "Updated Client Name"}
|
||||||
|
response = await authenticated_client.put(f"/api/oauth2/clients/{client_id}", json=update_data)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["clientName"] == "Updated Client Name"
|
||||||
|
|
||||||
|
cleanup_oauth2_client.append(client_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_delete_oauth2_client_success(self, authenticated_client, test_oauth2_client_data, cleanup_oauth2_client):
|
||||||
|
"""测试删除OAuth2客户端成功"""
|
||||||
|
create_response = await authenticated_client.post("/api/oauth2/clients", json=test_oauth2_client_data)
|
||||||
|
client_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await authenticated_client.delete(f"/api/oauth2/clients/{client_id}")
|
||||||
|
|
||||||
|
assert response.status_code == 204
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
"""
|
||||||
|
角色管理测试用例
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from api.role_api import RoleAPI
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.role
|
||||||
|
@pytest.mark.regression
|
||||||
|
class TestRole:
|
||||||
|
"""角色管理测试类"""
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_role_success(self, authenticated_client, test_role_data, cleanup_role):
|
||||||
|
"""测试创建角色成功"""
|
||||||
|
role_api = RoleAPI(authenticated_client)
|
||||||
|
response = await role_api.create_role(test_role_data)
|
||||||
|
|
||||||
|
assert response.status_code == 201
|
||||||
|
data = response.json()
|
||||||
|
assert "id" in data
|
||||||
|
assert data["name"] == test_role_data["name"]
|
||||||
|
assert data["description"] == test_role_data["description"]
|
||||||
|
assert data["permissions"] == test_role_data["permissions"]
|
||||||
|
|
||||||
|
cleanup_role.append(data["id"])
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_role_duplicate_name(self, authenticated_client, test_role_data, cleanup_role):
|
||||||
|
"""测试创建重复角色名"""
|
||||||
|
role_api = RoleAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await role_api.create_role(test_role_data)
|
||||||
|
role_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await role_api.create_role(test_role_data)
|
||||||
|
|
||||||
|
assert response.status_code in [400, 409]
|
||||||
|
|
||||||
|
cleanup_role.append(role_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_role_by_id_success(self, authenticated_client, test_role_data, cleanup_role):
|
||||||
|
"""测试根据ID获取角色成功"""
|
||||||
|
role_api = RoleAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await role_api.create_role(test_role_data)
|
||||||
|
role_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await role_api.get_role_by_id(role_id)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["id"] == role_id
|
||||||
|
assert data["name"] == test_role_data["name"]
|
||||||
|
|
||||||
|
cleanup_role.append(role_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_role_by_id_not_found(self, authenticated_client):
|
||||||
|
"""测试获取不存在的角色"""
|
||||||
|
role_api = RoleAPI(authenticated_client)
|
||||||
|
response = await role_api.get_role_by_id(999999)
|
||||||
|
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_role_by_name_success(self, authenticated_client, test_role_data, cleanup_role):
|
||||||
|
"""测试根据名称获取角色成功"""
|
||||||
|
role_api = RoleAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await role_api.create_role(test_role_data)
|
||||||
|
role_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await role_api.get_role_by_name(test_role_data["name"])
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["name"] == test_role_data["name"]
|
||||||
|
|
||||||
|
cleanup_role.append(role_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_all_roles_success(self, authenticated_client):
|
||||||
|
"""测试获取所有角色成功"""
|
||||||
|
role_api = RoleAPI(authenticated_client)
|
||||||
|
response = await role_api.get_all_roles()
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert isinstance(data, list)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_update_role_success(self, authenticated_client, test_role_data, cleanup_role):
|
||||||
|
"""测试更新角色成功"""
|
||||||
|
role_api = RoleAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await role_api.create_role(test_role_data)
|
||||||
|
role_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
update_data = {"description": "Updated description"}
|
||||||
|
response = await role_api.update_role(role_id, update_data)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["description"] == "Updated description"
|
||||||
|
|
||||||
|
cleanup_role.append(role_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_delete_role_success(self, authenticated_client, test_role_data, cleanup_role):
|
||||||
|
"""测试删除角色成功"""
|
||||||
|
role_api = RoleAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await role_api.create_role(test_role_data)
|
||||||
|
role_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await role_api.delete_role(role_id)
|
||||||
|
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_logical_delete_role_success(self, authenticated_client, test_role_data, cleanup_role):
|
||||||
|
"""测试逻辑删除角色成功"""
|
||||||
|
role_api = RoleAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await role_api.create_role(test_role_data)
|
||||||
|
role_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await role_api.logical_delete_role(role_id)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
get_response = await role_api.get_role_by_id(role_id)
|
||||||
|
assert get_response.status_code == 404
|
||||||
|
|
||||||
|
get_deleted_response = await role_api.get_all_roles(include_deleted=True)
|
||||||
|
deleted_roles = get_deleted_response.json()
|
||||||
|
assert any(r["id"] == role_id for r in deleted_roles)
|
||||||
|
|
||||||
|
cleanup_role.append(role_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_restore_role_success(self, authenticated_client, test_role_data, cleanup_role):
|
||||||
|
"""测试恢复角色成功"""
|
||||||
|
role_api = RoleAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await role_api.create_role(test_role_data)
|
||||||
|
role_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
await role_api.logical_delete_role(role_id)
|
||||||
|
|
||||||
|
response = await role_api.restore_role(role_id)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
get_response = await role_api.get_role_by_id(role_id)
|
||||||
|
assert get_response.status_code == 200
|
||||||
|
|
||||||
|
cleanup_role.append(role_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_check_name_exists_true(self, authenticated_client, test_role_data, cleanup_role):
|
||||||
|
"""测试检查角色名存在-返回true"""
|
||||||
|
role_api = RoleAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await role_api.create_role(test_role_data)
|
||||||
|
role_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await role_api.check_name_exists(test_role_data["name"])
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() is True
|
||||||
|
|
||||||
|
cleanup_role.append(role_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_check_name_exists_false(self, authenticated_client):
|
||||||
|
"""测试检查角色名存在-返回false"""
|
||||||
|
role_api = RoleAPI(authenticated_client)
|
||||||
|
response = await role_api.check_name_exists("NONEXISTENT_ROLE")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() is False
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
"""
|
||||||
|
用户管理测试用例
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from api.user_api import UserAPI
|
||||||
|
from config.settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.user
|
||||||
|
@pytest.mark.regression
|
||||||
|
class TestUser:
|
||||||
|
"""用户管理测试类"""
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_user_success(self, authenticated_client, test_user_data, cleanup_user):
|
||||||
|
"""测试创建用户成功"""
|
||||||
|
user_api = UserAPI(authenticated_client)
|
||||||
|
response = await user_api.create_user(test_user_data)
|
||||||
|
|
||||||
|
print(f"Response status: {response.status_code}")
|
||||||
|
print(f"Response text: {response.text}")
|
||||||
|
|
||||||
|
assert response.status_code == 201
|
||||||
|
data = response.json()
|
||||||
|
assert "id" in data
|
||||||
|
assert data["username"] == test_user_data["username"]
|
||||||
|
assert data["email"] == test_user_data["email"]
|
||||||
|
assert "password" not in data or data["password"] != test_user_data["password"]
|
||||||
|
|
||||||
|
cleanup_user.append(data["id"])
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_user_duplicate_username(self, authenticated_client, test_user_data, cleanup_user):
|
||||||
|
"""测试创建重复用户名"""
|
||||||
|
user_api = UserAPI(authenticated_client)
|
||||||
|
|
||||||
|
await user_api.create_user(test_user_data)
|
||||||
|
response = await user_api.create_user(test_user_data)
|
||||||
|
|
||||||
|
assert response.status_code in [400, 409]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_user_by_id_success(self, authenticated_client, test_user_data, cleanup_user):
|
||||||
|
"""测试根据ID获取用户成功"""
|
||||||
|
user_api = UserAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await user_api.create_user(test_user_data)
|
||||||
|
user_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await user_api.get_user_by_id(user_id)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["id"] == user_id
|
||||||
|
assert data["username"] == test_user_data["username"]
|
||||||
|
|
||||||
|
cleanup_user.append(user_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_user_by_id_not_found(self, authenticated_client):
|
||||||
|
"""测试获取不存在的用户"""
|
||||||
|
user_api = UserAPI(authenticated_client)
|
||||||
|
response = await user_api.get_user_by_id(999999)
|
||||||
|
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_all_users_success(self, authenticated_client):
|
||||||
|
"""测试获取所有用户成功"""
|
||||||
|
user_api = UserAPI(authenticated_client)
|
||||||
|
response = await user_api.get_all_users()
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert isinstance(data, list)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_update_user_success(self, authenticated_client, test_user_data, cleanup_user):
|
||||||
|
"""测试更新用户成功"""
|
||||||
|
user_api = UserAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await user_api.create_user(test_user_data)
|
||||||
|
user_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
update_data = {"email": "updated@example.com"}
|
||||||
|
response = await user_api.update_user(user_id, update_data)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["email"] == "updated@example.com"
|
||||||
|
|
||||||
|
cleanup_user.append(user_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_delete_user_success(self, authenticated_client, test_user_data, cleanup_user):
|
||||||
|
"""测试删除用户成功"""
|
||||||
|
user_api = UserAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await user_api.create_user(test_user_data)
|
||||||
|
user_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await user_api.delete_user(user_id)
|
||||||
|
|
||||||
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_logical_delete_user_success(self, authenticated_client, test_user_data, cleanup_user):
|
||||||
|
"""测试逻辑删除用户成功"""
|
||||||
|
user_api = UserAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await user_api.create_user(test_user_data)
|
||||||
|
user_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await user_api.logical_delete_user(user_id)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
get_response = await user_api.get_user_by_id(user_id)
|
||||||
|
assert get_response.status_code == 404
|
||||||
|
|
||||||
|
get_deleted_response = await user_api.get_all_users(include_deleted=True)
|
||||||
|
deleted_users = get_deleted_response.json()
|
||||||
|
assert any(u["id"] == user_id for u in deleted_users)
|
||||||
|
|
||||||
|
cleanup_user.append(user_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_restore_user_success(self, authenticated_client, test_user_data, cleanup_user):
|
||||||
|
"""测试恢复用户成功"""
|
||||||
|
user_api = UserAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await user_api.create_user(test_user_data)
|
||||||
|
user_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
await user_api.logical_delete_user(user_id)
|
||||||
|
|
||||||
|
response = await user_api.restore_user(user_id)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
get_response = await user_api.get_user_by_id(user_id)
|
||||||
|
assert get_response.status_code == 200
|
||||||
|
|
||||||
|
cleanup_user.append(user_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_check_username_exists_true(self, authenticated_client, test_user_data, cleanup_user):
|
||||||
|
"""测试检查用户名存在-返回true"""
|
||||||
|
user_api = UserAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await user_api.create_user(test_user_data)
|
||||||
|
user_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await user_api.check_username_exists(test_user_data["username"])
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() is True
|
||||||
|
|
||||||
|
cleanup_user.append(user_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_check_username_exists_false(self, authenticated_client):
|
||||||
|
"""测试检查用户名存在-返回false"""
|
||||||
|
user_api = UserAPI(authenticated_client)
|
||||||
|
response = await user_api.check_username_exists("nonexistent_user")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() is False
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_check_email_exists_true(self, authenticated_client, test_user_data, cleanup_user):
|
||||||
|
"""测试检查邮箱存在-返回true"""
|
||||||
|
user_api = UserAPI(authenticated_client)
|
||||||
|
|
||||||
|
create_response = await user_api.create_user(test_user_data)
|
||||||
|
user_id = create_response.json()["id"]
|
||||||
|
|
||||||
|
response = await user_api.check_email_exists(test_user_data["email"])
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() is True
|
||||||
|
|
||||||
|
cleanup_user.append(user_id)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_check_email_exists_false(self, authenticated_client):
|
||||||
|
"""测试检查邮箱存在-返回false"""
|
||||||
|
user_api = UserAPI(authenticated_client)
|
||||||
|
response = await user_api.check_email_exists("nonexistent@example.com")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() is False
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
工具模块
|
||||||
|
"""
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
"""
|
||||||
|
断言工具
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
from httpx import Response
|
||||||
|
|
||||||
|
|
||||||
|
class Assertions:
|
||||||
|
"""断言工具类"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def assert_status_code(response: Response, expected_status: int):
|
||||||
|
"""断言状态码"""
|
||||||
|
assert response.status_code == expected_status, \
|
||||||
|
f"Expected status code {expected_status}, got {response.status_code}. Response: {response.text}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def assert_response_contains(response: Response, key: str, value: Any = None):
|
||||||
|
"""断言响应包含指定字段"""
|
||||||
|
data = response.json()
|
||||||
|
assert key in data, f"Response does not contain key '{key}'. Response: {data}"
|
||||||
|
if value is not None:
|
||||||
|
assert data[key] == value, \
|
||||||
|
f"Expected {value} for key '{key}', got {data[key]}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def assert_response_is_list(response: Response):
|
||||||
|
"""断言响应是列表"""
|
||||||
|
data = response.json()
|
||||||
|
assert isinstance(data, list), f"Expected list, got {type(data)}. Response: {data}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def assert_response_not_empty(response: Response):
|
||||||
|
"""断言响应不为空"""
|
||||||
|
data = response.json()
|
||||||
|
assert data, f"Response is empty. Response: {data}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def assert_response_field_type(response: Response, field: str, expected_type: type):
|
||||||
|
"""断言响应字段类型"""
|
||||||
|
data = response.json()
|
||||||
|
assert field in data, f"Response does not contain field '{field}'"
|
||||||
|
assert isinstance(data[field], expected_type), \
|
||||||
|
f"Expected field '{field}' to be {expected_type}, got {type(data[field])}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def assert_response_fields_present(response: Response, fields: List[str]):
|
||||||
|
"""断言响应包含所有指定字段"""
|
||||||
|
data = response.json()
|
||||||
|
missing_fields = [field for field in fields if field not in data]
|
||||||
|
assert not missing_fields, \
|
||||||
|
f"Response is missing fields: {missing_fields}. Response: {data}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def assert_response_field_length(response: Response, field: str, min_length: int = None, max_length: int = None):
|
||||||
|
"""断言响应字段长度"""
|
||||||
|
data = response.json()
|
||||||
|
assert field in data, f"Response does not contain field '{field}'"
|
||||||
|
field_value = data[field]
|
||||||
|
|
||||||
|
if isinstance(field_value, (str, list, dict)):
|
||||||
|
length = len(field_value)
|
||||||
|
if min_length is not None:
|
||||||
|
assert length >= min_length, \
|
||||||
|
f"Field '{field}' length {length} is less than minimum {min_length}"
|
||||||
|
if max_length is not None:
|
||||||
|
assert length <= max_length, \
|
||||||
|
f"Field '{field}' length {length} is greater than maximum {max_length}"
|
||||||
|
else:
|
||||||
|
raise AssertionError(f"Field '{field}' is not a string, list, or dict")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def assert_error_response(response: Response, expected_message: str = None):
|
||||||
|
"""断言错误响应"""
|
||||||
|
Assertions.assert_status_code(response, 400)
|
||||||
|
if expected_message:
|
||||||
|
data = response.json()
|
||||||
|
assert expected_message in str(data), \
|
||||||
|
f"Expected error message '{expected_message}' not found in response: {data}"
|
||||||
|
|
||||||
|
|
||||||
|
assertions = Assertions()
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
"""
|
||||||
|
测试数据生成器
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
from faker import Faker
|
||||||
|
|
||||||
|
|
||||||
|
class DataGenerator:
|
||||||
|
"""测试数据生成器"""
|
||||||
|
|
||||||
|
def __init__(self, locale: str = "zh_CN"):
|
||||||
|
self.faker = Faker(locale)
|
||||||
|
|
||||||
|
def generate_username(self) -> str:
|
||||||
|
"""生成用户名"""
|
||||||
|
return f"testuser_{''.join(random.choices(string.ascii_lowercase + string.digits, k=8))}"
|
||||||
|
|
||||||
|
def generate_password(self, length: int = 12) -> str:
|
||||||
|
"""生成密码"""
|
||||||
|
chars = string.ascii_letters + string.digits + "!@#$%^&*"
|
||||||
|
return ''.join(random.choices(chars, k=length))
|
||||||
|
|
||||||
|
def generate_email(self) -> str:
|
||||||
|
"""生成邮箱"""
|
||||||
|
return self.faker.email()
|
||||||
|
|
||||||
|
def generate_phone(self) -> str:
|
||||||
|
"""生成手机号"""
|
||||||
|
return self.faker.phone_number()
|
||||||
|
|
||||||
|
def generate_name(self) -> str:
|
||||||
|
"""生成姓名"""
|
||||||
|
return self.faker.name()
|
||||||
|
|
||||||
|
def generate_role_name(self) -> str:
|
||||||
|
"""生成角色名"""
|
||||||
|
return f"ROLE_{''.join(random.choices(string.ascii_uppercase, k=6))}"
|
||||||
|
|
||||||
|
def generate_dict_type(self) -> str:
|
||||||
|
"""生成字典类型"""
|
||||||
|
return f"DICT_TYPE_{''.join(random.choices(string.ascii_uppercase, k=4))}"
|
||||||
|
|
||||||
|
def generate_dict_code(self) -> str:
|
||||||
|
"""生成字典编码"""
|
||||||
|
return f"CODE_{''.join(random.choices(string.ascii_uppercase + string.digits, k=6))}"
|
||||||
|
|
||||||
|
def generate_url(self) -> str:
|
||||||
|
"""生成URL"""
|
||||||
|
return self.faker.url()
|
||||||
|
|
||||||
|
def generate_company_name(self) -> str:
|
||||||
|
"""生成公司名"""
|
||||||
|
return self.faker.company()
|
||||||
|
|
||||||
|
def generate_address(self) -> str:
|
||||||
|
"""生成地址"""
|
||||||
|
return self.faker.address()
|
||||||
|
|
||||||
|
def generate_description(self) -> str:
|
||||||
|
"""生成描述"""
|
||||||
|
return self.faker.text(max_nb_chars=200)
|
||||||
|
|
||||||
|
def generate_permissions(self) -> str:
|
||||||
|
"""生成权限字符串"""
|
||||||
|
permissions = ["READ", "WRITE", "DELETE", "ADMIN", "MANAGE"]
|
||||||
|
selected = random.sample(permissions, random.randint(1, len(permissions)))
|
||||||
|
return ",".join(selected)
|
||||||
|
|
||||||
|
|
||||||
|
data_generator = DataGenerator()
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
"""
|
||||||
|
日志工具
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from loguru import logger
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logger(log_file: str = "e2e_tests.log", log_level: str = "INFO"):
|
||||||
|
"""配置日志"""
|
||||||
|
logger.remove()
|
||||||
|
|
||||||
|
logger.add(
|
||||||
|
sys.stdout,
|
||||||
|
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
|
||||||
|
level=log_level,
|
||||||
|
colorize=True
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.add(
|
||||||
|
log_file,
|
||||||
|
format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}",
|
||||||
|
level=log_level,
|
||||||
|
rotation="10 MB",
|
||||||
|
retention="7 days",
|
||||||
|
compression="zip"
|
||||||
|
)
|
||||||
|
|
||||||
|
return logger
|
||||||
|
|
||||||
|
|
||||||
|
setup_logger()
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>cn.novalon.manage</groupId>
|
||||||
|
<artifactId>novalon-manage-api</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>manage-sys</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>Manage Sys</name>
|
||||||
|
<description>System Management Module</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-config</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
|
<artifactId>caffeine</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-impl</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>r2dbc-postgresql</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-database-postgresql</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package cn.novalon.manage.sys;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class ManageSysApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ManageSysApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
package cn.novalon.manage.sys.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||||
|
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebFluxSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
||||||
|
return http
|
||||||
|
.csrf(ServerHttpSecurity.CsrfSpec::disable)
|
||||||
|
.authorizeExchange(exchanges -> exchanges
|
||||||
|
.pathMatchers("/api/auth/**").permitAll()
|
||||||
|
.pathMatchers("/api/public/**").permitAll()
|
||||||
|
.anyExchange().authenticated()
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
package cn.novalon.manage.sys.config;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.socket.CloseStatus;
|
||||||
|
import org.springframework.web.socket.TextMessage;
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SystemWebSocketHandler extends TextWebSocketHandler {
|
||||||
|
|
||||||
|
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
||||||
|
sessions.put(session.getId(), session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
|
||||||
|
sessions.remove(session.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||||
|
// Handle incoming messages if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMessageToUser(String userId, String message) {
|
||||||
|
sessions.values().forEach(session -> {
|
||||||
|
try {
|
||||||
|
if (session.isOpen()) {
|
||||||
|
session.sendMessage(new TextMessage(message));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void broadcast(String message) {
|
||||||
|
sessions.values().forEach(session -> {
|
||||||
|
try {
|
||||||
|
if (session.isOpen()) {
|
||||||
|
session.sendMessage(new TextMessage(message));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package cn.novalon.manage.sys.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||||
|
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||||
|
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSocket
|
||||||
|
public class WebSocketConfig implements WebSocketConfigurer {
|
||||||
|
|
||||||
|
private final SystemWebSocketHandler webSocketHandler;
|
||||||
|
|
||||||
|
public WebSocketConfig(SystemWebSocketHandler webSocketHandler) {
|
||||||
|
this.webSocketHandler = webSocketHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||||
|
registry.addHandler(webSocketHandler, "/ws")
|
||||||
|
.setAllowedOrigins("*");
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
package cn.novalon.manage.sys.core.domain;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public abstract class BaseDomain {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
private LocalDateTime deletedAt;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getUpdatedAt() {
|
||||||
|
return updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getDeletedAt() {
|
||||||
|
return deletedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeletedAt(LocalDateTime deletedAt) {
|
||||||
|
this.deletedAt = deletedAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
package cn.novalon.manage.sys.core.domain;
|
||||||
|
|
||||||
|
public class OperationLog extends BaseDomain {
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
private String operation;
|
||||||
|
private String method;
|
||||||
|
private String params;
|
||||||
|
private String result;
|
||||||
|
private String ip;
|
||||||
|
private Long duration;
|
||||||
|
private String status;
|
||||||
|
private String errorMsg;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOperation() {
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOperation(String operation) {
|
||||||
|
this.operation = operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMethod(String method) {
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getParams() {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParams(String params) {
|
||||||
|
this.params = params;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResult(String result) {
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIp() {
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIp(String ip) {
|
||||||
|
this.ip = ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getDuration() {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDuration(Long duration) {
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorMsg() {
|
||||||
|
return errorMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorMsg(String errorMsg) {
|
||||||
|
this.errorMsg = errorMsg;
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package cn.novalon.manage.sys.core.domain;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class SysConfig {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String configName;
|
||||||
|
private String configKey;
|
||||||
|
private String configValue;
|
||||||
|
private String configType;
|
||||||
|
private String createBy;
|
||||||
|
private String updateBy;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public void setId(Long id) { this.id = id; }
|
||||||
|
public String getConfigName() { return configName; }
|
||||||
|
public void setConfigName(String configName) { this.configName = configName; }
|
||||||
|
public String getConfigKey() { return configKey; }
|
||||||
|
public void setConfigKey(String configKey) { this.configKey = configKey; }
|
||||||
|
public String getConfigValue() { return configValue; }
|
||||||
|
public void setConfigValue(String configValue) { this.configValue = configValue; }
|
||||||
|
public String getConfigType() { return configType; }
|
||||||
|
public void setConfigType(String configType) { this.configType = configType; }
|
||||||
|
public String getCreateBy() { return createBy; }
|
||||||
|
public void setCreateBy(String createBy) { this.createBy = createBy; }
|
||||||
|
public String getUpdateBy() { return updateBy; }
|
||||||
|
public void setUpdateBy(String updateBy) { this.updateBy = updateBy; }
|
||||||
|
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||||
|
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||||
|
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
package cn.novalon.manage.sys.core.domain;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class SysDictData {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long dictTypeId;
|
||||||
|
private String dictLabel;
|
||||||
|
private String dictValue;
|
||||||
|
private Integer dictSort;
|
||||||
|
private String dictType;
|
||||||
|
private String cssClass;
|
||||||
|
private String listClass;
|
||||||
|
private String isDefault;
|
||||||
|
private String status;
|
||||||
|
private String createBy;
|
||||||
|
private String updateBy;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public void setId(Long id) { this.id = id; }
|
||||||
|
public Long getDictTypeId() { return dictTypeId; }
|
||||||
|
public void setDictTypeId(Long dictTypeId) { this.dictTypeId = dictTypeId; }
|
||||||
|
public String getDictLabel() { return dictLabel; }
|
||||||
|
public void setDictLabel(String dictLabel) { this.dictLabel = dictLabel; }
|
||||||
|
public String getDictValue() { return dictValue; }
|
||||||
|
public void setDictValue(String dictValue) { this.dictValue = dictValue; }
|
||||||
|
public Integer getDictSort() { return dictSort; }
|
||||||
|
public void setDictSort(Integer dictSort) { this.dictSort = dictSort; }
|
||||||
|
public String getDictType() { return dictType; }
|
||||||
|
public void setDictType(String dictType) { this.dictType = dictType; }
|
||||||
|
public String getCssClass() { return cssClass; }
|
||||||
|
public void setCssClass(String cssClass) { this.cssClass = cssClass; }
|
||||||
|
public String getListClass() { return listClass; }
|
||||||
|
public void setListClass(String listClass) { this.listClass = listClass; }
|
||||||
|
public String getIsDefault() { return isDefault; }
|
||||||
|
public void setIsDefault(String isDefault) { this.isDefault = isDefault; }
|
||||||
|
public String getStatus() { return status; }
|
||||||
|
public void setStatus(String status) { this.status = status; }
|
||||||
|
public String getCreateBy() { return createBy; }
|
||||||
|
public void setCreateBy(String createBy) { this.createBy = createBy; }
|
||||||
|
public String getUpdateBy() { return updateBy; }
|
||||||
|
public void setUpdateBy(String updateBy) { this.updateBy = updateBy; }
|
||||||
|
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||||
|
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||||
|
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package cn.novalon.manage.sys.core.domain;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class SysDictType {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String dictName;
|
||||||
|
private String dictType;
|
||||||
|
private String status;
|
||||||
|
private String remark;
|
||||||
|
private String createBy;
|
||||||
|
private String updateBy;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public void setId(Long id) { this.id = id; }
|
||||||
|
public String getDictName() { return dictName; }
|
||||||
|
public void setDictName(String dictName) { this.dictName = dictName; }
|
||||||
|
public String getDictType() { return dictType; }
|
||||||
|
public void setDictType(String dictType) { this.dictType = dictType; }
|
||||||
|
public String getStatus() { return status; }
|
||||||
|
public void setStatus(String status) { this.status = status; }
|
||||||
|
public String getRemark() { return remark; }
|
||||||
|
public void setRemark(String remark) { this.remark = remark; }
|
||||||
|
public String getCreateBy() { return createBy; }
|
||||||
|
public void setCreateBy(String createBy) { this.createBy = createBy; }
|
||||||
|
public String getUpdateBy() { return updateBy; }
|
||||||
|
public void setUpdateBy(String updateBy) { this.updateBy = updateBy; }
|
||||||
|
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||||
|
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||||
|
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
package cn.novalon.manage.sys.core.domain;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class SysExceptionLog {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String username;
|
||||||
|
private String title;
|
||||||
|
private String exceptionName;
|
||||||
|
private String methodName;
|
||||||
|
private String methodParams;
|
||||||
|
private String exceptionMsg;
|
||||||
|
private String exceptionStack;
|
||||||
|
private String ip;
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public void setId(Long id) { this.id = id; }
|
||||||
|
public String getUsername() { return username; }
|
||||||
|
public void setUsername(String username) { this.username = username; }
|
||||||
|
public String getTitle() { return title; }
|
||||||
|
public void setTitle(String title) { this.title = title; }
|
||||||
|
public String getExceptionName() { return exceptionName; }
|
||||||
|
public void setExceptionName(String exceptionName) { this.exceptionName = exceptionName; }
|
||||||
|
public String getMethodName() { return methodName; }
|
||||||
|
public void setMethodName(String methodName) { this.methodName = methodName; }
|
||||||
|
public String getMethodParams() { return methodParams; }
|
||||||
|
public void setMethodParams(String methodParams) { this.methodParams = methodParams; }
|
||||||
|
public String getExceptionMsg() { return exceptionMsg; }
|
||||||
|
public void setExceptionMsg(String exceptionMsg) { this.exceptionMsg = exceptionMsg; }
|
||||||
|
public String getExceptionStack() { return exceptionStack; }
|
||||||
|
public void setExceptionStack(String exceptionStack) { this.exceptionStack = exceptionStack; }
|
||||||
|
public String getIp() { return ip; }
|
||||||
|
public void setIp(String ip) { this.ip = ip; }
|
||||||
|
public LocalDateTime getCreateTime() { return createTime; }
|
||||||
|
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
package cn.novalon.manage.sys.core.domain;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class SysFile {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String fileName;
|
||||||
|
private String filePath;
|
||||||
|
private String fileSize;
|
||||||
|
private String fileType;
|
||||||
|
private String storageType;
|
||||||
|
private String createBy;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public void setId(Long id) { this.id = id; }
|
||||||
|
public String getFileName() { return fileName; }
|
||||||
|
public void setFileName(String fileName) { this.fileName = fileName; }
|
||||||
|
public String getFilePath() { return filePath; }
|
||||||
|
public void setFilePath(String filePath) { this.filePath = filePath; }
|
||||||
|
public String getFileSize() { return fileSize; }
|
||||||
|
public void setFileSize(String fileSize) { this.fileSize = fileSize; }
|
||||||
|
public String getFileType() { return fileType; }
|
||||||
|
public void setFileType(String fileType) { this.fileType = fileType; }
|
||||||
|
public String getStorageType() { return storageType; }
|
||||||
|
public void setStorageType(String storageType) { this.storageType = storageType; }
|
||||||
|
public String getCreateBy() { return createBy; }
|
||||||
|
public void setCreateBy(String createBy) { this.createBy = createBy; }
|
||||||
|
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package cn.novalon.manage.sys.core.domain;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class SysLoginLog {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String username;
|
||||||
|
private String ip;
|
||||||
|
private String location;
|
||||||
|
private String browser;
|
||||||
|
private String os;
|
||||||
|
private String status;
|
||||||
|
private String message;
|
||||||
|
private LocalDateTime loginTime;
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public void setId(Long id) { this.id = id; }
|
||||||
|
public String getUsername() { return username; }
|
||||||
|
public void setUsername(String username) { this.username = username; }
|
||||||
|
public String getIp() { return ip; }
|
||||||
|
public void setIp(String ip) { this.ip = ip; }
|
||||||
|
public String getLocation() { return location; }
|
||||||
|
public void setLocation(String location) { this.location = location; }
|
||||||
|
public String getBrowser() { return browser; }
|
||||||
|
public void setBrowser(String browser) { this.browser = browser; }
|
||||||
|
public String getOs() { return os; }
|
||||||
|
public void setOs(String os) { this.os = os; }
|
||||||
|
public String getStatus() { return status; }
|
||||||
|
public void setStatus(String status) { this.status = status; }
|
||||||
|
public String getMessage() { return message; }
|
||||||
|
public void setMessage(String message) { this.message = message; }
|
||||||
|
public LocalDateTime getLoginTime() { return loginTime; }
|
||||||
|
public void setLoginTime(LocalDateTime loginTime) { this.loginTime = loginTime; }
|
||||||
|
}
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
package cn.novalon.manage.sys.core.domain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SysMenu extends BaseDomain {
|
||||||
|
|
||||||
|
private String menuName;
|
||||||
|
private Long parentId;
|
||||||
|
private Integer orderNum;
|
||||||
|
private String menuType;
|
||||||
|
private String perms;
|
||||||
|
private String component;
|
||||||
|
private String status;
|
||||||
|
private List<SysMenu> children;
|
||||||
|
|
||||||
|
public String getMenuName() {
|
||||||
|
return menuName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMenuName(String menuName) {
|
||||||
|
this.menuName = menuName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getParentId() {
|
||||||
|
return parentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParentId(Long parentId) {
|
||||||
|
this.parentId = parentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getOrderNum() {
|
||||||
|
return orderNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrderNum(Integer orderNum) {
|
||||||
|
this.orderNum = orderNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMenuType() {
|
||||||
|
return menuType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMenuType(String menuType) {
|
||||||
|
this.menuType = menuType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPerms() {
|
||||||
|
return perms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPerms(String perms) {
|
||||||
|
this.perms = perms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getComponent() {
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComponent(String component) {
|
||||||
|
this.component = component;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SysMenu> getChildren() {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChildren(List<SysMenu> children) {
|
||||||
|
this.children = children;
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package cn.novalon.manage.sys.core.domain;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class SysNotice {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String noticeTitle;
|
||||||
|
private String noticeType;
|
||||||
|
private String noticeContent;
|
||||||
|
private String status;
|
||||||
|
private String createBy;
|
||||||
|
private String updateBy;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public void setId(Long id) { this.id = id; }
|
||||||
|
public String getNoticeTitle() { return noticeTitle; }
|
||||||
|
public void setNoticeTitle(String noticeTitle) { this.noticeTitle = noticeTitle; }
|
||||||
|
public String getNoticeType() { return noticeType; }
|
||||||
|
public void setNoticeType(String noticeType) { this.noticeType = noticeType; }
|
||||||
|
public String getNoticeContent() { return noticeContent; }
|
||||||
|
public void setNoticeContent(String noticeContent) { this.noticeContent = noticeContent; }
|
||||||
|
public String getStatus() { return status; }
|
||||||
|
public void setStatus(String status) { this.status = status; }
|
||||||
|
public String getCreateBy() { return createBy; }
|
||||||
|
public void setCreateBy(String createBy) { this.createBy = createBy; }
|
||||||
|
public String getUpdateBy() { return updateBy; }
|
||||||
|
public void setUpdateBy(String updateBy) { this.updateBy = updateBy; }
|
||||||
|
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||||
|
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||||
|
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package cn.novalon.manage.sys.core.domain;
|
||||||
|
|
||||||
|
public class SysRole extends BaseDomain {
|
||||||
|
|
||||||
|
private String roleName;
|
||||||
|
private String roleKey;
|
||||||
|
private Integer roleSort;
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
public String getRoleName() {
|
||||||
|
return roleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoleName(String roleName) {
|
||||||
|
this.roleName = roleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoleKey() {
|
||||||
|
return roleKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoleKey(String roleKey) {
|
||||||
|
this.roleKey = roleKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getRoleSort() {
|
||||||
|
return roleSort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoleSort(Integer roleSort) {
|
||||||
|
this.roleSort = roleSort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(Integer status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
package cn.novalon.manage.sys.core.domain;
|
||||||
|
|
||||||
|
public class SysUser extends BaseDomain {
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
private String email;
|
||||||
|
private Long roleId;
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getRoleId() {
|
||||||
|
return roleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoleId(Long roleId) {
|
||||||
|
this.roleId = roleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(Integer status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
package cn.novalon.manage.sys.core.domain;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class SysUserMessage {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long userId;
|
||||||
|
private String title;
|
||||||
|
private String content;
|
||||||
|
private String messageType;
|
||||||
|
private String isRead;
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public void setId(Long id) { this.id = id; }
|
||||||
|
public Long getUserId() { return userId; }
|
||||||
|
public void setUserId(Long userId) { this.userId = userId; }
|
||||||
|
public String getTitle() { return title; }
|
||||||
|
public void setTitle(String title) { this.title = title; }
|
||||||
|
public String getContent() { return content; }
|
||||||
|
public void setContent(String content) { this.content = content; }
|
||||||
|
public String getMessageType() { return messageType; }
|
||||||
|
public void setMessageType(String messageType) { this.messageType = messageType; }
|
||||||
|
public String getIsRead() { return isRead; }
|
||||||
|
public void setIsRead(String isRead) { this.isRead = isRead; }
|
||||||
|
public LocalDateTime getCreateTime() { return createTime; }
|
||||||
|
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
|
||||||
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package cn.novalon.manage.sys.core.repository;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.OperationLog;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface IOperationLogRepository {
|
||||||
|
|
||||||
|
Mono<OperationLog> findById(Long id);
|
||||||
|
|
||||||
|
Mono<OperationLog> save(OperationLog operationLog);
|
||||||
|
|
||||||
|
Mono<Void> deleteById(Long id);
|
||||||
|
|
||||||
|
Flux<OperationLog> findAll();
|
||||||
|
|
||||||
|
Flux<OperationLog> findByUsername(String username);
|
||||||
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package cn.novalon.manage.sys.core.repository;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysMenu;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface ISysMenuRepository {
|
||||||
|
|
||||||
|
Mono<SysMenu> findById(Long id);
|
||||||
|
|
||||||
|
Flux<SysMenu> findAll();
|
||||||
|
|
||||||
|
Flux<SysMenu> findByParentId(Long parentId);
|
||||||
|
|
||||||
|
Mono<SysMenu> save(SysMenu sysMenu);
|
||||||
|
|
||||||
|
Mono<Void> deleteById(Long id);
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package cn.novalon.manage.sys.core.repository;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysRole;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface ISysRoleRepository {
|
||||||
|
|
||||||
|
Mono<SysRole> findById(Long id);
|
||||||
|
|
||||||
|
Mono<SysRole> save(SysRole sysRole);
|
||||||
|
|
||||||
|
Mono<Void> deleteById(Long id);
|
||||||
|
|
||||||
|
Flux<SysRole> findAll();
|
||||||
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package cn.novalon.manage.sys.core.repository;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysUser;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface ISysUserRepository {
|
||||||
|
|
||||||
|
Mono<SysUser> findByUsername(String username);
|
||||||
|
|
||||||
|
Mono<SysUser> findById(Long id);
|
||||||
|
|
||||||
|
Mono<SysUser> save(SysUser sysUser);
|
||||||
|
|
||||||
|
Mono<Void> deleteById(Long id);
|
||||||
|
|
||||||
|
Flux<SysUser> findAll();
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.OperationLog;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface IOperationLogService {
|
||||||
|
Mono<OperationLog> save(OperationLog log);
|
||||||
|
Flux<OperationLog> findAll();
|
||||||
|
Flux<OperationLog> findByUsername(String username);
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysConfig;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface ISysConfigService {
|
||||||
|
Flux<SysConfig> findAll();
|
||||||
|
Mono<SysConfig> findById(Long id);
|
||||||
|
Mono<SysConfig> findByConfigKey(String configKey);
|
||||||
|
Mono<SysConfig> save(SysConfig config);
|
||||||
|
Mono<Void> deleteById(Long id);
|
||||||
|
Mono<String> getConfigValue(String configKey);
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysDictData;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface ISysDictDataService {
|
||||||
|
Flux<SysDictData> findAll();
|
||||||
|
Flux<SysDictData> findByDictType(String dictType);
|
||||||
|
Flux<SysDictData> findByDictTypeAndStatus(String dictType, String status);
|
||||||
|
Mono<SysDictData> findById(Long id);
|
||||||
|
Mono<SysDictData> save(SysDictData dictData);
|
||||||
|
Mono<Void> deleteById(Long id);
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysDictType;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface ISysDictTypeService {
|
||||||
|
Flux<SysDictType> findAll();
|
||||||
|
Mono<SysDictType> findById(Long id);
|
||||||
|
Mono<SysDictType> findByDictType(String dictType);
|
||||||
|
Mono<SysDictType> save(SysDictType dictType);
|
||||||
|
Mono<Void> deleteById(Long id);
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysExceptionLog;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public interface ISysExceptionLogService {
|
||||||
|
Flux<SysExceptionLog> findAll();
|
||||||
|
Flux<SysExceptionLog> findByUsername(String username);
|
||||||
|
Flux<SysExceptionLog> findByCreateTimeBetween(LocalDateTime startTime, LocalDateTime endTime);
|
||||||
|
Mono<SysExceptionLog> save(SysExceptionLog exceptionLog);
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysFile;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
public interface ISysFileService {
|
||||||
|
Flux<SysFile> findAll();
|
||||||
|
Flux<SysFile> findByCreateBy(String createBy);
|
||||||
|
Mono<SysFile> findById(Long id);
|
||||||
|
Mono<SysFile> upload(MultipartFile file, String createBy);
|
||||||
|
Mono<Void> deleteById(Long id);
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysLoginLog;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public interface ISysLoginLogService {
|
||||||
|
Flux<SysLoginLog> findAll();
|
||||||
|
Flux<SysLoginLog> findByUsername(String username);
|
||||||
|
Flux<SysLoginLog> findByLoginTimeBetween(LocalDateTime startTime, LocalDateTime endTime);
|
||||||
|
Mono<SysLoginLog> save(SysLoginLog loginLog);
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysMenu;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface ISysMenuService {
|
||||||
|
Mono<SysMenu> findById(Long id);
|
||||||
|
Flux<SysMenu> findAll();
|
||||||
|
Flux<SysMenu> findByParentId(Long parentId);
|
||||||
|
Mono<SysMenu> createMenu(SysMenu menu);
|
||||||
|
Mono<SysMenu> updateMenu(SysMenu menu);
|
||||||
|
Mono<Void> deleteMenu(Long id);
|
||||||
|
Flux<SysMenu> buildMenuTree(Flux<SysMenu> menus);
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysNotice;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface ISysNoticeService {
|
||||||
|
Flux<SysNotice> findAll();
|
||||||
|
Flux<SysNotice> findByStatus(String status);
|
||||||
|
Mono<SysNotice> findById(Long id);
|
||||||
|
Mono<SysNotice> save(SysNotice notice);
|
||||||
|
Mono<Void> deleteById(Long id);
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysRole;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface ISysRoleService {
|
||||||
|
Mono<SysRole> findById(Long id);
|
||||||
|
Flux<SysRole> findAll();
|
||||||
|
Mono<SysRole> createRole(SysRole role);
|
||||||
|
Mono<SysRole> updateRole(SysRole role);
|
||||||
|
Mono<Void> deleteRole(Long id);
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysUserMessage;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface ISysUserMessageService {
|
||||||
|
Flux<SysUserMessage> findByUserId(Long userId);
|
||||||
|
Flux<SysUserMessage> findByUserIdAndIsRead(Long userId, String isRead);
|
||||||
|
Mono<Long> countUnread(Long userId);
|
||||||
|
Mono<SysUserMessage> save(SysUserMessage message);
|
||||||
|
Mono<Void> markAsRead(Long id);
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysUser;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
public interface ISysUserService {
|
||||||
|
Mono<SysUser> findById(Long id);
|
||||||
|
Mono<SysUser> findByUsername(String username);
|
||||||
|
Mono<SysUser> createUser(SysUser user);
|
||||||
|
Mono<SysUser> updateUser(SysUser user);
|
||||||
|
Mono<Void> deleteUser(Long id);
|
||||||
|
Mono<SysUser> changePassword(Long userId, String oldPassword, String newPassword);
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service.impl;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.OperationLog;
|
||||||
|
import cn.novalon.manage.sys.core.repository.IOperationLogRepository;
|
||||||
|
import cn.novalon.manage.sys.core.service.IOperationLogService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class OperationLogService implements IOperationLogService {
|
||||||
|
|
||||||
|
private final IOperationLogRepository logRepository;
|
||||||
|
|
||||||
|
public OperationLogService(IOperationLogRepository logRepository) {
|
||||||
|
this.logRepository = logRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<OperationLog> save(OperationLog log) {
|
||||||
|
log.setCreatedAt(LocalDateTime.now());
|
||||||
|
return logRepository.save(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<OperationLog> findAll() {
|
||||||
|
return logRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<OperationLog> findByUsername(String username) {
|
||||||
|
return logRepository.findByUsername(username);
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service.impl;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysConfig;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysConfigService;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.converter.SysConfigConverter;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.dao.SysConfigDao;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SysConfigServiceImpl implements ISysConfigService {
|
||||||
|
|
||||||
|
private final SysConfigDao dao;
|
||||||
|
private final SysConfigConverter converter;
|
||||||
|
|
||||||
|
public SysConfigServiceImpl(SysConfigDao dao, SysConfigConverter converter) {
|
||||||
|
this.dao = dao;
|
||||||
|
this.converter = converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysConfig> findAll() {
|
||||||
|
return dao.findByDeletedAtIsNull()
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysConfig> findById(Long id) {
|
||||||
|
return dao.findById(id)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysConfig> findByConfigKey(String configKey) {
|
||||||
|
return dao.findByConfigKeyAndDeletedAtIsNull(configKey)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysConfig> save(SysConfig config) {
|
||||||
|
return dao.save(converter.toEntity(config))
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> deleteById(Long id) {
|
||||||
|
return dao.deleteByIdAndDeletedAtIsNull(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<String> getConfigValue(String configKey) {
|
||||||
|
return findByConfigKey(configKey)
|
||||||
|
.map(SysConfig::getConfigValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service.impl;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysDictData;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysDictDataService;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.converter.SysDictDataConverter;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.dao.SysDictDataDao;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SysDictDataServiceImpl implements ISysDictDataService {
|
||||||
|
|
||||||
|
private final SysDictDataDao dao;
|
||||||
|
private final SysDictDataConverter converter;
|
||||||
|
|
||||||
|
public SysDictDataServiceImpl(SysDictDataDao dao, SysDictDataConverter converter) {
|
||||||
|
this.dao = dao;
|
||||||
|
this.converter = converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysDictData> findAll() {
|
||||||
|
return dao.findByDeletedAtIsNull()
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysDictData> findByDictType(String dictType) {
|
||||||
|
return dao.findByDictTypeAndDeletedAtIsNull(dictType)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysDictData> findByDictTypeAndStatus(String dictType, String status) {
|
||||||
|
return dao.findByDictTypeAndStatusAndDeletedAtIsNull(dictType, status)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysDictData> findById(Long id) {
|
||||||
|
return dao.findById(id)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysDictData> save(SysDictData dictData) {
|
||||||
|
return dao.save(converter.toEntity(dictData))
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> deleteById(Long id) {
|
||||||
|
return dao.deleteByIdAndDeletedAtIsNull(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service.impl;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysDictType;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysDictTypeService;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.converter.SysDictTypeConverter;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.dao.SysDictTypeDao;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SysDictTypeServiceImpl implements ISysDictTypeService {
|
||||||
|
|
||||||
|
private final SysDictTypeDao dao;
|
||||||
|
private final SysDictTypeConverter converter;
|
||||||
|
|
||||||
|
public SysDictTypeServiceImpl(SysDictTypeDao dao, SysDictTypeConverter converter) {
|
||||||
|
this.dao = dao;
|
||||||
|
this.converter = converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysDictType> findAll() {
|
||||||
|
return dao.findByDeletedAtIsNull()
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysDictType> findById(Long id) {
|
||||||
|
return dao.findById(id)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysDictType> findByDictType(String dictType) {
|
||||||
|
return dao.findByDictTypeAndDeletedAtIsNull(dictType)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysDictType> save(SysDictType dictType) {
|
||||||
|
return dao.save(converter.toEntity(dictType))
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> deleteById(Long id) {
|
||||||
|
return dao.deleteByIdAndDeletedAtIsNull(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service.impl;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysExceptionLog;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysExceptionLogService;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.converter.SysExceptionLogConverter;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.dao.SysExceptionLogDao;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SysExceptionLogServiceImpl implements ISysExceptionLogService {
|
||||||
|
|
||||||
|
private final SysExceptionLogDao dao;
|
||||||
|
private final SysExceptionLogConverter converter;
|
||||||
|
|
||||||
|
public SysExceptionLogServiceImpl(SysExceptionLogDao dao, SysExceptionLogConverter converter) {
|
||||||
|
this.dao = dao;
|
||||||
|
this.converter = converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysExceptionLog> findAll() {
|
||||||
|
return dao.findAllByOrderByCreateTimeDesc()
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysExceptionLog> findByUsername(String username) {
|
||||||
|
return dao.findByUsernameOrderByCreateTimeDesc(username)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysExceptionLog> findByCreateTimeBetween(LocalDateTime startTime, LocalDateTime endTime) {
|
||||||
|
return dao.findByCreateTimeBetweenOrderByCreateTimeDesc(startTime, endTime)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysExceptionLog> save(SysExceptionLog exceptionLog) {
|
||||||
|
return dao.save(converter.toEntity(exceptionLog))
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
}
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service.impl;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysFile;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysFileService;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.converter.SysFileConverter;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.dao.SysFileDao;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SysFileServiceImpl implements ISysFileService {
|
||||||
|
|
||||||
|
private final SysFileDao dao;
|
||||||
|
private final SysFileConverter converter;
|
||||||
|
private final Path uploadPath = Paths.get("./uploads");
|
||||||
|
|
||||||
|
public SysFileServiceImpl(SysFileDao dao, SysFileConverter converter) {
|
||||||
|
this.dao = dao;
|
||||||
|
this.converter = converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysFile> findAll() {
|
||||||
|
return dao.findByDeletedAtIsNullOrderByCreatedAtDesc()
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysFile> findByCreateBy(String createBy) {
|
||||||
|
return dao.findByCreateByOrderByCreatedAtDesc(createBy)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysFile> findById(Long id) {
|
||||||
|
return dao.findById(id)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysFile> upload(MultipartFile file, String createBy) {
|
||||||
|
try {
|
||||||
|
if (!Files.exists(uploadPath)) {
|
||||||
|
Files.createDirectories(uploadPath);
|
||||||
|
}
|
||||||
|
String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename();
|
||||||
|
Path filePath = uploadPath.resolve(fileName);
|
||||||
|
Files.copy(file.getInputStream(), filePath);
|
||||||
|
|
||||||
|
SysFile sysFile = new SysFile();
|
||||||
|
sysFile.setFileName(file.getOriginalFilename());
|
||||||
|
sysFile.setFilePath("/api/files/download/" + fileName);
|
||||||
|
sysFile.setFileSize(String.valueOf(file.getSize()));
|
||||||
|
sysFile.setFileType(file.getContentType());
|
||||||
|
sysFile.setStorageType("local");
|
||||||
|
sysFile.setCreateBy(createBy);
|
||||||
|
sysFile.setCreatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
|
return dao.save(converter.toEntity(sysFile))
|
||||||
|
.map(converter::toDomain);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return Mono.error(new RuntimeException("文件上传失败: " + e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> deleteById(Long id) {
|
||||||
|
return dao.deleteByIdAndDeletedAtIsNull(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service.impl;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysLoginLog;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysLoginLogService;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.converter.SysLoginLogConverter;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.dao.SysLoginLogDao;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SysLoginLogServiceImpl implements ISysLoginLogService {
|
||||||
|
|
||||||
|
private final SysLoginLogDao dao;
|
||||||
|
private final SysLoginLogConverter converter;
|
||||||
|
|
||||||
|
public SysLoginLogServiceImpl(SysLoginLogDao dao, SysLoginLogConverter converter) {
|
||||||
|
this.dao = dao;
|
||||||
|
this.converter = converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysLoginLog> findAll() {
|
||||||
|
return dao.findAllByOrderByLoginTimeDesc()
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysLoginLog> findByUsername(String username) {
|
||||||
|
return dao.findByUsernameOrderByLoginTimeDesc(username)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysLoginLog> findByLoginTimeBetween(LocalDateTime startTime, LocalDateTime endTime) {
|
||||||
|
return dao.findByLoginTimeBetweenOrderByLoginTimeDesc(startTime, endTime)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysLoginLog> save(SysLoginLog loginLog) {
|
||||||
|
return dao.save(converter.toEntity(loginLog))
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
}
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service.impl;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysMenu;
|
||||||
|
import cn.novalon.manage.sys.core.repository.ISysMenuRepository;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysMenuService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SysMenuService implements ISysMenuService {
|
||||||
|
|
||||||
|
private final ISysMenuRepository menuRepository;
|
||||||
|
|
||||||
|
public SysMenuService(ISysMenuRepository menuRepository) {
|
||||||
|
this.menuRepository = menuRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysMenu> findById(Long id) {
|
||||||
|
return menuRepository.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysMenu> findAll() {
|
||||||
|
return menuRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysMenu> findByParentId(Long parentId) {
|
||||||
|
return menuRepository.findByParentId(parentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysMenu> createMenu(SysMenu menu) {
|
||||||
|
menu.setCreatedAt(LocalDateTime.now());
|
||||||
|
menu.setStatus("0");
|
||||||
|
return menuRepository.save(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysMenu> updateMenu(SysMenu menu) {
|
||||||
|
menu.setUpdatedAt(LocalDateTime.now());
|
||||||
|
return menuRepository.save(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> deleteMenu(Long id) {
|
||||||
|
return menuRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysMenu> buildMenuTree(Flux<SysMenu> menus) {
|
||||||
|
return menus.collectList()
|
||||||
|
.map(list -> buildTree(list, 0L))
|
||||||
|
.flatMapMany(Flux::fromIterable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SysMenu> buildTree(List<SysMenu> menus, Long parentId) {
|
||||||
|
return menus.stream()
|
||||||
|
.filter(m -> m.getParentId().equals(parentId))
|
||||||
|
.peek(m -> m.setChildren(buildTree(menus, m.getId())))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service.impl;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysNotice;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysNoticeService;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.converter.SysNoticeConverter;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.dao.SysNoticeDao;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SysNoticeServiceImpl implements ISysNoticeService {
|
||||||
|
|
||||||
|
private final SysNoticeDao dao;
|
||||||
|
private final SysNoticeConverter converter;
|
||||||
|
|
||||||
|
public SysNoticeServiceImpl(SysNoticeDao dao, SysNoticeConverter converter) {
|
||||||
|
this.dao = dao;
|
||||||
|
this.converter = converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysNotice> findAll() {
|
||||||
|
return dao.findByDeletedAtIsNull()
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysNotice> findByStatus(String status) {
|
||||||
|
return dao.findByStatusAndDeletedAtIsNull(status)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysNotice> findById(Long id) {
|
||||||
|
return dao.findById(id)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysNotice> save(SysNotice notice) {
|
||||||
|
return dao.save(converter.toEntity(notice))
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> deleteById(Long id) {
|
||||||
|
return dao.deleteByIdAndDeletedAtIsNull(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service.impl;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysRole;
|
||||||
|
import cn.novalon.manage.sys.core.repository.ISysRoleRepository;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysRoleService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SysRoleService implements ISysRoleService {
|
||||||
|
|
||||||
|
private final ISysRoleRepository roleRepository;
|
||||||
|
|
||||||
|
public SysRoleService(ISysRoleRepository roleRepository) {
|
||||||
|
this.roleRepository = roleRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysRole> findById(Long id) {
|
||||||
|
return roleRepository.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysRole> findAll() {
|
||||||
|
return roleRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysRole> createRole(SysRole role) {
|
||||||
|
role.setCreatedAt(LocalDateTime.now());
|
||||||
|
role.setStatus(1);
|
||||||
|
return roleRepository.save(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysRole> updateRole(SysRole role) {
|
||||||
|
role.setUpdatedAt(LocalDateTime.now());
|
||||||
|
return roleRepository.save(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> deleteRole(Long id) {
|
||||||
|
return roleRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service.impl;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysUserMessage;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysUserMessageService;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.converter.SysUserMessageConverter;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.dao.SysUserMessageDao;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SysUserMessageServiceImpl implements ISysUserMessageService {
|
||||||
|
|
||||||
|
private final SysUserMessageDao dao;
|
||||||
|
private final SysUserMessageConverter converter;
|
||||||
|
|
||||||
|
public SysUserMessageServiceImpl(SysUserMessageDao dao, SysUserMessageConverter converter) {
|
||||||
|
this.dao = dao;
|
||||||
|
this.converter = converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysUserMessage> findByUserId(Long userId) {
|
||||||
|
return dao.findByUserIdOrderByCreateTimeDesc(userId)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<SysUserMessage> findByUserIdAndIsRead(Long userId, String isRead) {
|
||||||
|
return dao.findByUserIdAndIsReadOrderByCreateTimeDesc(userId, isRead)
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Long> countUnread(Long userId) {
|
||||||
|
return dao.countByUserIdAndIsRead(userId, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysUserMessage> save(SysUserMessage message) {
|
||||||
|
return dao.save(converter.toEntity(message))
|
||||||
|
.map(converter::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> markAsRead(Long id) {
|
||||||
|
return dao.findById(id)
|
||||||
|
.flatMap(entity -> {
|
||||||
|
entity.setIsRead("1");
|
||||||
|
return dao.save(entity);
|
||||||
|
})
|
||||||
|
.then();
|
||||||
|
}
|
||||||
|
}
|
||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
package cn.novalon.manage.sys.core.service.impl;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysUser;
|
||||||
|
import cn.novalon.manage.sys.core.repository.ISysUserRepository;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysUserService;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SysUserService implements ISysUserService {
|
||||||
|
|
||||||
|
private final ISysUserRepository userRepository;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
public SysUserService(ISysUserRepository userRepository, PasswordEncoder passwordEncoder) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysUser> findById(Long id) {
|
||||||
|
return userRepository.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysUser> findByUsername(String username) {
|
||||||
|
return userRepository.findByUsername(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysUser> createUser(SysUser user) {
|
||||||
|
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||||
|
user.setCreatedAt(LocalDateTime.now());
|
||||||
|
user.setStatus(1);
|
||||||
|
return userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysUser> updateUser(SysUser user) {
|
||||||
|
user.setUpdatedAt(LocalDateTime.now());
|
||||||
|
return userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> deleteUser(Long id) {
|
||||||
|
return userRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SysUser> changePassword(Long userId, String oldPassword, String newPassword) {
|
||||||
|
return userRepository.findById(userId)
|
||||||
|
.flatMap(user -> {
|
||||||
|
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
|
||||||
|
return Mono.error(new RuntimeException("旧密码不正确"));
|
||||||
|
}
|
||||||
|
user.setPassword(passwordEncoder.encode(newPassword));
|
||||||
|
user.setUpdatedAt(LocalDateTime.now());
|
||||||
|
return userRepository.save(user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package cn.novalon.manage.sys.dto.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public class LoginRequest {
|
||||||
|
|
||||||
|
@NotBlank(message = "用户名不能为空")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@NotBlank(message = "密码不能为空")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package cn.novalon.manage.sys.dto.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public class PasswordChangeRequest {
|
||||||
|
|
||||||
|
@NotBlank(message = "旧密码不能为空")
|
||||||
|
private String oldPassword;
|
||||||
|
|
||||||
|
@NotBlank(message = "新密码不能为空")
|
||||||
|
private String newPassword;
|
||||||
|
|
||||||
|
public String getOldPassword() {
|
||||||
|
return oldPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOldPassword(String oldPassword) {
|
||||||
|
this.oldPassword = oldPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewPassword() {
|
||||||
|
return newPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNewPassword(String newPassword) {
|
||||||
|
this.newPassword = newPassword;
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
package cn.novalon.manage.sys.dto.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
|
public class UserRegisterRequest {
|
||||||
|
|
||||||
|
@NotBlank(message = "用户名不能为空")
|
||||||
|
@Size(min = 3, max = 50, message = "用户名长度必须在3-50之间")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@NotBlank(message = "密码不能为空")
|
||||||
|
@Size(min = 6, max = 100, message = "密码长度必须在6-100之间")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Email(message = "邮箱格式不正确")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
package cn.novalon.manage.sys.dto.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
|
||||||
|
public class UserUpdateRequest {
|
||||||
|
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
private Long roleId;
|
||||||
|
|
||||||
|
@Email(message = "邮箱格式不正确")
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(Integer status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getRoleId() {
|
||||||
|
return roleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoleId(Long roleId) {
|
||||||
|
this.roleId = roleId;
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package cn.novalon.manage.sys.dto.response;
|
||||||
|
|
||||||
|
public class AuthResponse {
|
||||||
|
|
||||||
|
private String token;
|
||||||
|
private Long userId;
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
public AuthResponse() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthResponse(String token, Long userId, String username) {
|
||||||
|
this.token = token;
|
||||||
|
this.userId = userId;
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(Long userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
}
|
||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
package cn.novalon.manage.sys.dto.response;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class UserResponse {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String username;
|
||||||
|
private String email;
|
||||||
|
private Long roleId;
|
||||||
|
private Integer status;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getRoleId() {
|
||||||
|
return roleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoleId(Long roleId) {
|
||||||
|
this.roleId = roleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(Integer status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getUpdatedAt() {
|
||||||
|
return updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserResponse fromDomain(cn.novalon.manage.sys.core.domain.SysUser user) {
|
||||||
|
UserResponse response = new UserResponse();
|
||||||
|
response.setId(user.getId());
|
||||||
|
response.setUsername(user.getUsername());
|
||||||
|
response.setEmail(user.getEmail());
|
||||||
|
response.setRoleId(user.getRoleId());
|
||||||
|
response.setStatus(user.getStatus());
|
||||||
|
response.setCreatedAt(user.getCreatedAt());
|
||||||
|
response.setUpdatedAt(user.getUpdatedAt());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
package cn.novalon.manage.sys.handler;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysExceptionLog;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysExceptionLogService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.context.request.WebRequest;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||||
|
|
||||||
|
private final ISysExceptionLogService exceptionLogService;
|
||||||
|
|
||||||
|
public GlobalExceptionHandler(ISysExceptionLogService exceptionLogService) {
|
||||||
|
this.exceptionLogService = exceptionLogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public ResponseEntity<Map<String, Object>> handleException(Exception ex, WebRequest request) {
|
||||||
|
logger.error("Exception occurred: ", ex);
|
||||||
|
|
||||||
|
SysExceptionLog exceptionLog = new SysExceptionLog();
|
||||||
|
exceptionLog.setTitle("System Exception");
|
||||||
|
exceptionLog.setExceptionName(ex.getClass().getSimpleName());
|
||||||
|
exceptionLog.setExceptionMsg(ex.getMessage());
|
||||||
|
exceptionLog.setMethodName(request.getDescription(false));
|
||||||
|
exceptionLog.setIp(getClientIp(request));
|
||||||
|
exceptionLog.setCreateTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
StringBuilder stackTrace = new StringBuilder();
|
||||||
|
for (StackTraceElement element : ex.getStackTrace()) {
|
||||||
|
stackTrace.append(element.toString()).append("\n");
|
||||||
|
}
|
||||||
|
exceptionLog.setExceptionStack(stackTrace.toString());
|
||||||
|
|
||||||
|
exceptionLogService.save(exceptionLog).subscribe();
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||||
|
response.put("message", ex.getMessage());
|
||||||
|
response.put("timestamp", LocalDateTime.now());
|
||||||
|
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(IllegalArgumentException.class)
|
||||||
|
public ResponseEntity<Map<String, Object>> handleIllegalArgumentException(IllegalArgumentException ex, WebRequest request) {
|
||||||
|
logger.warn("Illegal argument: ", ex);
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("code", HttpStatus.BAD_REQUEST.value());
|
||||||
|
response.put("message", ex.getMessage());
|
||||||
|
response.put("timestamp", LocalDateTime.now());
|
||||||
|
|
||||||
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getClientIp(WebRequest request) {
|
||||||
|
String ip = request.getHeader("X-Forwarded-For");
|
||||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("X-Real-IP");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = "127.0.0.1";
|
||||||
|
}
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
package cn.novalon.manage.sys.handler.auth;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.dto.request.LoginRequest;
|
||||||
|
import cn.novalon.manage.sys.dto.request.UserRegisterRequest;
|
||||||
|
import cn.novalon.manage.sys.dto.response.AuthResponse;
|
||||||
|
import cn.novalon.manage.sys.security.JwtTokenProvider;
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysUser;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysUserService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/auth")
|
||||||
|
public class SysAuthHandler {
|
||||||
|
|
||||||
|
private final ISysUserService userService;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
|
|
||||||
|
public SysAuthHandler(ISysUserService userService, PasswordEncoder passwordEncoder, JwtTokenProvider jwtTokenProvider) {
|
||||||
|
this.userService = userService;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.jwtTokenProvider = jwtTokenProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public Mono<ResponseEntity<AuthResponse>> login(@Valid @RequestBody LoginRequest request) {
|
||||||
|
return userService.findByUsername(request.getUsername())
|
||||||
|
.filter(user -> passwordEncoder.matches(request.getPassword(), user.getPassword()))
|
||||||
|
.filter(user -> 1 == user.getStatus())
|
||||||
|
.map(user -> {
|
||||||
|
String token = jwtTokenProvider.generateToken(user.getUsername(), user.getId());
|
||||||
|
AuthResponse response = new AuthResponse(token, user.getId(), user.getUsername());
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
})
|
||||||
|
.defaultIfEmpty(ResponseEntity.status(HttpStatus.UNAUTHORIZED).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/register")
|
||||||
|
public Mono<ResponseEntity<SysUser>> register(@Valid @RequestBody UserRegisterRequest request) {
|
||||||
|
SysUser user = new SysUser();
|
||||||
|
user.setUsername(request.getUsername());
|
||||||
|
user.setPassword(request.getPassword());
|
||||||
|
user.setEmail(request.getEmail());
|
||||||
|
return userService.findByUsername(request.getUsername())
|
||||||
|
.flatMap(existing -> Mono.<ResponseEntity<SysUser>>error(new RuntimeException("用户名已存在")))
|
||||||
|
.switchIfEmpty(userService.createUser(user)
|
||||||
|
.map(u -> ResponseEntity.status(HttpStatus.CREATED).body(u)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/logout")
|
||||||
|
public Mono<ResponseEntity<Void>> logout() {
|
||||||
|
return Mono.just(ResponseEntity.ok().build());
|
||||||
|
}
|
||||||
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
package cn.novalon.manage.sys.handler.sys;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysConfig;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysConfigService;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/config")
|
||||||
|
public class SysConfigHandler {
|
||||||
|
|
||||||
|
private final ISysConfigService configService;
|
||||||
|
|
||||||
|
public SysConfigHandler(ISysConfigService configService) {
|
||||||
|
this.configService = configService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public Flux<SysConfig> getAllConfigs() {
|
||||||
|
return configService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<SysConfig>> getConfigById(@PathVariable Long id) {
|
||||||
|
return configService.findById(id)
|
||||||
|
.map(config -> ResponseEntity.ok(config))
|
||||||
|
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/key/{configKey}")
|
||||||
|
public Mono<ResponseEntity<SysConfig>> getConfigByKey(@PathVariable String configKey) {
|
||||||
|
return configService.findByConfigKey(configKey)
|
||||||
|
.map(config -> ResponseEntity.ok(config))
|
||||||
|
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public Mono<ResponseEntity<SysConfig>> createConfig(@RequestBody SysConfig config) {
|
||||||
|
config.setConfigType("N");
|
||||||
|
config.setCreatedAt(LocalDateTime.now());
|
||||||
|
return configService.save(config)
|
||||||
|
.map(saved -> ResponseEntity.ok(saved));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<SysConfig>> updateConfig(@PathVariable Long id, @RequestBody SysConfig config) {
|
||||||
|
config.setId(id);
|
||||||
|
config.setUpdatedAt(LocalDateTime.now());
|
||||||
|
return configService.save(config)
|
||||||
|
.map(saved -> ResponseEntity.ok(saved));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<Void>> deleteConfig(@PathVariable Long id) {
|
||||||
|
return configService.deleteById(id)
|
||||||
|
.then(Mono.just(ResponseEntity.noContent().build()));
|
||||||
|
}
|
||||||
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
package cn.novalon.manage.sys.handler.sys;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysDictType;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysDictTypeService;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/dict")
|
||||||
|
public class SysDictHandler {
|
||||||
|
|
||||||
|
private final ISysDictTypeService dictTypeService;
|
||||||
|
|
||||||
|
public SysDictHandler(ISysDictTypeService dictTypeService) {
|
||||||
|
this.dictTypeService = dictTypeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/types")
|
||||||
|
public Flux<SysDictType> getAllDictTypes() {
|
||||||
|
return dictTypeService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/types/{id}")
|
||||||
|
public Mono<ResponseEntity<SysDictType>> getDictTypeById(@PathVariable Long id) {
|
||||||
|
return dictTypeService.findById(id)
|
||||||
|
.map(dictType -> ResponseEntity.ok(dictType))
|
||||||
|
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/types/type/{dictType}")
|
||||||
|
public Mono<ResponseEntity<SysDictType>> getDictTypeByDictType(@PathVariable String dictType) {
|
||||||
|
return dictTypeService.findByDictType(dictType)
|
||||||
|
.map(dictTypeResult -> ResponseEntity.ok(dictTypeResult))
|
||||||
|
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/types")
|
||||||
|
public Mono<ResponseEntity<SysDictType>> createDictType(@RequestBody SysDictType dictType) {
|
||||||
|
dictType.setStatus("0");
|
||||||
|
dictType.setCreatedAt(LocalDateTime.now());
|
||||||
|
return dictTypeService.save(dictType)
|
||||||
|
.map(saved -> ResponseEntity.ok(saved));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/types/{id}")
|
||||||
|
public Mono<ResponseEntity<SysDictType>> updateDictType(@PathVariable Long id, @RequestBody SysDictType dictType) {
|
||||||
|
dictType.setId(id);
|
||||||
|
dictType.setUpdatedAt(LocalDateTime.now());
|
||||||
|
return dictTypeService.save(dictType)
|
||||||
|
.map(saved -> ResponseEntity.ok(saved));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/types/{id}")
|
||||||
|
public Mono<ResponseEntity<Void>> deleteDictType(@PathVariable Long id) {
|
||||||
|
return dictTypeService.deleteById(id)
|
||||||
|
.then(Mono.just(ResponseEntity.noContent().build()));
|
||||||
|
}
|
||||||
|
}
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
package cn.novalon.manage.sys.handler.sys;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysFile;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysFileService;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.UrlResource;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/files")
|
||||||
|
public class SysFileHandler {
|
||||||
|
|
||||||
|
private final ISysFileService fileService;
|
||||||
|
private final Path uploadPath = Paths.get("./uploads");
|
||||||
|
|
||||||
|
public SysFileHandler(ISysFileService fileService) {
|
||||||
|
this.fileService = fileService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public Flux<SysFile> getAllFiles() {
|
||||||
|
return fileService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<SysFile>> getFileById(@PathVariable Long id) {
|
||||||
|
return fileService.findById(id)
|
||||||
|
.map(file -> ResponseEntity.ok(file))
|
||||||
|
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/upload")
|
||||||
|
public Mono<ResponseEntity<SysFile>> uploadFile(@RequestParam("file") MultipartFile file,
|
||||||
|
@RequestParam(value = "createBy", required = false, defaultValue = "anonymous") String createBy) {
|
||||||
|
return fileService.upload(file, createBy)
|
||||||
|
.map(saved -> ResponseEntity.ok(saved));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/download/{fileName}")
|
||||||
|
public Mono<Resource> downloadFile(@PathVariable String fileName) throws MalformedURLException {
|
||||||
|
Path filePath = uploadPath.resolve(fileName);
|
||||||
|
Resource resource = new UrlResource(filePath.toUri());
|
||||||
|
return Mono.just(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/preview/{fileName}")
|
||||||
|
public Mono<ResponseEntity<byte[]>> previewFile(@PathVariable String fileName) throws MalformedURLException {
|
||||||
|
return Mono.fromCallable(() -> {
|
||||||
|
Path filePath = uploadPath.resolve(fileName);
|
||||||
|
byte[] data = Files.readAllBytes(filePath);
|
||||||
|
return data;
|
||||||
|
}).map(data -> {
|
||||||
|
String contentType = "application/octet-stream";
|
||||||
|
try {
|
||||||
|
contentType = Files.probeContentType(uploadPath.resolve(fileName));
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.parseMediaType(contentType))
|
||||||
|
.body(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<Void>> deleteFile(@PathVariable Long id) {
|
||||||
|
return fileService.findById(id)
|
||||||
|
.flatMap(file -> {
|
||||||
|
try {
|
||||||
|
String fileName = file.getFilePath().substring(file.getFilePath().lastIndexOf("/") + 1);
|
||||||
|
Files.deleteIfExists(uploadPath.resolve(fileName));
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
return fileService.deleteById(id);
|
||||||
|
})
|
||||||
|
.then(Mono.just(ResponseEntity.noContent().build()));
|
||||||
|
}
|
||||||
|
}
|
||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
package cn.novalon.manage.sys.handler.sys;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysLoginLog;
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysExceptionLog;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysLoginLogService;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysExceptionLogService;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/logs")
|
||||||
|
public class SysLogHandler {
|
||||||
|
|
||||||
|
private final ISysLoginLogService loginLogService;
|
||||||
|
private final ISysExceptionLogService exceptionLogService;
|
||||||
|
|
||||||
|
public SysLogHandler(ISysLoginLogService loginLogService, ISysExceptionLogService exceptionLogService) {
|
||||||
|
this.loginLogService = loginLogService;
|
||||||
|
this.exceptionLogService = exceptionLogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/login")
|
||||||
|
public Flux<SysLoginLog> getLoginLogs() {
|
||||||
|
return loginLogService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/login/{id}")
|
||||||
|
public Mono<ResponseEntity<SysLoginLog>> getLoginLogById(@PathVariable Long id) {
|
||||||
|
return loginLogService.findAll()
|
||||||
|
.filter(log -> log.getId().equals(id))
|
||||||
|
.next()
|
||||||
|
.map(log -> ResponseEntity.ok(log))
|
||||||
|
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public Mono<ResponseEntity<SysLoginLog>> createLoginLog(@RequestBody SysLoginLog log) {
|
||||||
|
log.setLoginTime(LocalDateTime.now());
|
||||||
|
return loginLogService.save(log)
|
||||||
|
.map(saved -> ResponseEntity.ok(saved));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/login/user/{username}")
|
||||||
|
public Flux<SysLoginLog> getLoginLogsByUsername(@PathVariable String username) {
|
||||||
|
return loginLogService.findByUsername(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/exception")
|
||||||
|
public Flux<SysExceptionLog> getExceptionLogs() {
|
||||||
|
return exceptionLogService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/exception/{id}")
|
||||||
|
public Mono<ResponseEntity<SysExceptionLog>> getExceptionLogById(@PathVariable Long id) {
|
||||||
|
return exceptionLogService.findAll()
|
||||||
|
.filter(log -> log.getId().equals(id))
|
||||||
|
.next()
|
||||||
|
.map(log -> ResponseEntity.ok(log))
|
||||||
|
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/exception/user/{username}")
|
||||||
|
public Flux<SysExceptionLog> getExceptionLogsByUsername(@PathVariable String username) {
|
||||||
|
return exceptionLogService.findByUsername(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/exception")
|
||||||
|
public Mono<ResponseEntity<SysExceptionLog>> createExceptionLog(@RequestBody SysExceptionLog log) {
|
||||||
|
log.setCreateTime(LocalDateTime.now());
|
||||||
|
return exceptionLogService.save(log)
|
||||||
|
.map(saved -> ResponseEntity.ok(saved));
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
package cn.novalon.manage.sys.handler.sys;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysMenu;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysMenuService;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/menus")
|
||||||
|
public class SysMenuHandler {
|
||||||
|
|
||||||
|
private final ISysMenuService menuService;
|
||||||
|
|
||||||
|
public SysMenuHandler(ISysMenuService menuService) {
|
||||||
|
this.menuService = menuService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public Flux<SysMenu> getAllMenus() {
|
||||||
|
return menuService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/tree")
|
||||||
|
public Flux<SysMenu> getMenuTree() {
|
||||||
|
return menuService.buildMenuTree(menuService.findAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<SysMenu>> getMenuById(@PathVariable Long id) {
|
||||||
|
return menuService.findById(id)
|
||||||
|
.map(ResponseEntity::ok)
|
||||||
|
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public Mono<ResponseEntity<SysMenu>> createMenu(@RequestBody SysMenu menu) {
|
||||||
|
return menuService.createMenu(menu)
|
||||||
|
.map(m -> ResponseEntity.status(HttpStatus.CREATED).body(m));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<SysMenu>> updateMenu(@PathVariable Long id, @RequestBody SysMenu menu) {
|
||||||
|
menu.setId(id);
|
||||||
|
return menuService.updateMenu(menu)
|
||||||
|
.map(ResponseEntity::ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<Void>> deleteMenu(@PathVariable Long id) {
|
||||||
|
return menuService.deleteMenu(id)
|
||||||
|
.then(Mono.just(ResponseEntity.noContent().build()));
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package cn.novalon.manage.sys.handler.sys;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysUserMessage;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysUserMessageService;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/messages")
|
||||||
|
public class SysMessageHandler {
|
||||||
|
|
||||||
|
private final ISysUserMessageService messageService;
|
||||||
|
|
||||||
|
public SysMessageHandler(ISysUserMessageService messageService) {
|
||||||
|
this.messageService = messageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/user/{userId}")
|
||||||
|
public Flux<SysUserMessage> getMessagesByUserId(@PathVariable Long userId) {
|
||||||
|
return messageService.findByUserId(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/user/{userId}/unread")
|
||||||
|
public Mono<Long> getUnreadCount(@PathVariable Long userId) {
|
||||||
|
return messageService.countUnread(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/user/{userId}/unread/list")
|
||||||
|
public Flux<SysUserMessage> getUnreadMessages(@PathVariable Long userId) {
|
||||||
|
return messageService.findByUserIdAndIsRead(userId, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public Mono<ResponseEntity<SysUserMessage>> createMessage(@RequestBody SysUserMessage message) {
|
||||||
|
message.setIsRead("0");
|
||||||
|
return messageService.save(message)
|
||||||
|
.map(saved -> ResponseEntity.ok(saved));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}/read")
|
||||||
|
public Mono<ResponseEntity<Void>> markAsRead(@PathVariable Long id) {
|
||||||
|
return messageService.markAsRead(id)
|
||||||
|
.then(Mono.just(ResponseEntity.ok().<Void>build()));
|
||||||
|
}
|
||||||
|
}
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
package cn.novalon.manage.sys.handler.sys;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysNotice;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysNoticeService;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/notices")
|
||||||
|
public class SysNoticeHandler {
|
||||||
|
|
||||||
|
private final ISysNoticeService noticeService;
|
||||||
|
|
||||||
|
public SysNoticeHandler(ISysNoticeService noticeService) {
|
||||||
|
this.noticeService = noticeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public Flux<SysNotice> getAllNotices() {
|
||||||
|
return noticeService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<SysNotice>> getNoticeById(@PathVariable Long id) {
|
||||||
|
return noticeService.findById(id)
|
||||||
|
.map(notice -> ResponseEntity.ok(notice))
|
||||||
|
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/status/{status}")
|
||||||
|
public Flux<SysNotice> getNoticesByStatus(@PathVariable String status) {
|
||||||
|
return noticeService.findByStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public Mono<ResponseEntity<SysNotice>> createNotice(@RequestBody SysNotice notice) {
|
||||||
|
notice.setStatus("0");
|
||||||
|
notice.setCreatedAt(LocalDateTime.now());
|
||||||
|
return noticeService.save(notice)
|
||||||
|
.map(saved -> ResponseEntity.ok(saved));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<SysNotice>> updateNotice(@PathVariable Long id, @RequestBody SysNotice notice) {
|
||||||
|
notice.setId(id);
|
||||||
|
notice.setUpdatedAt(LocalDateTime.now());
|
||||||
|
return noticeService.save(notice)
|
||||||
|
.map(saved -> ResponseEntity.ok(saved));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<Void>> deleteNotice(@PathVariable Long id) {
|
||||||
|
return noticeService.deleteById(id)
|
||||||
|
.then(Mono.just(ResponseEntity.noContent().build()));
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
package cn.novalon.manage.sys.handler.sys;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysRole;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysRoleService;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/roles")
|
||||||
|
public class SysRoleHandler {
|
||||||
|
|
||||||
|
private final ISysRoleService roleService;
|
||||||
|
|
||||||
|
public SysRoleHandler(ISysRoleService roleService) {
|
||||||
|
this.roleService = roleService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public Flux<SysRole> getAllRoles() {
|
||||||
|
return roleService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<SysRole>> getRoleById(@PathVariable Long id) {
|
||||||
|
return roleService.findById(id)
|
||||||
|
.map(ResponseEntity::ok)
|
||||||
|
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public Mono<ResponseEntity<SysRole>> createRole(@RequestBody SysRole role) {
|
||||||
|
return roleService.createRole(role)
|
||||||
|
.map(r -> ResponseEntity.status(HttpStatus.CREATED).body(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<SysRole>> updateRole(@PathVariable Long id, @RequestBody SysRole role) {
|
||||||
|
role.setId(id);
|
||||||
|
return roleService.updateRole(role)
|
||||||
|
.map(ResponseEntity::ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<Void>> deleteRole(@PathVariable Long id) {
|
||||||
|
return roleService.deleteRole(id)
|
||||||
|
.then(Mono.just(ResponseEntity.noContent().build()));
|
||||||
|
}
|
||||||
|
}
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
package cn.novalon.manage.sys.handler.user;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysUser;
|
||||||
|
import cn.novalon.manage.sys.core.service.ISysUserService;
|
||||||
|
import cn.novalon.manage.sys.dto.request.PasswordChangeRequest;
|
||||||
|
import cn.novalon.manage.sys.dto.request.UserUpdateRequest;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/users")
|
||||||
|
public class SysUserHandler {
|
||||||
|
|
||||||
|
private final ISysUserService userService;
|
||||||
|
|
||||||
|
public SysUserHandler(ISysUserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<SysUser>> getUserById(@PathVariable Long id) {
|
||||||
|
return userService.findById(id)
|
||||||
|
.map(ResponseEntity::ok)
|
||||||
|
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/username/{username}")
|
||||||
|
public Mono<ResponseEntity<SysUser>> getUserByUsername(@PathVariable String username) {
|
||||||
|
return userService.findByUsername(username)
|
||||||
|
.map(ResponseEntity::ok)
|
||||||
|
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public Mono<ResponseEntity<SysUser>> createUser(@RequestBody SysUser user) {
|
||||||
|
return userService.createUser(user)
|
||||||
|
.map(u -> ResponseEntity.status(HttpStatus.CREATED).body(u));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<SysUser>> updateUser(@PathVariable Long id, @Valid @RequestBody UserUpdateRequest request) {
|
||||||
|
return userService.findById(id)
|
||||||
|
.flatMap(existing -> {
|
||||||
|
if (request.getEmail() != null) {
|
||||||
|
existing.setEmail(request.getEmail());
|
||||||
|
}
|
||||||
|
if (request.getStatus() != null) {
|
||||||
|
existing.setStatus(request.getStatus());
|
||||||
|
}
|
||||||
|
if (request.getRoleId() != null) {
|
||||||
|
existing.setRoleId(request.getRoleId());
|
||||||
|
}
|
||||||
|
return userService.updateUser(existing);
|
||||||
|
})
|
||||||
|
.map(ResponseEntity::ok)
|
||||||
|
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public Mono<ResponseEntity<Void>> deleteUser(@PathVariable Long id) {
|
||||||
|
return userService.deleteUser(id)
|
||||||
|
.then(Mono.just(ResponseEntity.noContent().build()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/password")
|
||||||
|
public Mono<ResponseEntity<SysUser>> changePassword(
|
||||||
|
@PathVariable Long id,
|
||||||
|
@Valid @RequestBody PasswordChangeRequest request) {
|
||||||
|
return userService.changePassword(id, request.getOldPassword(), request.getNewPassword())
|
||||||
|
.map(ResponseEntity::ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
package cn.novalon.manage.sys.infrastructure.db.converter;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.OperationLog;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.entity.OperationLogEntity;
|
||||||
|
|
||||||
|
public class OperationLogConverter {
|
||||||
|
|
||||||
|
public OperationLog toDomain(OperationLogEntity entity) {
|
||||||
|
if (entity == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
OperationLog domain = new OperationLog();
|
||||||
|
domain.setId(entity.getId());
|
||||||
|
domain.setUsername(entity.getUsername());
|
||||||
|
domain.setOperation(entity.getOperation());
|
||||||
|
domain.setMethod(entity.getMethod());
|
||||||
|
domain.setParams(entity.getParams());
|
||||||
|
domain.setResult(entity.getResult());
|
||||||
|
domain.setIp(entity.getIp());
|
||||||
|
domain.setDuration(entity.getDuration());
|
||||||
|
domain.setStatus(entity.getStatus());
|
||||||
|
domain.setErrorMsg(entity.getErrorMsg());
|
||||||
|
domain.setCreatedAt(entity.getCreatedAt());
|
||||||
|
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||||
|
domain.setDeletedAt(entity.getDeletedAt());
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OperationLogEntity toEntity(OperationLog domain) {
|
||||||
|
if (domain == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
OperationLogEntity entity = new OperationLogEntity();
|
||||||
|
entity.setId(domain.getId());
|
||||||
|
entity.setUsername(domain.getUsername());
|
||||||
|
entity.setOperation(domain.getOperation());
|
||||||
|
entity.setMethod(domain.getMethod());
|
||||||
|
entity.setParams(domain.getParams());
|
||||||
|
entity.setResult(domain.getResult());
|
||||||
|
entity.setIp(domain.getIp());
|
||||||
|
entity.setDuration(domain.getDuration());
|
||||||
|
entity.setStatus(domain.getStatus());
|
||||||
|
entity.setErrorMsg(domain.getErrorMsg());
|
||||||
|
entity.setCreatedAt(domain.getCreatedAt());
|
||||||
|
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||||
|
entity.setDeletedAt(domain.getDeletedAt());
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package cn.novalon.manage.sys.infrastructure.db.converter;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysConfig;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.entity.SysConfigEntity;
|
||||||
|
|
||||||
|
public class SysConfigConverter {
|
||||||
|
|
||||||
|
public SysConfig toDomain(SysConfigEntity entity) {
|
||||||
|
if (entity == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
SysConfig domain = new SysConfig();
|
||||||
|
domain.setId(entity.getId());
|
||||||
|
domain.setConfigName(entity.getConfigName());
|
||||||
|
domain.setConfigKey(entity.getConfigKey());
|
||||||
|
domain.setConfigValue(entity.getConfigValue());
|
||||||
|
domain.setConfigType(entity.getConfigType());
|
||||||
|
domain.setCreateBy(entity.getCreateBy());
|
||||||
|
domain.setUpdateBy(entity.getUpdateBy());
|
||||||
|
domain.setCreatedAt(entity.getCreatedAt());
|
||||||
|
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysConfigEntity toEntity(SysConfig domain) {
|
||||||
|
if (domain == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
SysConfigEntity entity = new SysConfigEntity();
|
||||||
|
entity.setId(domain.getId());
|
||||||
|
entity.setConfigName(domain.getConfigName());
|
||||||
|
entity.setConfigKey(domain.getConfigKey());
|
||||||
|
entity.setConfigValue(domain.getConfigValue());
|
||||||
|
entity.setConfigType(domain.getConfigType());
|
||||||
|
entity.setCreateBy(domain.getCreateBy());
|
||||||
|
entity.setUpdateBy(domain.getUpdateBy());
|
||||||
|
entity.setCreatedAt(domain.getCreatedAt());
|
||||||
|
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
package cn.novalon.manage.sys.infrastructure.db.converter;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysDictData;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.entity.SysDictDataEntity;
|
||||||
|
|
||||||
|
public class SysDictDataConverter {
|
||||||
|
|
||||||
|
public SysDictData toDomain(SysDictDataEntity entity) {
|
||||||
|
if (entity == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
SysDictData domain = new SysDictData();
|
||||||
|
domain.setId(entity.getId());
|
||||||
|
domain.setDictSort(entity.getDictSort());
|
||||||
|
domain.setDictLabel(entity.getDictLabel());
|
||||||
|
domain.setDictValue(entity.getDictValue());
|
||||||
|
domain.setDictType(entity.getDictType());
|
||||||
|
domain.setCssClass(entity.getCssClass());
|
||||||
|
domain.setListClass(entity.getListClass());
|
||||||
|
domain.setIsDefault(entity.getIsDefault());
|
||||||
|
domain.setStatus(entity.getStatus());
|
||||||
|
domain.setCreateBy(entity.getCreateBy());
|
||||||
|
domain.setUpdateBy(entity.getUpdateBy());
|
||||||
|
domain.setCreatedAt(entity.getCreatedAt());
|
||||||
|
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysDictDataEntity toEntity(SysDictData domain) {
|
||||||
|
if (domain == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
SysDictDataEntity entity = new SysDictDataEntity();
|
||||||
|
entity.setId(domain.getId());
|
||||||
|
entity.setDictSort(domain.getDictSort());
|
||||||
|
entity.setDictLabel(domain.getDictLabel());
|
||||||
|
entity.setDictValue(domain.getDictValue());
|
||||||
|
entity.setDictType(domain.getDictType());
|
||||||
|
entity.setCssClass(domain.getCssClass());
|
||||||
|
entity.setListClass(domain.getListClass());
|
||||||
|
entity.setIsDefault(domain.getIsDefault());
|
||||||
|
entity.setStatus(domain.getStatus());
|
||||||
|
entity.setCreateBy(domain.getCreateBy());
|
||||||
|
entity.setUpdateBy(domain.getUpdateBy());
|
||||||
|
entity.setCreatedAt(domain.getCreatedAt());
|
||||||
|
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package cn.novalon.manage.sys.infrastructure.db.converter;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysDictType;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.entity.SysDictTypeEntity;
|
||||||
|
|
||||||
|
public class SysDictTypeConverter {
|
||||||
|
|
||||||
|
public SysDictType toDomain(SysDictTypeEntity entity) {
|
||||||
|
if (entity == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
SysDictType domain = new SysDictType();
|
||||||
|
domain.setId(entity.getId());
|
||||||
|
domain.setDictName(entity.getDictName());
|
||||||
|
domain.setDictType(entity.getDictType());
|
||||||
|
domain.setStatus(entity.getStatus());
|
||||||
|
domain.setRemark(entity.getRemark());
|
||||||
|
domain.setCreateBy(entity.getCreateBy());
|
||||||
|
domain.setUpdateBy(entity.getUpdateBy());
|
||||||
|
domain.setCreatedAt(entity.getCreatedAt());
|
||||||
|
domain.setUpdatedAt(entity.getUpdatedAt());
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysDictTypeEntity toEntity(SysDictType domain) {
|
||||||
|
if (domain == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
SysDictTypeEntity entity = new SysDictTypeEntity();
|
||||||
|
entity.setId(domain.getId());
|
||||||
|
entity.setDictName(domain.getDictName());
|
||||||
|
entity.setDictType(domain.getDictType());
|
||||||
|
entity.setStatus(domain.getStatus());
|
||||||
|
entity.setRemark(domain.getRemark());
|
||||||
|
entity.setCreateBy(domain.getCreateBy());
|
||||||
|
entity.setUpdateBy(domain.getUpdateBy());
|
||||||
|
entity.setCreatedAt(domain.getCreatedAt());
|
||||||
|
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
package cn.novalon.manage.sys.infrastructure.db.converter;
|
||||||
|
|
||||||
|
import cn.novalon.manage.sys.core.domain.SysExceptionLog;
|
||||||
|
import cn.novalon.manage.sys.infrastructure.db.entity.SysExceptionLogEntity;
|
||||||
|
|
||||||
|
public class SysExceptionLogConverter {
|
||||||
|
|
||||||
|
public SysExceptionLog toDomain(SysExceptionLogEntity entity) {
|
||||||
|
if (entity == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
SysExceptionLog domain = new SysExceptionLog();
|
||||||
|
domain.setId(entity.getId());
|
||||||
|
domain.setUsername(entity.getUsername());
|
||||||
|
domain.setTitle(entity.getTitle());
|
||||||
|
domain.setExceptionName(entity.getExceptionName());
|
||||||
|
domain.setMethodName(entity.getMethodName());
|
||||||
|
domain.setMethodParams(entity.getMethodParams());
|
||||||
|
domain.setExceptionMsg(entity.getExceptionMsg());
|
||||||
|
domain.setExceptionStack(entity.getExceptionStack());
|
||||||
|
domain.setIp(entity.getIp());
|
||||||
|
domain.setCreateTime(entity.getCreateTime());
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysExceptionLogEntity toEntity(SysExceptionLog domain) {
|
||||||
|
if (domain == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
SysExceptionLogEntity entity = new SysExceptionLogEntity();
|
||||||
|
entity.setId(domain.getId());
|
||||||
|
entity.setUsername(domain.getUsername());
|
||||||
|
entity.setTitle(domain.getTitle());
|
||||||
|
entity.setExceptionName(domain.getExceptionName());
|
||||||
|
entity.setMethodName(domain.getMethodName());
|
||||||
|
entity.setMethodParams(domain.getMethodParams());
|
||||||
|
entity.setExceptionMsg(domain.getExceptionMsg());
|
||||||
|
entity.setExceptionStack(domain.getExceptionStack());
|
||||||
|
entity.setIp(domain.getIp());
|
||||||
|
entity.setCreateTime(domain.getCreateTime());
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user