新增后台管理系统(答辩用
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
# 认证管理模块 API 文档
|
||||
|
||||
> **文档版本**: v1.0
|
||||
> **创建日期**: 2026-06-16
|
||||
> **作者**: 张翔
|
||||
> **状态**: 正式发布
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [概述](#概述)
|
||||
2. [基础路径](#基础路径)
|
||||
3. [认证接口](#认证接口)
|
||||
- [用户名+密码登录](#用户名密码登录)
|
||||
- [获取用户信息](#获取用户信息)
|
||||
4. [数据模型](#数据模型)
|
||||
- [LoginRequest](#loginrequest)
|
||||
- [LoginResponse](#loginresponse)
|
||||
- [UserInfo](#userinfo)
|
||||
5. [响应码说明](#响应码说明)
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
认证管理模块提供用户登录认证和用户信息查询功能。采用 Spring WebFlux 响应式编程,支持高并发场景。
|
||||
|
||||
## 基础路径
|
||||
|
||||
所有接口的基础路径为: `http://{host}:{port}/api/auth`
|
||||
|
||||
---
|
||||
|
||||
## 认证接口
|
||||
|
||||
### 用户名+密码登录
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| **HTTP方法** | POST |
|
||||
| **接口路径** | `/api/auth/login` |
|
||||
| **所属文件** | `AuthHandler.java` |
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| username | string | 是 | - | 用户名 |
|
||||
| password | string | 是 | - | 密码 |
|
||||
|
||||
**成功响应** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "登录成功",
|
||||
"data": {
|
||||
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"tokenType": "Bearer",
|
||||
"expiresIn": 7200,
|
||||
"userInfo": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"email": "admin@example.com",
|
||||
"phone": "13800138000",
|
||||
"nickname": "超级管理员",
|
||||
"status": 1,
|
||||
"roleId": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**失败响应** (400 Bad Request):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "用户名或密码错误"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 获取用户信息
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| **HTTP方法** | GET |
|
||||
| **接口路径** | `/api/auth/users/{id}` |
|
||||
| **所属文件** | `AuthHandler.java` |
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| id | Long | 是 | 用户ID |
|
||||
|
||||
**成功响应** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取用户信息成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"email": "admin@example.com",
|
||||
"phone": "13800138000",
|
||||
"nickname": "超级管理员",
|
||||
"status": 1,
|
||||
"roleId": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**失败响应** (400 Bad Request):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "无效的用户ID"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 数据模型
|
||||
|
||||
### LoginRequest
|
||||
|
||||
用户登录请求对象。
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 示例 |
|
||||
|------|------|------|------|------|
|
||||
| username | string | 是 | 用户名 | admin |
|
||||
| password | string | 是 | 密码 | Test@123 |
|
||||
|
||||
### LoginResponse
|
||||
|
||||
用户登录响应对象。
|
||||
|
||||
| 字段 | 类型 | 说明 | 示例 |
|
||||
|------|------|------|------|
|
||||
| accessToken | string | 访问令牌 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... |
|
||||
| refreshToken | string | 刷新令牌 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... |
|
||||
| tokenType | string | 令牌类型 | Bearer |
|
||||
| expiresIn | Long | 过期时间(秒) | 7200 |
|
||||
| userInfo | UserInfo | 用户信息 | - |
|
||||
|
||||
### UserInfo
|
||||
|
||||
用户信息对象。
|
||||
|
||||
| 字段 | 类型 | 说明 | 示例 |
|
||||
|------|------|------|------|
|
||||
| id | Long | 用户ID | 1 |
|
||||
| username | string | 用户名 | admin |
|
||||
| email | string | 邮箱 | admin@example.com |
|
||||
| phone | string | 手机号 | 13800138000 |
|
||||
| nickname | string | 昵称 | 超级管理员 |
|
||||
| status | Integer | 状态:0-禁用,1-正常 | 1 |
|
||||
| roleId | Long | 角色ID | 1 |
|
||||
|
||||
---
|
||||
|
||||
## 响应码说明
|
||||
|
||||
| 响应码 | 说明 |
|
||||
|--------|------|
|
||||
| 200 | 成功 |
|
||||
| 400 | 请求参数错误(如用户名密码错误、无效的用户ID) |
|
||||
| 401 | 未授权(认证失败) |
|
||||
| 403 | 禁止访问 |
|
||||
| 404 | 资源未找到 |
|
||||
| 409 | 冲突(如重复数据) |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
---
|
||||
|
||||
## 业务规则
|
||||
|
||||
1. 登录成功后返回的 `accessToken` 用于后续接口的身份认证
|
||||
2. `accessToken` 有效期为 2 小时(7200 秒)
|
||||
3. `refreshToken` 用于刷新 `accessToken`,有效期为 7 天
|
||||
4. 用户状态 `status` 为 0 时表示禁用,无法登录
|
||||
5. 所有需要认证的接口需要在请求头中携带 `Authorization: Bearer {accessToken}`
|
||||
@@ -0,0 +1,343 @@
|
||||
# 员工管理模块 API 文档
|
||||
|
||||
> **文档版本**: v1.0
|
||||
> **创建日期**: 2026-06-20
|
||||
> **作者**: AI Assistant
|
||||
> **状态**: 正式发布
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [概述](#概述)
|
||||
2. [基础路径](#基础路径)
|
||||
3. [员工管理接口](#员工管理接口)
|
||||
- [分页获取员工列表(含角色信息)](#分页获取员工列表含角色信息)
|
||||
4. [用户管理接口(复用)](#用户管理接口复用)
|
||||
- [创建员工账号](#创建员工账号)
|
||||
- [获取员工详情](#获取员工详情)
|
||||
- [更新员工信息](#更新员工信息)
|
||||
- [逻辑删除员工](#逻辑删除员工)
|
||||
- [修改密码](#修改密码)
|
||||
- [为用户分配角色](#为用户分配角色)
|
||||
- [获取用户的角色](#获取用户的角色)
|
||||
5. [角色管理接口(复用)](#角色管理接口复用)
|
||||
- [获取所有角色](#获取所有角色)
|
||||
6. [数据模型](#数据模型)
|
||||
- [UserRegisterRequest](#userregisterrequest)
|
||||
- [EmployeePageResponse](#employeepageresponse)
|
||||
- [RoleInfo](#roleinfo)
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
员工管理模块为店长(超级管理员 admin)提供新员工账号创建和权限分配功能。该模块复用已有的用户管理、角色管理接口,并新增了带角色信息的员工分页查询接口。
|
||||
|
||||
## 基础路径
|
||||
|
||||
所有接口的基础路径为: `http://{host}:{port}/api`
|
||||
|
||||
---
|
||||
|
||||
## 员工管理接口
|
||||
|
||||
### 分页获取员工列表(含角色信息)
|
||||
|
||||
> **新增接口**:该接口在已有 `/api/users/page` 基础上,额外返回每个员工的角色详细信息。
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| **HTTP方法** | GET |
|
||||
| **接口路径** | `/api/employees/page` |
|
||||
| **所属文件** | `SysUserHandler.java` |
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| page | int | 否 | 0 | 页码(从0开始) |
|
||||
| size | int | 否 | 10 | 每页数量 |
|
||||
| sort | string | 否 | id | 排序字段 |
|
||||
| order | string | 否 | asc | 排序方向(asc/desc) |
|
||||
| keyword | string | 否 | - | 搜索关键字(匹配用户名/昵称) |
|
||||
|
||||
**成功响应** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"nickname": "超级管理员",
|
||||
"email": "admin@novalon.com",
|
||||
"phone": "13800138000",
|
||||
"avatar": null,
|
||||
"status": 1,
|
||||
"roles": [
|
||||
{
|
||||
"id": 1,
|
||||
"roleName": "超级管理员",
|
||||
"roleKey": "admin",
|
||||
"roleSort": 1
|
||||
}
|
||||
],
|
||||
"createdAt": "2026-03-13T10:00:00",
|
||||
"updatedAt": "2026-03-13T10:00:00"
|
||||
}
|
||||
],
|
||||
"totalPages": 1,
|
||||
"totalElements": 1,
|
||||
"currentPage": 0,
|
||||
"pageSize": 10,
|
||||
"first": true,
|
||||
"last": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 用户管理接口(复用)
|
||||
|
||||
以下接口为 `SysUserHandler` 中已有的用户管理接口,可直接用于员工管理。
|
||||
|
||||
### 创建员工账号
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| **HTTP方法** | POST |
|
||||
| **接口路径** | `/api/users` |
|
||||
| **所属文件** | `SysUserHandler.java` |
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "newstaff",
|
||||
"password": "Staff@123",
|
||||
"nickname": "新员工",
|
||||
"email": "staff@novalon.com",
|
||||
"phone": "13900139001",
|
||||
"roles": [2, 3]
|
||||
}
|
||||
```
|
||||
|
||||
**成功响应** (201 Created):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 11,
|
||||
"username": "newstaff",
|
||||
"nickname": "新员工",
|
||||
"email": "staff@novalon.com",
|
||||
"phone": "13900139001",
|
||||
"status": 1,
|
||||
"createdAt": "2026-06-20T10:00:00",
|
||||
"updatedAt": "2026-06-20T10:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
**校验规则**:
|
||||
- `username`: 3-50位,只能包含字母、数字、下划线和横线
|
||||
- `password`: 8-20位,必须包含大小写字母和数字
|
||||
- `email`: 合法邮箱格式
|
||||
- `phone`: 中国大陆手机号格式(1[3-9]开头的11位数字)
|
||||
|
||||
### 获取员工详情
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| **HTTP方法** | GET |
|
||||
| **接口路径** | `/api/users/{id}` |
|
||||
| **所属文件** | `SysUserHandler.java` |
|
||||
|
||||
**成功响应** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"nickname": "超级管理员",
|
||||
"email": "admin@novalon.com",
|
||||
"phone": "13800138000",
|
||||
"avatar": null,
|
||||
"status": 1,
|
||||
"roles": [1],
|
||||
"createdAt": "2026-03-13T10:00:00",
|
||||
"updatedAt": "2026-03-13T10:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
### 更新员工信息
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| **HTTP方法** | PUT |
|
||||
| **接口路径** | `/api/users/{id}` |
|
||||
| **所属文件** | `SysUserHandler.java` |
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "newemail@novalon.com",
|
||||
"roleId": 2,
|
||||
"status": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 逻辑删除员工
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| **HTTP方法** | POST |
|
||||
| **接口路径** | `/api/users/{id}/action/logical-delete` |
|
||||
| **所属文件** | `SysUserHandler.java` |
|
||||
|
||||
### 修改密码
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| **HTTP方法** | POST |
|
||||
| **接口路径** | `/api/users/{id}/action/change-password` |
|
||||
| **所属文件** | `SysUserHandler.java` |
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"oldPassword": "Old@123",
|
||||
"newPassword": "New@456"
|
||||
}
|
||||
```
|
||||
|
||||
### 为用户分配角色
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| **HTTP方法** | POST |
|
||||
| **接口路径** | `/api/users/{id}/roles` |
|
||||
| **所属文件** | `SysUserHandler.java` |
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"roleIds": ["2", "3"]
|
||||
}
|
||||
```
|
||||
|
||||
### 获取用户的角色
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| **HTTP方法** | GET |
|
||||
| **接口路径** | `/api/users/{id}/roles` |
|
||||
| **所属文件** | `SysUserHandler.java` |
|
||||
|
||||
---
|
||||
|
||||
## 角色管理接口(复用)
|
||||
|
||||
### 获取所有角色
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| **HTTP方法** | GET |
|
||||
| **接口路径** | `/api/roles` |
|
||||
| **所属文件** | `SysRoleHandler.java` |
|
||||
|
||||
**成功响应** (200 OK):
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"roleName": "超级管理员",
|
||||
"roleKey": "admin",
|
||||
"roleSort": 1,
|
||||
"status": 1,
|
||||
"createdAt": "2026-03-13T10:00:00",
|
||||
"updatedAt": "2026-03-13T10:00:00"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"roleName": "测试管理员",
|
||||
"roleKey": "test_admin",
|
||||
"roleSort": 2,
|
||||
"status": 1,
|
||||
"createdAt": "2026-03-13T10:00:00",
|
||||
"updatedAt": "2026-03-13T10:00:00"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"roleName": "普通用户",
|
||||
"roleKey": "normal_user",
|
||||
"roleSort": 3,
|
||||
"status": 1,
|
||||
"createdAt": "2026-03-13T10:00:00",
|
||||
"updatedAt": "2026-03-13T10:00:00"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"roleName": "访客",
|
||||
"roleKey": "guest",
|
||||
"roleSort": 4,
|
||||
"status": 1,
|
||||
"createdAt": "2026-03-13T10:00:00",
|
||||
"updatedAt": "2026-03-13T10:00:00"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 数据模型
|
||||
|
||||
### UserRegisterRequest
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| username | String | 是 | 用户名(3-50位字母数字下划线横线) |
|
||||
| password | String | 是 | 密码(8-20位含大小写字母和数字) |
|
||||
| nickname | String | 否 | 昵称 |
|
||||
| email | String | 是 | 邮箱 |
|
||||
| phone | String | 是 | 手机号(中国大陆11位) |
|
||||
| roles | List\<Long\> | 否 | 角色ID列表 |
|
||||
|
||||
### EmployeePageResponse
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| content | List\<EmployeeInfo\> | 员工列表 |
|
||||
| totalPages | int | 总页数 |
|
||||
| totalElements | long | 总记录数 |
|
||||
| currentPage | int | 当前页码 |
|
||||
| pageSize | int | 每页数量 |
|
||||
| first | boolean | 是否第一页 |
|
||||
| last | boolean | 是否最后一页 |
|
||||
|
||||
### EmployeeInfo
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Long | 用户ID |
|
||||
| username | String | 用户名 |
|
||||
| nickname | String | 昵称 |
|
||||
| email | String | 邮箱 |
|
||||
| phone | String | 手机号 |
|
||||
| avatar | String | 头像URL |
|
||||
| status | Integer | 状态:0-禁用, 1-正常 |
|
||||
| roles | List\<RoleInfo\> | 角色列表 |
|
||||
| createdAt | String | 创建时间 |
|
||||
| updatedAt | String | 更新时间 |
|
||||
|
||||
### RoleInfo
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Long | 角色ID |
|
||||
| roleName | String | 角色名称 |
|
||||
| roleKey | String | 角色标识 |
|
||||
| roleSort | Integer | 排序号 |
|
||||
@@ -0,0 +1,51 @@
|
||||
# soybean-admin-mock
|
||||
|
||||
## Docs
|
||||
- [🦊一分钟,了解 Apifox !](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/doc-2825188.md):
|
||||
|
||||
## API Docs
|
||||
- Auth [用户名+密码登录](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-100526985.md):
|
||||
- Auth [获取用户信息](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-120399825.md):
|
||||
- Auth [刷新token](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-120415125.md):
|
||||
- Auth [自定义后端错误](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-158477619.md):
|
||||
- 前端路由 [获取用户路由数据](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-120415303.md):
|
||||
- 前端路由 [路由是否存在](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-120415373.md): 当用户访问一个路由失败时,有可能是没有该路由的权限,或者没有该路由,所以需要判断路由是否存在
|
||||
- 前端路由 [获取固定的路由数据(不需要权限)](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-158516240.md):
|
||||
- 前端路由 [获取react用户路由](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-273820808.md):
|
||||
- 调试 [debug](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-125438392.md):
|
||||
- 调试 [debug post](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-134021864.md):
|
||||
- 系统管理 [系统管理 - 获取角色列表](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-145344387.md):
|
||||
- 系统管理 [系统管理 - 获取用户列表](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-149754769.md):
|
||||
- 系统管理 [系统管理 - 获取用户列表(废弃)](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-145387463.md):
|
||||
- 系统管理 [获取所有角色](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-145409928.md):
|
||||
- 系统管理 [获取菜单列表](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-145421249.md):
|
||||
- 系统管理 [系统管理 - 获取菜单列表](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-157945109.md):
|
||||
- 系统管理 [获取所有页面组件](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-157988353.md):
|
||||
- 系统管理 [获取菜单树](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-158414289.md):
|
||||
- 项目配置 [获取用户配置](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-229492806.md):
|
||||
- 项目配置 [保存用户配置](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-229493074.md):
|
||||
- 用户 [postApiUsersLogin](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003433.md): 用户登录
|
||||
- 用户 [postApiUsersRegister](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003434.md): 用户注册
|
||||
- 用户 [getApiUsersProfile](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003435.md): 获取用户个人信息
|
||||
- 用户 [putApiUsersPassword](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003436.md): 更新用户密码
|
||||
- 用户 [postApiUsersLogout](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003437.md): 用户登出
|
||||
- 角色 [getApiRoles](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003438.md): 获取所有角色
|
||||
- 角色 [postApiRoles](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003439.md): 创建角色
|
||||
- 角色 [getApiRolesById](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003440.md): 获取角色详情
|
||||
- 角色 [putApiRolesById](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003441.md): 更新角色
|
||||
- 角色 [deleteApiRolesById](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003442.md): 删除角色
|
||||
- 权限 [getApiPermissions](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003443.md): 获取所有权限
|
||||
- 权限 [postApiPermissions](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003444.md): 创建权限
|
||||
- 权限 [getApiPermissionsById](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003445.md): 获取权限详情
|
||||
- 权限 [putApiPermissionsById](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003446.md): 更新权限
|
||||
- 权限 [deleteApiPermissionsById](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003447.md): 删除权限
|
||||
- 菜单 [getApiMenusUser](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003448.md): 获取用户菜单
|
||||
- 菜单 [getApiMenus](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003449.md): 获取所有菜单
|
||||
- 菜单 [postApiMenus](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003450.md): 创建菜单
|
||||
- 菜单 [getApiMenusById](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003451.md): 获取菜单详情
|
||||
- 菜单 [putApiMenusById](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003452.md): 更新菜单
|
||||
- 菜单 [deleteApiMenusById](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003453.md): 删除菜单
|
||||
- 文件 [postApiFilesUpload](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003454.md): 上传文件
|
||||
- 文件 [getApiFilesById](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003455.md): 获取文件信息
|
||||
- 文件 [deleteApiFilesById](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003456.md): 删除文件
|
||||
- 文件 [getApiFilesUser](https://s.apifox.cn/35c8727a-d3ab-47e9-8863-ef8e37df6887/api-281003457.md): 获取用户文件列表
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package cn.novalon.gym.manage.datacount.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 会员每日签到明细
|
||||
*
|
||||
* @author system
|
||||
* @date 2026-06-17
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberDailySignIn {
|
||||
|
||||
/**
|
||||
* 会员ID
|
||||
*/
|
||||
private Long memberId;
|
||||
|
||||
/**
|
||||
* 签到时间
|
||||
*/
|
||||
private String signInTime;
|
||||
|
||||
/**
|
||||
* 签到状态:SUCCESS / FAILED
|
||||
*/
|
||||
private String signInStatus;
|
||||
|
||||
/**
|
||||
* 签到方式:QR_CODE / MANUAL / FACE
|
||||
*/
|
||||
private String signInType;
|
||||
|
||||
/**
|
||||
* 失败原因(仅失败时)
|
||||
*/
|
||||
private String failReason;
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package cn.novalon.gym.manage.datacount.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 会员月度签到统计
|
||||
*
|
||||
* @author system
|
||||
* @date 2026-06-17
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberMonthlySignIn {
|
||||
|
||||
/**
|
||||
* 会员ID
|
||||
*/
|
||||
private Long memberId;
|
||||
|
||||
/**
|
||||
* 会员姓名
|
||||
*/
|
||||
private String memberName;
|
||||
|
||||
/**
|
||||
* 统计月份(格式:yyyy-MM)
|
||||
*/
|
||||
private String month;
|
||||
|
||||
/**
|
||||
* 当月签到总次数
|
||||
*/
|
||||
private Long totalSignIns;
|
||||
|
||||
/**
|
||||
* 当月成功签到次数
|
||||
*/
|
||||
private Long successSignIns;
|
||||
|
||||
/**
|
||||
* 当月失败签到次数
|
||||
*/
|
||||
private Long failedSignIns;
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package cn.novalon.gym.manage.datacount.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 会员签到明细
|
||||
*
|
||||
* @author system
|
||||
* @date 2026-06-16
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberSignInDetail {
|
||||
|
||||
/**
|
||||
* 会员ID
|
||||
*/
|
||||
private Long memberId;
|
||||
|
||||
/**
|
||||
* 会员姓名
|
||||
*/
|
||||
private String memberName;
|
||||
|
||||
/**
|
||||
* 会员手机号
|
||||
*/
|
||||
private String memberPhone;
|
||||
|
||||
/**
|
||||
* 签到总次数
|
||||
*/
|
||||
private Long totalSignIns;
|
||||
|
||||
/**
|
||||
* 成功签到次数
|
||||
*/
|
||||
private Long successSignIns;
|
||||
|
||||
/**
|
||||
* 失败签到次数
|
||||
*/
|
||||
private Long failedSignIns;
|
||||
|
||||
/**
|
||||
* 最近签到时间
|
||||
*/
|
||||
private String lastSignInTime;
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package cn.novalon.gym.manage.groupcourse.scheduler;
|
||||
|
||||
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 团课超时与自动结课定时任务
|
||||
*
|
||||
* 功能:
|
||||
* 1. 检查已超时未开课的课程,标记为超时并返还用户权益
|
||||
* 2. 检查已超时未结课的课程,自动结课并发送教练警告
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-23
|
||||
*/
|
||||
@Component
|
||||
public class GroupCourseTimeoutScheduler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GroupCourseTimeoutScheduler.class);
|
||||
|
||||
private final IGroupCourseService groupCourseService;
|
||||
|
||||
public GroupCourseTimeoutScheduler(IGroupCourseService groupCourseService) {
|
||||
this.groupCourseService = groupCourseService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 每分钟检查一次超时未开课的课程
|
||||
* 超时条件:status='0' 且 actual_start_time IS NULL 且 start_time + 10分钟 < 当前时间
|
||||
*/
|
||||
@Scheduled(fixedRate = 60000)
|
||||
public void checkAndHandleTimeoutCourses() {
|
||||
logger.debug("定时任务开始检查超时未开课的团课");
|
||||
|
||||
groupCourseService.processTimeoutCourses()
|
||||
.subscribe(
|
||||
count -> {
|
||||
if (count > 0) {
|
||||
logger.warn("定时任务完成,处理了 {} 个超时团课,已返还用户权益", count);
|
||||
}
|
||||
},
|
||||
error -> logger.error("超时团课处理定时任务执行失败:{}", error.getMessage(), error)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 每分钟检查一次需要自动结课的课程
|
||||
* 自动结课条件:status='3'(进行中) 且 actual_end_time IS NULL 且 end_time + 10分钟 < 当前时间
|
||||
*/
|
||||
@Scheduled(fixedRate = 60000)
|
||||
public void checkAndHandleAutoEndCourses() {
|
||||
logger.debug("定时任务开始检查需要自动结课的团课");
|
||||
|
||||
groupCourseService.processAutoEndCourses()
|
||||
.subscribe(
|
||||
count -> {
|
||||
if (count > 0) {
|
||||
logger.warn("定时任务完成,自动结课 {} 个团课,请通知相关教练", count);
|
||||
}
|
||||
},
|
||||
error -> logger.error("自动结课定时任务执行失败:{}", error.getMessage(), error)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
wrapperVersion=3.3.4
|
||||
distributionType=only-script
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
|
||||
@@ -0,0 +1,114 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>gym-manage-api</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>gym-manage-cuit</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>gym-manage-cuit</name>
|
||||
<description>Auth Management Module</description>
|
||||
<url/>
|
||||
<licenses>
|
||||
<license/>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer/>
|
||||
</developers>
|
||||
<scm>
|
||||
<connection/>
|
||||
<developerConnection/>
|
||||
<tag/>
|
||||
<url/>
|
||||
</scm>
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>manage-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>manage-sys</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.novalon.gym.manage</groupId>
|
||||
<artifactId>manage-db</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<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.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>
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations-jakarta</artifactId>
|
||||
<version>2.2.43</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- Redis依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<!-- Spring Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<!-- JWT -->
|
||||
<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>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package cn.novalon.gym.manage.auth.converter;
|
||||
|
||||
import cn.novalon.gym.manage.auth.domain.User;
|
||||
import cn.novalon.gym.manage.auth.dto.UserInfo;
|
||||
import cn.novalon.gym.manage.auth.entity.UserEntity;
|
||||
|
||||
public class UserConverter {
|
||||
|
||||
public static User toDomain(UserEntity entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
User user = new User();
|
||||
user.setId(entity.getId());
|
||||
user.setUsername(entity.getUsername());
|
||||
user.setEmail(entity.getEmail());
|
||||
user.setPhone(entity.getPhone());
|
||||
user.setNickname(entity.getNickname());
|
||||
user.setStatus(entity.getStatus());
|
||||
user.setRoleId(entity.getRoleId());
|
||||
user.setCreateBy(entity.getCreateBy());
|
||||
user.setUpdateBy(entity.getUpdateBy());
|
||||
user.setCreatedAt(entity.getCreatedAt());
|
||||
user.setUpdatedAt(entity.getUpdatedAt());
|
||||
user.setDeletedAt(entity.getDeletedAt());
|
||||
return user;
|
||||
}
|
||||
|
||||
public static UserEntity toEntity(User domain) {
|
||||
if (domain == null) {
|
||||
return null;
|
||||
}
|
||||
UserEntity entity = new UserEntity();
|
||||
entity.setId(domain.getId());
|
||||
entity.setUsername(domain.getUsername());
|
||||
entity.setEmail(domain.getEmail());
|
||||
entity.setPhone(domain.getPhone());
|
||||
entity.setNickname(domain.getNickname());
|
||||
entity.setStatus(domain.getStatus());
|
||||
entity.setRoleId(domain.getRoleId());
|
||||
entity.setCreateBy(domain.getCreateBy());
|
||||
entity.setUpdateBy(domain.getUpdateBy());
|
||||
entity.setCreatedAt(domain.getCreatedAt());
|
||||
entity.setUpdatedAt(domain.getUpdatedAt());
|
||||
entity.setDeletedAt(domain.getDeletedAt());
|
||||
return entity;
|
||||
}
|
||||
|
||||
public static UserInfo toUserInfo(User user) {
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
UserInfo userInfo = new UserInfo();
|
||||
userInfo.setId(user.getId());
|
||||
userInfo.setUsername(user.getUsername());
|
||||
userInfo.setEmail(user.getEmail());
|
||||
userInfo.setPhone(user.getPhone());
|
||||
userInfo.setNickname(user.getNickname());
|
||||
userInfo.setStatus(user.getStatus());
|
||||
userInfo.setRoleId(user.getRoleId());
|
||||
return userInfo;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package cn.novalon.gym.manage.auth.dao;
|
||||
|
||||
import cn.novalon.gym.manage.auth.entity.UserEntity;
|
||||
import org.springframework.data.r2dbc.repository.Query;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Repository
|
||||
public interface UserDao extends R2dbcRepository<UserEntity, Long> {
|
||||
|
||||
@Query("SELECT * FROM sys_user WHERE username = :username AND deleted_at IS NULL")
|
||||
Mono<UserEntity> findByUsername(String username);
|
||||
|
||||
@Query("SELECT * FROM sys_user WHERE id = :id AND deleted_at IS NULL")
|
||||
Mono<UserEntity> findByIdAndDeletedAtIsNull(Long id);
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package cn.novalon.gym.manage.auth.domain;
|
||||
|
||||
import cn.novalon.gym.manage.sys.core.domain.BaseDomain;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
public class User extends BaseDomain {
|
||||
|
||||
@Schema(description = "用户名", example = "admin")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "邮箱", example = "admin@example.com")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "手机号", example = "13800138000")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "昵称", example = "超级管理员")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "状态:0-禁用,1-正常", example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "角色ID", example = "1")
|
||||
private Long roleId;
|
||||
|
||||
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 String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
|
||||
public void setNickname(String nickname) {
|
||||
this.nickname = nickname;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cn.novalon.gym.manage.auth.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "用户登录请求")
|
||||
public class LoginRequest {
|
||||
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
@Schema(description = "用户名", example = "admin", required = true)
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@Schema(description = "密码", example = "Test@123", required = true)
|
||||
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;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package cn.novalon.gym.manage.auth.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "用户登录响应")
|
||||
public class LoginResponse {
|
||||
|
||||
@Schema(description = "访问令牌")
|
||||
private String accessToken;
|
||||
|
||||
@Schema(description = "刷新令牌")
|
||||
private String refreshToken;
|
||||
|
||||
@Schema(description = "令牌类型", example = "Bearer")
|
||||
private String tokenType;
|
||||
|
||||
@Schema(description = "过期时间(秒)", example = "7200")
|
||||
private Long expiresIn;
|
||||
|
||||
@Schema(description = "用户信息")
|
||||
private UserInfo userInfo;
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public void setRefreshToken(String refreshToken) {
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
public String getTokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
public void setTokenType(String tokenType) {
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
|
||||
public Long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public void setExpiresIn(Long expiresIn) {
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
|
||||
public UserInfo getUserInfo() {
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
public void setUserInfo(UserInfo userInfo) {
|
||||
this.userInfo = userInfo;
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package cn.novalon.gym.manage.auth.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "用户信息")
|
||||
public class UserInfo {
|
||||
|
||||
@Schema(description = "用户ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户名", example = "admin")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "邮箱", example = "admin@example.com")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "手机号", example = "13800138000")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "昵称", example = "超级管理员")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "状态:0-禁用,1-正常", example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "角色ID", example = "1")
|
||||
private Long roleId;
|
||||
|
||||
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 String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
|
||||
public void setNickname(String nickname) {
|
||||
this.nickname = nickname;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package cn.novalon.gym.manage.auth.entity;
|
||||
|
||||
import cn.novalon.gym.manage.db.entity.BaseEntity;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
@Table("sys_user")
|
||||
public class UserEntity extends BaseEntity {
|
||||
|
||||
@Column("username")
|
||||
private String username;
|
||||
|
||||
@Column("password")
|
||||
private String password;
|
||||
|
||||
@Column("email")
|
||||
private String email;
|
||||
|
||||
@Column("phone")
|
||||
private String phone;
|
||||
|
||||
@Column("nickname")
|
||||
private String nickname;
|
||||
|
||||
@Column("status")
|
||||
private Integer status;
|
||||
|
||||
@Column("role_id")
|
||||
private Long roleId;
|
||||
|
||||
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 String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
|
||||
public void setNickname(String nickname) {
|
||||
this.nickname = nickname;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package cn.novalon.gym.manage.auth.handler;
|
||||
|
||||
import cn.novalon.gym.manage.auth.dto.LoginRequest;
|
||||
import cn.novalon.gym.manage.auth.dto.LoginResponse;
|
||||
import cn.novalon.gym.manage.auth.dto.UserInfo;
|
||||
import cn.novalon.gym.manage.auth.service.IAuthService;
|
||||
import cn.novalon.gym.manage.auth.util.ResponseCode;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Validator;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Tag(name = "认证管理", description = "用户认证相关操作")
|
||||
public class AuthHandler {
|
||||
|
||||
private final IAuthService authService;
|
||||
private final Validator validator;
|
||||
|
||||
public AuthHandler(IAuthService authService, Validator validator) {
|
||||
this.authService = authService;
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
@Operation(summary = "用户名+密码登录", description = "使用用户名和密码进行登录认证")
|
||||
public Mono<ServerResponse> login(ServerRequest request) {
|
||||
return request.bodyToMono(LoginRequest.class)
|
||||
.flatMap(loginRequest -> {
|
||||
return authService.login(loginRequest)
|
||||
.flatMap(response -> {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", ResponseCode.SUCCESS);
|
||||
result.put("message", "登录成功");
|
||||
result.put("data", response);
|
||||
return ServerResponse.ok().bodyValue(result);
|
||||
})
|
||||
.onErrorResume(error -> {
|
||||
Map<String, Object> errorResponse = new HashMap<>();
|
||||
errorResponse.put("code", ResponseCode.BAD_REQUEST);
|
||||
errorResponse.put("message", error.getMessage());
|
||||
return ServerResponse.badRequest().bodyValue(errorResponse);
|
||||
});
|
||||
})
|
||||
.onErrorResume(error -> {
|
||||
Map<String, Object> errorResponse = new HashMap<>();
|
||||
errorResponse.put("code", ResponseCode.BAD_REQUEST);
|
||||
errorResponse.put("message", "请求参数错误: " + error.getMessage());
|
||||
return ServerResponse.badRequest().bodyValue(errorResponse);
|
||||
});
|
||||
}
|
||||
|
||||
@Operation(summary = "获取用户信息", description = "根据用户ID获取用户详细信息")
|
||||
public Mono<ServerResponse> getUserInfo(ServerRequest request) {
|
||||
try {
|
||||
Long userId = Long.valueOf(request.pathVariable("id"));
|
||||
return authService.getUserInfo(userId)
|
||||
.flatMap(userInfo -> {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", ResponseCode.SUCCESS);
|
||||
result.put("message", "获取用户信息成功");
|
||||
result.put("data", userInfo);
|
||||
return ServerResponse.ok().bodyValue(result);
|
||||
})
|
||||
.onErrorResume(error -> {
|
||||
Map<String, Object> errorResponse = new HashMap<>();
|
||||
errorResponse.put("code", ResponseCode.BAD_REQUEST);
|
||||
errorResponse.put("message", error.getMessage());
|
||||
return ServerResponse.badRequest().bodyValue(errorResponse);
|
||||
});
|
||||
} catch (NumberFormatException e) {
|
||||
Map<String, Object> errorResponse = new HashMap<>();
|
||||
errorResponse.put("code", ResponseCode.BAD_REQUEST);
|
||||
errorResponse.put("message", "无效的用户ID");
|
||||
return ServerResponse.badRequest().bodyValue(errorResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package cn.novalon.gym.manage.auth.repository;
|
||||
|
||||
import cn.novalon.gym.manage.auth.domain.User;
|
||||
import cn.novalon.gym.manage.auth.dto.LoginRequest;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface AuthRepository {
|
||||
|
||||
Mono<User> findByUsername(String username);
|
||||
|
||||
Mono<User> findById(Long id);
|
||||
|
||||
Mono<User> validateLogin(LoginRequest request);
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package cn.novalon.gym.manage.auth.repository.impl;
|
||||
|
||||
import cn.novalon.gym.manage.auth.converter.UserConverter;
|
||||
import cn.novalon.gym.manage.auth.dao.UserDao;
|
||||
import cn.novalon.gym.manage.auth.domain.User;
|
||||
import cn.novalon.gym.manage.auth.dto.LoginRequest;
|
||||
import cn.novalon.gym.manage.auth.repository.AuthRepository;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
public class AuthRepositoryImpl implements AuthRepository {
|
||||
|
||||
private final UserDao userDao;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
public AuthRepositoryImpl(UserDao userDao, PasswordEncoder passwordEncoder) {
|
||||
this.userDao = userDao;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<User> findByUsername(String username) {
|
||||
return userDao.findByUsername(username)
|
||||
.map(UserConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<User> findById(Long id) {
|
||||
return userDao.findByIdAndDeletedAtIsNull(id)
|
||||
.map(UserConverter::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<User> validateLogin(LoginRequest request) {
|
||||
return userDao.findByUsername(request.getUsername())
|
||||
.flatMap(userEntity -> {
|
||||
if (userEntity.getStatus() == 0) {
|
||||
return Mono.error(new RuntimeException("用户已被禁用"));
|
||||
}
|
||||
if (passwordEncoder.matches(request.getPassword(), userEntity.getPassword())) {
|
||||
return Mono.just(UserConverter.toDomain(userEntity));
|
||||
} else {
|
||||
return Mono.error(new RuntimeException("用户名或密码错误"));
|
||||
}
|
||||
})
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("用户名或密码错误")));
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package cn.novalon.gym.manage.auth.service;
|
||||
|
||||
import cn.novalon.gym.manage.auth.dto.LoginRequest;
|
||||
import cn.novalon.gym.manage.auth.dto.LoginResponse;
|
||||
import cn.novalon.gym.manage.auth.dto.UserInfo;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface IAuthService {
|
||||
|
||||
Mono<LoginResponse> login(LoginRequest request);
|
||||
|
||||
Mono<UserInfo> getUserInfo(Long userId);
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package cn.novalon.gym.manage.auth.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.auth.converter.UserConverter;
|
||||
import cn.novalon.gym.manage.auth.dto.LoginRequest;
|
||||
import cn.novalon.gym.manage.auth.dto.LoginResponse;
|
||||
import cn.novalon.gym.manage.auth.dto.UserInfo;
|
||||
import cn.novalon.gym.manage.auth.repository.AuthRepository;
|
||||
import cn.novalon.gym.manage.auth.service.IAuthService;
|
||||
import cn.novalon.gym.manage.auth.util.JwtUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Service
|
||||
public class AuthServiceImpl implements IAuthService {
|
||||
|
||||
private final AuthRepository authRepository;
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
public AuthServiceImpl(AuthRepository authRepository, JwtUtil jwtUtil) {
|
||||
this.authRepository = authRepository;
|
||||
this.jwtUtil = jwtUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<LoginResponse> login(LoginRequest request) {
|
||||
return authRepository.validateLogin(request)
|
||||
.flatMap(user -> {
|
||||
String accessToken = jwtUtil.generateToken(user.getId(), user.getUsername());
|
||||
String refreshToken = jwtUtil.generateRefreshToken(user.getId(), user.getUsername());
|
||||
|
||||
LoginResponse response = new LoginResponse();
|
||||
response.setAccessToken(accessToken);
|
||||
response.setRefreshToken(refreshToken);
|
||||
response.setTokenType("Bearer");
|
||||
response.setExpiresIn(7200L);
|
||||
response.setUserInfo(UserConverter.toUserInfo(user));
|
||||
|
||||
return Mono.just(response);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<UserInfo> getUserInfo(Long userId) {
|
||||
return authRepository.findById(userId)
|
||||
.map(UserConverter::toUserInfo)
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("用户不存在")));
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package cn.novalon.gym.manage.auth.util;
|
||||
|
||||
import cn.novalon.gym.manage.common.config.JwtProperties;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class JwtUtil {
|
||||
|
||||
private final JwtProperties jwtProperties;
|
||||
|
||||
public JwtUtil(JwtProperties jwtProperties) {
|
||||
this.jwtProperties = jwtProperties;
|
||||
}
|
||||
|
||||
private SecretKey getSigningKey() {
|
||||
return Keys.hmacShaKeyFor(jwtProperties.getSecret().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public String generateToken(Long userId, String username) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("userId", userId);
|
||||
claims.put("username", username);
|
||||
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setSubject(username)
|
||||
.setIssuedAt(new Date())
|
||||
.setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getExpiration()))
|
||||
.signWith(getSigningKey())
|
||||
.compact();
|
||||
}
|
||||
|
||||
public String generateRefreshToken(Long userId, String username) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("userId", userId);
|
||||
claims.put("username", username);
|
||||
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setSubject(username + "_refresh")
|
||||
.setIssuedAt(new Date())
|
||||
.setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getExpiration() * 7))
|
||||
.signWith(getSigningKey())
|
||||
.compact();
|
||||
}
|
||||
|
||||
public Claims parseToken(String token) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(getSigningKey())
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
public Long getUserIdFromToken(String token) {
|
||||
return parseToken(token).get("userId", Long.class);
|
||||
}
|
||||
|
||||
public String getUsernameFromToken(String token) {
|
||||
return parseToken(token).getSubject();
|
||||
}
|
||||
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
parseToken(token);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package cn.novalon.gym.manage.auth.util;
|
||||
|
||||
/**
|
||||
* HTTP 响应状态码常量类
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-06-16
|
||||
*/
|
||||
public class ResponseCode {
|
||||
|
||||
private ResponseCode() {
|
||||
}
|
||||
|
||||
/** 成功 */
|
||||
public static final int SUCCESS = 200;
|
||||
|
||||
/** 客户端错误:请求参数有误 */
|
||||
public static final int BAD_REQUEST = 400;
|
||||
|
||||
/** 客户端错误:未授权 */
|
||||
public static final int UNAUTHORIZED = 401;
|
||||
|
||||
/** 客户端错误:禁止访问 */
|
||||
public static final int FORBIDDEN = 403;
|
||||
|
||||
/** 客户端错误:资源未找到 */
|
||||
public static final int NOT_FOUND = 404;
|
||||
|
||||
/** 客户端错误:冲突(如重复数据) */
|
||||
public static final int CONFLICT = 409;
|
||||
|
||||
/** 服务器错误:内部服务器错误 */
|
||||
public static final int INTERNAL_SERVER_ERROR = 500;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# JWT Configuration
|
||||
jwt:
|
||||
secret: mySecretKeyForJwtTokenGenerationThatIsLongEnoughForHS256Algorithm
|
||||
expiration: 7200 # 2 hours in seconds
|
||||
refresh-expiration: 604800 # 7 days in seconds
|
||||
|
||||
# Server Configuration
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
# Spring Configuration
|
||||
spring:
|
||||
application:
|
||||
name: gym-manage-cuit
|
||||
profiles:
|
||||
active: dev
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package cn.novalon.gym.manage.cuit.gymmanagecuit;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
@Disabled("模块无独立SpringBootApplication,需通过manage-app运行集成测试")
|
||||
class GymManageCuitApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user