不会飞的章鱼

熟能生巧,勤能补拙;静能生慧,为而不争;莫向外求,但求心觅;念念不忘,必有回响。

【慕聘网】后端项目初始化和基础框架设计

项目层次结构

在分布式系统中,合理的工程结构是高内聚、低耦合的基础。本项目采用 Maven 多模块聚合工程(Aggregation Project) 进行构建,确保代码的可复用性和版本管理的一致性。

1. 整体架构图解

下图展示了本项目核心模块之间的依赖关系和层次逻辑:

项目层次结构图解


2. 核心模块职责拆解

基于 Maven 聚合 的理念,我们将项目拆分为以下关键模块:

① Root Parent (根工程)

  • 职责:不包含任何业务代码,仅负责 版本管理依赖管理
  • 核心配置:通过 dependencyManagement 统一管理 Spring Boot、Spring Cloud、MyBatis Plus 以及常用工具类(如 Apache Commons, HuTool)的版本,防止版本冲突。
  • 打包方式<packaging>pom</packaging>

② hire-common (通用工具包)

  • 职责:项目的“底座”,包含所有子模块共用的基础类。
  • 内容:工具类(Utils)、全局枚举(Enums)、自定义异常(Exceptions)、返回结果封装类(JSONResult)、常量定义等。

③ hire-pojo (实体类模块)

  • 职责:统一管理所有数据模型,避免跨模块重复定义。
  • 层次结构
    • Entity: 与数据库表一一对应的持久化对象。
    • VO (View Object): 返回给前端的数据展示对象。
    • DTO (Data Transfer Object): 跨服务传输的数据载体。
    • BO (Business Object): 业务逻辑层使用的封装对象。

④ hire-api (接口依赖包)

  • 职责:定义公共的 Web 相关依赖,作为业务子模块的基础。

⑤ service-* (业务微服务集群)

  • 职责:按照业务边界拆分的独立运行单元。
  • 目前已规划模块
    • service-user: 用户中心、简历管理、鉴权服务。
    • service-company: 企业信息、职位发布、审核管理。

3. 为什么选择 Maven 聚合工程?

  1. 统一管理:只需在父工程中运行一次 mvn install,即可按顺序构建所有子模块。
  2. 依赖简洁:子模块只需声明依赖而无需指定版本,继承父工程的配置,极大降低了维护成本。
  3. 利于协作:不同团队可以专注于不同的 service-* 模块开发,同时共享 commonpojo 的成果。

[!TIP]
技术选型速览:本项目运行环境基于 JDK 1.8,核心框架采用 Spring Boot 2.7.3 搭配 Spring Cloud 2021.0.4,持久层使用 MyBatis Plus,保证了技术栈的成熟与稳定。

Maven 依赖面向对象配置

在微服务项目中,依赖管理往往是复杂度的源头。本项目采用了类似于“面向对象(OO)”的理念来配置 Maven 依赖,将父工程视为“基类”,统一管理所有依赖的版本和规格。

1. 核心理念:父子继承与解耦

我们使用 dependencyManagement 标签来实现依赖的“面向对象”管理:

  • 父工程(基类)负责“定义”:只锁定依赖的版本号(Coordinate & Version),不直接引入 Jar 包。这样可以保证父工程的“纯净”,只输出约束,不增加体积。
  • 子工程(实现类)负责“引入”:子模块按需引入依赖,且无需注明版本号。这就像是重写父类的方法或属性,直接继承父类的版本约束,彻底避免了多模块项目中的版本冲突问题。

2. Spring Boot 版本选型

本项目选择了 Spring Boot 2.7.3 作为核心。

  • 理由:2.7.x 系列是 Spring Boot 2 的最后一个大版本,生态最为成熟稳定,且能完美适配 Spring Cloud 2021.0.4Spring Cloud Alibaba 2021.0.1.0
  • 注意点:避免使用 SNAPSHOT(快照)版本,确保开发环境的一致性。

3. 核心依赖全景 (dependencyManagement)

以下是我们在根工程中统一管理的部分核心技术栈及其版本:

① 数据库与中间件

  • MySQL: 8.0.26 (驱动版本)
  • MyBatis Plus: 3.5.0 (持久层增强)
  • PageHelper: 1.4.1 (分页插件)
  • MongoDB: 3.12.11
  • Zookeeper & Curator: 3.8.0 & 5.3.0 (分布式协调)

② 工具类库

  • Apache Commons: 涉及 lang3, codec, io, fileupload 等。
  • Google Guava: 28.2-jre (强大的基础库)
  • Joda-Time: 2.10.6 (更易用的时间处理)

③ 接口与多媒体

  • Knife4j: 2.0.9 (增强型 Swagger 文档)
  • MinIO: 8.2.1 (私有云存储)
  • JavaCV & FFmpeg: 用于视频封面抓取、时长计算等处理流程。

4. parent 标签的巧妙应用

1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
<relativePath />
</parent>

通过集成官方的 spring-boot-starter-parent,我们额外获得了一大批默认的编码规范(UTF-8)、资源编译配置以及大量的默认版本号(BOM),进一步精简了我们的配置。


[!CAUTION]
重要提示:切记不要在总工程(Root)中直接使用 <dependencies> 标签引入上述所有依赖,否则所有的子微服务都会强制加载这些 Jar 包,这会违反微服务的轻量化原则。

如何快速构建 Web 接口并且暴露 API

在搭建完 Maven 聚合工程的基础架构后,下一步就是验证我们的微服务是否具备处理 Web 请求的能力。我们将通过构建一个简单的 Hello Controller 来打通整个流程。

1. 引入 Web 依赖

由于我们的微服务需要处理 HTTP 请求,必须在对应的子模块(如 service-userservice-company)中引入 Spring Boot 的 Web 启动器。通常我们会将这些通用 Web 依赖放在 hire-api 中,以便所有微服务统一种子:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

2. 编写项目配置文件 (application.yml)

在每个微服务的 src/main/resources 目录下创建 application.yml,配置其运行端口和 Tomcat 相关属性。

用户微服务示例 (7001):

1
2
3
4
5
server:
port: 7001
tomcat:
uri-encoding: UTF-8 # 确保中文路径或参数不乱码
max-swallow-size: -1 # 不限制上传文件的大小的吞吐量

企业微服务示例 (6001):

1
2
3
4
5
server:
port: 6001
tomcat:
uri-encoding: UTF-8
max-swallow-size: -1

3. 创建接口控制器 (Controller)

在对应的包路径下(如 com.imooc.controller)创建接口类,使用 Spring MVC 注解暴露 API。

1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("u") // 定义一级路由
public class HelloController {

@GetMapping("hello") // 定义二级路由
public Object hello() {
return "Hello User Service~~~";
}
}

4. 编写启动类 (Application)

创建一个带有 @SpringBootApplication 注解的启动类,执行 run 方法启动容器。

1
2
3
4
5
6
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

5. Web 接口测试

启动项目后,打开浏览器访问对应的地址进行验证:

  • 访问地址http://localhost:7001/u/hello
  • 预期结果:页面显示字符串 "Hello User Service~~~"

常见问题:404 错误

如果在访问时看到 **Whitelabel Error Page (status=404)**,请检查以下几点:

  1. 包扫描路径:启动类 Application 是否在包含 Controller 类文件夹的父包中?(Spring Boot 默认扫描启动类所在的包及其子包)。
  2. 映射路径:检查 @RequestMapping@GetMapping 的拼接是否正确。
  3. 端口冲突:确保配置文件中的端口号未被其他程序占用。

通过以上简单的五步,我们就完成了第一个微服务接口的开发与暴露。接下来,我们可以以此为基础,开始正式的业务功能开发。

SpringBoot 多环境配置与启动配置

在真实的生产开发中,一个项目往往需要运行在不同的环境中(如:开发环境 dev、测试环境 test、生产环境 prod)。每个环境的数据库地址、端口号、中间件配置各不相同。Spring Boot 提供了强大的 Profile 机制来优雅地解决这一问题。

1. 多环境配置文件命名规范

Spring Boot 约定,多环境配置文件必须遵循以下命名规则:

  • 主轴文件application.yml (存放所有环境共用的通用配置)
  • 环境规格文件application-{profile}.yml (存放特定环境的差异化配置)

