【苍穹外卖|Day01】项目初识:从多模块结构到 OpenAPI 接口文档踩坑

从今天开始,博主准备更新苍穹外卖项目相关的学习笔记。

这篇是 Day01,目标不是一上来就写复杂业务,而是先解决三个基础问题:

  1. 一个后端项目从需求到上线大概会经历哪些环节。
  2. 苍穹外卖这个项目的多模块结构应该怎么看。
  3. 当前项目使用 Spring Boot 4.0.6 时,接口文档为什么不能继续照搬旧版 Swagger 2 / Knife4j 思路。

本文内容主要来自三部分:我的课程笔记、当前项目代码,以及 springdoc 官方文档。因为接口文档生成和框架版本强相关,所以这部分会以当前项目依赖为准,不把旧教程里的配置直接当成结论。

一、先看软件开发流程

Day01 笔记里先讲了软件开发流程。我一开始觉得这部分偏理论,但放到项目里看,其实它对应的是后面每一类文件为什么会出现。

阶段 常见产物 和后端开发的关系
需求分析 需求规格说明书、产品原型 确定系统要做哪些功能
设计 UI 设计、数据库设计、接口设计 决定页面、表结构、接口路径和参数
编码 项目代码、单元测试 按照设计实现 Controller、Service、Mapper
测试 测试用例、测试报告 验证接口、业务规则和边界条件
上线运维 环境安装、配置、部署 让项目在真实环境稳定运行

1.1 角色分工

一个完整项目里通常会有这些角色:

  • 项目经理:负责整体进度和任务分配。
  • 产品经理:整理需求,输出需求文档和产品原型。
  • UI 设计师:根据原型设计界面效果。
  • 架构师:负责整体架构和技术选型。
  • 开发工程师:完成业务编码、接口实现和必要的测试。
  • 测试工程师:编写测试用例,输出测试报告。
  • 运维工程师:负责环境搭建、部署和上线维护。

这部分先不用背得很死。对我来说,更重要的是形成一个意识:后端代码不是凭空写出来的,它背后一定有需求、接口、数据库和测试这些约束。

1.2 软件环境

项目环境一般分为三类:

环境 说明
开发环境 development 开发人员本地或团队内部调试使用
测试环境 testing 测试人员验证功能使用
生产环境 production 正式对外提供服务的环境

当前 Day01 主要是在开发环境里整理项目结构和接口文档。后面如果涉及生产环境配置,就不能直接照搬本地配置,尤其是数据库密码、JWT 密钥、OSS AccessKey 这类敏感信息。

二、苍穹外卖项目整体结构

苍穹外卖项目主要包含两个端:

  • 管理端:给商家或后台人员使用,例如员工管理、分类管理、菜品管理、订单管理。
  • 用户端:给用户使用,例如浏览菜品、下单、支付、查看订单。

前后端联调时会用到 Nginx。这里先把它理解成前端静态资源和后端接口之间的一层代理。它的常见作用包括:

  1. 提高访问速度:静态资源可以由 Nginx 处理。
  2. 进行负载均衡:后面多个服务实例时,可以由 Nginx 转发请求。
  3. 保护后端服务:后端服务不一定直接暴露给外部访问。

三、Maven 多模块:先知道每个模块放什么

当前项目是一个 Maven 父工程,下面聚合了三个子模块。

项目根目录的 pom.xml 里可以看到模块声明:

xml 复制代码
<!-- pom.xml -->
<modules>
    <module>sky-common</module>
    <module>sky-pojo</module>
    <module>sky-server</module>
</modules>

几个模块的职责如下:

模块 作用
sky-take-out Maven 父工程,统一管理依赖版本,聚合子模块
sky-common 公共模块,存放工具类、常量类、异常类、统一返回结果等
sky-pojo 模型模块,存放 Entity、DTO、VO 等
sky-server 后端服务模块,存放配置文件、Controller、Service、Mapper 等

这套结构的好处是职责比较清楚。

比如登录接口中,前端请求参数对应 sky-pojo 里的 EmployeeLoginDTO,登录成功的返回数据对应 EmployeeLoginVO,JWT 工具类在 sky-common,真正处理请求的 Controller 和 Service 在 sky-server

