前端工程师的后端起手式:5分钟用Spring Boot写可调试API

前端工程师的后端起手式:5分钟用Spring Boot写可调试API 1. 这不是“转岗指南”而是前端工程师亲手搭起后端第一块砖的真实路径你刚用 Vue 写完一个带表单校验和路由守卫的管理后台心里有点小得意——但当产品经理甩来一句“这个导出 Excel 的按钮后端接口下周才给你先 mock 一下”时你打开 Postman对着 localhost:8080 瞪了三分钟最后还是复制粘贴了网上找的 Express.js 示例代码改了两行就跑起来了。这很常见但不等于合理。前端上手后端起手式从来不是让你一夜之间变成 Spring Boot 架构师而是帮你把“调用 API”这件事从黑盒操作变成可理解、可调试、可修改、甚至可临时接管的白盒能力。它解决的不是“要不要学后端”的哲学问题而是“今天下午三点前我要让那个 400 错误消失且不让测试同事再发截图问我‘为什么导出按钮点了没反应’”的现实问题。核心关键词——前端、后端、Spring Boot、HTTP、API——不是并列关系而是一条因果链前端是发起者HTTP 是信使API 是契约后端以 Spring Boot 为典型代表是履约方。你作为前端天然站在 HTTP 请求的发起端对状态码、请求头、响应体、重定向逻辑、连接复用机制的理解深度直接决定了你排查问题的速度和质量。比如热搜里反复出现的unexpected status 502 bad gateway: unknown error, url: http://127.0.0.1:1572表面看是网关错误但如果你知道 Nginx 或 Spring Cloud Gateway 在转发失败时会返回 502而unknown error往往意味着上游服务根本没启动、端口被占、或健康检查失败你就能立刻跳过查前端代码直奔ps aux | grep java和netstat -tuln | grep 1572再比如api error: 400 invalid schema for function artifact这根本不是后端逻辑错而是你前端传过去的 JSON 结构不符合 OpenAPI 定义的 Schema少了个必填字段或多了一个非法字段——这种错误靠读接口文档不如靠自己起一个 Spring Boot 服务用Valid注解加BindingResult打印出具体校验失败原因来得快。这个起手式适合三类人一是刚入职、接手老项目的前端需要快速理解现有后端逻辑以便联调二是独立开发者或小团队成员前后端常由一人兼顾必须能自己补上 API 层三是准备面试的候选人2026 年的前端面试题已不再只问Promise.all和v-model原理而是会给你一段含fetch调用的代码问“如果后端返回 503前端该不该重试重试间隔怎么设如何避免雪崩”——这背后考的就是你对 HTTP 协议栈、服务治理、熔断降级等后端思维的底层认知。它不要求你写 MyBatis XML 映射文件但要求你能读懂RestController类里PostMapping(/user)对应的业务入口它不强求你配置application.yml里的 Redis 集群参数但必须清楚spring.redis.host改错会导致整个应用启动失败而不是某个接口报 500。起手式的本质是把后端从“远程服务”拉回本地变成你开发环境里一个可打断点、可改日志、可重启的 Java 进程——就像你习惯在 Chrome DevTools 里打断点调试 JS 一样自然。2. 为什么选 Spring Boot 而不是 Node.js 或 Python一次务实的技术选型推演很多人看到“前端上手后端”第一反应是“那我学 Express 或 Flask 吧JavaScript/Python 写起来熟”。这没错但忽略了真实协作场景中的摩擦成本。当你在团队里说“我来临时改个接口”后端同事递过来的是一份pom.xml和src/main/java/com/example/demo/controller/UserController.java你若用 Node.js 重写一遍不仅部署要额外配 PM2、Nginx 反向代理更关键的是数据模型、数据库连接、事务管理、安全认证这些非业务逻辑你得全部重新实现且无法复用现有项目资产。而 Spring Boot 的优势在于它把 Java 生态里最成熟、最标准化的后端能力封装成开箱即用的模块。你不需要懂 Spring AOP 的代理机制但Transactional加上去数据库操作就自动具备原子性你不必研究 Hibernate 的二级缓存原理但Cacheable注解一打查询结果就自动进 Redis——这些不是魔法而是经过十年以上企业级验证的抽象它们的存在就是为了让你这个前端能用最少的认知负荷撬动最大的后端能力。我们来算一笔账。假设你要实现一个简单的用户登录接口需校验账号密码、生成 JWT Token、记录登录日志。用 Express 实现// express-login.js const jwt require(jsonwebtoken); const bcrypt require(bcrypt); const { Pool } require(pg); // 假设用 PostgreSQL const pool new Pool({ /* config */ }); app.post(/login, async (req, res) { const { username, password } req.body; const user await pool.query(SELECT * FROM users WHERE username $1, [username]); if (!user.rows[0]) return res.status(401).json({ error: User not found }); const valid await bcrypt.compare(password, user.rows[0].password_hash); if (!valid) return res.status(401).json({ error: Invalid password }); const token jwt.sign({ userId: user.rows[0].id }, process.env.JWT_SECRET, { expiresIn: 1h }); await pool.query(INSERT INTO login_logs (user_id, ip) VALUES ($1, $2), [user.rows[0].id, req.ip]); res.json({ token }); });这段代码看似简洁但隐藏着大量未处理的细节SQL 注入防护需手动参数化、密码哈希强度bcrypt 盐值轮数、JWT 秘钥管理硬编码在 env 中、数据库连接泄漏没处理异常时的连接释放、日志格式统一不同环境输出结构不一致。而 Spring Boot 的等效实现// UserController.java RestController RequestMapping(/api) public class UserController { Autowired private UserService userService; PostMapping(/login) public ResponseEntityLoginResponse login(Valid RequestBody LoginRequest request) { LoginResponse response userService.login(request.getUsername(), request.getPassword()); return ResponseEntity.ok(response); } } // UserService.java Service Transactional public class UserService { Autowired private UserRepository userRepository; Autowired private JwtTokenProvider tokenProvider; Autowired private LoginLogRepository logRepository; public LoginResponse login(String username, String password) { User user userRepository.findByUsername(username) .orElseThrow(() - new UsernameNotFoundException(User not found)); if (!passwordEncoder.matches(password, user.getPasswordHash())) { throw new BadCredentialsException(Invalid password); } String token tokenProvider.generateToken(user.getId()); logRepository.save(new LoginLog(user.getId(), getCurrentIp())); return new LoginResponse(token); } }表面看代码行数更多但所有非业务逻辑都被框架接管Valid自动校验请求体、Transactional保证 DB 操作原子性、passwordEncoder封装了 BCrypt 的最佳实践、JwtTokenProvider统一管理密钥和过期策略、LoginLogRepository通过 JPA 自动生成 SQL 并防注入。你作为前端只需关注UserService.login()方法里那几行业务判断逻辑——这正是 Spring Boot “约定优于配置”的核心价值它用一套强约束的规范如RestController必须返回 JSON、Service层负责业务、Repository层负责数据替你屏蔽了 80% 的工程化陷阱让你能把注意力聚焦在“这个接口到底要做什么”上而不是“怎么让这个接口不崩”。更重要的是Spring Boot 的调试体验对前端极其友好。你用 IntelliJ IDEA 打开项目mvn spring-boot:run启动后在UserController.login()方法第一行打个断点然后用 Postman 发送请求IDEA 会立刻停住你可以逐行看request.getUsername()的值、userRepository.findByUsername()的 SQL 日志、passwordEncoder.matches()的返回布尔值——这一切和你在 Chrome 里调试fetch().then()的 Promise 链毫无二致。而 Node.js 的调试需要配置launch.json、设置--inspect参数、在 Chromechrome://inspect里连上Python 的 PyCharm 调试虽也直观但 Java 的堆栈跟踪、变量监视、内存快照分析工具链是经过大型企业多年锤炼的稳定性和信息密度远超其他语言。所以“前端上手后端起手式”选择 Spring Boot不是因为它多酷炫而是因为它最接近前端已有的工作流写代码 → 启动服务 → 断点调试 → 查看日志 → 修改发布。它降低的不是学习曲线而是认知切换成本。3. 从零启动一个可调试的 Spring Boot 服务5 分钟完成环境搭建与第一个 API别被“Java”“Maven”“JDK”这些词吓住。你不需要下载 JDK 17 并手动配置JAVA_HOME也不用去 Apache 官网找 Maven 压缩包。现代 Spring Boot 开发早已进入“开箱即用”时代。真正的第一步是访问 https://start.spring.io/ —— 这是 Spring 官方提供的项目初始化器它的存在意义就是让前端工程师也能像create-react-app一样三步生成一个可运行的后端骨架。3.1 初始化项目勾选即所得拒绝手动配置打开 start.spring.io你会看到一个表单。重点只填三项Project: Maven这是 Java 项目的标准构建工具无需深究把它当成npm就行Language: Java版本选 17 或 21LTS 长期支持版兼容性最好Spring Boot: 3.2.x当前最新稳定版避免选 2.x因 3.x 的 Jakarta EE 命名空间变更已成主流然后在 “Dependencies” 搜索框里输入并勾选三个核心依赖Spring Web提供 RESTful API 开发能力包含内嵌 Tomcat 服务器mvn spring-boot:run就能直接启动不用单独装 Tomcat。Spring Data JPAORM 框架让你用 Java 对象操作数据库告别手写 SQL初期可配 H2 内存数据库完全免安装。Lombok一个代码精简插件用Data、Builder等注解自动生成 getter/setter/构造函数避免写样板代码极大提升阅读效率。点击 “Generate” 按钮下载一个.zip文件。解压后你会得到一个标准 Maven 项目结构其中src/main/java/com/example/demo/DemoApplication.java是主启动类。现在打开终端进入项目根目录执行# 第一次运行会下载依赖耗时稍长约 1-2 分钟后续启动秒级 mvn spring-boot:run如果看到控制台输出Tomcat started on port(s): 8080 (http)和Started DemoApplication in X.XXX seconds恭喜你的第一个 Spring Boot 服务已成功启动此时打开浏览器访问http://localhost:8080/actuator/health会返回{status:UP}—— 这是 Spring Boot Actuator 提供的健康检查端点证明服务已活。注意actuator默认是开启的但/actuator/env、/actuator/beans等敏感端点默认关闭这是安全设计不是配置错误。3.2 编写第一个 API从 “Hello World” 到可调试的业务接口现在我们来写一个真正有用的接口根据用户 ID 查询用户信息。在src/main/java/com/example/demo/controller/目录下新建UserController.javapackage com.example.demo.controller; import com.example.demo.model.User; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api/users) public class UserController { Autowired private UserService userService; GetMapping(/{id}) public ResponseEntityUser getUserById(PathVariable Long id) { User user userService.findById(id); if (user null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(user); } }接着创建model/User.java用 Lombok 简化package com.example.demo.model; import lombok.Data; import lombok.NoArgsConstructor; import javax.persistence.*; Data NoArgsConstructor Entity Table(name users) public class User { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(name username, unique true, nullable false) private String username; Column(name email, nullable false) private String email; Column(name created_at) private java.time.LocalDateTime createdAt; }再创建service/UserService.javapackage com.example.demo.service; import com.example.demo.model.User; import com.example.demo.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Optional; Service public class UserService { Autowired private UserRepository userRepository; public User findById(Long id) { OptionalUser user userRepository.findById(id); return user.orElse(null); } }最后创建repository/UserRepository.javapackage com.example.demo.repository; import com.example.demo.model.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; Repository public interface UserRepository extends JpaRepositoryUser, Long { // JpaRepository 已内置 findById() 方法无需手动实现 }此时项目结构已完备。但还缺一个关键配置告诉 Spring Boot 使用 H2 内存数据库免安装、免配置。在src/main/resources/application.yml中添加spring: datasource: url: jdbc:h2:mem:testdb driver-class-name: org.h2.Driver username: sa password: password h2: console: enabled: true path: /h2-console jpa: database-platform: org.hibernate.dialect.H2Dialect hibernate: ddl-auto: create-drop保存后重启服务CtrlC停止再mvn spring-boot:run。启动成功后访问http://localhost:8080/h2-console在登录页面填入JDBC URL:jdbc:h2:mem:testdbUser Name:saPassword:password点击 “Connect”H2 控制台打开。在 SQL 窗口中执行INSERT INTO users (username, email, created_at) VALUES (zhangsan, zhangsanexample.com, NOW()); INSERT INTO users (username, email, created_at) VALUES (lisi, lisiexample.com, NOW());然后用 Postman 或浏览器访问http://localhost:8080/api/users/1你会得到{ id: 1, username: zhangsan, email: zhangsanexample.com, createdAt: 2024-06-15T10:20:30.123 }这就是你的第一个可调试 API。整个过程你没有写一行 XML 配置没有手动管理数据库连接池没有处理 HTTP 状态码转换——所有这些都由 Spring Boot 的自动配置Auto-Configuration完成。你只专注于定义“用户是什么”User实体类、“怎么查用户”UserService.findById()、“怎么暴露接口”GetMapping这三个核心契约。这种开发模式和你用 Vue Composition API 定义ref、computed、onMounted的心智模型完全一致声明意图框架负责执行。3.3 调试实战像调试前端一样调试后端逻辑现在我们故意制造一个 bug 来练习调试。修改UserService.findById()方法加入一个空指针风险public User findById(Long id) { OptionalUser user userRepository.findById(id); // 故意加一行可能 NPE 的代码 String emailDomain user.get().getEmail().split()[1]; // 如果 email 为空这里会抛 NullPointerException return user.orElse(null); }重启服务用 Postman 访问/api/users/1会看到浏览器返回 500 错误控制台打印出完整的堆栈跟踪。但更高效的方式是在user.get().getEmail().split()[1]这一行左侧灰色区域单击打上断点IntelliJ IDEA 或 VS Code Extension 都支持。再次发送请求IDEA 会自动暂停高亮显示当前行并在右侧 Variables 面板中展示user的值是一个OptionalUser、user.get()的结果User对象、user.get().getEmail()的值zhangsanexample.com。你可以鼠标悬停在split()上看到它返回[zhangsan, example.com]再点开数组查看索引 1 的值。如果email是null你会在 Variables 面板里一眼看到user.get().getEmail() null从而定位到问题根源。这种调试体验和你在 Vue 组件里console.log(props)或在 ReactuseEffect里debugger完全同源。区别只在于前端调试的是 DOM 渲染和事件流后端调试的是数据流转和业务决策点。当你习惯了在UserService里打断点看userRepository.findById()返回的Optional是否isPresent()你就自然理解了为什么后端接口要返回200 OK或404 Not Found而不是笼统的200加一个{ code: 404, msg: user not found }—— 因为 HTTP 状态码本身就是语义化的契约404比任何自定义 code 都更能被网关、CDN、浏览器缓存正确识别和处理。4. HTTP 协议深度拆解前端天天用却未必真懂的 7 个关键机制前端工程师每天都在和 HTTP 打交道fetch、axios、XMLHttpRequest但多数人只停留在“发请求、收响应”的表层。当遇到502 Bad Gateway、400 Invalid Schema、Connection reset by peer这类错误时第一反应往往是“后端崩了”或“网络不好”却忽略了 HTTP 本身就是一个精密的状态机每个状态码、每个 Header、每次连接复用都承载着明确的设计意图。掌握这些你才能把“调用 API”从被动等待变成主动掌控。4.1 状态码不是数字而是协议契约从 2xx 到 5xx 的真实语义HTTP 状态码是服务器对客户端请求的权威回应它不是后端程序员随意写的数字而是 RFC 7231 标准定义的语义化信号。前端必须理解其背后的真实含义才能做出正确响应。2xx 成功系列200 OK表示请求成功响应体包含所请求的资源201 Created表示 POST 创建资源成功且响应体应包含新资源的 URI如Location: /api/users/123204 No Content表示请求成功但无需返回任何内容如 DELETE 操作此时前端不应尝试解析响应体否则会报错。3xx 重定向系列301 Moved Permanently表示资源永久迁移到新地址浏览器会缓存此重定向后续请求直接发往新地址302 Found是临时重定向不被缓存304 Not Modified是条件 GET 的成功响应表示资源未修改客户端应使用本地缓存副本——这正是ETag和If-None-MatchHeader 协同工作的结果。4xx 客户端错误系列这是前端最该关注的类别。400 Bad Request表示请求语法错误如 JSON 格式非法、URL 参数缺失401 Unauthorized表示缺少有效认证凭证如 JWT Token 过期或未携带403 Forbidden表示凭证有效但权限不足404 Not Found表示请求的资源路径不存在注意不是后端服务挂了而是路由没匹配上422 Unprocessable Entity表示请求体语法正确但语义错误如邮箱格式合法但已被注册。5xx 服务器错误系列500 Internal Server Error是通用错误表示后端代码抛出未捕获异常502 Bad Gateway表示网关如 Nginx无法从上游服务如 Spring Boot获得有效响应常见原因有上游服务未启动、端口被占、健康检查失败503 Service Unavailable表示服务暂时不可用通常因过载或维护此时客户端应实现指数退避重试504 Gateway Timeout表示网关等待上游响应超时。关键洞察状态码是前端错误处理的唯一可靠依据。不要用response.data.code 400来判断错误而应直接检查response.status。例如处理登录失败// ❌ 错误依赖后端自定义 code if (res.data.code 401) { /* 处理未授权 */ } // ✅ 正确遵循 HTTP 协议 if (res.status 401) { // 清除本地 Token跳转登录页 localStorage.removeItem(token); router.push(/login); } else if (res.status 400) { // 显示表单校验错误如邮箱格式错误 showFormError(res.data.message); } else if (res.status 500) { // 提示服务异常建议重试 showToast(服务暂时不可用请稍后再试); }4.2 Header 不是装饰而是通信协议Accept、Content-Type 与 Connection 复用HTTP Header 是请求与响应之间的元数据通道它决定了数据如何被解释、如何被传输。前端常忽略其重要性导致跨域、乱码、性能低下等问题。Accept告诉服务器“我期望接收什么格式的数据”。前端发请求时fetch默认发送Accept: */*但更佳实践是明确指定headers: { Accept: application/json }。这样当后端同时支持 JSON 和 XML 时能确保返回 JSON。Content-Type告诉服务器“我发送的数据是什么格式”。POST提交 JSON 时必须设置Content-Type: application/json否则 Spring Boot 的RequestBody无法正确反序列化提交表单时用Content-Type: application/x-www-form-urlencoded上传文件时用Content-Type: multipart/form-data此时fetch会自动设置 boundary。Connection与连接复用HTTP/1.1 默认启用持久连接Connection: keep-alive即一个 TCP 连接可发送多个请求避免频繁建立/断开连接的开销。但前端常因错误配置导致连接无法复用。例如axios默认不设置keep-alive需手动配置// axios 配置连接复用 axios.defaults.headers.common[Connection] keep-alive; // 或在实例中配置 const apiClient axios.create({ baseURL: /api, headers: { Connection: keep-alive } });更深层的是Keep-AliveHeader 的timeout和max参数它们由服务器控制前端无法设置但需理解其影响timeout5表示连接空闲 5 秒后关闭max100表示单个连接最多处理 100 个请求。这意味着如果你的应用每秒发起 20 个请求每个请求耗时 100ms则单个连接平均 5 秒内就能处理完 100 次请求完美复用但如果请求间隔很长连接就会被服务器主动关闭下次请求需重建 TCP 连接三次握手 TLS 握手造成明显延迟。4.3 从 TCP 到 TLS一次 HTTPS 请求背后的七层真相当你在浏览器地址栏输入https://api.example.com/users/1并按下回车表面看是一次简单请求背后却涉及 OSI 七层模型的完整协作应用层HTTP/HTTPS浏览器构造 HTTP GET 请求目标/users/1。表示层TLS/SSL浏览器与服务器进行 TLS 握手协商加密算法、验证服务器证书由 CA 签发、生成会话密钥。这是https的核心确保传输内容不被窃听或篡改。会话层无显式协议TLS 握手成功后建立安全会话后续 HTTP 数据在此会话内加密传输。传输层TCP将加密后的 HTTP 数据分段添加 TCP Header含源/目的端口、序列号、确认号确保数据可靠、有序到达。TIME_WAIT状态就是 TCP 连接关闭后为防止网络延迟包干扰新连接而设置的等待期。网络层IP为 TCP 段添加 IP Header含源/目的 IP 地址进行路由寻址。DNS 解析api.example.com→192.0.2.1就发生在此层。数据链路层以太网/WiFi将 IP 包封装成帧添加 MAC 地址通过物理网络网线/WiFi发送。物理层电缆/无线电波将帧转换为电信号或光信号在物理介质上传输。前端工程师不必实现每一层但必须理解其影响。例如502 Bad Gateway常发生在网络层或传输层DNS 解析失败api.example.com无法解析为 IP、服务器防火墙拦截了 443 端口、TCP 连接被中间设备如公司代理重置。此时curl -v https://api.example.com的 verbose 输出会显示Failed to connect to api.example.com port 443: Connection refused这比任何后端日志都更早暴露问题根源。5. 前后端联调高频问题实战排查从 400 到 502 的 12 个真实案例在真实项目中联调不是一帆风顺的。以下是我过去三年在多个项目中记录的、前端工程师最常遇到的 12 个问题每个都附带现象、根因、排查命令、解决方案全是实操中踩过的坑。5.1 400 Bad RequestJSON Schema 校验失败现象前端fetch发送{ name: 张三, age: 25 }后端返回400响应体为{timestamp:2024-06-15T10:00:00,status:400,error:Bad Request,message:Validation failed,path:/api/users}。根因后端RequestBody User user的User类中NotNull校验email字段但前端未传email。排查在 Spring Boot 中启用spring.jackson.deserialization.fail-on-unknown-propertiestrue并在 Controller 方法上加Valid和BindingResult result参数打印result.getAllErrors()。方案前端确保请求体符合 OpenAPI 文档定义后端在ExceptionHandler(MethodArgumentNotValidException.class)中返回详细错误字段。5.2 401 UnauthorizedToken 无效或过期现象登录成功后后续请求均返回401。根因前端未在AuthorizationHeader 中携带 Token或 Token 格式错误如Bearer后少空格或 Token 已过期。排查用浏览器 DevTools 的 Network 面板检查请求 Header 是否有Authorization: Bearer token用 JWT 网站如 jwt.io解码 Token查看exp时间戳。方案前端封装axios.interceptors.request自动添加 Token后端EnableWebSecurity配置HttpSecurity允许 OPTIONS 预检请求。5.3 403 ForbiddenCSRF 或权限不足现象POST/PUT/DELETE 请求返回403GET 正常。根因Spring Security 默认开启 CSRF 保护要求非 GET 请求携带_csrfToken或用户角色无对应PreAuthorize(hasRole(ADMIN))权限。排查检查响应 Header 是否有X-CSRF-TOKEN查看 Spring Security 日志DEBUG级别输出。方案前端在登录后获取X-CSRF-TOKEN并存入axios.defaults.headers.common[X-CSRF-TOKEN]后端在WebSecurityConfigurerAdapter中禁用 CSRF仅开发环境或配置 Token 存储策略。5.4 404 Not Found路由不匹配现象http://localhost:8080/api/users/1返回404。根因前端请求 URL 与后端RequestMapping路径不一致如后端是/users前端写了/api/user或 Controller 类未加RestController。排查启动 Spring Boot 时控制台会打印Mapped {[/api/users/{id}],methods[GET]} onto public com.example.demo.model.User com.example.demo.controller.UserController.getUserById(java.lang.Long)核对路径。方案统一前后端 API 基础路径使用 Swagger UI/swagger-ui.html在线查看所有可用端点。5.5 500 Internal Server Error空指针或数据库异常现象请求返回500控制台打印NullPointerException或org.hibernate.exception.JDBCConnectionException。根因userService.findById(id)返回null后续调用user.getName()抛 NPE或数据库连接池耗尽。排查查看控制台完整堆栈定位到第 1 行异常代码检查application.yml中spring.datasource.hikari.maximum-pool-size是否过小。方案后端用Optional包装返回值前端用user?.name安全访问增加ExceptionHandler全局异常处理器。5.6 502 Bad Gateway上游服务不可达现象Nginx 日志显示upstream prematurely closed connection while reading response header from upstream前端请求返回502。根因Spring Boot 服务未启动、端口被占netstat -tuln | grep 8080、或application.yml中server.port与 Nginxproxy_pass端口不一致。排查curl -v http://localhost:8080/actuator/health测试服务是否存活ps aux | grep java查看进程。方案确保 Spring Boot 应用正常启动Nginx 配置proxy_next_upstream error timeout http_502;实现故障转移。5.7 503 Service Unavailable服务过载现象高峰期请求大量返回503。根因Tomcat 线程池满默认 200或 Hystrix 熔断器触发。排查jstack pid查看线程堆栈确认是否大量WAITING状态Spring Boot Actuator/actuator/metrics/tomcat.threads.current查看当前线程数。方案增加server.tomcat.max-threads500前端实现请求节流throttle和降级fallback。5.8 CORS 跨域错误No Access-Control-Allow-Origin现象浏览器控制台报CORS header Access-Control-Allow-Origin missing。根因后端未配置跨域或CrossOrigin注解未覆盖所有方法。排查检查响应 Header 是否有Access-Control-Allow-Origin: *确认请求是预检OPTIONS还是实际请求。方案后端Configuration类中添加CorsConfigurationSource允许特定 Origin前端避免在开发环境用file://协议打开 HTML。5.9 Connection ResetTCP 连接被强制关闭现象fetch报TypeError: Failed to fetchChrome Network 面板显示(failed) net::ERR_CONNECTION_RESET。根因防火墙拦截、代理服务器中断、或后端应用崩溃如 OOM Kill。排查telnet localhost 8080测试端口连通性dmesg | grep -i killed process查看是否被 OOM Killer 终止。方案后端增加 JVM 内存参数-Xms512m -Xmx1024m前端fetch加signal: AbortSignal.timeout(5000)设置超时。5.10 Slow Response首字节时间T