Compare commits

...

5 Commits

Author SHA1 Message Date
7c35cd11ac feat: user system first complete 2026-02-13 22:52:53 +08:00
set
d1be97a366 feat: doc change 2026-02-13 20:56:11 +08:00
set
1eafeacf36 feat: add base docs 2026-02-13 20:49:22 +08:00
set
6c0ed89397 feat: remove example 2026-02-05 18:53:34 +08:00
set
d03897c525 feat: Add complete user system implementation
- Create User, Role, Permission entities with their relationships
- Implement service interfaces and implementations for user system
- Add controllers for User, Role, Permission, UserRole, and RolePermission
- Include SQL schema files for the user system
- Document project structure in CLAUDE.md

This adds a complete RBAC (Role-Based Access Control) system with:
- User entity for managing user accounts
- Role entity for defining roles
- Permission entity for defining permissions
- UserRole entity for user-role relationships
- RolePermission entity for role-permission relationships

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 18:52:37 +08:00
43 changed files with 1495 additions and 198 deletions

View File

@@ -0,0 +1,8 @@
{
"permissions": {
"allow": [
"Bash(git add:*)",
"Bash(git commit:*)"
]
}
}

66
CLAUDE.md Normal file
View File

@@ -0,0 +1,66 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Build & Run Commands
```bash
# Build the project
./mvnw clean package
# Run the application
./mvnw spring-boot:run
# Run all tests
./mvnw test
# Run a single test class
./mvnw test -Dtest=TestClassName
# Run a single test method
./mvnw test -Dtest=TestClassName#methodName
```
## Project Overview
- **Framework**: Spring Boot 3.2.3 with Java 21
- **ORM**: MyBatis Plus 3.5.5
- **Database**: MySQL 8.0.33
- **Cache**: Redis (Spring Data Redis)
- **API Docs**: Swagger UI at `/swagger-ui.html`
- **Auth**: JWT (jjwt 0.11.5)
## Architecture
Base package: `icu.sunway.ai_spring_example`
This project follows a layered architecture with MyBatis Plus:
| Layer | Location | Pattern |
|-------|----------|---------|
| Controller | `Controller/{EntityName}Controller/` | One subdirectory per entity |
| Service | `Service/` | Interface `I{Name}Service` extends `IService<Entity>` |
| Service Impl | `Service/Implements/` | `{Name}ServiceImpl` extends `ServiceImpl<Mapper, Entity>` |
| Mapper | `Mapper/` | `{Name}Mapper` extends `BaseMapper<Entity>` |
| Entity | `Entity/` | Lombok + MyBatis Plus annotations |
| Config | `Config/` | CORS, Security, Redis, OpenAPI |
| Utils | `Utils/` | Utility classes |
## Key Utilities
- **ResponseUtils**: Standard API responses - `ResponseUtils.success(data)` / `ResponseUtils.fail(message)`
- **PageUtils**: Pagination - `PageUtils.createPage(page, limit)` returns `Page<T>` for MyBatis Plus
- **RedisUtils**: Redis operations (inject with `@Resource`)
- **JsonUtils**: JSON serialization - `JsonUtils.toJson()` / `JsonUtils.toObject()`
- **ValidationUtils**: Input validation (email, phone, URL, IP, password strength, etc.)
## Configuration
- Copy `application-example.yaml` to `application.yaml` and fill in database/Redis credentials
- `application.yaml` is gitignored (contains sensitive data)
- Uses dynamic multi-datasource (primary datasource named "master")
## Security Notes
- Current `SecurityConfig` is permissive (all requests allowed) - configure for production
- Stateless session management configured for JWT authentication

48
pom.xml
View File

@@ -33,7 +33,7 @@
<lombok.version>1.18.30</lombok.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
<mysql.version>8.0.33</mysql.version>
<mysql.version>8.0.33</mysql.version>
</properties>
<dependencies>
<!-- 核心依赖 -->
@@ -43,14 +43,14 @@
</dependency>
<!-- 数据库相关 -->
<dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.version}</version>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
@@ -70,26 +70,26 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
<!-- 添加验证依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
<!-- 添加验证依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- API文档 -->
<dependency>
<groupId>org.springdoc</groupId>

View File

@@ -0,0 +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());
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -1,17 +0,0 @@
package icu.sunway.ai_spring_example.Controller.ExampleEntityController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import icu.sunway.ai_spring_example.Service.IExampleEntityService;
@Controller
@RestController
@RequestMapping("/example")
public class EaxmpleEntityController {
@Resource
private IExampleEntityService exampleEntityService;
}

View File

