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

基于 Spring Boot 的 Web 三大核心交互案例精讲
(2026年最实用写法 · 企业真实场景)

在 Spring Boot Web 开发中,真正决定项目质量和维护难度的,往往不是写了多少 Controller,而是你是否真正掌握了以下三大核心交互场景正确、优雅、可维护的处理方式:

  1. 前后端分离的登录 + Token 认证(JWT + 无感刷新)
  2. 文件上传下载(大文件、分片上传、断点续传)
  3. 实时消息推送(WebSocket + SSE 对比实战)

下面用最现代的 Spring Boot 3.x + Spring Security 6.x 写法,给你最实用的完整案例。

1. 前后端分离登录 + Token 认证(JWT + 无感刷新)

核心要点

  • Access Token 短效(15~60分钟)
  • Refresh Token 长效(7~30天),HttpOnly Cookie 存储
  • 前端拦截 401 自动刷新
  • 滚动刷新(可选,安全性更高)

后端关键代码片段(简化版)

// 登录返回双 token
@PostMapping("/api/auth/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request, HttpServletResponse response) {
    // 验证用户名密码...
    String userId = "user-1001";

    String accessToken = jwtUtil.generateAccessToken(userId, 30);   // 30分钟
    String refreshToken = UUID.randomUUID().toString();

    // 存 redis(带过期时间)
    redisTemplate.opsForValue().set("refresh:" + refreshToken, userId, 14, TimeUnit.DAYS);

    // 设置 HttpOnly Cookie
    Cookie refreshCookie = new Cookie("rt", refreshToken);
    refreshCookie.setHttpOnly(true);
    refreshCookie.setSecure(true);          // 生产必须 https
    refreshCookie.setPath("/");
    refreshCookie.setMaxAge(14 * 24 * 60 * 60);
    refreshCookie.setAttribute("SameSite", "Strict");
    response.addCookie(refreshCookie);

    return ResponseEntity.ok(new TokenVO(accessToken));
}

// 刷新 token 接口
@PostMapping("/api/auth/refresh")
public ResponseEntity<?> refresh(HttpServletRequest request) {
    String refreshToken = null;
    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
        refreshToken = Arrays.stream(cookies)
                .filter(c -> "rt".equals(c.getName()))
                .map(Cookie::getValue)
                .findFirst()
                .orElse(null);
    }

    if (refreshToken == null) {
        return ResponseEntity.status(401).body("无 refresh token");
    }

    String userId = redisTemplate.opsForValue().get("refresh:" + refreshToken);
    if (userId == null) {
        return ResponseEntity.status(401).body("refresh token 已失效");
    }

    // 可选:滚动刷新(生成新 refresh token)
    String newRefreshToken = UUID.randomUUID().toString();
    redisTemplate.delete("refresh:" + refreshToken);
    redisTemplate.opsForValue().set("refresh:" + newRefreshToken, userId, 14, TimeUnit.DAYS);

    // 返回新 access token
    return ResponseEntity.ok(new TokenVO(jwtUtil.generateAccessToken(userId, 30)));
}

前端 Axios 拦截器(最经典写法)

