feat: user system first complete
This commit is contained in:
@@ -1,10 +1,42 @@
|
||||
package icu.sunway.ai_spring_example.Controller.AuthController;
|
||||
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import icu.sunway.ai_spring_example.Controller.AuthController.vo.LoginByEmailPasswordReq;
|
||||
import icu.sunway.ai_spring_example.Controller.AuthController.vo.RegisterByEmailReq;
|
||||
import icu.sunway.ai_spring_example.Exception.BusinessException;
|
||||
import icu.sunway.ai_spring_example.Service.AuthService;
|
||||
import icu.sunway.ai_spring_example.Utils.ResponseUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/auth")
|
||||
public class AuthController {
|
||||
|
||||
@Resource
|
||||
private AuthService authService;
|
||||
|
||||
@PostMapping("/login")
|
||||
public Object loginByEmailPassword(@RequestBody LoginByEmailPasswordReq req) {
|
||||
try {
|
||||
String token = authService.loginByEmailPassword(req.getEmail(), req.getPassword());
|
||||
return ResponseUtils.success(token);
|
||||
} catch (BusinessException e) {
|
||||
return ResponseUtils.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
public Object registerByEmail(@RequestBody RegisterByEmailReq req) {
|
||||
try {
|
||||
authService.registerByEmail(req.getEmail(), req.getPassword(), req.getVerifyCode());
|
||||
return ResponseUtils.success();
|
||||
} catch (BusinessException e) {
|
||||
return ResponseUtils.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,422 +0,0 @@
|
||||
# Auth模块接口文档
|
||||
|
||||
## 文档说明
|
||||
|
||||
本文档为Auth(认证授权)模块的接口详情,遵循《接口开发通用规范文档》的所有约定,基础路径为`/api/v1/auth`,数据交互格式为JSON,字符编码UTF-8,时间戳单位为毫秒。
|
||||
|
||||
## 1. 登录接口
|
||||
|
||||
### 接口基本信息
|
||||
|
||||
- 接口名称:用户登录
|
||||
- 接口路径:`/api/v1/auth/login`
|
||||
- 请求方法:POST
|
||||
- 接口描述:用户通过账号(手机号/用户名)+密码/验证码完成登录,返回登录令牌(token)
|
||||
|
||||
### 请求头
|
||||
|
||||
| 字段名 | 类型 | 必填性 | 示例值 |
|
||||
| --------------- | ------ | ------ | ---------------- |
|
||||
| Content-Type | String | 是 | application/json |
|
||||
| Accept-Language | String | 否 | zh-CN |
|
||||
|
||||
### 请求参数(Body)
|
||||
|
||||
| 字段名 | 类型 | 必填性 | 描述 | 示例值 |
|
||||
| ---------- | ------- | ------ | ------------------------------------------------ | ------------- |
|
||||
| account | String | 是 | 登录账号(手机号/用户名) | "13800138000" |
|
||||
| password | String | 否 | 登录密码(与verifyCode二选一) | "Abc123456" |
|
||||
| verifyCode | String | 否 | 验证码(与password二选一) | "8888" |
|
||||
| isRemember | Boolean | 否 | 是否记住登录(默认false,记住则token有效期延长) | true |
|
||||
|
||||
### 响应示例(成功)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "登录成功",
|
||||
"timestamp": 1744238900000,
|
||||
"data": {
|
||||
"token": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJleHAiOjE3NDQ4NDM3MDB9.QP8kXjZ8e7R5t5s7k9L2m1n8b7v6c5x4s3a2d1f0g9h8j7k6l5p4o3i2u1y0t9s8r7e6w5q4a3s2d1f0g",
|
||||
"expireTime": 1744843700000,
|
||||
"userInfo": {
|
||||
"userId": 1,
|
||||
"userName": "admin",
|
||||
"phoneNumber": "138****8000",
|
||||
"role": "ADMIN",
|
||||
"isEnabled": true,
|
||||
"createTime": 1743238900000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 响应示例(失败)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "验证码错误或已过期",
|
||||
"timestamp": 1744238900000,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 状态码说明
|
||||
|
||||
| 状态码 | 说明 |
|
||||
| ------ | -------------------------------- |
|
||||
| 200 | 登录成功 |
|
||||
| 400 | 参数错误/验证码错误/账号密码错误 |
|
||||
| 403 | 账号被封禁 |
|
||||
| 429 | 登录失败次数过多,账号临时锁定 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 2. 退出登录接口
|
||||
|
||||
### 接口基本信息
|
||||
|
||||
- 接口名称:用户退出登录
|
||||
- 接口路径:`/api/v1/auth/logout`
|
||||
- 请求方法:DELETE
|
||||
- 接口描述:销毁当前用户的登录令牌,退出登录状态
|
||||
|
||||
### 请求头
|
||||
|
||||
| 字段名 | 类型 | 必填性 | 示例值 |
|
||||
| --------------- | ------ | ------ | ------------------------------ |
|
||||
| Authorization | String | 是 | Bearer eyJhbGciOiJIUzI1NiJ9... |
|
||||
| Content-Type | String | 是 | application/json |
|
||||
| Accept-Language | String | 否 | zh-CN |
|
||||
|
||||
### 请求参数
|
||||
|
||||
无业务请求参数(仅需请求头携带token)
|
||||
|
||||
### 响应示例(成功)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "退出登录成功",
|
||||
"timestamp": 1744238900000,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 响应示例(失败)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 401,
|
||||
"message": "Token已过期,请重新登录",
|
||||
"timestamp": 1744238900000,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 状态码说明
|
||||
|
||||
| 状态码 | 说明 |
|
||||
| ------ | ---------------- |
|
||||
| 200 | 退出登录成功 |
|
||||
| 401 | Token失效/未登录 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 3. 刷新Token接口
|
||||
|
||||
### 接口基本信息
|
||||
|
||||
- 接口名称:刷新登录令牌
|
||||
- 接口路径:`/api/v1/auth/refresh-token`
|
||||
- 请求方法:POST
|
||||
- 接口描述:通过旧Token刷新获取新的登录令牌,延长登录有效期
|
||||
|
||||
### 请求头
|
||||
|
||||
| 字段名 | 类型 | 必填性 | 示例值 |
|
||||
| --------------- | ------ | ------ | ------------------------------ |
|
||||
| Authorization | String | 是 | Bearer eyJhbGciOiJIUzI1NiJ9... |
|
||||
| Content-Type | String | 是 | application/json |
|
||||
| Accept-Language | String | 否 | zh-CN |
|
||||
|
||||
### 请求参数(Body)
|
||||
|
||||
| 字段名 | 类型 | 必填性 | 描述 | 示例值 |
|
||||
| ------ | ------ | ------ | ------------ | ------------------------- |
|
||||
| token | String | 是 | 旧的登录令牌 | "eyJhbGciOiJIUzI1NiJ9..." |
|
||||
|
||||
### 响应示例(成功)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "Token刷新成功",
|
||||
"timestamp": 1744238900000,
|
||||
"data": {
|
||||
"newToken": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJleHAiOjE3NDU0NDg1MDB9.Z7x6y5w4v3u2t1s0r9e8w7q6a5z4s3d2f1g0h9j8k7l6p5o4i3u2y1t0s9r8e7w6q5a4s3d2f1g0h9j8k7l6p5o4i",
|
||||
"expireTime": 1745448500000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 响应示例(失败)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 401,
|
||||
"message": "Token已失效,无法刷新,请重新登录",
|
||||
"timestamp": 1744238900000,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 状态码说明
|
||||
|
||||
| 状态码 | 说明 |
|
||||
| ------ | ------------------ |
|
||||
| 200 | Token刷新成功 |
|
||||
| 400 | 旧Token格式错误 |
|
||||
| 401 | 旧Token已过期/无效 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 4. 获取当前用户信息接口
|
||||
|
||||
### 接口基本信息
|
||||
|
||||
- 接口名称:获取当前登录用户信息
|
||||
- 接口路径:`/api/v1/auth/user/info`
|
||||
- 请求方法:GET
|
||||
- 接口描述:根据登录令牌获取当前用户的基础信息(脱敏展示敏感字段)
|
||||
|
||||
### 请求头
|
||||
|
||||
| 字段名 | 类型 | 必填性 | 示例值 |
|
||||
| --------------- | ------ | ------ | ------------------------------ |
|
||||
| Authorization | String | 是 | Bearer eyJhbGciOiJIUzI1NiJ9... |
|
||||
| Content-Type | String | 是 | application/json |
|
||||
| Accept-Language | String | 否 | zh-CN |
|
||||
|
||||
### 请求参数
|
||||
|
||||
无业务请求参数(仅需请求头携带token)
|
||||
|
||||
### 响应示例(成功)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "查询用户信息成功",
|
||||
"timestamp": 1744238900000,
|
||||
"data": {
|
||||
"userId": 1,
|
||||
"userName": "admin",
|
||||
"phoneNumber": "138****8000",
|
||||
"idCard": "310**********1234",
|
||||
"roleList": ["ADMIN"],
|
||||
"isEnabled": true,
|
||||
"createTime": 1743238900000,
|
||||
"createTimeStr": "2025-01-01 10:00:00",
|
||||
"lastLoginTime": 1744152500000,
|
||||
"lastLoginTimeStr": "2025-04-10 08:00:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 响应示例(失败)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 401,
|
||||
"message": "未登录,请先登录",
|
||||
"timestamp": 1744238900000,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 状态码说明
|
||||
|
||||
| 状态码 | 说明 |
|
||||
| ------ | ---------------- |
|
||||
| 200 | 查询用户信息成功 |
|
||||
| 401 | Token失效/未登录 |
|
||||
| 404 | 用户信息不存在 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 5. 发送登录验证码接口
|
||||
|
||||
### 接口基本信息
|
||||
|
||||
- 接口名称:发送登录验证码
|
||||
- 接口路径:`/api/v1/auth/send-login-code`
|
||||
- 请求方法:POST
|
||||
- 接口描述:向指定手机号发送登录验证码,限制发送频率
|
||||
|
||||
### 请求头
|
||||
|
||||
| 字段名 | 类型 | 必填性 | 示例值 |
|
||||
| --------------- | ------ | ------ | ---------------- |
|
||||
| Content-Type | String | 是 | application/json |
|
||||
| Accept-Language | String | 否 | zh-CN |
|
||||
|
||||
### 请求参数(Body)
|
||||
|
||||
| 字段名 | 类型 | 必填性 | 描述 | 示例值 |
|
||||
| ----------- | ------ | ------ | ------------------ | ------------- |
|
||||
| phoneNumber | String | 是 | 接收验证码的手机号 | "13800138000" |
|
||||
|
||||
### 响应示例(成功)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "验证码发送成功,请注意查收",
|
||||
"timestamp": 1744238900000,
|
||||
"data": {
|
||||
"expireTime": 1744239200000 // 验证码过期时间戳(5分钟有效期)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 响应示例(失败)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 429,
|
||||
"message": "验证码发送过于频繁,请1分钟后重试",
|
||||
"timestamp": 1744238900000,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 状态码说明
|
||||
|
||||
| 状态码 | 说明 |
|
||||
| ------ | ------------------------------ |
|
||||
| 200 | 验证码发送成功 |
|
||||
| 400 | 手机号格式错误 |
|
||||
| 429 | 发送频率超限 |
|
||||
| 404 | 手机号未注册 |
|
||||
| 500 | 验证码发送失败(短信服务异常) |
|
||||
|
||||
## 6. 重置密码接口
|
||||
|
||||
### 接口基本信息
|
||||
|
||||
- 接口名称:重置登录密码
|
||||
- 接口路径:`/api/v1/auth/reset-password`
|
||||
- 请求方法:PUT
|
||||
- 接口描述:用户通过手机号+验证码重置登录密码(全量更新密码)
|
||||
|
||||
### 请求头
|
||||
|
||||
| 字段名 | 类型 | 必填性 | 示例值 |
|
||||
| --------------- | ------ | ------ | ---------------- |
|
||||
| Content-Type | String | 是 | application/json |
|
||||
| Accept-Language | String | 否 | zh-CN |
|
||||
|
||||
### 请求参数(Body)
|
||||
|
||||
| 字段名 | 类型 | 必填性 | 描述 | 示例值 |
|
||||
| ----------- | ------ | ------ | ----------------------------------------------- | ------------- |
|
||||
| phoneNumber | String | 是 | 绑定的手机号 | "13800138000" |
|
||||
| verifyCode | String | 是 | 验证码 | "8888" |
|
||||
| newPassword | String | 是 | 新密码(需符合密码规则:8-20位,含大小写+数字) | "Abc123456" |
|
||||
|
||||
### 响应示例(成功)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "密码重置成功,请重新登录",
|
||||
"timestamp": 1744238900000,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 响应示例(失败)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "新密码格式不符合要求,需包含大小写字母和数字,长度8-20位",
|
||||
"timestamp": 1744238900000,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 状态码说明
|
||||
|
||||
| 状态码 | 说明 |
|
||||
| ------ | -------------------------------- |
|
||||
| 200 | 密码重置成功 |
|
||||
| 400 | 参数错误/验证码错误/密码格式错误 |
|
||||
| 429 | 验证码发送频率超限/验证次数过多 |
|
||||
| 404 | 手机号未注册 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 7. 校验Token有效性接口
|
||||
|
||||
### 接口基本信息
|
||||
|
||||
- 接口名称:校验Token有效性
|
||||
- 接口路径:`/api/v1/auth/verify-token`
|
||||
- 请求方法:GET
|
||||
- 接口描述:校验当前登录令牌是否有效,返回令牌基础信息
|
||||
|
||||
### 请求头
|
||||
|
||||
| 字段名 | 类型 | 必填性 | 示例值 |
|
||||
| --------------- | ------ | ------ | ------------------------------ |
|
||||
| Authorization | String | 是 | Bearer eyJhbGciOiJIUzI1NiJ9... |
|
||||
| Content-Type | String | 是 | application/json |
|
||||
| Accept-Language | String | 否 | zh-CN |
|
||||
|
||||
### 请求参数
|
||||
|
||||
无业务请求参数(仅需请求头携带token)
|
||||
|
||||
### 响应示例(成功)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "Token有效",
|
||||
"timestamp": 1744238900000,
|
||||
"data": {
|
||||
"userId": 1,
|
||||
"token": "eyJhbGciOiJIUzI1NiJ9...",
|
||||
"isValid": true,
|
||||
"expireTime": 1744843700000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 响应示例(失败)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 401,
|
||||
"message": "Token无效或已过期",
|
||||
"timestamp": 1744238900000,
|
||||
"data": {
|
||||
"isValid": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 状态码说明
|
||||
|
||||
| 状态码 | 说明 |
|
||||
| ------ | ---------------- |
|
||||
| 200 | Token有效 |
|
||||
| 401 | Token无效/已过期 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 通用说明
|
||||
|
||||
1. 所有接口响应均遵循通用响应格式,包含`code`、`message`、`timestamp`字段,`data`字段按需返回;
|
||||
2. 敏感信息(手机号、身份证号)均做脱敏处理,符合安全规范;
|
||||
3. 接口频率限制遵循通用规范:验证码接口1分钟/次、1小时/5次,登录接口5分钟连续失败5次锁定15分钟,通用接口QPS≤10;
|
||||
4. 所有入参均做合法性校验,参数错误返回400状态码并提示具体错误信息;
|
||||
5. Token格式为`Bearer + 空格 + token`,需登录的接口未传/传错Token均返回401状态码。
|
||||
@@ -0,0 +1,9 @@
|
||||
package icu.sunway.ai_spring_example.Controller.AuthController.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class LoginByEmailPasswordReq {
|
||||
private String email;
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package icu.sunway.ai_spring_example.Controller.AuthController.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RegisterByEmailReq {
|
||||
private String email;
|
||||
private String password;
|
||||
private String verifyCode;
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
-- Example Data for User System
|
||||
|
||||
-- Roles
|
||||
INSERT INTO
|
||||
`role` (
|
||||
`name`,
|
||||
`code`,
|
||||
`description`,
|
||||
`status`
|
||||
)
|
||||
VALUES (
|
||||
'Super Admin',
|
||||
'SUPER_ADMIN',
|
||||
'Full system access',
|
||||
1
|
||||
),
|
||||
(
|
||||
'Admin',
|
||||
'ADMIN',
|
||||
'Administrative access',
|
||||
1
|
||||
),
|
||||
(
|
||||
'User',
|
||||
'USER',
|
||||
'Regular user access',
|
||||
1
|
||||
),
|
||||
(
|
||||
'Guest',
|
||||
'GUEST',
|
||||
'Limited guest access',
|
||||
1
|
||||
);
|
||||
|
||||
-- Permissions
|
||||
INSERT INTO
|
||||
`permission` (
|
||||
`name`,
|
||||
`code`,
|
||||
`description`,
|
||||
`parent_id`,
|
||||
`type`,
|
||||
`path`,
|
||||
`sort`
|
||||
)
|
||||
VALUES
|
||||
-- Menu permissions
|
||||
(
|
||||
'Dashboard',
|
||||
'dashboard',
|
||||
'Dashboard menu',
|
||||
0,
|
||||
1,
|
||||
'/dashboard',
|
||||
1
|
||||
),
|
||||
(
|
||||
'User Management',
|
||||
'user:manage',
|
||||
'User management menu',
|
||||
0,
|
||||
1,
|
||||
'/user',
|
||||
2
|
||||
),
|
||||
(
|
||||
'Role Management',
|
||||
'role:manage',
|
||||
'Role management menu',
|
||||
0,
|
||||
1,
|
||||
'/role',
|
||||
3
|
||||
),
|
||||
(
|
||||
'System Settings',
|
||||
'system:settings',
|
||||
'System settings menu',
|
||||
0,
|
||||
1,
|
||||
'/settings',
|
||||
4
|
||||
),
|
||||
-- User management buttons/actions
|
||||
(
|
||||
'View Users',
|
||||
'user:view',
|
||||
'View user list',
|
||||
2,
|
||||
2,
|
||||
NULL,
|
||||
1
|
||||
),
|
||||
(
|
||||
'Create User',
|
||||
'user:create',
|
||||
'Create new user',
|
||||
2,
|
||||
2,
|
||||
NULL,
|
||||
2
|
||||
),
|
||||
(
|
||||
'Edit User',
|
||||
'user:edit',
|
||||
'Edit user info',
|
||||
2,
|
||||
2,
|
||||
NULL,
|
||||
3
|
||||
),
|
||||
(
|
||||
'Delete User',
|
||||
'user:delete',
|
||||
'Delete user',
|
||||
2,
|
||||
2,
|
||||
NULL,
|
||||
4
|
||||
),
|
||||
-- Role management buttons/actions
|
||||
(
|
||||
'View Roles',
|
||||
'role:view',
|
||||
'View role list',
|
||||
3,
|
||||
2,
|
||||
NULL,
|
||||
1
|
||||
),
|
||||
(
|
||||
'Create Role',
|
||||
'role:create',
|
||||
'Create new role',
|
||||
3,
|
||||
2,
|
||||
NULL,
|
||||
2
|
||||
),
|
||||
(
|
||||
'Edit Role',
|
||||
'role:edit',
|
||||
'Edit role info',
|
||||
3,
|
||||
2,
|
||||
NULL,
|
||||
3
|
||||
),
|
||||
(
|
||||
'Delete Role',
|
||||
'role:delete',
|
||||
'Delete role',
|
||||
3,
|
||||
2,
|
||||
NULL,
|
||||
4
|
||||
),
|
||||
-- API permissions
|
||||
(
|
||||
'User API',
|
||||
'api:user',
|
||||
'User API access',
|
||||
0,
|
||||
3,
|
||||
'/api/user/**',
|
||||
1
|
||||
),
|
||||
(
|
||||
'Role API',
|
||||
'api:role',
|
||||
'Role API access',
|
||||
0,
|
||||
3,
|
||||
'/api/role/**',
|
||||
2
|
||||
);
|
||||
|
||||
-- Users (passwords are bcrypt hashed for 'password123')
|
||||
INSERT INTO
|
||||
`user` (
|
||||
`username`,
|
||||
`password`,
|
||||
`email`,
|
||||
`phone`,
|
||||
`avatar`,
|
||||
`status`
|
||||
)
|
||||
VALUES (
|
||||
'admin',
|
||||
'$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi',
|
||||
'admin@example.com',
|
||||
'13800000001',
|
||||
NULL,
|
||||
1
|
||||
),
|
||||
(
|
||||
'john',
|
||||
'$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi',
|
||||
'john@example.com',
|
||||
'13800000002',
|
||||
NULL,
|
||||
1
|
||||
),
|
||||
(
|
||||
'jane',
|
||||
'$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi',
|
||||
'jane@example.com',
|
||||
'13800000003',
|
||||
NULL,
|
||||
1
|
||||
),
|
||||
(
|
||||
'guest',
|
||||
'$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi',
|
||||
'guest@example.com',
|
||||
'13800000004',
|
||||
NULL,
|
||||
1
|
||||
);
|
||||
|
||||
-- User-Role assignments
|
||||
INSERT INTO
|
||||
`user_role` (`user_id`, `role_id`)
|
||||
VALUES (1, 1), -- admin -> SUPER_ADMIN
|
||||
(2, 2), -- john -> ADMIN
|
||||
(3, 3), -- jane -> USER
|
||||
(4, 4);
|
||||
-- guest -> GUEST
|
||||
|
||||
-- Role-Permission assignments
|
||||
-- SUPER_ADMIN gets all permissions
|
||||
INSERT INTO
|
||||
`role_permission` (`role_id`, `permission_id`)
|
||||
VALUES (1, 1),
|
||||
(1, 2),
|
||||
(1, 3),
|
||||
(1, 4),
|
||||
(1, 5),
|
||||
(1, 6),
|
||||
(1, 7),
|
||||
(1, 8),
|
||||
(1, 9),
|
||||
(1, 10),
|
||||
(1, 11),
|
||||
(1, 12),
|
||||
(1, 13),
|
||||
(1, 14);
|
||||
|
||||
-- ADMIN gets user and role management
|
||||
INSERT INTO
|
||||
`role_permission` (`role_id`, `permission_id`)
|
||||
VALUES (2, 1),
|
||||
(2, 2),
|
||||
(2, 3),
|
||||
(2, 5),
|
||||
(2, 6),
|
||||
(2, 7),
|
||||
(2, 9),
|
||||
(2, 10),
|
||||
(2, 11),
|
||||
(2, 13),
|
||||
(2, 14);
|
||||
|
||||
-- USER gets dashboard and view permissions
|
||||
INSERT INTO
|
||||
`role_permission` (`role_id`, `permission_id`)
|
||||
VALUES (3, 1),
|
||||
(3, 5),
|
||||
(3, 9),
|
||||
(3, 13);
|
||||
|
||||
-- GUEST gets only dashboard
|
||||
INSERT INTO
|
||||
`role_permission` (`role_id`, `permission_id`)
|
||||
VALUES (4, 1);
|
||||
@@ -1,70 +0,0 @@
|
||||
-- User System Tables
|
||||
|
||||
-- User table
|
||||
CREATE TABLE `user` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`username` VARCHAR(50) NOT NULL,
|
||||
`password` VARCHAR(255) NOT NULL,
|
||||
`email` VARCHAR(100),
|
||||
`phone` VARCHAR(20),
|
||||
`avatar` VARCHAR(255),
|
||||
`status` INT DEFAULT 1 COMMENT '0: disabled, 1: enabled',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`last_login_time` DATETIME,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_username` (`username`),
|
||||
UNIQUE KEY `uk_email` (`email`),
|
||||
UNIQUE KEY `uk_phone` (`phone`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Role table
|
||||
CREATE TABLE `role` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(50) NOT NULL,
|
||||
`code` VARCHAR(50) NOT NULL,
|
||||
`description` VARCHAR(255),
|
||||
`status` INT DEFAULT 1 COMMENT '0: disabled, 1: enabled',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_code` (`code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Permission table
|
||||
CREATE TABLE `permission` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(50) NOT NULL,
|
||||
`code` VARCHAR(100) NOT NULL,
|
||||
`description` VARCHAR(255),
|
||||
`parent_id` BIGINT DEFAULT 0,
|
||||
`type` INT NOT NULL COMMENT '1: menu, 2: button, 3: api',
|
||||
`path` VARCHAR(255),
|
||||
`sort` INT DEFAULT 0,
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_code` (`code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- User-Role junction table
|
||||
CREATE TABLE `user_role` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL,
|
||||
`role_id` BIGINT NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_user_role` (`user_id`, `role_id`),
|
||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`role_id`) REFERENCES `role`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Role-Permission junction table
|
||||
CREATE TABLE `role_permission` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`role_id` BIGINT NOT NULL,
|
||||
`permission_id` BIGINT NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_role_permission` (`role_id`, `permission_id`),
|
||||
FOREIGN KEY (`role_id`) REFERENCES `role`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`permission_id`) REFERENCES `permission`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
@@ -0,0 +1,32 @@
|
||||
package icu.sunway.ai_spring_example.Exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 自定义业务异常类
|
||||
*/
|
||||
@Getter
|
||||
public class BusinessException extends RuntimeException {
|
||||
/**
|
||||
* 业务错误码(区分不同异常场景)
|
||||
*/
|
||||
private int code;
|
||||
|
||||
// 仅错误消息
|
||||
public BusinessException(String message) {
|
||||
super(message);
|
||||
this.code = 500; // 默认业务异常码
|
||||
}
|
||||
|
||||
// 错误码 + 错误消息
|
||||
public BusinessException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
// 错误码 + 错误消息 + 根因异常
|
||||
public BusinessException(int code, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package icu.sunway.ai_spring_example.Service;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import icu.sunway.ai_spring_example.Entity.User;
|
||||
import icu.sunway.ai_spring_example.Exception.BusinessException;
|
||||
import icu.sunway.ai_spring_example.Service.Implements.UserServiceImpl;
|
||||
import icu.sunway.ai_spring_example.Utils.JwtUtils;
|
||||
import icu.sunway.ai_spring_example.Utils.RedisUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
@Service
|
||||
public class AuthService {
|
||||
|
||||
@Resource
|
||||
private UserServiceImpl userService;
|
||||
@Resource
|
||||
private MailService mailService;
|
||||
@Resource
|
||||
private RedisUtils redisUtils;
|
||||
|
||||
public void registerByEmail(String email, String password, String verifyCode) {
|
||||
// 校验邮箱是否已存在
|
||||
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("email", email);
|
||||
if (userService.getOne(queryWrapper) != null) {
|
||||
throw new BusinessException("邮箱已存在");
|
||||
}
|
||||
// 校验验证码正确性
|
||||
if (!mailService.checkVerifyCode(email, verifyCode)) {
|
||||
throw new BusinessException("验证码错误");
|
||||
}
|
||||
User user = new User();
|
||||
user.setEmail(email);
|
||||
// 用户名是 not null,暂时设置为邮箱
|
||||
user.setUsername(email);
|
||||
user.setPassword(password);
|
||||
userService.save(user);
|
||||
}
|
||||
|
||||
public String loginByEmailPassword(String email, String password) {
|
||||
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("email", email);
|
||||
// 校验邮箱是否存在
|
||||
if (userService.getOne(queryWrapper) == null) {
|
||||
throw new BusinessException("邮箱不存在");
|
||||
}
|
||||
queryWrapper.eq("password", password);
|
||||
User user = userService.getOne(queryWrapper);
|
||||
if (user == null) {
|
||||
throw new BusinessException("邮箱或密码错误");
|
||||
}
|
||||
String token = JwtUtils.generateToken(user.getId().toString());
|
||||
// 存放 token 到 Redis
|
||||
redisUtils.set("token:" + token, token, JwtUtils.getTime(), TimeUnit.MILLISECONDS);
|
||||
return token;
|
||||
}
|
||||
|
||||
public boolean checkToken(String token) {
|
||||
return redisUtils.hasKey("token:" + token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package icu.sunway.ai_spring_example.Service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import icu.sunway.ai_spring_example.Utils.RedisUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
@Service
|
||||
public class MailService {
|
||||
@Resource
|
||||
private RedisUtils redisUtils;
|
||||
|
||||
public Boolean checkVerifyCode(String to, String toBeCheckedCode) {
|
||||
// if (redisUtils.hasKey("email:verify:" + to)) {
|
||||
// String verifyCode = redisUtils.get("email:verify:" + to).toString();
|
||||
// return verifyCode.equals(toBeCheckedCode);
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
|
||||
// 暂时没有发邮件,默认全部通过
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package icu.sunway.ai_spring_example.Utils;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtBuilder;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import lombok.Getter;
|
||||
|
||||
public class JwtUtils {
|
||||
|
||||
@Getter
|
||||
private static final long time = 1000 * 3600 * 24 * 7; // 7天过期
|
||||
private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
|
||||
|
||||
public static String generateToken(String id) {
|
||||
JwtBuilder jwtBuilder = Jwts.builder();
|
||||
return jwtBuilder
|
||||
.setHeaderParam("typ", "JWT")
|
||||
.setHeaderParam("alg", "HS256")
|
||||
.claim("id", id)
|
||||
.claim("date", new Date())
|
||||
.setExpiration(new Date(System.currentTimeMillis() + time))
|
||||
.signWith(SECRET_KEY, SignatureAlgorithm.HS256)
|
||||
.setId(UUID.randomUUID().toString())
|
||||
.setSubject("user-check")
|
||||
.compact();
|
||||
}
|
||||
|
||||
public static String parseTokenAsId(String token) {
|
||||
Claims claims = Jwts.parserBuilder()
|
||||
.setSigningKey(SECRET_KEY)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
try {
|
||||
if (claims.getExpiration().before(new Date())) {
|
||||
return null;
|
||||
}
|
||||
return claims.get("id", String.class);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package icu.sunway.ai_spring_example.Utils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class LogUtils {
|
||||
private static final Logger logger = LoggerFactory.getLogger(LogUtils.class);
|
||||
|
||||
public static void info(String message) {
|
||||
logger.info("------------ debug info start -----------");
|
||||
logger.info(message);
|
||||
logger.info("------------ debug info end -----------");
|
||||
}
|
||||
|
||||
public static void error(String message) {
|
||||
logger.error(message);
|
||||
}
|
||||
|
||||
public static void error(String message, Throwable throwable) {
|
||||
logger.error(message, throwable);
|
||||
}
|
||||
|
||||
public static void debug(String message) {
|
||||
logger.debug(message);
|
||||
}
|
||||
|
||||
public static void debug(String message, Throwable throwable) {
|
||||
logger.debug(message, throwable);
|
||||
}
|
||||
|
||||
public static void trace(String message) {
|
||||
logger.trace(message);
|
||||
}
|
||||
|
||||
public static void trace(String message, Throwable throwable) {
|
||||
logger.trace(message, throwable);
|
||||
}
|
||||
|
||||
public static void warn(String message) {
|
||||
logger.warn(message);
|
||||
}
|
||||
|
||||
public static void warn(String message, Throwable throwable) {
|
||||
logger.warn(message, throwable);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user