@@ -0,0 +1,73 @@
package icu.sunway.ai_spring_example.Controller.PermissionController;
import icu.sunway.ai_spring_example.Entity.Permission;
import icu.sunway.ai_spring_example.Service.IPermissionService;
import icu.sunway.ai_spring_example.Utils.ResponseUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
@RestController
@RequestMapping("/api/v1/permission")
public class PermissionController {
@Resource
private IPermissionService permissionService;
@GetMapping("/{id}")
public Object getPermissionById(@PathVariable Long id) {
Permission permission = permissionService.getById(id);
return ResponseUtils.success(permission);
}
@GetMapping
public Object getAllPermissions(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer limit) {
Page<Permission> permissionPage = new Page<>(page, limit);
Page<Permission> resultPage = permissionService.page(permissionPage);
return ResponseUtils.success(resultPage);
}
@PostMapping
public Object createPermission(@RequestBody Permission permission) {
boolean result = permissionService.save(permission);
if (result) {
return ResponseUtils.success(permission);
} else {
return ResponseUtils.fail("Failed to create permission");
}
}
@PutMapping("/{id}")
public Object updatePermission(@PathVariable Long id, @RequestBody Permission permission) {
permission.setId(id);
boolean result = permissionService.updateById(permission);
if (result) {
return ResponseUtils.success(permission);
} else {
return ResponseUtils.fail("Failed to update permission");
}
}
@DeleteMapping("/{id}")
public Object deletePermission(@PathVariable Long id) {
boolean result = permissionService.removeById(id);
if (result) {
return ResponseUtils.success("Permission deleted successfully");
} else {
return ResponseUtils.fail("Failed to delete permission");
}
}
@GetMapping("/name/{name}")
public Object getPermissionByName(@PathVariable String name) {
QueryWrapper<Permission> wrapper = new QueryWrapper<>();
wrapper.eq("name", name);
Permission permission = permissionService.getOne(wrapper);
return ResponseUtils.success(permission);
}
}

View File

@@ -0,0 +1,73 @@
package icu.sunway.ai_spring_example.Controller.RoleController;
import icu.sunway.ai_spring_example.Entity.Role;
import icu.sunway.ai_spring_example.Service.IRoleService;
import icu.sunway.ai_spring_example.Utils.ResponseUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
@RestController
@RequestMapping("/api/v1/role")
public class RoleController {
@Resource
private IRoleService roleService;
@GetMapping("/{id}")
public Object getRoleById(@PathVariable Long id) {
Role role = roleService.getById(id);
return ResponseUtils.success(role);
}
@GetMapping
public Object getAllRoles(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer limit) {
Page<Role> rolePage = new Page<>(page, limit);
Page<Role> resultPage = roleService.page(rolePage);
return ResponseUtils.success(resultPage);
}
@PostMapping
public Object createRole(@RequestBody Role role) {
boolean result = roleService.save(role);
if (result) {
return ResponseUtils.success(role);
} else {
return ResponseUtils.fail("Failed to create role");
}
}
@PutMapping("/{id}")
public Object updateRole(@PathVariable Long id, @RequestBody Role role) {
role.setId(id);
boolean result = roleService.updateById(role);
if (result) {
return ResponseUtils.success(role);
} else {
return ResponseUtils.fail("Failed to update role");
}
}
@DeleteMapping("/{id}")
public Object deleteRole(@PathVariable Long id) {
boolean result = roleService.removeById(id);
if (result) {
return ResponseUtils.success("Role deleted successfully");
} else {
return ResponseUtils.fail("Failed to delete role");
}
}
@GetMapping("/name/{name}")
public Object getRoleByName(@PathVariable String name) {
QueryWrapper<Role> wrapper = new QueryWrapper<>();
wrapper.eq("name", name);
Role role = roleService.getOne(wrapper);
return ResponseUtils.success(role);
}
}

View File

@@ -0,0 +1,82 @@
package icu.sunway.ai_spring_example.Controller.RolePermissionController;
import icu.sunway.ai_spring_example.Entity.RolePermission;
import icu.sunway.ai_spring_example.Service.IRolePermissionService;
import icu.sunway.ai_spring_example.Utils.ResponseUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping("/api/v1/role-permission")
public class RolePermissionController {
@Resource
private IRolePermissionService rolePermissionService;
@GetMapping("/{id}")
public Object getRolePermissionById(@PathVariable Long id) {
RolePermission rolePermission = rolePermissionService.getById(id);
return ResponseUtils.success(rolePermission);
}
@GetMapping
public Object getAllRolePermissions(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer limit) {
Page<RolePermission> rolePermissionPage = new Page<>(page, limit);
Page<RolePermission> resultPage = rolePermissionService.page(rolePermissionPage);
return ResponseUtils.success(resultPage);
}
@PostMapping
public Object createRolePermission(@RequestBody RolePermission rolePermission) {
boolean result = rolePermissionService.save(rolePermission);
if (result) {
return ResponseUtils.success(rolePermission);
} else {
return ResponseUtils.fail("Failed to create role permission relationship");
}
}
@PutMapping("/{id}")
public Object updateRolePermission(@PathVariable Long id, @RequestBody RolePermission rolePermission) {
rolePermission.setId(id);
boolean result = rolePermissionService.updateById(rolePermission);
if (result) {
return ResponseUtils.success(rolePermission);
} else {
return ResponseUtils.fail("Failed to update role permission relationship");
}
}
@DeleteMapping("/{id}")
public Object deleteRolePermission(@PathVariable Long id) {
boolean result = rolePermissionService.removeById(id);
if (result) {
return ResponseUtils.success("Role permission relationship deleted successfully");
} else {
return ResponseUtils.fail("Failed to delete role permission relationship");
}
}
@GetMapping("/role/{roleId}")
public Object getRolePermissionsByRoleId(@PathVariable Long roleId) {
QueryWrapper<RolePermission> wrapper = new QueryWrapper<>();
wrapper.eq("role_id", roleId);
List<RolePermission> rolePermissions = rolePermissionService.list(wrapper);
return ResponseUtils.success(rolePermissions);
}
@GetMapping("/permission/{permissionId}")
public Object getRolePermissionsByPermissionId(@PathVariable Long permissionId) {
QueryWrapper<RolePermission> wrapper = new QueryWrapper<>();
wrapper.eq("permission_id", permissionId);
List<RolePermission> rolePermissions = rolePermissionService.list(wrapper);
return ResponseUtils.success(rolePermissions);
}
}

