Spring cloud

模块化架构设计:从五层分层到 12 模块拆分再到 RuoYi 二开

2026-07-15 #Spring cloud#微服务#RuoYi

一个脚手架拆成 12 个 Maven 模块——每个公共能力独立成模块,按需引入。另一个项目用经典五层分层。还有一个用 RuoYi 18 功能模块开箱即用。三种架构设计思路有什么区别?本文把模块化架构的完整知识体系从头到尾讲透。


第一章:为什么要拆模块?

1.1 单体 vs 模块化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
单体(所有代码在一个module)           模块化(按职责拆分)
┌──────────────────────┐ ┌─────────────┐
│ │ │ common-core │ ← 基础CRUD
│ 所有配置、工具、 │ ├─────────────┤
│ 实体、安全、日志 │ │common-cache │ ← Redis
│ 全部在一起 │ ├─────────────┤
│ │ │common-security│ ← JWT
│ 问题: │ ├─────────────┤
│ • 改一个工具要全量 │ │ common-log │ ← AOP日志
│ 编译 │ ├─────────────┤
│ • 引入安全模块就 │ │ common-db │ ← 数据访问聚合
│ 连日志也一起引入 │ ├─────────────┤
│ • 无法按需依赖 │ │ server-auth │ ← 可启动服务
│ │ └─────────────┘
└──────────────────────┘

第二章:springboot-core-arch 的 12 模块设计

2.1 模块清单

模块 职责 依赖 有无代码
common-common 公共基础(注解、枚举、DTO) 无内部依赖
common-response 统一响应封装 common-common
common-exception 异常处理 common-common, common-response
common-security 安全(JWT、过滤器、策略) common-common, common-dto, common-entity, common-response, common-exception
common-entity 实体类 common-common, common-security
common-dto 数据传输对象 common-common, common-entity
common-core 泛型CRUD基类 common-response
common-cache Redis配置和工具 common-common, common-security
common-log AOP审计日志 common-common, common-security
common-db 数据访问聚合 大部分common模块 聚合点
server-auth 认证服务(可启动) common-core, common-db, common-security
server-user 用户服务(预留) 大部分common模块 仅启动类

2.2 模块依赖图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
           common-common (最底层)

┌───────────┼───────────┐
▼ ▼ ▼
common-response common-exception
│ │ │
▼ │ │
common-core │ │
▼ │
common-security <──┘

┌───────────┤
▼ ▼
common-entity common-cache common-log


common-dto


common-db (聚合点)

├──> server-auth
└──> server-user

common-db 是聚合点:它本身没有代码,只是把所有 common 模块的依赖集中起来,让 server-auth/server-user 只需依赖一个 common-db 即可获得全部公共能力。

2.3 泛型 CRUD 基类(common-core)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public abstract class BaseController<T, ID> {
protected final BaseService<T, ID> baseService;

protected BaseController(BaseService<T, ID> baseService) {
this.baseService = baseService;
}

@GetMapping("/{id}")
public R<T> getById(@PathVariable ID id) {
return R.success(baseService.selectById(id));
}

@GetMapping
public R<List<T>> getAll() {
return R.success(baseService.selectAll());
}

@PostMapping
public R<T> create(@RequestBody T entity) {
baseService.insert(entity);
return R.success(entity);
}

@PutMapping
public R<T> update(@RequestBody T entity) {
baseService.update(entity);
return R.success(entity);
}

@DeleteMapping("/{id}")
public R<Void> delete(@PathVariable ID id) {
baseService.deleteById(id);
return R.ok();
}
}

使用方式:子类继承即可获得标准 CRUD 接口:

1
2
3
4
5
6
@RestController
@RequestMapping("/admin")
public class AdminController extends BaseController<Admin, String> {
public AdminController(AdminService service) { super(service); }
// 自动有 getById, getAll, create, update, delete
}

2.4 统一响应(common-response)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class R<T> {
private boolean success;
private int code;
private String message;
private T data;

public static <T> R<T> success(T data) {
return new R<>(true, 20000, "成功", data);
}
}

public enum ResultCodeEnum {
SUCCESS(true, 20000, "成功"),
UNAUTHORIZED(false, 40100, "未登录或Token过期"),
FORBIDDEN(false, 40300, "无权限访问");
}

2.5 安全策略模式(common-security)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 策略接口
public interface RoleStrategy {
String getRole();
LoginResult login(Account account);
void updatePassword(Account account);
Account selectById(String userId);
}