也就是说,读项目时不要只看 Controller。一个接口能跑起来,背后通常是多个模块一起配合。

四、当前项目版本:为什么旧教程不能直接套

我的项目导入后做过版本适配,目前能从 pom.xml 中确认这些版本:

xml 复制代码
<!-- pom.xml -->
<parent>
    <artifactId>spring-boot-starter-parent</artifactId>
    <groupId>org.springframework.boot</groupId>
    <version>4.0.6</version>
    <relativePath/>
</parent>

<properties>
    <java.version>25</java.version>
    <mybatis.spring>4.0.1</mybatis.spring>
    <springdoc>3.0.3</springdoc>
    <swagger>2.2.47</swagger>
</properties>

这里最关键的是 Spring Boot 4.0.6Java 25

版本一旦比较新,很多旧资料里的依赖就不能直接复制。比如接口文档这块,旧项目里常见的是 Swagger 2 或旧版 Knife4j,但当前项目实际接入的是 springdoc:

xml 复制代码
<!-- sky-server/pom.xml -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>

所以这篇的接口文档部分,不写成"Knife4j 配置教程",而是写成"当前项目使用 springdoc/OpenAPI 3 生成接口文档"。

五、接口文档踩坑:从 Swagger 2 切到 OpenAPI 3

这一块是 Day01 最容易卡住的地方。

一开始看到"Swagger 可以生成接口文档,Knife4j 集成 Swagger"时,很容易沿着旧教程去找 @Api@ApiOperation@ApiModelProperty 这些注解。但当前项目已经不是这套写法。

根据 springdoc 官方文档,从 SpringFox / Swagger 2 迁移到 springdoc 时,需要移除旧的 SpringFox 和 Swagger 2 依赖,改用 springdoc-openapi-starter-webmvc-ui,并替换成 Swagger 3 注解。常见替换关系如下:

Swagger 2 旧注解 OpenAPI 3 新注解 作用
@Api @Tag 标注 Controller 分组
@ApiOperation @Operation 标注接口方法说明
@ApiParam @Parameter 标注请求参数
@ApiModel @Schema 标注模型类
@ApiModelProperty @Schema 标注模型字段
@ApiResponse(code = ...) @ApiResponse(responseCode = ...) 标注响应码

这里的重点不是记表格,而是先判断自己项目到底用了哪套依赖。依赖路线确认错了,后面注解、配置、静态资源路径都会跟着错。

5.1 Controller 上的接口分组

当前项目在 EmployeeController 上使用 @Tag 给接口分组:

java 复制代码
// sky-server/src/main/java/com/sky/controller/admin/EmployeeController.java
@RestController
@RequestMapping("/admin/employee")
@Slf4j
@Tag(name = "员工管理相关接口")
public class EmployeeController {
}

@Tag 的作用是让这一组接口在接口文档中归到同一个分类下。管理端接口多起来以后,如果没有分组,接口文档会比较乱。

5.2 方法上的接口说明

登录接口上使用 @Operation 描述接口作用:

java 复制代码
// sky-server/src/main/java/com/sky/controller/admin/EmployeeController.java
@PostMapping("/login")
@Operation(summary = "员工登录接口")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
    // 登录逻辑
}

这里还有一个值得注意的点:@RequestBody 表示请求体中的 JSON 会绑定到 EmployeeLoginDTO。所以前端传参、DTO 字段、接口文档展示的模型,其实是串在一起的。

5.3 DTO/VO 上的字段说明

当前项目里,登录请求参数用 EmployeeLoginDTO 表示:

java 复制代码
// sky-pojo/src/main/java/com/sky/dto/EmployeeLoginDTO.java
@Data
@Schema(description = "员工登录时传递的数据模型")
public class EmployeeLoginDTO implements Serializable {

    @Schema(description = "用户名")
    private String username;

    @Schema(description = "密码")
    private String password;
}

登录成功后的返回对象用 EmployeeLoginVO 表示:

java 复制代码
// sky-pojo/src/main/java/com/sky/vo/EmployeeLoginVO.java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "员工登录返回的数据格式")
public class EmployeeLoginVO implements Serializable {

    @Schema(description = "主键值")
    private Long id;

