纲要
- 项目源码的章节划分与工程结构演变
- 前五章:单一
UAA后端工程 - 第六章起:
uaa与uaa-ui双模块工程 - 第八章:多服务角色工程(
uaa、todo-service、data-plan) - 第九章:集成 Keycloak 授权服务器,基于 Docker Compose 的依赖环境
- 前五章:单一
- Maven 多模块项目 POM 配置与版本管理
- 外部依赖与环境变量
.env文件管理敏感信息(密钥、Secret).env.sample样例文件的作用- Docker Compose 快速启动依赖服务
- IntelliJ IDEA 工程导入与日常操作
- 通过
pom.xml导入 Maven 工程 - 启动 Spring Boot 应用
- 配置 EnvFile 插件加载
.env环境变量 - 单元测试与集成测试的启动及环境配置差异
- 通过
- 完整可运行的示例代码
- 多模块父 POM
.env.sample模板- 单元测试与集成测试样例
项目工程结构演进
本系列教程的源码按章节顺序提供,从后端单体逐渐过渡到多模块、多服务架构,最终集成 Keycloak 实现单点登录。不同阶段的工程结构如下:
阶段1
仅包含一个 uaa 工程(Java 后端项目),所有功能均通过 REST 接口进行测试。
dir
stage2/
├── uaa/ (后端 Spring Boot 工程)
└── pom.xml (父 POM,管理子模块与版本)
阶段2
引入前端工程,目录结构变为:
dir
stage2/
├── uaa/ (后端 Spring Boot 工程)
├── uaa-ui/ (前端工程)
└── pom.xml (父 POM,管理子模块与版本)
阶段3
为演示 OAuth2 的多服务器角色环境,增加了两个服务模块:
dir
stage3/
├── uaa/
├── todo-service/
├── data-plan/
└── pom.xml
阶段4
使用 Keycloak 作为授权服务器,演示与 Keycloak 的集成以及单点登录特性。此时工程结构变为:
dir
stage4/
├── sso-client-1
├── sso-client-2
├── docker-compose.yml (以 Docker 形式启动 Keycloak、MySQL 等依赖)
└── pom.xml
对于不熟悉 Docker 的用户,仍可手动下载并安装 MySQL、Keycloak 等组件。不过推荐使用 Docker Compose,它能极大简化依赖服务的启动流程。
每个需要外部依赖的章节都会提供对应的 docker-compose.yml 文件。
Maven 多模块配置要点
从第六章开始,项目采用标准的多模块 Maven 工程结构。父 POM 集中管理版本号与子模块声明,子模块的 pom.xml 无需重复定义版本号。
父 POM 示例:
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>chapter-06-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>uaa</module>
<module>uaa-ui</module>
</modules>
<properties>
<java.version>17</java.version>
<spring-boot.version>3.2.0</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
子模块 uaa/pom.xml 只需声明对父 POM 的继承即可,依赖版本由父 POM 统一控制。
环境变量与敏感信息管理
项目中的密钥、第三方服务 Secret(例如阿里云短信、Authing 等)均通过环境变量注入,不会硬编码在代码中,也不会提交到 Git 仓库。所有需要用户自定义的环境变量均存放在 .env 文件中,该文件已被添加到 .gitignore,不会出现在版本库中。
为了方便快速配置,仓库中提供了一份 .env.sample 样例文件,列出了所有必填的键值对模板。使用者需要将其复制为 .env 并根据自身申请的 Key/Secret 填写真实值。
.env.sample 示例内容:
properties
# 数据库配置
DB_HOST=localhost
DB_PORT=3306
DB_NAME=uaa_db
DB_USER=root
DB_PASSWORD=your_db_password
# 阿里云短信
ALIBABA_CLOUD_ACCESS_KEY_ID=your_access_key
ALIBABA_CLOUD_ACCESS_KEY_SECRET=your_access_secret
# Authing 配置
AUTHING_CLIENT_ID=your_client_id
AUTHING_CLIENT_SECRET=your_client_secret
AUTHING_ISSUER=https://your-tenant.authing.cn
生产环境中,这些敏感信息通常由运维人员在服务器上直接设置系统环境变量,开发人员无需知晓最终生产值。团队内部可以自行维护一份测试专用的 .env 文件,但严禁将其提交至代码仓库。
如果未正确配置这些环境变量,启动应用时可能因缺少必要配置而失败,此时应优先检查 .env 文件是否已就位。
IDEA 工程导入与启动
导入工程
- 打开 IntelliJ IDEA,点击
Open or Import。 - 定位到对应章节的工程目录,选中
pom.xml文件,点击Open。 - 在弹出的对话框中选择
Open as Project,IDEA 将自动识别 Maven 结构并下载依赖。
启动 Spring Boot 应用
通常每个后端模块都会有一个 *Application.java 入口类,类名左侧会显示绿色启动箭头。直接点击箭头,选择 Run 或 Debug 即可启动。
但若应用依赖 .env 中的环境变量,直接启动很可能会失败。此时需要借助 EnvFile 插件。
配置 EnvFile 插件
- 确保 IDEA 已安装
EnvFile插件(可在 Plugin Marketplace 中搜索安装)。 - 打开
Run/Debug Configurations,选中需要运行的 Spring Boot 启动配置。 - 在配置界面的
EnvFile标签页中,勾选Enable EnvFile。 - 点击
+添加文件,选择项目根目录下的.env文件。
完成上述配置后,启动应用时插件会自动将 .env 中的键值对加载为系统环境变量,确保程序能够正常读取。
若插件安装后找不到 EnvFile 标签页,可尝试向上滚动配置面板,检查是否被隐藏在下方。
测试的启动与环境配置
测试代码位于 src/test/java 目录下,包结构通常与主代码对应。测试分为两类:
单元测试
不依赖 Spring 上下文,不进行任何自动装配,所有依赖均为手工构造的对象。这类测试可以直接运行,无需额外的环境变量。
java
package com.example.uaa.service;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class UserServiceUnitTest {
@Test
void shouldFormatUsername() {
UserService userService = new UserService();
String formatted = userService.formatUsername("John");
assertThat(formatted).isEqualTo("JOHN");
}
}
集成测试
需要启动 Spring 应用上下文,通常会用到 @SpringBootTest、@Autowired 等注解。这类测试属于系统级别的验证,启动时必须保证环境变量齐全,否则会初始化失败。
java
package com.example.uaa.controller;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserControllerIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void shouldReturnDefaultUser() {
String body = this.restTemplate.getForObject("/api/users/1", String.class);
assertThat(body).contains("username");
}
}
集成测试同样需要借助 EnvFile 插件加载 .env 文件。在对应的运行配置中按照相同方式添加 .env 文件即可。若希望一次运行整个测试类,可直接点击类名旁的启动箭头,或使用 Maven 命令 mvn test。
工程结构可视化
以下 Mermaid 图展示了多模块 Maven 工程的依赖与层次关系:
#mermaid-svg-Uo5DrpTKKec8f4wD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Uo5DrpTKKec8f4wD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Uo5DrpTKKec8f4wD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Uo5DrpTKKec8f4wD .error-icon{fill:#552222;}#mermaid-svg-Uo5DrpTKKec8f4wD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Uo5DrpTKKec8f4wD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Uo5DrpTKKec8f4wD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Uo5DrpTKKec8f4wD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Uo5DrpTKKec8f4wD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Uo5DrpTKKec8f4wD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Uo5DrpTKKec8f4wD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Uo5DrpTKKec8f4wD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Uo5DrpTKKec8f4wD .marker.cross{stroke:#333333;}#mermaid-svg-Uo5DrpTKKec8f4wD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Uo5DrpTKKec8f4wD p{margin:0;}#mermaid-svg-Uo5DrpTKKec8f4wD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Uo5DrpTKKec8f4wD .cluster-label text{fill:#333;}#mermaid-svg-Uo5DrpTKKec8f4wD .cluster-label span{color:#333;}#mermaid-svg-Uo5DrpTKKec8f4wD .cluster-label span p{background-color:transparent;}#mermaid-svg-Uo5DrpTKKec8f4wD .label text,#mermaid-svg-Uo5DrpTKKec8f4wD span{fill:#333;color:#333;}#mermaid-svg-Uo5DrpTKKec8f4wD .node rect,#mermaid-svg-Uo5DrpTKKec8f4wD .node circle,#mermaid-svg-Uo5DrpTKKec8f4wD .node ellipse,#mermaid-svg-Uo5DrpTKKec8f4wD .node polygon,#mermaid-svg-Uo5DrpTKKec8f4wD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Uo5DrpTKKec8f4wD .rough-node .label text,#mermaid-svg-Uo5DrpTKKec8f4wD .node .label text,#mermaid-svg-Uo5DrpTKKec8f4wD .image-shape .label,#mermaid-svg-Uo5DrpTKKec8f4wD .icon-shape .label{text-anchor:middle;}#mermaid-svg-Uo5DrpTKKec8f4wD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Uo5DrpTKKec8f4wD .rough-node .label,#mermaid-svg-Uo5DrpTKKec8f4wD .node .label,#mermaid-svg-Uo5DrpTKKec8f4wD .image-shape .label,#mermaid-svg-Uo5DrpTKKec8f4wD .icon-shape .label{text-align:center;}#mermaid-svg-Uo5DrpTKKec8f4wD .node.clickable{cursor:pointer;}#mermaid-svg-Uo5DrpTKKec8f4wD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Uo5DrpTKKec8f4wD .arrowheadPath{fill:#333333;}#mermaid-svg-Uo5DrpTKKec8f4wD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Uo5DrpTKKec8f4wD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Uo5DrpTKKec8f4wD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Uo5DrpTKKec8f4wD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Uo5DrpTKKec8f4wD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Uo5DrpTKKec8f4wD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Uo5DrpTKKec8f4wD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Uo5DrpTKKec8f4wD .cluster text{fill:#333;}#mermaid-svg-Uo5DrpTKKec8f4wD .cluster span{color:#333;}#mermaid-svg-Uo5DrpTKKec8f4wD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Uo5DrpTKKec8f4wD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Uo5DrpTKKec8f4wD rect.text{fill:none;stroke-width:0;}#mermaid-svg-Uo5DrpTKKec8f4wD .icon-shape,#mermaid-svg-Uo5DrpTKKec8f4wD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Uo5DrpTKKec8f4wD .icon-shape p,#mermaid-svg-Uo5DrpTKKec8f4wD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Uo5DrpTKKec8f4wD .icon-shape .label rect,#mermaid-svg-Uo5DrpTKKec8f4wD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Uo5DrpTKKec8f4wD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Uo5DrpTKKec8f4wD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Uo5DrpTKKec8f4wD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} modules
modules
dependencyManagement
依赖
依赖
读取
注入
通过插件
父POM
uaa模块
uaa-ui模块
Spring Boot BOM
Node/前端构建
.env 环境变量
密钥/Secret
IDEA Run Configuration
总结
本文梳理了 Spring Security + OAuth2 实战教程所涉及的项目工程结构演变、Maven 多模块配置、环境变量管理以及 IDEA 中的开发与测试环境搭建。掌握这些基础配置,是后续顺利开发 OAuth2 认证授权流程的前提。