# Controller 接口 `@RequestBody` 缺失审计 > 审计时间:2026-09-16 · 范围:`ai-server/src/main/java/**/*Controller.java`(45 个 controller) > 生成方式:脚本静态扫描后端映射注解与方法签名 + 交叉比对前端全部 `defHttp` POST 调用点 ## 〇、修复记录(2026-09-16 当日完成) **结论:76 个缺注解端点已全部处理完毕;复扫 `broken = 0`、`missingBindingAnnotation = 0`。** 下方第一~八节保留的是**修复前**的审计状态,作为问题清单与判定依据留档。 | 项 | 内容 | | --- | --- | | 改动文件 | 32 个 controller + 新增 1 个 DTO(`common/model/person/dto/DeletePersonGroupDTO.java`) | | 改动内容 | 74 个 POJO 参数前加 `@RequestBody`;`/pg/delete` 改 `@RequestBody DeletePersonGroupDTO`;`statSecondCallNight` 的无效 MyBatis `@Param("query")` 替换为 `@RequestBody` | | 清理 | 删除被替换后失效的 `import org.apache.ibatis.annotations.Param`(CallNightController);删除 4 处与通配符 import 重复的显式 `RequestBody` import;顺带删除 3 个 controller 中未使用的 `org.apache.ibatis.annotations.Delete` import | | 结构校验 | 脚本比对修复前后:**映射注解(URL)零改动**、方法签名除 `@RequestBody` 外零改动、新增行仅 75 条签名行 + 32 条 import | | 编译 | `mvn -pl ai-server clean compile` → **BUILD SUCCESS**(621 源文件) | | 复扫 | `writeEndpoints=98 / missingBindingAnnotation=0 / broken=0 / annotated=97 + multipart=1` | | 回退方式 | 32 个文件中有 31 个改动前与 HEAD 一致 → `git checkout -- <文件>` 可完整回退;**`DataProfileController.java` 修复前已有他人/既有本地改动**(import 由 4 行显式改为 `annotation.*` 通配符),`git checkout` 会连带丢弃该改动,回退请用备份副本 | **尚未做**:运行期端到端验证(起后端发 JSON body 请求确认筛选条件真正生效)。静态层面已闭环。 ## 一、结论摘要 | 指标 | 数量 | | --- | --- | | Controller 文件 | 45(其中 36 个含 POST/PUT 端点,9 个仅 GET,1 个为空壳) | | 写端点(POST/PUT/PATCH)总数 | 98 | | **缺 `@RequestBody` 的端点** | **76** | | ├ 其中**已确认有问题**(前端以 JSON body 调用) | **75** | | └ 其中暂无前端调用方(需人工确认) | 1 | | 已正确(含 `@RequestBody`) | 21 | | multipart 上传端点(写法正确) | 1 | ## 二、判定标准(为什么是「有问题」而不是「风格问题」) 1. 前端 axios 默认 `content-type` 是 **`application/x-www-form-urlencoded`**(`core/utils/http/axios/index.ts`), 只有 `defHttp.postJson()` 会显式设成 `application/json` 并把 `data` 作为 **JSON 请求体**发出。 2. 后端方法参数**不加注解**时,Spring MVC 走 `ModelAttributeMethodProcessor`,只从 **query string / form 字段**绑定, **完全不会解析 JSON body**。 3. ⇒ 前端发 JSON body + 后端参数无注解 = **body 被丢弃,参数对象所有字段为 null**。 表现:筛选条件(号码/时间/分页)静默失效、返回全量或空数据;个别场景直接 500/NPE。 > 同类缺陷在本仓库已有先例并被修过:`/case/create`、`/case/open`、`/dm/preFile` 等(提交前用 `curl` 观察 `parameters={}` → 500)。 ## 三、确认有问题的端点(75 个,按模块分组) ### 通信分析(7) | # | 位置 | 方法 | 路径 | 缺注解的参数 | 前端调用点(postJson) | | --- | --- | --- | --- | --- | --- | | 1 | `controller/CallContinuousController.java:34` | `statFirstCallContinuous` | `/call/continuous/statFirstCallContinuous` | `CallContinuousQuery query` | `api/callApi.ts:14` | | 2 | `controller/CallContinuousController.java:46` | `statSecondCallContinuous` | `/call/continuous/statSecondCallContinuous` | `CallContinuousQuery query` | `api/callApi.ts:23` | | 3 | `controller/CallNightController.java:37` | `statFirstCallNight` | `/call/night/statFirstCallNight` | `CallNightQuery query` | `api/callApi.ts:34` | | 4 | `controller/CallNightController.java:49` | `statSecondCallNight` | `/call/night/statSecondCallNight` | `@Param("query") CallNightQuery query` | `api/callApi.ts:40` | | 5 | `controller/CallRecordController.java:33` | `getCallRecord` | `/cr/getCallRecord` | `CallRecordQuery query` | `api/callApi.ts:62` | | 6 | `controller/CallSensitiveController.java:35` | `statFirstCallSensitive` | `/call/sensitive/statFirstCallSensitive` | `CallSensitiveQuery query` | `api/callApi.ts:48` | | 7 | `controller/CallSensitiveController.java:47` | `statSecondCallSensitive` | `/call/sensitive/statSecondCallSensitive` | `CallSensitiveQuery query` | `api/callApi.ts:54` | ### 数据治理(6) | # | 位置 | 方法 | 路径 | 缺注解的参数 | 前端调用点(postJson) | | --- | --- | --- | --- | --- | --- | | 1 | `controller/GovernTreeController.java:58` | `getTreeTransPersonPage` | `/gt/getTreeTransPersonPage` | `GovernTreeQuery dto` | `govern/governApi.ts:117` | | 2 | `controller/GovernTreeController.java:70` | `getTreeTransBankPage` | `/gt/getTreeTransBankPage` | `GovernTreeQuery dto` | `govern/governApi.ts:123` | | 3 | `controller/GovernTreeController.java:82` | `getTreeCallPersonPage` | `/gt/getTreeCallPersonPage` | `GovernTreeQuery dto` | `govern/governApi.ts:129` | | 4 | `controller/GovernTreeController.java:94` | `getTreeCallOperPage` | `/gt/getTreeCallOperPage` | `GovernTreeQuery dto` | `govern/governApi.ts:135` | | 5 | `controller/GovernTreeController.java:106` | `getTreeCallPersonUseOpenInfoPage` | `/gt/getTreeCallPersonUseOpenInfoPage` | `GovernTreeQuery dto` | `govern/governApi.ts:141` | | 6 | `controller/GovernTreeController.java:118` | `getTreeCallOperUseOpenInfoPage` | `/gt/getTreeCallOperUseOpenInfoPage` | `GovernTreeQuery dto` | `govern/governApi.ts:147` | ### 关系图谱(4) | # | 位置 | 方法 | 路径 | 缺注解的参数 | 前端调用点(postJson) | | --- | --- | --- | --- | --- | --- | | 1 | `controller/GraphController.java:39` | `graph` | `/graph/view` | `GraphQuery query` | `api/analysis.ts:1249` | | 2 | `controller/GraphController.java:51` | `transDetail` | `/graph/transDetail` | `GraphTransDetailQuery query` | `api/analysis.ts:1264` | | 3 | `controller/GraphController.java:63` | `callDetail` | `/graph/callDetail` | `GraphCallDetailQuery query` | `api/analysis.ts:1277` | | 4 | `controller/GraphController.java:68` | `nodeDetail` | `/graph/nodeDetail` | `GraphCallDetailQuery query` | `api/analysis.ts:1289` | ### 时空/轨迹基础(5) | # | 位置 | 方法 | 路径 | 缺注解的参数 | 前端调用点(postJson) | | --- | --- | --- | --- | --- | --- | | 1 | `controller/SpecialDateController.java:26` | `create` | `/sdc/create` | `SpecialDateRecord sdr` | `api/personApi.ts:61` | | 2 | `controller/SpecialDateController.java:32` | `update` | `/sdc/update` | `SpecialDateRecord sdr` | `api/personApi.ts:70` | | 3 | `controller/TimeSeriesController.java:22` | `getTimeSeries` | `/otg/timeSeries/list` | `TimeSeriesQuery query` | `api/otgApi.ts:54` | | 4 | `controller/TimeSeriesController.java:27` | `detailTimeSeries` | `/otg/timeSeries/detailTimeSeries` | `TimeSeriesQuery query` | `api/otgApi.ts:65` | | 5 | `controller/TimeSeriesController.java:32` | `jumpTimeSeries` | `/otg/timeSeries/jump` | `TimeSeriesQuery query` | `api/otgApi.ts:76` | ### 对象画像(15) | # | 位置 | 方法 | 路径 | 缺注解的参数 | 前端调用点(postJson) | | --- | --- | --- | --- | --- | --- | | 1 | `controller/DataProfileController.java:52` | `callOftenTop10` | `/dp/callOftenTop10` | `CallOftenTop10Query query` | `api/dpApi.ts:51` | | 2 | `controller/DataProfileController.java:65` | `callStatStation` | `/dp/callStatStation` | `CallStatStationQuery query` | `api/dpApi.ts:57` | | 3 | `controller/DataProfileController.java:78` | `getCallRecordSummary` | `/dp/getCallRecordSummary` | `CallRecordSummaryQuery query` | `api/dpApi.ts:63` | | 4 | `controller/DataProfileController.java:90` | `getCallOperPhoneInfo` | `/dp/getCallOperPhoneInfo` | `CallOperPhoneInfoQuery query` | `api/dpApi.ts:69` | | 5 | `controller/DataProfileController.java:153` | `getTransBankCardInfo` | `/dp/getTransBankCardInfo` | `TransBankCardInfoQuery query` | `api/dpApi.ts:95` | | 6 | `controller/DataProfileController.java:165` | `transAmountTrendYM` | `/dp/transAmountTrendYM` | `TransTrendQuery query` | `api/dpApi.ts:107` | | 7 | `controller/DataProfileController.java:177` | `transAmountTrendY` | `/dp/transAmountTrendY` | `TransTrendQuery query` | `api/dpApi.ts:113` | | 8 | `controller/IntimacyController.java:32` | `update` | `/intimacy/modify` | `List intimacyList` | `api/personApi.ts:43` | | 9 | `controller/PersonBasicInfoController.java:24` | `create` | `/pbi/create` | `PersonBasicInfoDTO dto` | `api/personApi.ts:90` | | 10 | `controller/PersonBasicInfoController.java:46` | `update` | `/pbi/update` | `PersonBasicInfoDTO dto` | `api/personApi.ts:108` | | 11 | `controller/PersonBasicInfoController.java:57` | `personOrder` | `/pbi/personOrder` | `List list` | `api/personApi.ts:141` | | 12 | `controller/PersonGroupController.java:31` | `create` | `/pg/create` | `PersonGroup personGroup` | `api/personApi.ts:160` | | 13 | `controller/PersonGroupController.java:42` | `update` | `/pg/update` | `PersonGroup personGroup` | `api/personApi.ts:169` | | 14 | `controller/PersonGroupController.java:53` | `delete` | `/pg/delete` | `Long id` | `api/personApi.ts:178` | | 15 | `controller/PersonGroupController.java:65` | `addPerson` | `/pg/addPerson` | `AddPersonGroupDTO dto` | `api/personApi.ts:187` | ### 平台(表/案件/系统)(2) | # | 位置 | 方法 | 路径 | 缺注解的参数 | 前端调用点(postJson) | | --- | --- | --- | --- | --- | --- | | 1 | `controller/TableInfoController.java:72` | `create` | `/tpl/create` | `TableInfo tableInfo` | `tpl/tplApi.ts:63` | | 2 | `controller/TableInfoController.java:81` | `update` | `/tpl/update` | `TableInfo tableInfo` | `tpl/tplApi.ts:69` | ### 活动轨迹(15) | # | 位置 | 方法 | 路径 | 缺注解的参数 | 前端调用点(postJson) | | --- | --- | --- | --- | --- | --- | | 1 | `controller/ExpressInfoController.java:32` | `statFirstExpressInfo` | `/expressInfo/statFirstExpressInfo` | `ExpressInfoQuery query` | `api/trackApi.ts:51` | | 2 | `controller/ExpressInfoController.java:43` | `statSecondExpressInfo` | `/expressInfo/statSecondExpressInfo` | `ExpressInfoQuery query` | `api/trackApi.ts:60` | | 3 | `controller/TrackCellTowerController.java:32` | `statFirstTrackCellTower` | `/track/cellTower/statFirstTrackCellTower` | `TrackCellTowerQuery query` | `api/trackApi.ts:71` | | 4 | `controller/TrackCellTowerController.java:44` | `statSecondTrackCellTower` | `/track/cellTower/statSecondTrackCellTower` | `TrackCellTowerQuery query` | `api/trackApi.ts:77` | | 5 | `controller/TrackEnLocalController.java:35` | `statFirstTrackEnLocal` | `/track/enLocal/statFirstTrackEnLocal` | `TrackEnLocalQuery query` | `api/trackApi.ts:85` | | 6 | `controller/TrackEnLocalController.java:41` | `statSecondTrackEnLocal` | `/track/enLocal/statSecondTrackEnLocal` | `TrackEnLocalQuery query` | `api/trackApi.ts:94` | | 7 | `controller/TrackMeetController.java:33` | `statFirstTrackMeet` | `/track/meet/statFirstTrackMeet` | `TrackMeetQuery query` | `api/trackApi.ts:112` | | 8 | `controller/TrackMeetController.java:45` | `statSecondTrackMeet` | `/track/meet/statSecondTrackMeet` | `TrackMeetQuery query` | `api/trackApi.ts:121` | | 9 | `controller/TrackTogetherLiveController.java:36` | `statFirstTrackTogetherLive` | `/track/togetherLive/statFirstTrackTogetherLive` | `TrackTogetherLiveQuery query` | `api/trackApi.ts:132` | | 10 | `controller/TrackTogetherLiveController.java:41` | `statSecondTrackTogetherLive` | `/track/togetherLive/statSecondTrackTogetherLive` | `TrackTogetherLiveQuery query` | `api/trackApi.ts:141` | | 11 | `controller/TrackTogetherLiveController.java:52` | `hotelStayInfoDetail` | `/track/togetherLive/hotelStayInfoDetail` | `TogetherLiveDetailQuery togetherLiveDetailQuery` | `api/trackApi.ts:153` | | 12 | `controller/TrackTogetherLiveController.java:64` | `togetherLiveInfoDetail` | `/track/togetherLive/togetherLiveInfoDetail` | `TogetherLiveDetailQuery togetherLiveDetailQuery` | `api/trackApi.ts:165` | | 13 | `controller/TrackTogetherTravelController.java:33` | `statTogetherTravel` | `/track/togetherTravel/statTogetherTravel` | `TrackTogetherTravelQuery query` | `api/trackApi.ts:179` | | 14 | `controller/TrackTogetherTravelController.java:38` | `statSecondTogetherTravel` | `/track/togetherTravel/statSecondTogetherTravel` | `TrackTogetherTravelQuery query` | `api/trackApi.ts:188` | | 15 | `controller/TrackTogetherTravelController.java:43` | `statThreeTogetherTravel` | `/track/togetherTravel/statThreeTogetherTravel` | `TrackTogetherTravelQuery query` | `api/trackApi.ts:200` | ### 交易分析(21) | # | 位置 | 方法 | 路径 | 缺注解的参数 | 前端调用点(postJson) | | --- | --- | --- | --- | --- | --- | | 1 | `controller/CashKeyController.java:44` | `create` | `/trans/cashKey/createOrUpdate` | `TransCashKey transCashKey` | `api/transApi.ts:62` | | 2 | `controller/FinancialKeyController.java:38` | `createOrUpdate` | `/trans/financialKey/createOrUpdate` | `TransFinancialKey transFinancialKey` | `api/transApi.ts:82` | | 3 | `controller/FixedDepositKeyController.java:40` | `createOrUpdate` | `/fixedDepositKey/createOrUpdate` | `TransFixedDepositKey transFixedDepositKey` | `api/transApi.ts:102` | | 4 | `controller/TransBigController.java:39` | `statFirstTransBig` | `/trans/big/statFirstTransBig` | `TransBigQuery query` | `api/transApi.ts:128` | | 5 | `controller/TransBigController.java:51` | `statSecondTransBig` | `/trans/big/statSecondTransBig` | `TransBigQuery query` | `api/transApi.ts:134` | | 6 | `controller/TransBigController.java:63` | `statThirdTransBig` | `/trans/big/statThirdTransBig` | `TransBigQuery query` | `api/transApi.ts:140` | | 7 | `controller/TransCardHoldController.java:55` | `statTogetherDoTrans` | `/trans/cardHold/statTogetherDoTrans` | `StatTogetherDoTransQuery query` | `api/transApi.ts:206` | | 8 | `controller/TransCardHoldController.java:75` | `statTransCardHold` | `/trans/cardHold/statTransCardHold` | `StatTransCardHoldQuery query` | `api/transApi.ts:215` | | 9 | `controller/TransCashFlowController.java:33` | `statFirstTransCashFlow` | `/trans/cashFlow/statFirstTransCashFlow` | `TransRecordSummaryQuery query` | `api/transApi.ts:116` | | 10 | `controller/TransContinuousController.java:34` | `statFirstTransContinuous` | `/trans/continuous/statFirstTransContinuous` | `TransContinuousQuery query` | `api/transApi.ts:148` | | 11 | `controller/TransContinuousController.java:46` | `statSecondTransContinuous` | `/trans/continuous/statSecondTransContinuous` | `TransContinuousQuery query` | `api/transApi.ts:157` | | 12 | `controller/TransFinancialController.java:31` | `statFirstTransFinancial` | `/trans/financial/statFirstTransFinancial` | `TransFinancialQuery query` | `api/transApi.ts:265` | | 13 | `controller/TransFixedDepositController.java:36` | `statFirstTransFixedDeposit` | `/trans/fixedDeposit/statFirstTransFixedDeposit` | `TransFixedDepositQuery query` | `api/transApi.ts:273` | | 14 | `controller/TransFixedDepositController.java:48` | `statSecondTransFixedDepositList` | `/trans/fixedDeposit/statSecondTransFixedDepositList` | `TransFixedDepositQuery query` | `api/transApi.ts:282` | | 15 | `controller/TransFrequencyController.java:33` | `statFirstTransFrequency` | `/trans/frequency/statFirstTransFrequency` | `TransFrequencyQuery query` | `api/transApi.ts:226` | | 16 | `controller/TransFundFlowController.java:31` | `getTransFundFlowPage` | `/trans/fundsFlow/getTransFundFlowPage` | `TransRecordQuery query` | `api/transApi.ts:168` | | 17 | `controller/TransFundFlowController.java:42` | `transFlowGraph` | `/trans/fundsFlow/graph` | `TransFlowGraphQuery query` | `api/transApi.ts:177` | | 18 | `controller/TransFundFlowController.java:53` | `transFlowTrace` | `/trans/fundsFlow/trace` | `TransFlowGraphQuery param` | `api/transApi.ts:186` | | 19 | `controller/TransRecordController.java:19` | `getTransRecord` | `/trans/getTransRecord` | `TransRecordQuery query` | `api/transApi.ts:257` | | 20 | `controller/TransSensitiveController.java:34` | `statFirstTransSensitive` | `/trans/sensitive/statFirstTransSensitive` | `TransSensitiveQuery query` | `api/transApi.ts:237` | | 21 | `controller/TransSensitiveController.java:46` | `statSecondTransSensitive` | `/trans/sensitive/statSecondTransSensitive` | `TransSensitiveQuery query` | `api/transApi.ts:246` | ## 四、暂无前端调用方(1 个,需人工确认) | 位置 | 方法 | 路径 | 参数 | | --- | --- | --- | --- | | `controller/TransCashFlowController.java:45` | `statTransCashCallBeforeAfterTimeLine` | `/trans/cashFlow/statTransCashCallBeforeAfterTimeLine` | `TransCashCallBeforeAfterQuery query` | > 前端未在 `ai-frontend/src/**` 中找到调用点(可能:未接入、由旧页面/外部调用、或拼 URL 方式特殊)。 > 若确认前端会用 JSON body 调用,同样需要补 `@RequestBody`。 ## 五、特殊修法(不能简单加注解) | 端点 | 现状 | 问题 | 建议 | | --- | --- | --- | --- | | `POST /pg/delete`(PersonGroupController:53) | 后端 `delete(Long id)`,前端 body `{ id }` | 直接给 `Long` 加 `@RequestBody` 会变成"JSON 对象 → Long"反序列化失败(400) | 前端改 `params: { id }` + 后端 `@RequestParam Long id`;或新增 `IdDTO` 用 `@RequestBody` | | `POST /intimacy/modify`(IntimacyController:32) | 参数 `List`,前端发数组 | 需 `@RequestBody List`(数组体) | 加 `@RequestBody` 即可,字段与前端 `IntimacyConfigDTO` 兼容 | | `POST /pbi/personOrder`(PersonBasicInfoController:57) | 参数 `List`,前端发数组 | 同上 | 加 `@RequestBody` | | `POST /call/night/statSecondCallNight`(CallNightController:49) | 参数写成 `@Param("query") CallNightQuery query` | `@Param` 是 MyBatis 注解,在 controller 上无效,仍属"无绑定注解" | 去掉 `@Param` 换 `@RequestBody` | ## 六、写法正确的对照(21 个 + 1 个 multipart,供参照) - `/chat/sessions` —— `controller/AgentChatController.java:35` - `/chat/sessions/{sessionId}/title` —— `controller/AgentChatController.java:59` - `/chat/sessions/{sessionId}/pin` —— `controller/AgentChatController.java:75` - `/chat/messages` —— `controller/AgentChatController.java:91` - `/chat/messages/{messageId}/star` —— `controller/AgentChatController.java:107` - `/chat/stream` —— `controller/AgentChatController.java:129` - `/models` —— `controller/AgentModelController.java:48` - `/models/{id}` —— `controller/AgentModelController.java:56` - `/models/{id}/default` —— `controller/AgentModelController.java:74` - `/dm/preFile` —— `controller/DmController.java:57` - `/dm/rawPreview` —— `controller/DmController.java:93` - `/dm/doClean` —— `controller/DmController.java:104` - `/dm/getFiles` —— `controller/DmController.java:139` - `/dm/deletedFiles` —— `controller/DmController.java:149` - `/dm/saveGovernCon` —— `controller/DmController.java:168` - `/gt/treeTablePage` —— `controller/GovernTreeController.java:46` - `/dp/transOfPersons` —— `controller/DataProfileController.java:117` - `/dp/getTransRecordSummary` —— `controller/DataProfileController.java:129` - `/case/create` —— `controller/CaseInfoController.java:50` - `/case/updatePwd` —— `controller/CaseInfoController.java:73` - `/case/open` —— `controller/CaseInfoController.java:85` - `/dm/preFileUpload`(multipart,写法正确)—— `controller/DmController.java:78` ## 七、其它发现 - `ResidentPopulationController` 只有类级 `@RequestMapping("/rp")`、**没有任何端点方法**,属空壳类(可删或待补)。 - 9 个 controller 只有 GET 端点(`SystemController`/`TowerController`/`GovernController`/`SearchController`/`CleanErrorLogController`/`AgentResultController`/`AgentPythonFileController`/`TransFastFundFlowController`/`ResidentPopulationController`),本次审计不涉及。 - AI Agent 模块(`AgentChatController`/`AgentModelController`/`DmController`)此前已补过注解,本次复扫全部通过。 ## 八、修复建议(已于当日执行完毕,见第〇节) 对 75 个端点:给唯一的 POJO/实体参数加 `@RequestBody`(`import org.springframework.web.bind.annotation.RequestBody;`), 属机械改动、可用脚本批量处理;改完 `mvn -pl ai-server clean compile` 通过即可。 `/pg/delete` 与 `/call/night/statSecondCallNight` 需按上表特殊处理。 验证方式:起后端后对 1~2 个端点发 JSON body 请求,确认筛选条件生效(对照今天的日志里是否出现 `parameters={}`)。