    @Schema(description = "用户名")
    private String userName;

    @Schema(description = "姓名")
    private String name;

    @Schema(description = "jwt令牌")
    private String token;
}

DTO 和 VO 分开以后,接口文档也会更清楚:请求时需要什么字段,响应时返回什么字段,读者和前端都能直接看出来。

六、springdoc 配置和验证方式

项目里通过 OpenAPI Bean 设置了接口文档的标题、版本和描述:

java 复制代码
// sky-server/src/main/java/com/sky/config/WebMvcConfiguration.java
@Bean
public OpenAPI customOpenAPI() {
    return new OpenAPI()
            .info(new Info()
                    .title("苍穹外卖项目接口文档")
                    .version("2.0")
                    .description("苍穹外卖项目接口文档"));
}

同时配置了 Swagger UI 相关静态资源映射:

java 复制代码
// sky-server/src/main/java/com/sky/config/WebMvcConfiguration.java
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    log.info("开始设置静态资源映射...");
    registry.addResourceHandler("/swagger-ui/**")
            .addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/");
    registry.addResourceHandler("/webjars/**")
            .addResourceLocations("classpath:/META-INF/resources/webjars/");
}

当前可以通过下面的地址查看接口文档:

text 复制代码
http://localhost:8080/swagger-ui/index.html#/

如果访问不到,我会优先按这个顺序排查:

  1. pom.xml 中是否存在 springdoc-openapi-starter-webmvc-ui
  2. 是否还残留旧版 SpringFox / Swagger 2 依赖。
  3. Controller、DTO、VO 的注解包是否来自 io.swagger.v3.oas.annotations
  4. 静态资源路径是否配置正确。
  5. 拦截器是否误拦截了接口文档路径。

第 5 点也要看当前代码。项目中注册了管理端 JWT 拦截器:

java 复制代码
// sky-server/src/main/java/com/sky/config/WebMvcConfiguration.java
@Override
public void addInterceptors(InterceptorRegistry registry) {
    log.info("开始注册自定义拦截器...");
    registry.addInterceptor(jwtTokenAdminInterceptor)
            .addPathPatterns("/admin/**")
            .excludePathPatterns("/admin/employee/login");
}