// 上下文(自动收集所有策略实现)
@Component
public class RoleStrategyContext {
private final Map<String, RoleStrategy> strategyMap = new HashMap<>();

public RoleStrategyContext(List<RoleStrategy> strategies) {
for (RoleStrategy strategy : strategies) {
strategyMap.put(strategy.getRole().toUpperCase(), strategy);
}
}

public RoleStrategy getStrategy(String roleCode) {
RoleEnum.fromCode(roleCode);
return strategyMap.get(roleCode.toUpperCase());
}
}

2.6 Token 自动续期(common-security)

1
2
3
4
5
6
7
8
9
10
// JwtAuthenticationFilter 中
if (shouldRenewToken(token, 30 * 60 * 1000)) { // 剩余<30分钟
String newToken = createToken(userId + "-" + role,
account.getPassword(), 24); // 新Token 24小时
response.setHeader("Renew-Token", newToken);
}

// 前端只需检查响应头
const renewToken = response.headers['renew-token'];
if (renewToken) localStorage.setItem('token', renewToken);

2.7 AOP 审计日志(common-log)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Aspect
@Component
public class AuditLogAspect {
@Around("@annotation(auditLogRecord)")
public Object handleAudit(ProceedingJoinPoint joinPoint,
AuditLogRecord auditLogRecord) throws Throwable {
String username = SecurityContextHolder.getContext()
.getAuthentication().getName();
String ip = ((ServletRequestAttributes) RequestContextHolder
.currentRequestAttributes()).getRequest().getRemoteAddr();
try {
return joinPoint.proceed();
} finally {
AuditLog log = AuditLog.builder()
.username(username)
.action(auditLogRecord.action())
.resource(auditLogRecord.resource())
.ipAddress(ip)
.details(Arrays.toString(joinPoint.getArgs()))
.build();
auditLogService.saveLog(log);
}
}
}

2.8 常见架构陷阱

循环依赖

1
2
3
common-security 依赖 common-entity(需要Account实体)
common-entity 依赖 common-security(需要Account继承Security的BaseEntity)
↑ 循环!Maven编译报错

解法:提取公共接口到 common-common。

两套 JWT 体系并存

1
2
JwtUtil.java   → subject + roles claim → AuthController使用
TokenUtils.java → audience="userId-role" → JwtAuthenticationFilter使用

两套 Token 格式不兼容。解法:统一为一套。


第三章:springbootmultimodule 的五层分层

3.1 五层结构

1
2
3
4
5
6
7
8
9
10
11
┌──────────────────────────────────┐
│ web (Controller) │ 接收请求、返回响应
├──────────────────────────────────┤
│ server (Service) │ 业务逻辑
├──────────────────────────────────┤
│ dao (Mapper) │ 数据访问
├──────────────────────────────────┤
│ model (Entity) │ 数据模型
├──────────────────────────────────┤
│ base (Utils) │ 基础工具
└──────────────────────────────────┘

3.2 依赖关系

1
2
3
4
5
web 依赖 server
server 依赖 dao, model, base
dao 依赖 model, base
model 依赖 base
base 不依赖任何内部模块

依赖方向:上层依赖下层,下层不依赖上层。base 是最底层。

3.3 各层代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// base 模块
public class JwtUtils {
public static String createToken(String data, String sign) {
return JWT.create().withAudience(data)
.withExpiresAt(DateUtil.offsetDay(new Date(), 1))
.sign(Algorithm.HMAC256(sign));
}
}

// model 模块
@Data
@TableName("vue_login")
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
private String password;
}

// dao 模块
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT * FROM vue_login WHERE username = #{username}")
User findByUsername(@Param("username") String username);
}

// server 模块
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public User login(String username, String password) {
User user = userMapper.findByUsername(username);
if (user == null || !BCrypt.checkpw(password, user.getPassword())) {
throw new RuntimeException("用户名或密码错误");
}
return user;
}
}

// web 模块
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;

@PostMapping("/login")
public R login(@RequestBody Map<String, String> params) {
User user = userService.login(params.get("username"), params.get("password"));
String token = JwtUtils.createToken(
user.getId() + "-" + user.getRole(), user.getPassword());
return R.success().put("token", token);
}
}

3.4 五层 vs 三层

维度 三层 五层
模块数 3 (Controller/Service/DAO) 5 (+Model/Base)
实体归属 在Service或DAO中 独立model模块
工具归属 散落各处 独立base模块
复用性
适合 小项目 多团队协作

