2团
Published on 2025-03-25 / 6 Visits
0
0

Spring Boot多模块项目通过包路径添加API前缀

1. 前言

在微服务项目中,建议所有接口使用统一的API前缀,多类业务可用不同前缀区分。这样便于Nginx/Openresty等反向代理统一处理,好处如下:

  1. 简化配置:统一前缀可减少API代理配置项,降低管理复杂度和出错风险;

  2. 便于开发:统一前缀利于为相同业务编写Lua插件,实现诸如鉴权等功能;

  3. 保障安全:避免Swagger、Actuator等敏感接口暴露到公网,降低安全风险。

2. 解决方案

在项目开发中,通常采用Maven构建多模块架构,将项目拆分为多个独立的模块,每个模块专注于特定的功能或业务逻辑。在此基础上,我们可以将API前缀的统一配置项集中放置在基础模块中,供各个业务模块进行引用。

接下来,将以yudao-cloud项目为例进行详细说明。

2.1 配置统一的controller包名

在统一API前缀之前,必须确保模块中的Controller包名遵循统一的命名规则。在yudao-cloud项目中,与Admin相关的API所在的Controller包名采用“**.controller.admin.**”的形式,而业务相关的API所在的Controller包名则采用“**.controller.app.**”的形式,具体如下所示:

@ConfigurationProperties(prefix = "yudao.web")
@Validated
@Data
public class WebProperties {

    @NotNull(message = "APP API 不能为空")
    private Api appApi = new Api("/app-api", "**.controller.app.**");
    @NotNull(message = "Admin API 不能为空")
    private Api adminApi = new Api("/admin-api", "**.controller.admin.**");

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Valid
    public static class Api {

        /**
         * API 前缀,实现所有 Controller 提供的 RESTFul API 的统一前缀
         *
         *
         * 意义:通过该前缀,避免 Swagger、Actuator 意外通过 Nginx 暴露出来给外部,带来安全性问题
         *      这样,Nginx 只需要配置转发到 /api/* 的所有接口即可。
         *
         * @see YudaoWebAutoConfiguration#configurePathMatch(PathMatchConfigurer)
         */
        @NotEmpty(message = "API 前缀不能为空")
        private String prefix;

        /**
         * Controller 所在包的 Ant 路径规则
         *
         * 主要目的是,给该 Controller 设置指定的 {@link #prefix}
         */
        @NotEmpty(message = "Controller 所在包不能为空")
        private String controller;
    }
}

2.2 添加统一API前缀

通过实现 WebMvcConfigurer 接口并重写 configurePathMatch 方法,可以利用 AntPathMatcher 进行路径匹配,从而为符合特定要求的请求路径添加统一的 API 前缀。

@AutoConfiguration
@EnableConfigurationProperties(WebProperties.class)
public class YudaoWebAutoConfiguration implements WebMvcConfigurer {

    @Resource
    private WebProperties webProperties;
    /**
     * 应用名
     */
    @Value("${spring.application.name}")
    private String applicationName;

    @Override
    public void configurePathMatch(@NotNull PathMatchConfigurer configurer) {
        configurePathMatch(configurer, webProperties.getAdminApi());
        configurePathMatch(configurer, webProperties.getAppApi());
    }

    /**
     * 设置 API 前缀,仅仅匹配 controller 包下的
     *
     * @param configurer 配置
     * @param api        API 配置
     */
    private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) {
        AntPathMatcher antPathMatcher = new AntPathMatcher(".");
        configurer.addPathPrefix(api.getPrefix(), clazz -> clazz.isAnnotationPresent(RestController.class)
                && antPathMatcher.match(api.getController(), clazz.getPackage().getName())); // 仅仅匹配 controller 包
    }
}


Comment