View File

@@ -0,0 +1,73 @@
package icu.sunway.ai_spring_example.Controller.UserController;
import icu.sunway.ai_spring_example.Entity.User;
import icu.sunway.ai_spring_example.Service.IUserService;
import icu.sunway.ai_spring_example.Utils.ResponseUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
@RestController
@RequestMapping("/api/v1/user")
public class UserController {
@Resource
private IUserService userService;
@GetMapping("/{id}")
public Object getUserById(@PathVariable Long id) {
User user = userService.getById(id);
return ResponseUtils.success(user);
}
@GetMapping
public Object getAllUsers(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer limit) {
Page<User> userPage = new Page<>(page, limit);
Page<User> resultPage = userService.page(userPage);
return ResponseUtils.success(resultPage);
}
@PostMapping
public Object createUser(@RequestBody User user) {
boolean result = userService.save(user);
if (result) {
return ResponseUtils.success(user);
} else {
return ResponseUtils.fail("Failed to create user");
}
}
@PutMapping("/{id}")
public Object updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
boolean result = userService.updateById(user);
if (result) {
return ResponseUtils.success(user);
} else {
return ResponseUtils.fail("Failed to update user");
}
}
@DeleteMapping("/{id}")
public Object deleteUser(@PathVariable Long id) {
boolean result = userService.removeById(id);
if (result) {
return ResponseUtils.success("User deleted successfully");
} else {
return ResponseUtils.fail("Failed to delete user");
}
}
@GetMapping("/username/{username}")
public Object getUserByUsername(@PathVariable String username) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
User user = userService.getOne(wrapper);
return ResponseUtils.success(user);
}
}

View File

@@ -0,0 +1,84 @@
package icu.sunway.ai_spring_example.Controller.UserRoleController;
import icu.sunway.ai_spring_example.Entity.UserRole;
import icu.sunway.ai_spring_example.Service.IUserRoleService;
import icu.sunway.ai_spring_example.Utils.ResponseUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
@RestController
@RequestMapping("/api/v1/user-role")
public class UserRoleController {
@Resource
private IUserRoleService userRoleService;
@GetMapping("/{id}")
public Object getUserRoleById(@PathVariable Long id) {
UserRole userRole = userRoleService.getById(id);
return ResponseUtils.success(userRole);
}
@GetMapping
public Object getAllUserRoles(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer limit) {
Page<UserRole> userRolePage = new Page<>(page, limit);
Page<UserRole> resultPage = userRoleService.page(userRolePage);
return ResponseUtils.success(resultPage);
}
@PostMapping
public Object createUserRole(@RequestBody UserRole userRole) {
boolean result = userRoleService.save(userRole);
if (result) {
return ResponseUtils.success(userRole);
} else {
return ResponseUtils.fail("Failed to create user role relationship");
}
}
@PutMapping("/{id}")
public Object updateUserRole(@PathVariable Long id, @RequestBody UserRole userRole) {
userRole.setId(id);
boolean result = userRoleService.updateById(userRole);
if (result) {
return ResponseUtils.success(userRole);
} else {
return ResponseUtils.fail("Failed to update user role relationship");
}
}
@DeleteMapping("/{id}")
public Object deleteUserRole(@PathVariable Long id) {
boolean result = userRoleService.removeById(id);
if (result) {
return ResponseUtils.success("User role relationship deleted successfully");
} else {
return ResponseUtils.fail("Failed to delete user role relationship");
}
}
@GetMapping("/user/{userId}")
public Object getUserRolesByUserId(@PathVariable Long userId) {
QueryWrapper<UserRole> wrapper = new QueryWrapper<>();
wrapper.eq("user_id", userId);
List<UserRole> userRoles = userRoleService.list(wrapper);
return ResponseUtils.success(userRoles);
}
@GetMapping("/role/{roleId}")
public Object getUserRolesByRoleId(@PathVariable Long roleId) {
QueryWrapper<UserRole> wrapper = new QueryWrapper<>();
wrapper.eq("role_id", roleId);
List<UserRole> userRoles = userRoleService.list(wrapper);
return ResponseUtils.success(userRoles);
}
}

