一个脚手架拆成 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); } }
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 if (shouldRenewToken(token, 30 * 60 * 1000 )) { String newToken = createToken(userId + "-" + role, account.getPassword(), 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 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)); } } @Data @TableName("vue_login") public class User { @TableId(type = IdType.AUTO) private Integer id; private String username; private String password; } @Mapper public interface UserMapper extends BaseMapper <User> { @Select("SELECT * FROM vue_login WHERE username = #{username}") User findByUsername (@Param("username") String username) ; } @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; } } @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 @PostMapping("/login") public R login (@RequestBody User user) { User dbUser = userMapper.findByUsername(user.getUsername()); if (dbUser == null ) return R.error("用户不存在" ); if (!BCrypt.checkpw(user.getPassword(), dbUser.getPassword())) { return R.error("密码错误" ); } return R.success(); } @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 依赖
开闭原则
新增角色只需新增策略类,不改已有代码
核心思想 :模块化不是为了炫技,而是让团队可以按需引入——只需要缓存的模块不引入安全,只需要日志的模块不引入数据库。每个模块独立编译、独立测试、独立版本管理。