
写接口的人多把接口写明白的人少。能跑通的接口和能在线上活过三个大促的接口中间隔的不是框架版本而是一堆在敲回车前觉得“以后再说”的小决定。能跑通只是起点能在异常流量下保持数据正确才是接口的真正及格线。SpringBoot让一个空服务三分钟就能启动也让那些省略掉的细节在流量放大后变成事故。今天聊的不是JVM调优不是分库分表而是每个controller里都会遇到的、让人熟视无睹的边界问题。【参数校验信任是Bug的温床】如果你现在打开项目搜索Controller里的方法参数也许能看到大量没有Valid的实体。前端说“用户名必填”后端就信了等第三方误传空串接口开始返回让你脸红的500。不校验参数的接口等于把异常的解释权交给数据库、框架甚至远程客户端。常见误解是加了Validated就万事大吉却忘了它不会级联校验内部嵌套的DTO用错分组又会放过本不该放过的字段。更值得留意的是DTO与实体不应该混用。让实体直接承担HTTP请求体会在未来扩展实体字段时意外扩大接口暴露面。在一切追求快的年代把DTO和Entity分开不叫“代码冗余”叫守住接口的边界。校验失败的信息也要统一最好通过全局异常把MethodArgumentNotValidException翻译成固定的错误码和参数名而不是让前端看到“must not be null”这种原生态判决书。【统一响应不仅仅是一层壳】为了便于客户端处理团队习惯封装ResultT但封装很快衍生新花样有的返回code有的返回status成功时data有值失败时塞一段英文文本在message里。没有约定的一致响应体接口文档越厚越堵不住客户端代码的互相拆台。一个容易被忽略的点是data里只应该放真正的业务结果traceId和timestamp等元数据别混进业务模型。真正想清楚响应体的团队能让前端只用一次拦截器处理完所有情况业务成功、参数非法、认证失败、系统异常。如果你把HttpStatus与业务码混在一起网关重试的时候就不知道按什么语义处理你的响应。业务码需要分层参数错误、业务规则错误、外部依赖错误、未知系统错误每一类都应有明确的数字区间。【异常处理把“哦”变成“这里错了”】写接口时很多人以为用try-catch包住业务代码就可以但在线上成千上万行日志不能靠catch看清。没有全局异常处理的接口排错过程就像在深夜档案室找一本没有编号的书。推荐建立RestControllerAdvice把异常按类别映射到统一的错误码参数校验失败映射到400认证/授权失败映射到401/403业务异常映射到独立业务码未预期异常降级到500。更隐蔽的坑是advice类中的异常处理方法顺序子类异常的处理方法若写在父类异常之后可能永远不会被触发。处理器的优先级和顺序和异常本身一样需要被测试覆盖。此外不要将堆栈直接返回给客户端但要把traceId放进响应体。一个可落地的经验让客服的反馈从“系统报错了”进化成“错误码A0032订单号88时间戳是……”你在凌晨三点会感激这个设计。【日志别让排查请求变成考古】很多接口的“日志”只是方法入口打一行“xxx进来了”然后就没有然后。请求失败后你连它走到哪一步都不知道。没有traceId的日志是一堆没有页码的碎纸。从网关或过滤器生成traceId扔进MDC配合logback的pattern输出整条链路的日志就能被一键聚合。但记录日志要克制用户密码、手机号、身份证号一旦出现在日志里那已经不是技术问题而是安全事故。日志应该记录什么入参只打印必要的脱敏关键值出参打印状态和耗时外部调用记录目标和耗时。好的接口日志不在于量而在于是否能在五步之内重现一次请求的完整路径。对每一次“用户说报错了你说查不了”的窘境都需要在日志设计阶段提前考虑好。【幂等用户远比你想象的更执着】双击保存、网络超时、支付回调重复通知这些场景在真实世界中的频率远高于开发环境。如果写接口没有幂等保护一次支付回调可能创建两笔订单一个保存操作重复插入N条记录。没有幂等设计的写接口是在邀请用户和上游系统帮你制造脏数据。实现幂等不能用简单的if (exists) return因为两个并发请求都查不到历史记录仍会同时插入。通常需要两条防线第一道是Redis token或状态机前置判断拦截绝大多数重复请求第二道是数据库唯一键或乐观锁版本号在最后关头挡住并发穿透。只有当重复请求拿到和第一次相同的结果幂等才开始成立。还要想清楚Redis中的令牌消费与数据库写入不是原子操作令牌消费失败是否要业务回滚回滚后如果恢复请求重放是否应该重新返回原结果这些问题在写设计文档时就要回答。【并发SpringBoot并不保证正确性】Controller默认是线程安全的吗不单例bean中的共享变量才是噩梦源泉。许多人写SQL时想得清楚回到代码里却忘记加锁。典型的账户扣减逻辑先select余额判断足够再update两个线程同时读100同时减10最终余额变成90而不是80。如果每次都先select再update并发一旦升高事故不是“万一”而是“必然”。正确做法是在SQL层用原子条件set balance balance - ? where balance ?或者在更新语句加上版本号判断。SpringBoot让事务管理变得简单但锁的粒度和隔离级别需要你自己决定。最怕的是用分布式事务的复杂度去解决一个本该用版本号解决的简单问题。上线前问自己这个数据会被多个线程同时修改吗乐观锁够不够要不要Redis分布式锁把并发思考前置接口才能扛过峰值。【事务边界别让一个请求把数据库拧成麻绳】事务经常被加到service方法上但很多人没想清楚它的边界。一个大事务如果包住远程调用、消息发送、文件下载会导致连接长期被占用线程池耗尽只是时间问题。事务不是越大越安全把外部调用移到事务提交之后才是对自己数据库连接的爱护。另一个暗坑是事务方法必须通过代理调用在类内部用this调用会让Transactional静默失效spring初学者的接口因此常常死在“有事务注解却不存在事务”的幻觉里。在写接口前先梳理一下这个请求涉及几条SQL、是否必须同一数据源、能不能异步处理。接口里最贵重的资源不是CPU而是那些有限且不可置疑的连接与线程。【分页别把慢查询藏进“下一页”】分页是列表接口的标配它也是慢查询高发地。多数人会用PageHelper或Spring Data的PageRequest却忘了对pageSize限流。一个max size不设限的分页接口等同于为“拖垮数据库”提供了合规入口。如果需要做导出请走异步任务而不是把第10000页的数据直接拉进HTTP响应。当页码足够大时深分页需要扫描大量行再丢弃性能会急剧恶化。深分页场景更适合游标分页用唯一有序字段的where条件代替offset每次只取一页。排序参数也必须用白名单校验不能把外部字符串直接拼进order by否则就为SQL注入开了一扇后门。【接口版本你欠旧调用方一个交代】服务越来越被多方调用时版本边界就能决定事故范围。如果一开始没有版本策略后续任何破坏性变更都可能让整个系统变成一场灾难。没有版本管理的接口等于把自己钉在“永远不能改”的十字架上。有人把版本写进URL简单直接但每个大版本都要复制一套controller用Accept-Version请求头管理版本则更考验路由设计。重要的不是选哪种形式而是提前约好兼容窗口。还要考虑“破坏性变更”不止是删除字段字段类型变化、枚举增加取值、默认值修改都是潜在不兼容点。建立契约测试用消费者驱动的模式发布变更才能让接口升级不那么像赌博。【安全认证通过不等于权限过关】接口安全最容易出现的问题是前置拦截器校验完token就放行到controllerService里直接拿请求参数中的userId去查询却不判断这个userId是不是当前人。认证解决“你是谁”鉴权解决“你能做什么”这两个概念经常被混为一谈。如果用了Spring Security建议在controller或service方法上使用PreAuthorize(hasAuthority(order:update))这类表达式而不是在方法里到处写if/else。任何接口都不要相信前端传来的“当前用户”当前用户只能从SecurityContextHolder中取值。JWT要设过期时间RefreshToken单独设计。不要为了少数客户端的方便把有效期拉长到一年那等于给未来的数据泄露案提前准备好长期的犯罪时间。【文档与契约别等三个月后再来猜字段】SpringBoot集成Swagger/OpenAPI非常容易难的是文档与代码同步。很多接口的注解停留在上线那天后来字段改了文档忘了改前端照着旧文档传参后端默默返回400。一个不能与代码自动校验的接口文档最终会成为误导工具。可以在CI中跑OpenAPI schema的diff一旦对外字段发生破坏性修改就让构建失败迫使开发者显式决定版本升级。接口能否长期健康不取决于注释写得多优美而在于契约是否被自动化守护。每一个对外接口都应当有明确的example、错误码清单、限流阈值和维护人否则三个月后填坑的还是你自己。【超时与限流给依赖和流量都戴上缰绳】最常见的接口事故是Controller调第三方HTTP服务时没有超时对端响应缓慢你的tomcat线程被占满随后新请求排队链路雪崩。不设超时的接口等于把自己服务的可用性押注在别人的响应速度上。无论RestTemplate还是WebClient连接超时与读取超时都必须显式配置。另一步是保护自身限流方案可以选Guava、RedisLua或Sentinel但核心在于失败反馈。当流量超过阈值返回429并附带Retry-After让客户端知道何时重试而不是用500刺激客户端无限重试。超时、重试与熔断是三个不同概念重试必须配合幂等熔断要设置半开恢复冷冰冰的超时配置无法替代整个链路的稳定性设计。回到那个新建Controller的午后。我们总倾向于告诉自己“先实现再优化”可一旦接口部署到公网所有不守规则的细节都会被流量放大。决定一个接口稳定性的往往不是架构上的灵光一现而是这些边界处反复被磨过的约束。SpringBoot把开发门槛降得很低但这恰恰要求我们主动抬高审美线。把每一个接口当成要交付的API去设计把每一次参数校验当成一扇门去锁把每一次日志输出当成向未来的自己传递线索。能做到这些你写的就不仅仅是一个可以运行的接口而是可以在生产环境中真正被信任的服务。