View File

@@ -0,0 +1,218 @@
# 接口开发通用规范文档
## 文档说明
本规范为项目所有接口的通用开发准则,适用于前后端接口对接、后端接口开发、接口文档编写等场景,所有业务接口均需遵循本规范。
### 基础约定
- 接口基础路径:`/api/v1`各业务模块在基础路径后追加如Auth模块`/api/v1/auth`
- 数据交互格式JSON
- 字符编码UTF-8
- 时间戳单位:毫秒(整数类型)
- 接口版本管理通过URL路径版本号如v1区分不使用请求参数/请求头区分
## 一、通用请求规范
### 1. 请求方式RESTful规范
| 操作类型 | 请求方法 | 典型场景 | 示例 |
| ------------- | -------- | -------------------------------- | ----------------------------- |
| 新增/提交数据 | POST | 登录、发送验证码、创建资源 | `/api/v1/auth/login` |
| 查询数据 | GET | 获取详情、校验状态、列表查询 | `/api/v1/auth/user/info` |
| 修改数据 | PUT | 全量更新(如重置密码、修改信息) | `/api/v1/auth/reset-password` |
| 部分更新 | PATCH | 局部更新(如修改用户昵称) | `/api/v1/user/nickname` |
| 删除数据 | DELETE | 退出登录、删除资源 | `/api/v1/auth/logout` |
### 2. 请求头规范
| 字段名 | 类型 | 必填性 | 描述 | 示例 |
| --------------- | ------ | ------ | --------------------------------------------------------- | -------------------------------- |
| Authorization | String | 非必选 | 登录令牌,格式为`Bearer + 空格 + token`(需登录接口必传) | "Bearer eyJhbGciOiJIUzI1NiJ9..." |
| Content-Type | String | 必选 | 请求体格式,仅支持`application/json` | "application/json" |
| Accept-Language | String | 非必选 | 语言类型默认zh-CN | "zh-CN" |
### 3. 分页请求参数(列表接口必传)
所有列表查询接口均需支持分页,分页参数统一通过`GET`请求参数传递POST请求可放在Body中参数规范如下
| 字段名 | 类型 | 必填性 | 默认值 | 描述 | 示例 |
| ------ | ------- | ------ | ------ | ----------------------------- | ---- |
| page | Integer | 否 | 1 | 当前页码从1开始 | 1 |
| limit | Integer | 否 | 10 | 每页显示条数最大不超过100 | 10 |
#### 示例
```plain
GET /api/v1/user/list?current=1&size=10
```
### 4. 请求参数命名规范
- 字段名使用**小驼峰命名**(如`userName``phoneNumber`),禁止使用下划线/连字符;
- 布尔类型字段命名:使用`isXXX`格式(如`isRemember``isEnabled`
- 时间类字段:统一使用`createTime``updateTime`等,禁止使用`create_at`
- 参数值:手机号、身份证号等字符串类型参数,值为纯数字时仍以字符串传递。
## 二、通用响应规范
### 1. 基础响应格式(所有接口统一)
所有接口响应必须包含`code``message``timestamp`字段,`data`字段为可选(无业务数据时返回`null`)。
#### 格式定义
```json
{
"code": 200, // 整数,业务状态码
"message": "操作成功", // 字符串,提示信息(前端可直接展示)
"timestamp": 1744238900000, // 整数,服务器响应时间戳(毫秒)
"data": {} // 任意类型业务数据无数据时为null
}
```
#### 字段说明
| 字段名 | 类型 | 必填性 | 描述 |
| --------- | ------- | ------ | --------------------------------------------------------------------------- |
| code | Integer | 是 | 业务状态码200表示成功非200表示异常 |
| message | String | 是 | 响应提示信息(成功/失败描述,需友好、简洁) |
| timestamp | Integer | 是 | 服务器处理请求的时间戳(毫秒),前端可用于时间展示/校验 |
| data | Any | 否 | 业务数据体1. 无数据时为null2. 单条数据为Object3. 列表数据遵循分页规范 |
### 2. 分页响应格式(列表接口专用)
列表接口的`data`字段必须遵循以下结构,禁止自定义分页字段名/格式。
#### 格式定义
```json
{
"code": 200,
"message": "查询成功",
"timestamp": 1744238900000,
"data": {
"records": [], // 数组,当前页数据列表
"total": 0, // 整数,符合条件的总记录数
"size": 10, // 整数,每页显示条数(实际返回条数)
"current": 1, // 整数,当前页码
"pages": 0 // 整数总页数total/size向上取整
}
}
```
#### 字段说明
| 字段名 | 类型 | 必填性 | 描述 |
| ------- | ------- | ------ | ----------------------------------------- |
| records | Array | 是 | 当前页数据列表(无数据时为空数组) |
| total | Integer | 是 | 总记录数(用于计算总页数/分页展示) |
| size | Integer | 是 | 每页请求条数与请求参数size一致 |
| current | Integer | 是 | 当前页码与请求参数current一致 |
| pages | Integer | 是 | 总页数计算公式Math.ceil(total/size) |
#### 分页响应示例
```json
{
"code": 200,
"message": "用户列表查询成功",
"timestamp": 1744238900000,
"data": {
"records": [
{"id": 1, "username": "admin", "role": "ADMIN"},
{"id": 2, "username": "user1", "role": "USER"}
],
"total": 20,
"size": 10,
"current": 1,
"pages": 2
}
}
```
### 3. 通用状态码规范
| 状态码 | 含义 | 核心场景 |
| ------ | -------------- | -------------------------------------- |
| 200 | 操作成功 | 所有接口正常响应 |
| 400 | 参数错误 | 请求参数缺失/格式错误/校验不通过 |
| 401 | 未授权 | token失效/未登录/令牌格式错误 |
| 403 | 禁止访问 | 账号被封禁/权限不足/IP受限 |
| 404 | 资源不存在 | 用户不存在/接口路径错误/数据记录不存在 |
| 409 | 资源冲突 | 用户名重复/手机号已绑定/操作重复提交 |
| 429 | 请求过于频繁 | 验证码发送频率超限/接口调用频率超限 |
| 500 | 服务器内部错误 | 服务端逻辑异常/第三方接口调用失败 |
## 三、接口命名规范
### 1. URL路径命名
- 路径全部使用**小写字母**,多个单词用连字符(-)分隔(如`/api/v1/auth/refresh-token`
- 路径使用名词而非动词RESTful规范`/api/v1/user`(用户资源)而非`/api/v1/getUser`
- 资源ID放在路径末尾`/api/v1/user/123`查询ID为123的用户
### 2. 响应字段命名
- 与请求参数一致,使用**小驼峰命名**
- 时间字段优先返回时间戳Integer毫秒如需返回格式化时间字段名加`Str`后缀(如`createTimeStr`
- 金额字段:以分为单位返回整数(避免浮点精度问题),字段名加`Cent`后缀(如`amountCent`)。
## 四、安全规范(必遵循)
### 1. 密码/敏感信息处理
- 敏感信息返回:手机号、身份证号等脱敏展示(如`138****8000`
- Token处理JWT Token不存储敏感信息如密码Redis存储的Token设置合理过期时间。
### 2. 接口频率限制
- 验证码接口同一手机号1分钟内最多1次1小时内最多5次
- 登录接口同一账号5分钟内连续失败5次临时锁定15分钟
- 通用接口单IP/单用户接口调用频率不超过QPS 10。
### 3. 数据校验
- 所有入参必须做合法性校验(格式、长度、范围);
## 五、异常响应示例
### 1. 参数错误400
```json
{
"code": 400,
"message": "手机号格式错误",
"timestamp": 1744238900000,
"data": null
}
```
### 2. 未授权401
```json
{
"code": 401,
"message": "Token已过期请重新登录",
"timestamp": 1744238900000,
"data": null
}
```
### 3. 服务器错误500
```json
{
"code": 500,
"message": "服务器内部错误,请稍后重试",
"timestamp": 1744238900000,
"data": null
}
```
## 总结
1. 所有接口必须遵循**统一响应格式**包含code/message/timestamp列表接口强制分页并使用指定分页结构
2. 接口命名、参数/字段命名需符合RESTful规范和小驼峰/小写连字符约定,保证可读性;
3. 安全规范是核心底线密码、Token、频率限制等规则必须严格执行避免安全漏洞
4. 本规范为基础准则,各业务模块可在本规范基础上补充模块专属规则,但不得与本规范冲突。

View File

@@ -0,0 +1,245 @@
# 接口开发规范
## 0. 与API文档规范对照说明
本开发规范与`Controller/api-doc-standard.md`互为补充:
- **api-dev-standard.md**:指导**代码实现**(如何编写符合规范的代码)
- **api-doc-standard.md**:定义**接口契约**(请求/响应格式标准)
---
## 1. 整体架构规范
### 1.1 分层结构
遵循CLAUDE.md定义的分层架构特别注意
- **Controller层**:业务逻辑的主要编写位置
- **Service层**仅包含接口定义和MyBatis Plus继承实现
- 接口:`I{Name}Service` 继承 `IService<Entity>`
- 实现:`{Name}ServiceImpl` 继承 `ServiceImpl<Mapper, Entity>`
- **业务逻辑不应下沉到Service层**Controller直接调用Service提供的CRUD方法
### 1.2 目录规范
- Controller按实体划分目录`Controller/{EntityName}Controller/`
- **Entity目录**`Entity/` 下直接存放实体类(**无需子目录**
- **Mapper目录**`Mapper/` 下直接存放Mapper接口**无需子目录**
- **Service目录**
- 接口:`Service/`
- 实现:`Service/Implements/`
- 示例:
- `User.java``Entity/User.java`
- `UserMapper.java``Mapper/UserMapper.java`
## 2. Controller编写规范
### 2.1 服务注入
```java
@Resource
private IUserService userService;
```
- **必须**使用`@Resource`而非`@Autowired`
- **不得**在Service层编写业务逻辑
### 2.2 响应处理(必须遵守)
**所有接口必须使用ResponseUtils统一响应格式**
对于 Controller 中的函数返回类型,统一使用 Object 类型,而不是具体的实体类。
如果需要编写具体 VO 实体类,在对应的 Controller 目录下创建 vo 文件夹并在其中定义。
```java
// 成功响应
return ResponseUtils.success(data);
// 失败响应(需指定状态码)
return ResponseUtils.fail(400, "用户名格式错误");
```
- **必须包含**
- `code`:按[api-doc-standard#状态码规范](Controller/api-doc-standard.md#三、通用状态码规范)设置
- `timestamp`:当前时间戳(`System.currentTimeMillis()`
- `data`:业务数据(列表接口需返回标准分页结构)
- **禁止**直接返回原始对象
### 2.3 分页处理
**必须使用MyBatis Plus原生Page对象**
```java
@GetMapping
public Object getAllUsers(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer limit) {
Page<User> pageObj = new Page<>(page, limit);
return ResponseUtils.success(userService.page(pageObj));
}
```
- **关键点**
- Page对象字段与API响应结构完全匹配records/total/size/current/pages
- **禁止**手动构建分页响应
- 分页参数**代码中命名为`page`/`limit`**
### 2.4 查询操作
**简单查询**直接使用Service方法
```java
@GetMapping("/{id}")
public Object getUserById(@PathVariable Long id) {
return ResponseUtils.success(userService.getById(id));
}
```
**复杂查询**使用QueryWrapper
```java
@GetMapping("/username/{username}")
public Object getUserByUsername(@PathVariable String username) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
return ResponseUtils.success(userService.getOne(wrapper));
}
```
- **注意**查询条件构建应在Controller层完成
## 3. Entity层规范
### 3.1 基础结构
```java
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
// ...其他字段
}
```
- **必须使用**
- Lombok注解`@Data` + `@NoArgsConstructor` + `@AllArgsConstructor`
- MyBatis Plus注解`@TableName` + `@TableId`
- **字段命名**:小驼峰命名(`createTime`而非`create_time`
### 3.2 时间字段处理
- **Entity层**:使用`LocalDateTime`类型(如`createTime`
## 4. Mapper层规范
### 4.1 基础结构
```java
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 仅允许MyBatis Plus内置方法自定义SQL需走Service层
}
```
- **必须**继承`BaseMapper<Entity>`
- **禁止**在Mapper接口中添加方法自定义SQL需通过Service层
## 5. Service层规范
### 5.1 接口定义
```java
public interface IUserService extends IService<User> {
// 仅允许扩展MyBatis Plus未提供的方法
}
```
- **必须**继承`IService<Entity>`
- **仅当需要**扩展MyBatis Plus未提供的方法时才添加方法
### 5.2 实现类
```java
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Resource
private UserMapper userMapper;
}
```
- **必须**继承`ServiceImpl<Mapper, Entity>`
- **禁止**在实现类中编写业务逻辑仅用于注入Mapper
## 6. 禁止行为清单
| 类型 | 错误示例 | 正确做法 |
| ------------ | ----------------------------------- | ------------------------------------ |
| 业务逻辑位置 | 在ServiceImpl中编写业务 | 所有业务逻辑放在Controller |
| 响应格式 | 直接返回`new ResponseEntity<>(...)` | 用ResponseUtils生成带timestamp的响应 |
| 分页参数 | 使用`offset`/`pageSize` | 统一用`page`/`limit`参数 |
| 状态码 | 所有错误返回500 | 按[api-doc-standard]返回400/401等 |
| 时间字段 | 直接返回LocalDateTime | 转换为时间戳或格式化字符串 |
## 7. 必须使用的工具类
| 工具类 | 用途 | 示例 |
| --------------- | -------------- | ------------------------------------- |
| `ResponseUtils` | 生成标准化响应 | `ResponseUtils.fail(400, "参数错误")` |
## 8. 接口设计原则
1. **RESTful规范**
- 路径使用**复数名词**`/users`而非`/user`
- **参数命名**:代码用`page`/`limit`,文档用`current`/`size`
2. **字段转换**
- Entity的`LocalDateTime` → 接口响应需转为时间戳
- 敏感字段如密码需在Controller层过滤`user.setPassword(null)`
3. **错误处理**
- 按[api-doc-standard#通用状态码规范](Controller/api-doc-standard.md#三、通用状态码规范)返回精确错误
- 示例:`return ResponseUtils.fail(409, "用户名已存在");`
## 9. 完整示例
```java
@RestController
@RequestMapping("/users")
public class UserController {
@Resource
private IUserService userService;
@GetMapping("/{id}")
public Object getUser(@PathVariable Long id) {
User user = userService.getById(id);
// 过滤敏感字段
user.setPassword(null);
// 转换时间格式
return ResponseUtils.success(user);
}
@GetMapping
public Object listUsers(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer limit) {
// 参数校验
if (limit > 100) {
return ResponseUtils.fail(400, "每页数量不能超过100");
}
Page<User> pageObj = new Page<>(page, limit);
Page<User> resultPage = userService.page(pageObj);
return ResponseUtils.success(resultPage);
}
}
```

View File

@@ -0,0 +1,29 @@
package icu.sunway.ai_spring_example.Entity;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("permission")
public class Permission {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String code;
private String description;
private Long parentId;
private Integer type; // 1: menu, 2: button, 3: api
private String path;
private Integer sort;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,26 @@
package icu.sunway.ai_spring_example.Entity;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("role")
public class Role {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String code;
private String description;
private Integer status; // 0: disabled, 1: enabled
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,20 @@
package icu.sunway.ai_spring_example.Entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("role_permission")
public class RolePermission {
@TableId(type = IdType.AUTO)
private Long id;
private Long roleId;
private Long permissionId;
}

View File

@@ -0,0 +1,29 @@
package icu.sunway.ai_spring_example.Entity;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private String email;
private String phone;
private String avatar;
private Integer status; // 0: disabled, 1: enabled
private LocalDateTime createTime;
private LocalDateTime updateTime;
private LocalDateTime lastLoginTime;
}

View File

@@ -1,7 +1,7 @@
package icu.sunway.ai_spring_example.Entity;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
@@ -11,9 +11,10 @@ import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("example_entity")
public class ExampleEntity {
@TableName("user_role")
public class UserRole {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private LocalDateTime createTime;
private Long userId;
private Long roleId;
}

View File

@@ -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;
}
}

View File

@@ -3,8 +3,8 @@ package icu.sunway.ai_spring_example.Mapper;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import icu.sunway.ai_spring_example.Entity.ExampleEntity;
import icu.sunway.ai_spring_example.Entity.Permission;
@Mapper
public interface ExampleEntityMapper extends BaseMapper<ExampleEntity> {
public interface PermissionMapper extends BaseMapper<Permission> {
}

View File

@@ -0,0 +1,10 @@
package icu.sunway.ai_spring_example.Mapper;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import icu.sunway.ai_spring_example.Entity.Role;
@Mapper
public interface RoleMapper extends BaseMapper<Role> {
}

View File

@@ -0,0 +1,10 @@
package icu.sunway.ai_spring_example.Mapper;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import icu.sunway.ai_spring_example.Entity.RolePermission;
@Mapper
public interface RolePermissionMapper extends BaseMapper<RolePermission> {
}

View File

@@ -0,0 +1,10 @@
package icu.sunway.ai_spring_example.Mapper;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import icu.sunway.ai_spring_example.Entity.User;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

View File

@@ -0,0 +1,10 @@
package icu.sunway.ai_spring_example.Mapper;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import icu.sunway.ai_spring_example.Entity.UserRole;
@Mapper
public interface UserRoleMapper extends BaseMapper<UserRole> {
}

View File

@@ -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);
}
}

View File

@@ -1,7 +0,0 @@
package icu.sunway.ai_spring_example.Service;
import com.baomidou.mybatisplus.extension.service.IService;
import icu.sunway.ai_spring_example.Entity.ExampleEntity;
public interface IExampleEntityService extends IService<ExampleEntity> {
}

View File

@@ -0,0 +1,7 @@
package icu.sunway.ai_spring_example.Service;
import com.baomidou.mybatisplus.extension.service.IService;
import icu.sunway.ai_spring_example.Entity.Permission;
public interface IPermissionService extends IService<Permission> {
}

View File

@@ -0,0 +1,7 @@
package icu.sunway.ai_spring_example.Service;
import com.baomidou.mybatisplus.extension.service.IService;
import icu.sunway.ai_spring_example.Entity.RolePermission;
public interface IRolePermissionService extends IService<RolePermission> {
}

View File

@@ -0,0 +1,7 @@
package icu.sunway.ai_spring_example.Service;
import com.baomidou.mybatisplus.extension.service.IService;
import icu.sunway.ai_spring_example.Entity.Role;
public interface IRoleService extends IService<Role> {
}

View File

@@ -0,0 +1,7 @@
package icu.sunway.ai_spring_example.Service;
import com.baomidou.mybatisplus.extension.service.IService;
import icu.sunway.ai_spring_example.Entity.UserRole;
public interface IUserRoleService extends IService<UserRole> {
}

View File

@@ -0,0 +1,7 @@
package icu.sunway.ai_spring_example.Service;
import com.baomidou.mybatisplus.extension.service.IService;
import icu.sunway.ai_spring_example.Entity.User;
public interface IUserService extends IService<User> {
}

View File

@@ -1,18 +0,0 @@
package icu.sunway.ai_spring_example.Service.Implements;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import icu.sunway.ai_spring_example.Entity.ExampleEntity;
import icu.sunway.ai_spring_example.Mapper.ExampleEntityMapper;
import icu.sunway.ai_spring_example.Service.IExampleEntityService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class ExampleEntityServiceImpl extends ServiceImpl<ExampleEntityMapper, ExampleEntity>
implements IExampleEntityService {
@Resource
private ExampleEntityMapper exampleEntityMapper;
}

View File

@@ -0,0 +1,16 @@
package icu.sunway.ai_spring_example.Service.Implements;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import icu.sunway.ai_spring_example.Entity.Permission;
import icu.sunway.ai_spring_example.Mapper.PermissionMapper;
import icu.sunway.ai_spring_example.Service.IPermissionService;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
@Service
public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permission> implements IPermissionService {
@Resource
private PermissionMapper permissionMapper;
}

View File

@@ -0,0 +1,17 @@
package icu.sunway.ai_spring_example.Service.Implements;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import icu.sunway.ai_spring_example.Entity.RolePermission;
import icu.sunway.ai_spring_example.Mapper.RolePermissionMapper;
import icu.sunway.ai_spring_example.Service.IRolePermissionService;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
@Service
public class RolePermissionServiceImpl extends ServiceImpl<RolePermissionMapper, RolePermission>
implements IRolePermissionService {
@Resource
private RolePermissionMapper rolePermissionMapper;
}

View File

@@ -0,0 +1,16 @@
package icu.sunway.ai_spring_example.Service.Implements;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import icu.sunway.ai_spring_example.Entity.Role;
import icu.sunway.ai_spring_example.Mapper.RoleMapper;
import icu.sunway.ai_spring_example.Service.IRoleService;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IRoleService {
@Resource
private RoleMapper roleMapper;
}

View File

@@ -0,0 +1,16 @@
package icu.sunway.ai_spring_example.Service.Implements;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import icu.sunway.ai_spring_example.Entity.UserRole;
import icu.sunway.ai_spring_example.Mapper.UserRoleMapper;
import icu.sunway.ai_spring_example.Service.IUserRoleService;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
@Service
public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> implements IUserRoleService {
@Resource
private UserRoleMapper userRoleMapper;
}

View File

@@ -0,0 +1,16 @@
package icu.sunway.ai_spring_example.Service.Implements;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import icu.sunway.ai_spring_example.Entity.User;
import icu.sunway.ai_spring_example.Mapper.UserMapper;
import icu.sunway.ai_spring_example.Service.IUserService;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Resource
private UserMapper userMapper;
}

View File

@@ -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;
}
}

View File

@@ -145,19 +145,6 @@ public class JsonUtils {
return toObject(json, Map.class, keyClazz, valueClazz);
}
/**
* 将对象转换为Map
*
* @param obj 对象
* @return Map
*/
public static Map<String, Object> toMap(Object obj) {
if (obj == null) {
return null;
}
return objectMapper.convertValue(obj, Map.class);
}
/**
* 验证JSON字符串是否有效
*

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -1,111 +0,0 @@
package icu.sunway.ai_spring_example.Utils;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 分页工具类
*/
public class PageUtils {
/**
* 默认页码
*/
public static final Integer DEFAULT_PAGE = 1;
/**
* 默认每页数量
*/
public static final Integer DEFAULT_LIMIT = 10;
/**
* 最大每页数量
*/
public static final Integer MAX_LIMIT = 100;
/**
* 分页结果类
*
* @param <T> 数据类型
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class PaginationResult<T> {
private Integer currentPage;
private Integer pageSize;
private Long totalItems;
private Integer totalPages;
private List<T> list;
}
/**
* 验证和调整分页参数
*
* @param page 页码
* @param limit 每页数量
* @return 调整后的分页参数数组 [page, limit]
*/
public static Integer[] validatePageParams(Integer page, Integer limit) {
// 验证页码
if (page == null || page < 1) {
page = DEFAULT_PAGE;
}
// 验证每页数量
if (limit == null || limit < 1 || limit > MAX_LIMIT) {
limit = DEFAULT_LIMIT;
}
return new Integer[] { page, limit };
}
/**
* 创建Page对象
*
* @param page 页码
* @param limit 每页数量
* @return Page对象
*/
public static <T> Page<T> createPage(Integer page, Integer limit) {
Integer[] params = validatePageParams(page, limit);
return new Page<>(params[0], params[1]);
}
/**
* 将MyBatis Plus的Page结果转换为分页结果
*
* @param pageResult MyBatis Plus的Page结果
* @return 分页结果
*/
public static <T> PaginationResult<T> createPaginationResult(IPage<T> pageResult) {
return new PaginationResult<>(
(int) pageResult.getCurrent(),
(int) pageResult.getSize(),
pageResult.getTotal(),
(int) pageResult.getPages(),
pageResult.getRecords());
}
/**
* 创建空的分页结果
*
* @param page 页码
* @param limit 每页数量
* @return 空的分页结果
*/
public static <T> PaginationResult<T> createEmptyPaginationResult(Integer page, Integer limit) {
Integer[] params = validatePageParams(page, limit);
return new PaginationResult<>(
params[0],
params[1],
0L,
0,
List.of());
}
}