# `/applications` 接口 500 问题诊断报告 > 前端联调:2026-09-29 > 测试账号:`19900009021` / `ZhilaiTest!2026#A` > 后端地址:`https://job.youthome.com.cn/api/v1` --- ## 问题复现 | 请求 | HTTP | code | message | |---|---|---|---| | `GET /applications`(带 Bearer token) | **500** | 500 | `internal server error` | | `GET /applications?page=0&size=20`(带 token) | **500** | 500 | `internal server error` | | `GET /applications`(不带 token) | 401 | 401 | `not authenticated` | | `GET /applications/me`(带 token) | 200 | 200 | `ok` ✓ | | `POST /applications`(带 token,投递) | 404 | 7001 | `job not found` ✓(正确路由,只是 jobId 不存在) | | `GET /applications/me/timeline`(带 token) | 404 | 8002 | `application not found` ✓(正确路由) | **结论:`GET /applications` 带 token 必现 HTTP 500**,是后端 bug。 --- ## 根因分析 ### 1. OpenAPI 文档中根本没有定义 `GET /applications` 后端公开的 28 个端点里,applications 模块只有 **5 个**: | 方法 | 路径 | 用途 | 状态 | |---|---|---|---| | POST | `/applications` | 提交投递 | ✅ 正常(jobId 不存在时返回 404) | | GET | `/applications/me` | 我的投递列表(分页) | ✅ 正常(返回 200 + 分页结构) | | GET | `/applications/{id}` | 投递详情 | ✅ 正常(id 不存在返回 404) | | PATCH | `/applications/{id}/status` | 企业改投递状态 | — 未测 | | POST | `/applications/{id}/withdraw` | 撤回投递 | — 未测 | | GET | `/applications/{id}/timeline` | 状态时间线 | ✅ 正常(id 不存在返回 404) | **没有 `GET /applications` 这条路由。** ### 2. 为什么不是 404 而是 500? 这是 Spring Boot Controller **路由冲突** 导致的。 后端 Controller 大概率写了: ```java @RestController @RequestMapping("/api/v1/applications") public class ApplicationController { @GetMapping("/me") // GET /applications/me → 200 ✓ public Page listMine() { ... } @GetMapping("/{id}") // GET /applications/{id} → 404(id 不存在)✓ public Application getById(@PathVariable Long id) { ... } @PostMapping("") // POST /applications → 404(jobId 不存在)✓ public Application create(@RequestBody ...) { ... } // 但没有写 @GetMapping("")! } ``` 当请求 `GET /applications` 进来时: - Spring 路由解析器在候选路由里找匹配 - `/applications/me` 需要路径是 `/me`,不匹配 - **`/{id}` 匹配成功** —— `id` 被解析为字符串 `"applications"` - 但方法签名上 `@PathVariable Long id` 期望 `Long` 类型 - Spring 尝试把 `"applications"` 转成 `Long` → **类型转换异常** - 全局异常处理器没兜住这个异常 → 返回 HTTP 500 `internal server error` 这是 Spring Boot Controller 常见的路由设计陷阱:**一个 Controller 既有 `/{id}` 又有 `/me`,但忘了写根路径 `@GetMapping("")` 或显式拒绝根路径的 GET 请求**。 --- ## 修复方案(后端二选一) ### 方案 A:显式拦截并返回 404(推荐,防御性编程) ```java @GetMapping("") public ResponseEntity listAll() { // applications 没有"列出所有投递"这个语义 return ResponseEntity.notFound().build(); } ``` ### 方案 B:确认不需要这个路由,忽略 如果后端根本不打算提供 `GET /applications`(全量投递列表——企业端才有意义,而且需要按企业 ID 过滤),那 Spring Boot 默认会 404。但 **当前 500 是路由冲突导致的**,需要清理路由或者统一异常处理。 ### 方案 C:按角色分发(如果后端真打算提供) ```java @GetMapping("") public ResponseEntity listAll(Authentication auth) { // STUDENT 应该只查自己的 → 404 引导去 /applications/me // COMPANY 可以查自己企业下的投递 → 需要 companyId 过滤 return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of( "code", 404, "message", "这个路由不存在,请使用 /applications/me 或 /applications/{id}" )); } ``` --- ## 其他建议 ### 1. Spring Boot 的 `/{id}` 路由建议加类型约束 ```java // 只匹配数字 ID,排除 "/me" 等子路径 @GetMapping("/{id:\\d+}") public Application getById(@PathVariable Long id) { ... } ``` 这样 `GET /applications` 和 `GET /applications/me` 不会被 `/{id}` 吃掉。 ### 2. 建议加 Controller 测试 ```java @WebMvcTest(ApplicationController.class) class ApplicationControllerTest { @Test void get_root_shouldReturn404() throws Exception { mockMvc.perform(get("/api/v1/applications")) .andExpect(status().isNotFound()); } } ``` ### 3. 统一异常处理加类型转换兜底 ```java @ExceptionHandler(MethodArgumentTypeMismatchException.class) public ResponseEntity> handleTypeMismatch(MethodArgumentTypeMismatchException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of( "code", 404, "message", "资源不存在" )); } ``` --- ## 给后端同事的话 > XX,`GET /applications`(不带 /me)带 token 返回 500 我测出来了,OpenAPI 文档里也没定义这个路由。 > > 根因是 Spring 路由冲突:Controller 里有 `GET /applications/{id}` 但没有 `GET /applications`,所以 `"applications"` 这个字符串被当成了 `@PathVariable Long id` 传给方法,类型转换失败抛异常,全局异常处理器没兜住就 500 了。 > > 前端这边**不需要这个路由**——学生端调的是 `/applications/me`(正常),投递调的是 `POST /applications`(正常),详情调的是 `GET /applications/{id}`(正常)。所以最简单的修复就是给 Controller 加个 `@GetMapping("")` 返回 404,或者 `/{id}` 加类型约束 `@GetMapping("/{id:\\d+}")` 排除子路径。 > > 具体怎么修看你那边代码风格,上面三个方案选一个就行。 --- ## 附:其他需要后端补的端点清单 (来自全量联调扫描,前端有调用但后端没端点) ### 🔴 高优先级 | 前端路径 | 用途 | 前端函数 | |---|---|---| | `GET /jobs/{id}/score` | 岗位综合评分(8 维度雷达图) | `fetchJobScore` | | `GET /jobs/{id}/salary` | 薪资分布数据 | `fetchJobSalary` | | `GET /jobs/{id}/reviews` | 面经/岗位评价列表 | `fetchJobReviews` | | `GET /jobs/{id}/competition-ranking` | 同岗位竞争力排名 | `fetchRankData` | | `GET /profile/me/career-progress` | 求职进度时间轴 | `fetchJobProgress` | | `GET /jobs/favorites` | 我的收藏岗位 | `fetchFavorites` | | `GET /conversations` | 沟通会话列表 | `fetchConversations` | | `GET /notifications` | 系统通知 | `fetchNotifications` | ### 🟡 中优先级 | 前端路径 | 用途 | 前端函数 | |---|---|---| | `GET /jobs/compare` | 岗位对比 | `fetchJobCompare` | | `GET /offers/compare` | Offer 对比 | `fetchOfferCompare` | | `GET /profile/me/preferences` | 求职偏好 | `fetchJobPreference` | | `PUT /profile/me/preferences` | 保存求职偏好 | 更新 | | `POST /jobs/{id}/reviews` | 发布评价 | `submitReview` | | `GET /interviews/me` | 面试列表 | `fetchMyInterviews` | | `GET /interviews/{id}/score` | 面试评分 | `fetchInterviewScore` | | `GET /company/candidates/{id}` | 候选人详情 | `fetchCandidateDetail` | ### 🔴 已对齐但后端仍有问题 | 端点 | 问题 | |---|---| | `POST /auth/sms/send` | `sms sender unavailable`(短信网关未通,无 mock code 旁路) | | `GET /jobs/search` / `GET /jobs` / `GET /companies/{id}/jobs` | `items: []`(后端无 job seed 数据) | | `GET /profile/me` | 返回空对象 `{}`(后端未定义 STUDENT profile 字段) | | `GET /companies/{id}` | 只有 id=1 有数据(企业 seed 不足) |