第四章:RuoYi 框架二次开发

4.1 7 模块结构

1
2
3
4
5
6
7
8
Vue3-RuoYi/
├── ruoyi-admin # 主启动模块(可部署)
├── ruoyi-common # 通用工具
├── ruoyi-framework # 框架核心(安全、缓存、AOP)
├── ruoyi-generator # 代码生成器
├── ruoyi-quartz # 定时任务
├── ruoyi-system # 系统模块(用户、角色、菜单)
└── ruoyi-ui # Vue前端

4.2 18 个内置功能

用户管理、部门管理、岗位管理、菜单管理、角色管理、字典管理、参数配置、通知公告、操作日志、登录日志、在线用户、定时任务、代码生成、API文档、服务器监控、缓存监控、表单构建、连接池监控。

4.3 代码生成器

使用 Velocity 模板引擎,从数据库表结构自动生成 CRUD 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
## controller.java.vm
@RestController
@RequestMapping("/${moduleName}/${businessName}")
public class ${ClassName}Controller extends BaseController {

@Autowired
private I${ClassName}Service ${className}Service;

@PreAuthorize("@ss.hasPermi('${moduleName}:${businessName}:list')")
@GetMapping("/list")
public TableDataInfo list(${ClassName} ${className}) {
startPage();
List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
return getDataTable(list);
}
}

4.4 RBAC 五表

1
2
3
4
5
用户(sys_user) ── 用户角色(sys_user_role) ── 角色(sys_role)

角色菜单(sys_role_menu)

菜单(sys_menu)

4.5 权限校验

1
2
3
4
@PreAuthorize("@ss.hasPermi('system:user:list')")    // 查询
@PreAuthorize("@ss.hasPermi('system:user:add')") // 新增
@PreAuthorize("@ss.hasPermi('system:user:edit')") // 修改
@PreAuthorize("@ss.hasPermi('system:user:remove')") // �删除

格式模块:业务:操作

4.6 Quartz 定时任务

1
2
3
4
5
6
@Component("ryTask")
public class RyTask {
public void ryParams(String params) {
System.out.println("执行定时任务,参数:" + params);
}
}

在管理界面配置调用目标:ryTask.ryParams('hello'),Cron表达式:0 0/5 * * * ?

4.7 三种架构对比

维度 springboot-core-arch springbootmultimodule Vue3-RuoYi
模块数 12 5 7
模块粒度 每个公共能力独立 按功能域聚合 按功能域聚合
可启动模块 2 (auth/user) 1 (web) 1 (admin)
代码生成器
定时任务 Quartz
复杂度
适合场景 学习架构设计 小项目 快速开发后台

第五章:分层原则

5.1 依赖方向

1
2
3
✅ 正确:web → server → dao → model → base
❌ 错误:dao → server(下层依赖上层)
❌ 错误:model → dao(实体依赖数据访问)

5.2 职责边界

应该做 不应该做
web 参数校验、调用Service、封装响应 业务逻辑、数据库操作
server 业务逻辑、事务管理 HTTP相关代码、SQL
dao 数据库CRUD 业务逻辑
model 数据定义 任何逻辑
base 通用工具 业务相关代码

5.3 典型违反

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ❌ Controller中写了业务逻辑
@PostMapping("/login")
public R login(@RequestBody User user) {
User dbUser = userMapper.findByUsername(user.getUsername()); // 直接操作DAO
if (dbUser == null) return R.error("用户不存在");
if (!BCrypt.checkpw(user.getPassword(), dbUser.getPassword())) { // 业务逻辑在Controller
return R.error("密码错误");
}
return R.success();
}

// ✅ 正确:Controller只做调度
@PostMapping("/login")
public R login(@RequestBody User user) {
User result = userService.login(user.getUsername(), user.getPassword());
return R.success(result);
}

总结

设计原则 说明
单一职责 每个模块只做一件事(缓存就是缓存,日志就是日志)
依赖方向 公共模块不依赖业务模块,业务模块按需引入公共模块
聚合点 common-db 聚合所有公共依赖,减少 server 层的依赖声明
可启动 只有 server-auth/server-user 有 main 方法,common 模块是 jar 依赖
开闭原则 新增角色只需新增策略类,不改已有代码

核心思想:模块化不是为了炫技,而是让团队可以按需引入——只需要缓存的模块不引入安全,只需要日志的模块不引入数据库。每个模块独立编译、独立测试、独立版本管理。

评论
分享