diff --git a/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/AuthController.java b/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/AuthController.java index 1d603c1..19abc3a 100644 --- a/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/AuthController.java +++ b/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/AuthController.java @@ -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()); + } + } + } diff --git a/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/docs.md b/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/docs.md deleted file mode 100644 index 0aa8619..0000000 --- a/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/docs.md +++ /dev/null @@ -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状态码。 diff --git a/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/vo/LoginByEmailPasswordReq.java b/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/vo/LoginByEmailPasswordReq.java new file mode 100644 index 0000000..6a52b62 --- /dev/null +++ b/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/vo/LoginByEmailPasswordReq.java @@ -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; +} diff --git a/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/vo/RegisterByEmailReq.java b/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/vo/RegisterByEmailReq.java new file mode 100644 index 0000000..51afdb9 --- /dev/null +++ b/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/vo/RegisterByEmailReq.java @@ -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; +} diff --git a/src/main/java/icu/sunway/ai_spring_example/Entity/example.sql b/src/main/java/icu/sunway/ai_spring_example/Entity/example.sql deleted file mode 100644 index f3035c8..0000000 --- a/src/main/java/icu/sunway/ai_spring_example/Entity/example.sql +++ /dev/null @@ -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); \ No newline at end of file diff --git a/src/main/java/icu/sunway/ai_spring_example/Entity/main.sql b/src/main/java/icu/sunway/ai_spring_example/Entity/main.sql deleted file mode 100644 index 5471bb2..0000000 --- a/src/main/java/icu/sunway/ai_spring_example/Entity/main.sql +++ /dev/null @@ -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; diff --git a/src/main/java/icu/sunway/ai_spring_example/Exception/BusinessException.java b/src/main/java/icu/sunway/ai_spring_example/Exception/BusinessException.java new file mode 100644 index 0000000..4f97154 --- /dev/null +++ b/src/main/java/icu/sunway/ai_spring_example/Exception/BusinessException.java @@ -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; + } +} diff --git a/src/main/java/icu/sunway/ai_spring_example/Service/AuthService.java b/src/main/java/icu/sunway/ai_spring_example/Service/AuthService.java new file mode 100644 index 0000000..66a4ce2 --- /dev/null +++ b/src/main/java/icu/sunway/ai_spring_example/Service/AuthService.java @@ -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 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 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); + } +} diff --git a/src/main/java/icu/sunway/ai_spring_example/Service/MailService.java b/src/main/java/icu/sunway/ai_spring_example/Service/MailService.java new file mode 100644 index 0000000..d347edd --- /dev/null +++ b/src/main/java/icu/sunway/ai_spring_example/Service/MailService.java @@ -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; + } +} diff --git a/src/main/java/icu/sunway/ai_spring_example/Utils/JwtUtils.java b/src/main/java/icu/sunway/ai_spring_example/Utils/JwtUtils.java new file mode 100644 index 0000000..35344ff --- /dev/null +++ b/src/main/java/icu/sunway/ai_spring_example/Utils/JwtUtils.java @@ -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; + } + } + +} \ No newline at end of file diff --git a/src/main/java/icu/sunway/ai_spring_example/Utils/LogUtils.java b/src/main/java/icu/sunway/ai_spring_example/Utils/LogUtils.java new file mode 100644 index 0000000..6f4c869 --- /dev/null +++ b/src/main/java/icu/sunway/ai_spring_example/Utils/LogUtils.java @@ -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); + } +}