一、项目概述
1. 项目目标
构建一个完整的用户管理系统,整合所有学习过的知识点:
- Spring Boot 快速启动
- JPA 数据持久化
- RESTful API 开发
- 数据验证与异常处理
- 前后端联调与跨域配置
2. 功能清单
| 功能 | HTTP 方法 | URL | 说明 |
|---|---|---|---|
| 获取所有用户 | GET | /api/users | 分页查询用户列表 |
| 获取单个用户 | GET | /api/users/{id} | 根据 ID 查询用户 |
| 创建用户 | POST | /api/users | 创建新用户 |
| 更新用户 | PUT | /api/users/{id} | 更新用户信息 |
| 删除用户 | DELETE | /api/users/{id} | 删除用户 |
| 搜索用户 | GET | /api/users/search | 根据姓名搜索用户 |
二、项目结构
my-app/
├── pom.xml
└── src/
└── main/
├── java/com/example/myapp/
│ ├── MyApplication.java # 启动类
│ ├── config/
│ │ └── CorsConfig.java # CORS 配置
│ ├── controller/
│ │ └── UserController.java # 用户控制器
│ ├── dto/
│ │ ├── CreateUserDTO.java # 创建用户 DTO
│ │ ├── UpdateUserDTO.java # 更新用户 DTO
│ │ └── UserDTO.java # 用户响应 DTO
│ ├── exception/
│ │ ├── GlobalExceptionHandler.java # 全局异常处理
│ │ └── ResourceNotFoundException.java # 资源未找到异常
│ ├── model/
│ │ └── User.java # 用户实体
│ ├── repository/
│ │ └── UserRepository.java # 用户仓库
│ └── service/
│ └── UserService.java # 用户服务
└── resources/
└── application.properties # 配置文件
三、完整代码实现
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0</version>
<name>my-app</name>
<description>Spring Boot CRUD 实战项目</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. application.properties
# 服务器配置
server.port=8080
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA 配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
# 日志配置
logging.level.org.springframework.web=INFO
logging.level.com.example.myapp=DEBUG
3. MyApplication.java(启动类)
package com.example.myapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
4. CorsConfig.java(CORS 配置)
package com.example.myapp.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins(
"http://localhost:3000", // React
"http://localhost:5173" // Vue
)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
5. User.java(实体类)
package com.example.myapp.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "姓名不能为空")
@Size(min = 2, max = 50, message = "姓名长度必须在 2-50 个字符之间")
@Column(nullable = false)
private String name;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
@Column(nullable = false, unique = true)
private String email;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 100, message = "密码长度必须在 6-100 个字符之间")
@Column(nullable = false)
private String password;
@Size(max = 20, message = "电话号码长度不能超过 20 个字符")
private String phone;
@Column(nullable = false)
private Boolean active = true;
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(nullable = false)
private LocalDateTime updatedAt;
}
6. UserRepository.java(仓库接口)
package com.example.myapp.repository;
import com.example.myapp.model.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 根据邮箱查询用户
Optional<User> findByEmail(String email);
// 检查邮箱是否存在
boolean existsByEmail(String email);
// 根据姓名模糊查询用户
List<User> findByNameContainingIgnoreCase(String name);
// 根据姓名模糊查询(分页)
Page<User> findByNameContainingIgnoreCase(String name, Pageable pageable);
// 查询活跃用户
List<User> findByActiveTrue();
}
7. UserDTO.java(响应 DTO)
package com.example.myapp.dto;
import com.example.myapp.model.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
private Long id;
private String name;
private String email;
private String phone;
private Boolean active;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// 从 User 转换为 UserDTO
public static UserDTO fromEntity(User user) {
return new UserDTO(
user.getId(),
user.getName(),
user.getEmail(),
user.getPhone(),
user.getActive(),
user.getCreatedAt(),
user.getUpdatedAt()
);
}
}
8. CreateUserDTO.java(创建用户 DTO)
package com.example.myapp.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateUserDTO {
@NotBlank(message = "姓名不能为空")
@Size(min = 2, max = 50, message = "姓名长度必须在 2-50 个字符之间")
private String name;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 100, message = "密码长度必须在 6-100 个字符之间")
private String password;
@Size(max = 20, message = "电话号码长度不能超过 20 个字符")
private String phone;
}
9. UpdateUserDTO.java(更新用户 DTO)
package com.example.myapp.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UpdateUserDTO {
@Size(min = 2, max = 50, message = "姓名长度必须在 2-50 个字符之间")
private String name;
@Email(message = "邮箱格式不正确")
private String email;
@Size(max = 20, message = "电话号码长度不能超过 20 个字符")
private String phone;
private Boolean active;
}
10. ResourceNotFoundException.java(资源未找到异常)
package com.example.myapp.exception;
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
public ResourceNotFoundException(String resource, Long id) {
super(String.format("%s not found with id: %d", resource, id));
}
}
11. GlobalExceptionHandler.java(全局异常处理)
package com.example.myapp.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理资源未找到异常
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFoundException(
ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
LocalDateTime.now()
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
// 处理数据验证异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ValidationErrorResponse> handleValidationException(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach(error -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
ValidationErrorResponse error = new ValidationErrorResponse(
HttpStatus.BAD_REQUEST.value(),
"数据验证失败",
LocalDateTime.now(),
errors
);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
// 处理其他异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
ex.getMessage(),
LocalDateTime.now()
);
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
// 错误响应 DTO
record ErrorResponse(int status, String message, LocalDateTime timestamp) {}
// 验证错误响应 DTO
record ValidationErrorResponse(
int status,
String message,
LocalDateTime timestamp,
Map<String, String> errors
) {}
}
12. UserService.java(用户服务)
package com.example.myapp.service;
import com.example.myapp.dto.CreateUserDTO;
import com.example.myapp.dto.UpdateUserDTO;
import com.example.myapp.dto.UserDTO;
import com.example.myapp.exception.ResourceNotFoundException;
import com.example.myapp.model.User;
import com.example.myapp.repository.UserRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 获取所有用户(分页)
public Page<UserDTO> findAll(Pageable pageable) {
return userRepository.findAll(pageable)
.map(UserDTO::fromEntity);
}
// 获取所有用户(列表)
public List<UserDTO> findAll() {
return userRepository.findAll().stream()
.map(UserDTO::fromEntity)
.collect(Collectors.toList());
}
// 根据 ID 获取用户
public UserDTO findById(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User", id));
return UserDTO.fromEntity(user);
}
// 创建用户
public UserDTO create(CreateUserDTO userDTO) {
// 检查邮箱是否已存在
if (userRepository.existsByEmail(userDTO.getEmail())) {
throw new IllegalArgumentException("邮箱已被使用: " + userDTO.getEmail());
}
// 转换 DTO 为 Entity
User user = new User();
user.setName(userDTO.getName());
user.setEmail(userDTO.getEmail());
user.setPassword(userDTO.getPassword());
user.setPhone(userDTO.getPhone());
// 保存用户
User savedUser = userRepository.save(user);
return UserDTO.fromEntity(savedUser);
}
// 更新用户
public UserDTO update(Long id, UpdateUserDTO userDTO) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User", id));
// 检查邮箱是否被其他用户使用
if (userDTO.getEmail() != null &&
!userDTO.getEmail().equals(user.getEmail()) &&
userRepository.existsByEmail(userDTO.getEmail())) {
throw new IllegalArgumentException("邮箱已被使用: " + userDTO.getEmail());
}
// 更新用户信息
if (userDTO.getName() != null) {
user.setName(userDTO.getName());
}
if (userDTO.getEmail() != null) {
user.setEmail(userDTO.getEmail());
}
if (userDTO.getPhone() != null) {
user.setPhone(userDTO.getPhone());
}
if (userDTO.getActive() != null) {
user.setActive(userDTO.getActive());
}
User updatedUser = userRepository.save(user);
return UserDTO.fromEntity(updatedUser);
}
// 删除用户
public void delete(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User", id));
userRepository.delete(user);
}
// 根据姓名搜索用户
public List<UserDTO> searchByName(String name) {
return userRepository.findByNameContainingIgnoreCase(name).stream()
.map(UserDTO::fromEntity)
.collect(Collectors.toList());
}
}
13. UserController.java(用户控制器)
package com.example.myapp.controller;
import com.example.myapp.dto.CreateUserDTO;
import com.example.myapp.dto.UpdateUserDTO;
import com.example.myapp.dto.UserDTO;
import com.example.myapp.service.UserService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
// 获取所有用户(分页)
@GetMapping
public ResponseEntity<Page<UserDTO>> findAll(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id") String sortBy,
@RequestParam(defaultValue = "asc") String sortDir
) {
Sort sort = sortDir.equalsIgnoreCase("desc")
? Sort.by(sortBy).descending()
: Sort.by(sortBy).ascending();
Pageable pageable = PageRequest.of(page, size, sort);
Page<UserDTO> users = userService.findAll(pageable);
return ResponseEntity.ok(users);
}
// 根据 ID 获取用户
@GetMapping("/{id}")
public ResponseEntity<UserDTO> findById(@PathVariable Long id) {
UserDTO user = userService.findById(id);
return ResponseEntity.ok(user);
}
// 创建用户
@PostMapping
public ResponseEntity<UserDTO> create(@Valid @RequestBody CreateUserDTO userDTO) {
UserDTO createdUser = userService.create(userDTO);
return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
}
// 更新用户
@PutMapping("/{id}")
public ResponseEntity<UserDTO> update(
@PathVariable Long id,
@Valid @RequestBody UpdateUserDTO userDTO
) {
UserDTO updatedUser = userService.update(id, userDTO);
return ResponseEntity.ok(updatedUser);
}
// 删除用户
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
// 根据姓名搜索用户
@GetMapping("/search")
public ResponseEntity<List<UserDTO>> searchByName(@RequestParam String name) {
List<UserDTO> users = userService.searchByName(name);
return ResponseEntity.ok(users);
}
}
四、API 测试
1. 创建用户
请求:
POST http://localhost:8080/api/users
Content-Type: application/json
{
"name": "张三",
"email": "zhangsan@example.com",
"password": "123456",
"phone": "13800138000"
}
响应:
{
"id": 1,
"name": "张三",
"email": "zhangsan@example.com",
"phone": "13800138000",
"active": true,
"createdAt": "2024-03-26T15:30:00",
"updatedAt": "2024-03-26T15:30:00"
}
2. 获取所有用户(分页)
请求:
GET http://localhost:8080/api/users?page=0&size=10&sortBy=id&sortDir=desc
响应:
{
"content": [
{
"id": 5,
"name": "王五",
"email": "wangwu@example.com",
"phone": "13800338000",
"active": true,
"createdAt": "2024-03-26T15:35:00",
"updatedAt": "2024-03-26T15:35:00"
}
],
"pageable": {
"pageNumber": 0,
"pageSize": 10,
"sort": {
"empty": false,
"sorted": true,
"unsorted": false
}
},
"totalPages": 1,
"totalElements": 1,
"last": true,
"numberOfElements": 1,
"first": true,
"empty": false
}
3. 根据 ID 获取用户
请求:
GET http://localhost:8080/api/users/1
响应:
{
"id": 1,
"name": "张三",
"email": "zhangsan@example.com",
"phone": "13800138000",
"active": true,
"createdAt": "2024-03-26T15:30:00",
"updatedAt": "2024-03-26T15:30:00"
}
4. 更新用户
请求:
PUT http://localhost:8080/api/users/1
Content-Type: application/json
{
"name": "张三(已更新)",
"phone": "13900139000"
}
响应:
{
"id": 1,
"name": "张三(已更新)",
"email": "zhangsan@example.com",
"phone": "13900139000",
"active": true,
"createdAt": "2024-03-26T15:30:00",
"updatedAt": "2024-03-26T15:40:00"
}
5. 删除用户
请求:
DELETE http://localhost:8080/api/users/1
响应:
HTTP 204 No Content
6. 搜索用户
请求:
GET http://localhost:8080/api/users/search?name=张
响应:
[
{
"id": 2,
"name": "张三",
"email": "zhangsan@example.com",
"phone": "13800138000",
"active": true,
"createdAt": "2024-03-26T15:30:00",
"updatedAt": "2024-03-26T15:30:00"
}
]
7. 数据验证失败
请求:
POST http://localhost:8080/api/users
Content-Type: application/json
{
"name": "张",
"email": "invalid-email",
"password": "123"
}
响应:
{
"status": 400,
"message": "数据验证失败",
"timestamp": "2024-03-26T15:45:00",
"errors": {
"name": "姓名长度必须在 2-50 个字符之间",
"email": "邮箱格式不正确",
"password": "密码长度必须在 6-100 个字符之间"
}
}
8. 资源未找到
请求:
GET http://localhost:8080/api/users/999
响应:
{
"status": 404,
"message": "User not found with id: 999",
"timestamp": "2024-03-26T15:50:00"
}
五、前端对接示例
React 示例
UserService.js:
import axios from 'axios';
const API_BASE_URL = 'http://localhost:8080/api/users';
export default {
// 获取所有用户
async findAll(page = 0, size = 10, sortBy = 'id', sortDir = 'asc') {
const response = await axios.get(API_BASE_URL, {
params: { page, size, sortBy, sortDir }
});
return response.data;
},
// 根据 ID 获取用户
async findById(id) {
const response = await axios.get(`${API_BASE_URL}/${id}`);
return response.data;
},
// 创建用户
async create(userData) {
const response = await axios.post(API_BASE_URL, userData);
return response.data;
},
// 更新用户
async update(id, userData) {
const response = await axios.put(`${API_BASE_URL}/${id}`, userData);
return response.data;
},
// 删除用户
async delete(id) {
await axios.delete(`${API_BASE_URL}/${id}`);
},
// 搜索用户
async searchByName(name) {
const response = await axios.get(`${API_BASE_URL}/search`, {
params: { name }
});
return response.data;
}
};
UserList.js:
import { useState, useEffect } from 'react';
import userService from './UserService';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [page, setPage] = useState(0);
const [totalPages, setTotalPages] = useState(0);
const fetchUsers = async () => {
try {
setLoading(true);
const data = await userService.findAll(page, 10);
setUsers(data.content);
setTotalPages(data.totalPages);
setError(null);
} catch (err) {
setError('获取用户列表失败');
console.error(err);
} finally {
setLoading(false);
}
};
const handleDelete = async (id) => {
if (window.confirm('确定要删除该用户吗?')) {
try {
await userService.delete(id);
fetchUsers();
alert('删除成功!');
} catch (err) {
alert('删除失败!');
console.error(err);
}
}
};
useEffect(() => {
fetchUsers();
}, [page]);
if (loading) return <div>加载中...</div>;
if (error) return <div>{error}</div>;
return (
<div>
<h2>用户列表</h2>
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
<th>电话</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.id}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.email}</td>
<td>{user.phone}</td>
<td>{user.active ? '活跃' : '禁用'}</td>
<td>
<button onClick={() => handleDelete(user.id)}>
删除
</button>
</td>
</tr>
))}
</tbody>
</table>
<div>
<button
disabled={page === 0}
onClick={() => setPage(page - 1)}
>
上一页
</button>
<span>第 {page + 1} / {totalPages} 页</span>
<button
disabled={page >= totalPages - 1}
onClick={() => setPage(page + 1)}
>
下一页
</button>
</div>
</div>
);
}
export default UserList;
六、运行项目
1. 创建数据库
CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
2. 启动项目
mvn spring-boot:run
3. 访问 API
使用 Postman、Apifox 或浏览器测试 API。
七、总结
| 组件 | 作用 |
|---|---|
| Controller | 接收 HTTP 请求,返回响应 |
| Service | 业务逻辑处理 |
| Repository | 数据访问层 |
| Entity | 数据库表映射 |
| DTO | 数据传输对象 |
| Exception | 异常处理 |
| CorsConfig | 跨域配置 |