从今天开始,博主准备更新苍穹外卖项目相关的学习笔记。
这篇是 Day01,目标不是一上来就写复杂业务,而是先解决三个基础问题:
- 一个后端项目从需求到上线大概会经历哪些环节。
- 苍穹外卖这个项目的多模块结构应该怎么看。
- 当前项目使用 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。这里先把它理解成前端静态资源和后端接口之间的一层代理。它的常见作用包括:
- 提高访问速度:静态资源可以由 Nginx 处理。
- 进行负载均衡:后面多个服务实例时,可以由 Nginx 转发请求。
- 保护后端服务:后端服务不一定直接暴露给外部访问。
三、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.6 和 Java 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#/
如果访问不到,我会优先按这个顺序排查:
pom.xml中是否存在springdoc-openapi-starter-webmvc-ui。- 是否还残留旧版 SpringFox / Swagger 2 依赖。
- Controller、DTO、VO 的注解包是否来自
io.swagger.v3.oas.annotations。 - 静态资源路径是否配置正确。
- 拦截器是否误拦截了接口文档路径。
第 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
自测时要重点看这些内容是否一致:
- Apifox 里的请求方法是不是
POST。 - 接口路径是不是
/admin/employee/login。 - 请求体字段是否和
EmployeeLoginDTO对应。 - 返回结果是否符合统一返回结构
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;
}
这段代码的判断顺序很清楚:
- 查不到员工,抛出账号不存在异常。
- 密码比对失败,抛出密码错误异常。
- 账号被禁用,抛出账号锁定异常。
- 都通过后,返回员工对象。
登录成功后,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 的核心不是写多少业务代码,而是把项目的入口摸清楚。
这一篇整理下来,我觉得有几个结论比较重要:
- 软件开发流程决定了代码不是孤立存在的。后端接口、DTO、数据库字段都和需求、原型、接口设计有关。
- 苍穹外卖是多模块项目 。
sky-common、sky-pojo、sky-server分别承担公共能力、模型对象和后端服务职责。 - 接口文档要以当前项目版本为准。当前项目使用 Spring Boot 4.0.6 和 springdoc,不适合直接套旧 Swagger 2 注解。
- 自测要落到具体路径和对象上 。比如登录接口要能对应到
POST /admin/employee/login、EmployeeLoginDTO、EmployeeLoginVO和统一返回结果。 - 安全内容要写边界。MD5 和 JWT 能帮助理解认证链路,但真实生产环境还要关注密钥保护、日志脱敏和更安全的密码存储。
参考资料:
- springdoc 官方文档:
https://springdoc.org/ - springdoc 迁移说明:
https://springdoc.org/migrating-from-springfox.html