7.7 KiB
/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 大概率写了:
@RestController
@RequestMapping("/api/v1/applications")
public class ApplicationController {
@GetMapping("/me") // GET /applications/me → 200 ✓
public Page<Application> 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(推荐,防御性编程)
@GetMapping("")
public ResponseEntity<Void> listAll() {
// applications 没有"列出所有投递"这个语义
return ResponseEntity.notFound().build();
}
方案 B:确认不需要这个路由,忽略
如果后端根本不打算提供 GET /applications(全量投递列表——企业端才有意义,而且需要按企业 ID 过滤),那 Spring Boot 默认会 404。但 当前 500 是路由冲突导致的,需要清理路由或者统一异常处理。
方案 C:按角色分发(如果后端真打算提供)
@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} 路由建议加类型约束
// 只匹配数字 ID,排除 "/me" 等子路径
@GetMapping("/{id:\\d+}")
public Application getById(@PathVariable Long id) { ... }
这样 GET /applications 和 GET /applications/me 不会被 /{id} 吃掉。
2. 建议加 Controller 测试
@WebMvcTest(ApplicationController.class)
class ApplicationControllerTest {
@Test
void get_root_shouldReturn404() throws Exception {
mockMvc.perform(get("/api/v1/applications"))
.andExpect(status().isNotFound());
}
}
3. 统一异常处理加类型转换兜底
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<Map<String, Object>> 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 不足) |