这个拦截器目前拦截的是 /admin/**,接口文档地址是 /swagger-ui/**,所以正常情况下不会被它拦住。后面如果自己扩大拦截范围,就要记得给接口文档和静态资源留出放行规则。

七、基于 Apifox 实现自我测试

课程资料里会提供接口定义,可以先在 YApi 中创建管理端接口项目和用户端接口项目,再把对应 JSON 文件导入进去。

我自己使用 Apifox 做接口自测时,可以通过:

项目设置 -> 导入设置 -> 选择 YApi -> 导入接口

这样做的意义是把"接口约定"和"自己写的后端代码"对起来。

比如员工登录接口,后端代码里的请求路径是:

java 复制代码
// sky-server/src/main/java/com/sky/controller/admin/EmployeeController.java
@PostMapping("/login")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
    // 登录逻辑
}

类上还有统一路径:

java 复制代码
@RequestMapping("/admin/employee")

所以完整登录接口路径就是:

text 复制代码
POST /admin/employee/login

自测时要重点看这些内容是否一致:

  1. Apifox 里的请求方法是不是 POST
  2. 接口路径是不是 /admin/employee/login
  3. 请求体字段是否和 EmployeeLoginDTO 对应。
  4. 返回结果是否符合统一返回结构 Result<EmployeeLoginVO>

这里不需要一开始就追求把所有接口测完。Day01 阶段先把导入、查看、发起一次请求这条链路跑通,后面每写一个模块再补对应接口测试。

八、登录安全:MD5 和 JWT 先看清链路

笔记里提到一个安全点:密码如果明文存放在数据库中,安全性很低,所以要进行加密后存储或比对。

当前员工登录逻辑中,Service 层会先根据用户名查询员工,再对用户输入的密码做 MD5 处理后比较:

java 复制代码
// sky-server/src/main/java/com/sky/service/impl/EmployeeServiceImpl.java
public Employee login(EmployeeLoginDTO employeeLoginDTO) {
    String username = employeeLoginDTO.getUsername();
    String password = employeeLoginDTO.getPassword();

    Employee employee = employeeMapper.getByUsername(username);

    if (employee == null) {
        throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
    }

    password = DigestUtils.md5DigestAsHex(password.getBytes());
    if (!password.equals(employee.getPassword())) {
        throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
    }

    if (employee.getStatus() == StatusConstant.DISABLE) {
        throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
    }

    return employee;
}

这段代码的判断顺序很清楚:

  1. 查不到员工,抛出账号不存在异常。
  2. 密码比对失败,抛出密码错误异常。
  3. 账号被禁用,抛出账号锁定异常。
  4. 都通过后,返回员工对象。

登录成功后,Controller 层会生成 JWT:

java 复制代码
// sky-server/src/main/java/com/sky/controller/admin/EmployeeController.java
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.EMP_ID, employee.getId());

String token = JwtUtil.createJWT(
        jwtProperties.getAdminSecretKey(),
        jwtProperties.getAdminTtl(),
        claims);

这里要注意两点:

  • 写博客时不要暴露真实 JWT 密钥、数据库密码、OSS AccessKey。
  • MD5 在课程项目里方便理解加密比对流程,但生产系统里通常还要考虑更安全的密码存储方案,例如加盐哈希或专门的密码编码器。

这也是专业度容易扣分的地方。不能只说"用了 MD5 所以安全",更准确的说法是:当前项目通过 MD5 避免明文比对,但这只是学习项目里的实现方式,生产环境还需要更严格的安全设计。

九、TODO 的作用:标记后续要补的坑

笔记里还提到了 TODO。它的作用不是随便写一行注释,而是标记"这里后面还要回来处理"。

比如某段逻辑暂时能跑通,但还有优化空间,就可以先用 TODO 标出来。这样后面在 IDE 里统一查看时,不容易忘掉。

不过 TODO 也不能滥用。如果一个问题已经修完,最好同步清理或改成准确注释。否则时间久了,别人看到 TODO 会误以为这个逻辑还没完成。

十、Day01 总结

Day01 的核心不是写多少业务代码,而是把项目的入口摸清楚。

这一篇整理下来,我觉得有几个结论比较重要:

  1. 软件开发流程决定了代码不是孤立存在的。后端接口、DTO、数据库字段都和需求、原型、接口设计有关。
  2. 苍穹外卖是多模块项目sky-commonsky-pojosky-server 分别承担公共能力、模型对象和后端服务职责。
  3. 接口文档要以当前项目版本为准。当前项目使用 Spring Boot 4.0.6 和 springdoc,不适合直接套旧 Swagger 2 注解。
  4. 自测要落到具体路径和对象上 。比如登录接口要能对应到 POST /admin/employee/loginEmployeeLoginDTOEmployeeLoginVO 和统一返回结果。
  5. 安全内容要写边界。MD5 和 JWT 能帮助理解认证链路,但真实生产环境还要关注密钥保护、日志脱敏和更安全的密码存储。

参考资料:

  • springdoc 官方文档:https://springdoc.org/
  • springdoc 迁移说明:https://springdoc.org/migrating-from-springfox.html
相关推荐
jasnet_u1 小时前
SpringCloud中服务集成PlumeLog日志系统
spring·spring cloud·plumelog·日志收集
李白的天不白1 小时前
针对你遇到的 Client.Timeout exceeded 问题,我判断是防火墙拦截了 HTTPS 流量
java
linweidong1 小时前
Java 后端开发面试 50 个高频易混淆知识点详解
java·spring boot·spring·spring cloud·面试·mybatis·spring事务
码语智行1 小时前
应用启动和关闭监听器功能分析
java·spring boot
Resky08181 小时前
什么是 Spring IOC:倒过来让容器帮你 new,而不是你到处 new
java·spring
AutumnWind04201 小时前
【JDK动态代理源码梳理】
java·后端·spring
暗夜猎手-大魔王1 小时前
转载--Hermes Agent 10 | 7 层安全防线:从用户授权到输入净化
java·数据库·安全
idolao3 小时前
Oligo 7.60 安装教程:引物设计+Java 环境配置
java·开发语言