本项目示例

  • application-dev.yml:本机开发、联调使用的配置。
  • application-prod.yml:线上生产环境使用的配置。

2. 环境切换的核心:spring.profiles.active

在主配置文件 application.yml 中,通过 spring.profiles.active 属性,我们可以像拨动开关一样一键切换环境。

1
2
3
spring:
profiles:
active: dev # 切换为 dev 即加载 application-dev.yml

为了给枯燥的开发生活增加一点乐趣,或是提升公司项目的专业感,我们可以自定义 Spring Boot 启动时在控制台输出的文字(Banner)或图片。

文字 Banner (banner.txt)

  1. src/main/resources 目录下创建 banner.txt
  2. 在文件中放入你想要展示的 ASCII 艺术字。
  3. 配置路径:
    1
    2
    3
    spring:
    banner:
    location: classpath:banner/banner.txt

图片 Logo (banner.img)

Spring Boot 甚至支持将图片转换为控制台字符画。

  1. 将图片(如 cat.png)放入资源目录。
  2. 配置路径:
    1
    2
    3
    4
    spring:
    banner:
    image:
    location: banner/cat.png

4. 总结:为什么要分环境配置?

  • 安全性:防止开发人员本地测试时误操作线上数据库。
  • 灵活性:无需修改代码,只需在启动命令中指定环境变量(如 java -jar app.jar --spring.profiles.active=prod)即可适应全场景部署。
  • 规范性:强制区分不同阶段的配置,是 DevOps 和持续集成的基石。

如何进行优雅的 Restful 响应封装

在 Web 应用中,前端与后端的交互本质上是“请求-响应”模式。如果后端直接根据业务逻辑返回 StringIntegerList,甚至在出错时直接抛出异常堆栈,前端将难以统一处理数据逻辑。

因此,我们需要对响应结果进行“二次包装”,确保无论是成功还是失败,返回的 JSON 结构都是一致的。

1. 响应包装流程示意图

下图展示了从 HTTP 请求到标准化 JSON 响应的转换过程:

请求响应包装流程


2. 核心组件介绍

在本项目(hire-common 模块)中,我们设计了两大组件来实现这一功能:

① ResponseStatusEnum (业务状态码枚举)

通过枚举类统一管理所有的业务状态码、是否成功标识以及响应消息。这种方式比直接在代码里写死字符串(Magic String)要优雅得多。

1
2
3
4
5
6
public enum ResponseStatusEnum {
SUCCESS(200, true, "操作成功!"),
FAILED(500, false, "操作失败!"),
UN_LOGIN(501, false, "请登录后再继续操作!"),
// ... 更多细粒度的业务状态码
}

② GraceJSONResult (统一结果包装类)

这是一个通用的 JSON 结果容器。它利用静态方法(如 okerrorMsg)快速构建响应对象。

  • status: 自定义业务状态码。
  • msg: 给前端展示的提示消息。
  • success: 布尔值,标识本次调用在业务逻辑上是否成功。
  • data: 具体承载的业务数据(Object/Map/List 等)。

3. 实战代码示例

场景一:成功返回数据

1
2
3
4
@GetMapping("hello")
public GraceJSONResult hello() {
return GraceJSONResult.ok("Hello Mupin!");
}

场景二:返回特定业务异常

1
2
3
4
public GraceJSONResult login() {
// 校验逻辑失败
return GraceJSONResult.errorCustom(ResponseStatusEnum.USER_NOT_EXIST_ERROR);
}

4. 为什么这样做?

  1. 前端友好:前端可以编写全局统一的 ResponseInterceptor,根据 status 码判断是跳转登录、弹出 Toast 还是渲染数据。
  2. 异常屏蔽:将复杂的后端异常封装为友好的消息,避免暴露底层的技术细节(如 SQL 语法错误或空指针)。
  3. 可维护性:状态码和消息集中管理,后期调整极其方便,实现了业务逻辑与响应规格的解耦。

[!TIP]
设计感悟:优雅的代码不仅仅能运行,更应该像艺术品一样整洁。统一响应封装是后端向前端交付的第一份“合同”,必须保持严谨与规范。

------ 本文结束------
如果你喜欢这篇文章,打赏一下让我开心到原地转圈圈~,金额随意,感谢鼓励与支持!