从今天开始,博主准备更新苍穹外卖项目相关的学习笔记。
这篇是 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里可以看到模块声明:
<!-- 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中确认这些版本:
<!-- 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:
<!-- 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给接口分组:
// sky-server/src/main/java/com/sky/controller/admin/EmployeeController.java@RestController@RequestMapping("/admin/employee")@Slf4j@Tag(name="员工管理相关接口")publicclassEmployeeController{}@Tag的作用是让这一组接口在接口文档中归到同一个分类下。管理端接口多起来以后,如果没有分组,接口文档会比较乱。
5.2 方法上的接口说明
登录接口上使用@Operation描述接口作用:
// sky-server/src/main/java/com/sky/controller/admin/EmployeeController.java@PostMapping("/login")@Operation(summary="员工登录接口")publicResult<EmployeeLoginVO>login(@RequestBodyEmployeeLoginDTOemployeeLoginDTO){// 登录逻辑}这里还有一个值得注意的点:@RequestBody表示请求体中的 JSON 会绑定到EmployeeLoginDTO。所以前端传参、DTO 字段、接口文档展示的模型,其实是串在一起的。
5.3 DTO/VO 上的字段说明
当前项目里,登录请求参数用EmployeeLoginDTO表示:
// sky-pojo/src/main/java/com/sky/dto/EmployeeLoginDTO.java@Data@Schema(description="员工登录时传递的数据模型")publicclassEmployeeLoginDTOimplementsSerializable{@Schema(description="用户名")privateStringusername;@Schema(description="密码")privateStringpassword;}登录成功后的返回对象用EmployeeLoginVO表示:
// sky-pojo/src/main/java/com/sky/vo/EmployeeLoginVO.java@Data@Builder@NoArgsConstructor@AllArgsConstructor@Schema(description="员工登录返回的数据格式")publicclassEmployeeLoginVOimplementsSerializable{@Schema(description="主键值")privateLongid;@Schema(description="用户名")privateStringuserName;@Schema(description="姓名")privateStringname;@Schema(description="jwt令牌")privateStringtoken;}DTO 和 VO 分开以后,接口文档也会更清楚:请求时需要什么字段,响应时返回什么字段,读者和前端都能直接看出来。
六、springdoc 配置和验证方式
项目里通过OpenAPIBean 设置了接口文档的标题、版本和描述:
// sky-server/src/main/java/com/sky/config/WebMvcConfiguration.java@BeanpublicOpenAPIcustomOpenAPI(){returnnewOpenAPI().info(newInfo().title("苍穹外卖项目接口文档").version("2.0").description("苍穹外卖项目接口文档"));}同时配置了 Swagger UI 相关静态资源映射:
// sky-server/src/main/java/com/sky/config/WebMvcConfiguration.java@OverridepublicvoidaddResourceHandlers(ResourceHandlerRegistryregistry){log.info("开始设置静态资源映射...");registry.addResourceHandler("/swagger-ui/**").addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");}当前可以通过下面的地址查看接口文档:
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 拦截器:
// sky-server/src/main/java/com/sky/config/WebMvcConfiguration.java@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){log.info("开始注册自定义拦截器...");registry.addInterceptor(jwtTokenAdminInterceptor).addPathPatterns("/admin/**").excludePathPatterns("/admin/employee/login");}这个拦截器目前拦截的是/admin/**,接口文档地址是/swagger-ui/**,所以正常情况下不会被它拦住。后面如果自己扩大拦截范围,就要记得给接口文档和静态资源留出放行规则。
七、基于 Apifox 实现自我测试
课程资料里会提供接口定义,可以先在 YApi 中创建管理端接口项目和用户端接口项目,再把对应 JSON 文件导入进去。
我自己使用 Apifox 做接口自测时,可以通过:
项目设置 -> 导入设置 -> 选择 YApi -> 导入接口
这样做的意义是把“接口约定”和“自己写的后端代码”对起来。
比如员工登录接口,后端代码里的请求路径是:
// sky-server/src/main/java/com/sky/controller/admin/EmployeeController.java@PostMapping("/login")publicResult<EmployeeLoginVO>login(@RequestBodyEmployeeLoginDTOemployeeLoginDTO){// 登录逻辑}类上还有统一路径:
@RequestMapping("/admin/employee")所以完整登录接口路径就是:
POST /admin/employee/login自测时要重点看这些内容是否一致:
- Apifox 里的请求方法是不是
POST。 - 接口路径是不是
/admin/employee/login。 - 请求体字段是否和
EmployeeLoginDTO对应。 - 返回结果是否符合统一返回结构
Result<EmployeeLoginVO>。
这里不需要一开始就追求把所有接口测完。Day01 阶段先把导入、查看、发起一次请求这条链路跑通,后面每写一个模块再补对应接口测试。
八、登录安全:MD5 和 JWT 先看清链路
笔记里提到一个安全点:密码如果明文存放在数据库中,安全性很低,所以要进行加密后存储或比对。
当前员工登录逻辑中,Service 层会先根据用户名查询员工,再对用户输入的密码做 MD5 处理后比较:
// sky-server/src/main/java/com/sky/service/impl/EmployeeServiceImpl.javapublicEmployeelogin(EmployeeLoginDTOemployeeLoginDTO){Stringusername=employeeLoginDTO.getUsername();Stringpassword=employeeLoginDTO.getPassword();Employeeemployee=employeeMapper.getByUsername(username);if(employee==null){thrownewAccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);}password=DigestUtils.md5DigestAsHex(password.getBytes());if(!password.equals(employee.getPassword())){thrownewPasswordErrorException(MessageConstant.PASSWORD_ERROR);}if(employee.getStatus()==StatusConstant.DISABLE){thrownewAccountLockedException(MessageConstant.ACCOUNT_LOCKED);}returnemployee;}这段代码的判断顺序很清楚:
- 查不到员工,抛出账号不存在异常。
- 密码比对失败,抛出密码错误异常。
- 账号被禁用,抛出账号锁定异常。
- 都通过后,返回员工对象。
登录成功后,Controller 层会生成 JWT:
// sky-server/src/main/java/com/sky/controller/admin/EmployeeController.javaMap<String,Object>claims=newHashMap<>();claims.put(JwtClaimsConstant.EMP_ID,employee.getId());Stringtoken=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