Spring Boot与Web开发
Spring Boot为Web开发提供了强大的支持,包括RESTful API开发和传统的Web页面开发。本章将重点介绍RESTful开发,同时也会涵盖Thymeleaf模板引擎的使用。
Web开发概述
RESTful vs Thymeleaf
RESTful API开发: - 专注于数据交换 - 前后端分离架构 - 返回JSON/XML数据 - 适合现代单页应用(SPA)和移动应用
Thymeleaf模板开发: - 服务端渲染 - 传统的MVC模式 - 返回HTML页面 - 适合传统Web应用
RESTful API开发
什么是RESTful
REST(Representational State Transfer)是一种架构风格,具有以下特点:
- 资源(Resource):URI标识资源
- 表示(Representation):JSON、XML等格式
- 状态转移(State Transfer):通过HTTP方法操作资源
- 无状态(Stateless):每个请求包含所有必要信息
HTTP方法与CRUD操作
HTTP方法 | 操作 | 描述 | 示例URL |
---|---|---|---|
GET | Read | 获取资源 | GET /users/1 |
POST | Create | 创建资源 | POST /users |
PUT | Update | 更新整个资源 | PUT /users/1 |
PATCH | Update | 部分更新资源 | PATCH /users/1 |
DELETE | Delete | 删除资源 | DELETE /users/1 |
创建RESTful控制器
基础用户控制器
@RestController
@RequestMapping("/api/v1/users")
@Validated
@Slf4j
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
// 获取所有用户
@GetMapping
public ResponseEntity<List<UserDTO>> getAllUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id") String sortBy) {
log.info("Fetching users: page={}, size={}, sortBy={}", page, size, sortBy);
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
Page<UserDTO> users = userService.findAll(pageable);
return ResponseEntity.ok()
.header("X-Total-Count", String.valueOf(users.getTotalElements()))
.body(users.getContent());
}
// 根据ID获取用户
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUserById(@PathVariable @Min(1) Long id) {
log.info("Fetching user with id: {}", id);
UserDTO user = userService.findById(id);
return ResponseEntity.ok(user);
}
// 创建用户
@PostMapping
public ResponseEntity<UserDTO> createUser(@Valid @RequestBody CreateUserRequest request) {
log.info("Creating user: {}", request.getUsername());
UserDTO createdUser = userService.create(request);
return ResponseEntity.status(HttpStatus.CREATED)
.location(URI.create("/api/v1/users/" + createdUser.getId()))
.body(createdUser);
}
// 更新用户
@PutMapping("/{id}")
public ResponseEntity<UserDTO> updateUser(
@PathVariable @Min(1) Long id,
@Valid @RequestBody UpdateUserRequest request) {
log.info("Updating user with id: {}", id);
UserDTO updatedUser = userService.update(id, request);
return ResponseEntity.ok(updatedUser);
}
// 部分更新用户
@PatchMapping("/{id}")
public ResponseEntity<UserDTO> patchUser(
@PathVariable @Min(1) Long id,
@RequestBody Map<String, Object> updates) {
log.info("Partially updating user with id: {}", id);
UserDTO updatedUser = userService.patch(id, updates);
return ResponseEntity.ok(updatedUser);
}
// 删除用户
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable @Min(1) Long id) {
log.info("Deleting user with id: {}", id);
userService.delete(id);
return ResponseEntity.noContent().build();
}
}
DTO和请求对象
UserDTO
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
private Long id;
private String username;
private String email;
private String fullName;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private boolean active;
}
CreateUserRequest
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateUserRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20字符之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
private String username;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotBlank(message = "密码不能为空")
@Size(min = 8, max = 20, message = "密码长度必须在8-20字符之间")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d@$!%*?&]{8,}$",
message = "密码必须包含至少一个大写字母、一个小写字母和一个数字")
private String password;
@NotBlank(message = "全名不能为空")
@Size(max = 50, message = "全名长度不能超过50字符")
private String fullName;
}
UpdateUserRequest
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UpdateUserRequest {
@Email(message = "邮箱格式不正确")
private String email;
@Size(max = 50, message = "全名长度不能超过50字符")
private String fullName;
private Boolean active;
}
响应格式标准化
统一响应格式
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
private long timestamp;
private String path;
public static <T> ApiResponse<T> success(T data) {
return ApiResponse.<T>builder()
.success(true)
.message("操作成功")
.data(data)
.timestamp(System.currentTimeMillis())
.build();
}
public static <T> ApiResponse<T> success(T data, String message) {
return ApiResponse.<T>builder()
.success(true)
.message(message)
.data(data)
.timestamp(System.currentTimeMillis())
.build();
}
public static <T> ApiResponse<T> error(String message) {
return ApiResponse.<T>builder()
.success(false)
.message(message)
.timestamp(System.currentTimeMillis())
.build();
}
}
响应包装器
@RestControllerAdvice
public class ResponseWrapper implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return !returnType.getDeclaringClass().isAnnotationPresent(NoResponseWrapper.class) &&
!returnType.hasMethodAnnotation(NoResponseWrapper.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof ApiResponse) {
return body;
}
return ApiResponse.success(body);
}
}
// 标记注解,用于跳过响应包装
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NoResponseWrapper {
}
内容协商
Spring Boot支持多种内容格式的自动协商:
@RestController
@RequestMapping("/api/v1/products")
public class ProductController {
// 支持JSON和XML格式
@GetMapping(value = "/{id}", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
Product product = productService.findById(id);
return ResponseEntity.ok(product);
}
// 根据Accept头返回不同格式
@GetMapping
public ResponseEntity<List<Product>> getProducts(HttpServletRequest request) {
List<Product> products = productService.findAll();
String acceptHeader = request.getHeader("Accept");
if (acceptHeader != null && acceptHeader.contains("application/xml")) {
// 返回XML格式
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_XML)
.body(products);
} else {
// 默认返回JSON格式
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(products);
}
}
}
跨域配置(CORS)
全局CORS配置
@Configuration
public class CorsConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// 允许的源
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
// 允许的HTTP方法
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
// 允许的头部
configuration.setAllowedHeaders(Arrays.asList("*"));
// 允许发送凭证
configuration.setAllowCredentials(true);
// 预检请求的缓存时间
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);
return source;
}
}
注解方式配置CORS
@RestController
@RequestMapping("/api/v1/users")
@CrossOrigin(origins = {"http://localhost:3000", "https://mydomain.com"},
methods = {RequestMethod.GET, RequestMethod.POST},
maxAge = 3600)
public class UserController {
// 控制器方法
}
API文档集成
集成Swagger/OpenAPI
添加依赖:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.7.0</version>
</dependency>
配置OpenAPI:
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("用户管理API")
.version("1.0.0")
.description("用户管理系统的RESTful API文档")
.contact(new Contact()
.name("开发团队")
.email("dev@example.com")
.url("https://example.com"))
.license(new License()
.name("Apache 2.0")
.url("http://www.apache.org/licenses/LICENSE-2.0.html")))
.addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
.components(new Components()
.addSecuritySchemes("bearerAuth", new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
}
在控制器中添加API文档注解:
@RestController
@RequestMapping("/api/v1/users")
@Tag(name = "用户管理", description = "用户相关的API接口")
public class UserController {
@GetMapping("/{id}")
@Operation(summary = "根据ID获取用户", description = "通过用户ID获取用户详细信息")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "成功获取用户信息",
content = @Content(schema = @Schema(implementation = UserDTO.class))),
@ApiResponse(responseCode = "404", description = "用户不存在"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public ResponseEntity<UserDTO> getUserById(
@Parameter(description = "用户ID", required = true) @PathVariable Long id) {
UserDTO user = userService.findById(id);
return ResponseEntity.ok(user);
}
}
Thymeleaf模板引擎
Thymeleaf简介
Thymeleaf是一个现代的服务端Java模板引擎,具有以下特点:
- 自然模板:Thymeleaf模板本身就是有效的HTML
- 丰富的表达式:支持变量表达式、消息表达式等
- Spring集成:与Spring Boot无缝集成
- 国际化支持:内置国际化功能
添加Thymeleaf依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Thymeleaf配置
spring:
thymeleaf:
# 模板位置
prefix: classpath:/templates/
# 模板后缀
suffix: .html
# 模板编码
encoding: UTF-8
# 内容类型
servlet:
content-type: text/html
# 开发时禁用缓存
cache: false
# 模板模式
mode: HTML
创建Web控制器
@Controller
@RequestMapping("/web")
@Slf4j
public class WebController {
private final UserService userService;
public WebController(UserService userService) {
this.userService = userService;
}
// 首页
@GetMapping("/")
public String index(Model model) {
model.addAttribute("title", "欢迎使用用户管理系统");
model.addAttribute("message", "这是一个基于Spring Boot和Thymeleaf的Web应用");
return "index";
}
// 用户列表页面
@GetMapping("/users")
public String userList(Model model,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
Pageable pageable = PageRequest.of(page, size);
Page<UserDTO> users = userService.findAll(pageable);
model.addAttribute("users", users.getContent());
model.addAttribute("currentPage", page);
model.addAttribute("totalPages", users.getTotalPages());
model.addAttribute("totalElements", users.getTotalElements());
return "users/list";
}
// 用户详情页面
@GetMapping("/users/{id}")
public String userDetail(@PathVariable Long id, Model model) {
try {
UserDTO user = userService.findById(id);
model.addAttribute("user", user);
return "users/detail";
} catch (UserNotFoundException e) {
model.addAttribute("error", "用户不存在");
return "error/404";
}
}
// 显示创建用户表单
@GetMapping("/users/new")
public String showCreateUserForm(Model model) {
model.addAttribute("user", new CreateUserRequest());
return "users/create";
}
// 处理创建用户表单提交
@PostMapping("/users")
public String createUser(@Valid @ModelAttribute("user") CreateUserRequest request,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
return "users/create";
}
try {
UserDTO createdUser = userService.create(request);
redirectAttributes.addFlashAttribute("message", "用户创建成功");
return "redirect:/web/users/" + createdUser.getId();
} catch (Exception e) {
model.addAttribute("error", "创建用户失败:" + e.getMessage());
return "users/create";
}
}
// 显示编辑用户表单
@GetMapping("/users/{id}/edit")
public String showEditUserForm(@PathVariable Long id, Model model) {
try {
UserDTO user = userService.findById(id);
model.addAttribute("user", user);
return "users/edit";
} catch (UserNotFoundException e) {
model.addAttribute("error", "用户不存在");
return "error/404";
}
}
// 处理编辑用户表单提交
@PostMapping("/users/{id}")
public String updateUser(@PathVariable Long id,
@Valid @ModelAttribute("user") UpdateUserRequest request,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
UserDTO user = userService.findById(id);
model.addAttribute("user", user);
return "users/edit";
}
try {
userService.update(id, request);
redirectAttributes.addFlashAttribute("message", "用户更新成功");
return "redirect:/web/users/" + id;
} catch (Exception e) {
model.addAttribute("error", "更新用户失败:" + e.getMessage());
return "users/edit";
}
}
// 删除用户
@PostMapping("/users/{id}/delete")
public String deleteUser(@PathVariable Long id, RedirectAttributes redirectAttributes) {
try {
userService.delete(id);
redirectAttributes.addFlashAttribute("message", "用户删除成功");
} catch (Exception e) {
redirectAttributes.addFlashAttribute("error", "删除用户失败:" + e.getMessage());
}
return "redirect:/web/users";
}
}
Thymeleaf模板示例
布局模板(layout.html)
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title th:text="${title} ?: '用户管理系统'">用户管理系统</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- 自定义CSS -->
<link th:href="@{/css/style.css}" rel="stylesheet">
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" th:href="@{/web/}">用户管理系统</a>
<div class="navbar-nav">
<a class="nav-link" th:href="@{/web/}">首页</a>
<a class="nav-link" th:href="@{/web/users}">用户列表</a>
<a class="nav-link" th:href="@{/web/users/new}">创建用户</a>
</div>
</div>
</nav>
<!-- 主要内容 -->
<div class="container mt-4">
<!-- 消息提示 -->
<div th:if="${message}" class="alert alert-success alert-dismissible fade show" role="alert">
<span th:text="${message}"></span>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<div th:if="${error}" class="alert alert-danger alert-dismissible fade show" role="alert">
<span th:text="${error}"></span>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<!-- 页面内容 -->
<div th:replace="${content}">
页面内容将在这里显示
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
用户列表页面(users/list.html)
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户列表</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
<div class="row">
<div class="col-md-12">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2>用户列表</h2>
<a th:href="@{/web/users/new}" class="btn btn-primary">
<i class="fas fa-plus"></i> 创建用户
</a>
</div>
<!-- 用户表格 -->
<div class="card">
<div class="card-body">
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>邮箱</th>
<th>全名</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td th:text="${user.id}">1</td>
<td th:text="${user.username}">admin</td>
<td th:text="${user.email}">admin@example.com</td>
<td th:text="${user.fullName}">管理员</td>
<td>
<span th:if="${user.active}" class="badge bg-success">激活</span>
<span th:unless="${user.active}" class="badge bg-secondary">禁用</span>
</td>
<td th:text="${#temporals.format(user.createdAt, 'yyyy-MM-dd HH:mm')}">2023-12-01 10:30</td>
<td>
<a th:href="@{/web/users/{id}(id=${user.id})}" class="btn btn-sm btn-outline-primary">查看</a>
<a th:href="@{/web/users/{id}/edit(id=${user.id})}" class="btn btn-sm btn-outline-warning">编辑</a>
<form th:action="@{/web/users/{id}/delete(id=${user.id})}" method="post" style="display: inline;">
<button type="submit" class="btn btn-sm btn-outline-danger"
onclick="return confirm('确定要删除这个用户吗?')">删除</button>
</form>
</td>
</tr>
<tr th:if="${#lists.isEmpty(users)}">
<td colspan="7" class="text-center">暂无用户数据</td>
</tr>
</tbody>
</table>
<!-- 分页 -->
<nav th:if="${totalPages > 1}">
<ul class="pagination justify-content-center">
<li class="page-item" th:classappend="${currentPage == 0} ? 'disabled'">
<a class="page-link" th:href="@{/web/users(page=${currentPage - 1})}">上一页</a>
</li>
<li th:each="pageNum : ${#numbers.sequence(0, totalPages - 1)}"
class="page-item" th:classappend="${pageNum == currentPage} ? 'active'">
<a class="page-link" th:href="@{/web/users(page=${pageNum})}" th:text="${pageNum + 1}">1</a>
</li>
<li class="page-item" th:classappend="${currentPage == totalPages - 1} ? 'disabled'">
<a class="page-link" th:href="@{/web/users(page=${currentPage + 1})}">下一页</a>
</li>
</ul>
</nav>
<div class="text-center mt-3">
<small class="text-muted">
共 <span th:text="${totalElements}">0</span> 条记录,
第 <span th:text="${currentPage + 1}">1</span> 页,
共 <span th:text="${totalPages}">1</span> 页
</small>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
用户表单页面(users/create.html)
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>创建用户</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h4>创建用户</h4>
</div>
<div class="card-body">
<form th:action="@{/web/users}" th:object="${user}" method="post">
<!-- 用户名 -->
<div class="mb-3">
<label for="username" class="form-label">用户名 *</label>
<input type="text" class="form-control"
th:field="*{username}"
th:classappend="${#fields.hasErrors('username')} ? 'is-invalid'"
id="username" placeholder="请输入用户名">
<div class="invalid-feedback" th:if="${#fields.hasErrors('username')}"
th:errors="*{username}">用户名错误信息</div>
</div>
<!-- 邮箱 -->
<div class="mb-3">
<label for="email" class="form-label">邮箱 *</label>
<input type="email" class="form-control"
th:field="*{email}"
th:classappend="${#fields.hasErrors('email')} ? 'is-invalid'"
id="email" placeholder="请输入邮箱">
<div class="invalid-feedback" th:if="${#fields.hasErrors('email')}"
th:errors="*{email}">邮箱错误信息</div>
</div>
<!-- 密码 -->
<div class="mb-3">
<label for="password" class="form-label">密码 *</label>
<input type="password" class="form-control"
th:field="*{password}"
th:classappend="${#fields.hasErrors('password')} ? 'is-invalid'"
id="password" placeholder="请输入密码">
<div class="invalid-feedback" th:if="${#fields.hasErrors('password')}"
th:errors="*{password}">密码错误信息</div>
<div class="form-text">密码必须包含至少一个大写字母、一个小写字母和一个数字,长度8-20字符</div>
</div>
<!-- 全名 -->
<div class="mb-3">
<label for="fullName" class="form-label">全名 *</label>
<input type="text" class="form-control"
th:field="*{fullName}"
th:classappend="${#fields.hasErrors('fullName')} ? 'is-invalid'"
id="fullName" placeholder="请输入全名">
<div class="invalid-feedback" th:if="${#fields.hasErrors('fullName')}"
th:errors="*{fullName}">全名错误信息</div>
</div>
<!-- 提交按钮 -->
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<a th:href="@{/web/users}" class="btn btn-secondary">取消</a>
<button type="submit" class="btn btn-primary">创建用户</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
静态资源处理
静态资源配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 自定义静态资源路径
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(3600); // 缓存1小时
// 文件上传路径
registry.addResourceHandler("/uploads/**")
.addResourceLocations("file:./uploads/")
.setCachePeriod(0); // 不缓存
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
文件上传处理
@RestController
@RequestMapping("/api/v1/files")
public class FileController {
@Value("${app.upload.dir:./uploads}")
private String uploadDir;
@PostMapping("/upload")
public ResponseEntity<Map<String, String>> uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
throw new IllegalArgumentException("文件不能为空");
}
try {
// 创建上传目录
Path uploadPath = Paths.get(uploadDir);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// 生成唯一文件名
String originalFilename = file.getOriginalFilename();
String extension = FilenameUtils.getExtension(originalFilename);
String newFilename = UUID.randomUUID().toString() + "." + extension;
// 保存文件
Path filePath = uploadPath.resolve(newFilename);
Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
// 返回文件信息
Map<String, String> response = new HashMap<>();
response.put("filename", newFilename);
response.put("originalName", originalFilename);
response.put("url", "/uploads/" + newFilename);
response.put("size", String.valueOf(file.getSize()));
return ResponseEntity.ok(response);
} catch (IOException e) {
throw new RuntimeException("文件上传失败", e);
}
}
}
小结
Spring Boot的Web开发支持包括:
RESTful API开发
- 完整的HTTP方法支持:GET、POST、PUT、PATCH、DELETE
- 数据绑定和验证:自动参数绑定和JSR-303验证
- 内容协商:支持JSON、XML等多种格式
- CORS支持:跨域资源共享配置
- API文档:Swagger/OpenAPI集成
Thymeleaf模板开发
- 自然模板:可以直接在浏览器中查看
- 丰富的表达式:变量、条件、循环等
- 表单处理:表单绑定和验证错误显示
- 国际化支持:多语言支持
- 布局系统:模板继承和片段复用
通过本章的学习,您可以根据项目需求选择合适的Web开发方式: - 前后端分离项目选择RESTful API - 传统Web应用选择Thymeleaf模板 - 或者两者结合使用,既提供API又提供Web界面