DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

Spring Boot 3 接口末尾多一个斜杠就返回 404

Spring Boot 3 默认不再自动匹配尾部斜杠。为旧接口添加明确双映射,或对指定 GET 路径返回临时重定向,核对查询参数、上传行为和权限范围。

同一个接口,/catalog 能返回结果,/catalog/ 却是 404;升级 Spring Boot 3 后,旧客户端恰好还在请求后一个地址。先比对这两个请求的 HTTP 方法、完整路径和参数。如果只有尾部 / 不同,而且不带斜杠的请求能够进入原控制器,再处理路由匹配。

Spring Framework 6.0 把默认尾部斜杠匹配从 true 改为 false。默认配置下,映射 /catalog 不再顺便接收 /catalog/。这里针对 Spring MVC;显式改过匹配器、代理已经改写路径,或项目使用 WebFlux 时,要分别核对实际配置。

旧客户端暂时不能改,给这个接口两个明确地址

需要两个地址都直接执行业务,可以把它们写在同一条映射里。下面是只返回固定值的控制器示例:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CatalogController {
    @GetMapping({"/catalog", "/catalog/"})
    public String catalog() {
        return "catalog";
    }
}

上传接口也可以按同样方式保留两个路径。下面是已有控制器内的方法片段,仍要求 multipart 请求以及名为 file 的文件字段:

@PostMapping(
        path = {"/uploads", "/uploads/"},
        consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String upload(@RequestParam("file") MultipartFile file) {
    return file.getOriginalFilename();
}

这个片段只演示映射和参数,不保存文件。需要导入 Spring 的 PostMapping、RequestParam、MediaType 和 MultipartFile;实际项目保留已有校验、鉴权、文件名处理和存储流程。

双映射增加的是一个明确别名。给 /catalog 配置的权限、限流、日志和缓存规则,也要核对是否覆盖 /catalog/。路径能进入同一控制器,并不证明两套安全规则已经一致。接口还有类级别前缀时,按组合后的完整路径检查,避免只修改方法路径。

希望只保留一个地址,让旧 GET 地址重定向

对于可安全重试的读取请求,可以保留 /catalog 为正式路径,让 /catalog/ 返回临时重定向。下面的方法放入已有控制器,假定 /catalog 已有 GET 处理方法:

import java.net.URI;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;

@GetMapping("/catalog/")
public ResponseEntity<Void> redirectCatalog(HttpServletRequest request) {
    String target = request.getContextPath() + "/catalog";
    String query = request.getQueryString();
    if (query != null) {
        target += "?" + query;
    }
    return ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT)
            .location(URI.create(target))
            .build();
}

该片段只处理这个 GET 路径,保留 servlet context path 和原始查询字符串,返回 307。地址仍须符合 URI 语法;前置代理若还增加了外部路径前缀,应按实际入口调整 Location,避免把内部地址暴露给客户端。307 是临时方案,不应与前面的双映射同时添加到同一控制器,否则 GET 路径会冲突。

不要把 GET 示例扩成“所有地址去掉斜杠”的过滤器。POST 上传或写入接口若重定向,客户端可能不跟随,301、302 又可能改变请求方法;即使使用保留方法的 307、308,也要确认客户端如何重发请求体,以及重试是否会重复执行业务。对无法改动的旧 POST 客户端,明确双映射通常更便于控制兼容范围。

Spring Framework 6.2 提供 UrlHandlerFilter,可在指定路径范围内重定向或包装请求。采用它仍需明确过滤器顺序和安全规则;旧版没有该类,不能直接复制新版配置。setUseTrailingSlashMatch(true) 从 6.0 起已弃用,不宜作为长期全站兼容方案。

比较第一跳响应,确认没有误改客户端行为

在隔离环境用原客户端检查两种路径。双映射应直接进入同一个预期处理方法;重定向方案应先返回 307 和正确的 Location,第二跳才返回业务结果。只看最终 200,可能看不到客户端把方法、查询字符串或认证信息改掉了。

同时检查未获准的请求仍被拒绝、POST 不会执行两次、外部路径前缀和编码参数没有丢失。跳转还跨越协议或主机时,额外核对客户端的认证头转发规则。

如果两个地址都 404,应回到控制器是否被扫描、类级路径、HTTP 方法、context path 和代理转发路径;如果响应是 403,先查安全过滤器。尾部斜杠规则只能解释路径差异,不能修复其他层的拒绝。撤回兼容处理时恢复之前的映射或重定向配置,并通知仍使用旧路径的客户端改为正式地址。