基于 Spring Boot 的 Web 三大核心交互案例精讲

基于 Spring Boot 的 Web 三大核心交互案例精讲

在 Spring Boot Web 开发中,最常见、最核心的三种前后端交互方式是:

  1. 表单提交(同步提交 / POST)
    传统网页最经典的方式(目前仍大量存在于后台管理系统、企业内部系统)
  2. Ajax / Fetch + JSON(前后端分离主流方式)
    现代 Web 应用(Vue/React/Angular + Spring Boot REST API)的事实标准
  3. 文件上传 & 下载(multipart/form-data + ResponseEntity)
    几乎每个中后台系统都会涉及的功能

下面用三个典型、实用的案例,把三种交互方式的核心写法、常见坑和最佳实践一次性讲清楚。

案例 1:表单提交(传统同步方式)

场景:用户注册 / 登录 / 修改个人信息(传统 Thymeleaf 或 JSP 页面)

后端 Controller 写法

@Controller
@RequestMapping("/user")
public class UserController {

    @GetMapping("/register")
    public String registerPage(Model model) {
        model.addAttribute("user", new UserForm());
        return "user/register";  // thymeleaf 模板
    }

    @PostMapping("/register")
    public String register(@ModelAttribute UserForm user,
                          BindingResult bindingResult,
                          RedirectAttributes redirectAttributes) {

        // 校验
        if (bindingResult.hasErrors()) {
            return "user/register";
        }

        // 业务逻辑(保存用户、加密密码等)
        // userService.register(user);

        redirectAttributes.addFlashAttribute("message", "注册成功,请登录");
        return "redirect:/user/login";
    }
}

前端(Thymeleaf 示例)

<form th:action="@{/user/register}" th:object="${user}" method="post">
    <div>
        <label>用户名:</label>
        <input type="text" th:field="*{username}" required />
        <span th:errors="*{username}" class="error"></span>
    </div>

    <div>
        <label>密码:</label>
        <input type="password" th:field="*{password}" required />
    </div>

    <button type="submit">注册</button>
</form>

关键点与常见坑

  • 使用 @ModelAttribute + BindingResult 做 JSR-303 校验
  • 表单提交成功后用重定向(PRG 模式)防止重复提交
  • 错误回显靠 BindingResult 和 thymeleaf 的 th:errors
  • 密码等敏感字段不要直接回显

案例 2:前后端分离 – RESTful JSON 接口(@RestController + @RequestBody)

场景:Vue/React 前端通过 axios/fetch 调用后端 API

后端代码

@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserApiController {

    private final UserService userService;

    @PostMapping
    public ResponseEntity<ApiResult<UserVO>> createUser(
            @Valid @RequestBody UserCreateDTO dto,
            BindingResult bindingResult) {

        if (bindingResult.hasErrors()) {
            return ResponseEntity.badRequest()
                    .body(ApiResult.error("参数校验失败", bindingResult));
        }

        UserVO vo = userService.createUser(dto);
        return ResponseEntity.ok(ApiResult.success(vo));
    }

    @GetMapping("/{id}")
    public ResponseEntity<ApiResult<UserVO>> getUser(@PathVariable Long id) {
        UserVO vo = userService.findById(id);
        return ResponseEntity.ok(ApiResult.success(vo));
    }

    @PutMapping("/{id}")
    public ResponseEntity<ApiResult<Void>> updateUser(
            @PathVariable Long id,
            @Valid @RequestBody UserUpdateDTO dto) {

        userService.updateUser(id, dto);
        return ResponseEntity.ok(ApiResult.success());
    }
}

统一响应结构(推荐做法)

@Data
public class ApiResult<T> {
    private boolean success;
    private T data;
    private String message;
    private String code;

    public static <T> ApiResult<T> success(T data) {
        ApiResult<T> result = new ApiResult<>();
        result.success = true;
        result.data = data;
        return result;
    }

    public static ApiResult<?> error(String msg, BindingResult errors) {
        // ... 收集 field error
    }
}

前端(axios 示例)

axios.post('/api/v1/users', {
  username: 'zhangsan',
  password: '123456',
  email: 'test@example.com'
})
.then(res => {
  if (res.data.success) {
    message.success('注册成功')
  }
})
.catch(err => {
  if (err.response?.data?.message) {
    message.error(err.response.data.message)
  }
})

常见坑与最佳实践

  • 统一使用 @RestController 而不是 @Controller
  • 校验失败统一返回 400 + 详细错误信息
  • 使用 @Valid + BindingResultMethodArgumentNotValidException 全局处理
  • 返回 ResponseEntity 而不是直接对象,更灵活控制状态码
  • 前端要处理 4xx5xx 不同的错误提示

案例 3:文件上传 & 下载(multipart + ResponseEntity)

文件上传(单文件 & 多文件)

@RestController
@RequestMapping("/api/files")
public class FileController {

    @PostMapping("/upload")
    public ApiResult<FileUploadVO> uploadFile(
            @RequestPart("file") MultipartFile file,
            @RequestParam(required = false) String bizType) {

        if (file.isEmpty()) {
            return ApiResult.error("文件不能为空");
        }

        String originalFilename = file.getOriginalFilename();
        String extension = FilenameUtils.getExtension(originalFilename);

        // 推荐:使用 UUID + 扩展名
        String newFileName = UUID.randomUUID() + "." + extension;
        Path dest = Paths.get("/uploads/", newFileName);

        Files.createDirectories(dest.getParent());
        file.transferTo(dest.toFile());

        return ApiResult.success(new FileUploadVO(newFileName, "/files/" + newFileName));
    }

    // 多文件上传
    @PostMapping("/batch-upload")
    public ApiResult<List<FileUploadVO>> uploadMultiple(
            @RequestPart("files") List<MultipartFile> files) {
        // 类似处理...
    }
}

文件下载(推荐 ResponseEntity 方式)

@GetMapping("/download/{fileName}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) throws IOException {

    Path filePath = Paths.get("/uploads/", fileName).normalize();

    if (!Files.exists(filePath)) {
        return ResponseEntity.notFound().build();
    }

    Resource resource = new InputStreamResource(Files.newInputStream(filePath));

    return ResponseEntity.ok()
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .header(HttpHeaders.CONTENT_DISPOSITION, 
                    "attachment; filename=\"" + URLEncoder.encode(fileName, StandardCharsets.UTF_8) + "\"")
            .body(resource);
}

前端下载(两种常用方式)

// 方式1:直接 a 标签(简单文件)
<a :href="downloadUrl" download>下载</a>

// 方式2:通过 axios(可处理 token、进度)
axios.get('/api/files/download/xxx.pdf', {
  responseType: 'blob'
}).then(response => {
  const url = window.URL.createObjectURL(new Blob([response.data]))
  const link = document.createElement('a')
  link.href = url
  link.setAttribute('download', 'filename.pdf')
  document.body.appendChild(link)
  link.click()
  link.remove()
})

总结:三大交互方式对比

交互方式前端技术后端注解核心典型场景现代主流度(2025-2026)
表单同步提交form + submit@Controller + @PostMapping后台管理系统、管理端★★☆☆☆
RESTful JSONaxios/fetch@RestController + @RequestBody前后端分离、B端、C 端 API★★★★★
文件上传/下载FormData / input@RequestPart + MultipartFile头像、附件、导入导出★★★★☆

你目前项目里用的是哪种交互方式占比最高?
或者你正在做的某个具体功能(比如富文本上传图片、分片上传、断点续传、Excel 导入导出等)遇到了痛点?可以继续细聊~

文章已创建 4206

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部