// request 拦截器 - 自动加 token
api.interceptors.request.use(config => {
  const token = localStorage.getItem('access_token')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

// response 拦截器 - 401 自动刷新
api.interceptors.response.use(
  response => response,
  async error => {
    const originalRequest = error.config

    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true

      try {
        const { data } = await api.post('/auth/refresh') // 自动携带 cookie
        localStorage.setItem('access_token', data.accessToken)

        // 重试原请求
        originalRequest.headers.Authorization = `Bearer ${data.accessToken}`
        return api(originalRequest)
      } catch (refreshErr) {
        // 刷新失败 → 跳转登录
        localStorage.removeItem('access_token')
        window.location.href = '/login'
        return Promise.reject(refreshErr)
      }
    }
    return Promise.reject(error)
  }
)

2. 文件上传下载(大文件、分片、断点续传)

三种主流方式对比(2026企业真实选型)

场景推荐方式最大文件断点续传复杂度代表技术栈
小文件(<50MB)普通 multipart50~100MB不支持★☆☆☆☆spring.servlet.multipart
中大文件(100MB~2GB)分片上传 + 秒传无上限支持★★★☆☆tus / Resumable.js / 前端分片
超大文件、企业级需求分片 + Redis 记录无上限完美支持★★★★☆minio + redis + 前端分片

推荐中型项目写法:分片上传 + 秒传(Redis 判断)

后端核心代码

@PostMapping("/upload/chunk")
public ResponseEntity<?> uploadChunk(
        @RequestParam("file") MultipartFile chunk,
        @RequestParam("md5") String fileMd5,
        @RequestParam("chunkIndex") int chunkIndex,
        @RequestParam("totalChunks") int totalChunks) {

    // 1. 秒传判断
    if (redisTemplate.hasKey("file:md5:" + fileMd5)) {
        return ResponseEntity.ok("秒传成功");
    }

    // 2. 保存分片(临时目录 + chunkIndex 命名)
    String chunkPath = uploadDir + "/" + fileMd5 + "/" + chunkIndex;
    chunk.transferTo(new File(chunkPath));

    // 3. 记录已上传分片
    redisTemplate.opsForSet().add("chunks:" + fileMd5, String.valueOf(chunkIndex));

    // 4. 判断是否全部上传完成
    Long uploadedCount = redisTemplate.opsForSet().size("chunks:" + fileMd5);
    if (uploadedCount == totalChunks) {
        // 合并分片(异步任务或立即合并)
        mergeChunks(fileMd5, totalChunks);
        // 记录完成
        redisTemplate.opsForValue().set("file:md5:" + fileMd5, "completed", 7, TimeUnit.DAYS);
    }

    return ResponseEntity.ok("分片上传成功");
}

前端推荐库(2026年最流行):

  • resumable.js(最稳定)
  • uploader.js / tus-js-client(支持 tus 协议)
  • spark-md5(前端算 MD5 实现秒传)

3. 实时消息推送(WebSocket vs SSE 终极对比实战)

2026年真实选型表

需求场景首选技术双向通信浏览器兼容断线重连实现复杂度代表框架/库
聊天、IM、协作编辑WebSocket极好需手动★★★★☆Spring WebSocket + STOMP
服务器主动推送(通知、进度)SSE极好自动★★☆☆☆SseEmitter
海量用户单向广播SSE极好自动★★☆☆☆Redis Pub/Sub + SSE
低频、兼容性极致要求Long Polling最好自动★☆☆☆☆基本不用了

SSE 推荐写法(最简单、最高性价比)

@GetMapping(value = "/progress/{taskId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter progress(@PathVariable String taskId) {
    SseEmitter emitter = new SseEmitter(Long.MAX_VALUE); // 长连接

    // 异步推送进度
    Executors.newSingleThreadExecutor().submit(() -> {
        try {
            for (int i = 0; i <= 100; i += 10) {
                emitter.send(SseEmitter.event()
                        .name("progress")
                        .data(new ProgressVO(i, "处理中...")));
                Thread.sleep(1000);
            }
            emitter.send(SseEmitter.event().name("complete").data("任务完成"));
            emitter.complete();
        } catch (Exception e) {
            emitter.completeWithError(e);
        }
    });

    return emitter;
}

前端 SSE 写法

const source = new EventSource(`/api/progress/${taskId}`);

source.addEventListener('progress', e => {
  const data = JSON.parse(e.data)
  console.log(`进度:${data.percent}% - ${data.message}`)
})

source.addEventListener('complete', e => {
  console.log('任务完成!')
  source.close()
})

source.onerror = () => {
  console.log('连接断开,浏览器会自动重连...')
}

总结:三大核心交互的企业级优先级排序(2026)

  1. 必须掌握:前后端分离 Token 认证 + 无感刷新(几乎所有项目都有)
  2. 强烈建议:文件分片上传 + 秒传(中大型项目必备)
  3. 根据业务选:SSE(单向推送首选) > WebSocket(双向实时首选)

你目前项目里最常遇到哪个交互的痛点?
是 Token 刷新并发问题?大文件上传卡顿?还是实时推送掉线重连?

告诉我,我可以给你更针对性的代码优化或避坑方案~

文章已创建 3806

发表回复

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

相关文章

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

返回顶部