第一次接手SaaS项目时,我盯着电脑屏幕发呆了整整半小时。作为习惯了单体架构的Java开发者,突然要设计一个支持多租户的系统,那种感觉就像被丢进深海却只学过泳池游泳。但三个月后,当系统成功上线并稳定运行,我才明白这段转型经历的价值。本文将分享如何用SpringBoot+Vue构建第一个多租户系统,重点不是代码细节,而是技术决策背后的思考过程。
面对多租户实现方案,我调研了四种主流方式:
最终选择MyBatis-Plus动态数据源实现独立数据库方案,主要基于以下考虑:
| 方案类型 | 隔离性 | 扩展性 | 运维复杂度 | 改造成本 |
|---|---|---|---|---|
| 独立Schema | 中 | 中 | 中 | 低 |
| 共享表 | 低 | 高 | 低 | 最低 |
| 独立数据库 | 最高 | 高 | 高 | 中 |
| 混合模式 | 高 | 最高 | 最高 | 高 |
提示:对于金融、医疗等对数据隔离要求高的领域,独立数据库几乎是必选项。而电商类应用可能更适合共享表方案。
MyBatis-Plus动态数据源的配置核心在于yml文件:
yaml复制spring:
datasource:
dynamic:
primary: master
datasource:
master:
url: jdbc:mysql://localhost:3306/tenant1
username: root
password: 123456
tenant2:
url: jdbc:mysql://localhost:3306/tenant2
username: root
password: 123456
实际项目中,这些配置应该来自配置中心而非硬编码。动态切换数据源的关键是继承AbstractRoutingDataSource:
java复制public class TenantDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TenantContext.getCurrentTenant();
}
}
作为后端开发者学习Vue,最大的挑战不是语法本身,而是思维模式的转换。我总结了几个关键突破点:
建议的学习路径:
一个典型的API调用示例:
javascript复制// 在Vue组件中调用后端API
async fetchUserData() {
try {
const response = await axios.get('/api/users', {
headers: {
'X-Tenant-ID': this.$store.state.tenantId
}
});
this.users = response.data;
} catch (error) {
console.error('获取用户数据失败:', error);
}
}
注意:多租户系统中,前端必须确保每个请求都携带租户标识。可以通过axios拦截器统一处理。
在初始项目中,我犯了一个典型错误:追求最新版本的依赖。结果遇到了各种兼容性问题,比如:
最终我确定了依赖选择的三原则:
推荐的基础依赖组合:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
多租户系统的数据库设计需要平衡灵活性和复杂性。我的经验是:先用最简单的结构验证核心概念,再逐步完善。
基础用户表示例:
sql复制CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`tenant_id` varchar(64) NOT NULL COMMENT '租户标识',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`status` tinyint DEFAULT '1' COMMENT '状态 0-禁用 1-正常',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_tenant` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
关键设计要点:
实际搭建过程中,有几个经验值得分享:
目录结构规划
code复制src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ ├── config/ # 配置类
│ │ ├── controller/ # 控制器
│ │ ├── entity/ # 实体类
│ │ ├── mapper/ # MyBatis映射接口
│ │ ├── service/ # 服务层
│ │ └── util/ # 工具类
│ └── resources/
│ ├── static/ # 静态资源
│ ├── templates/ # 模板文件
│ └── application.yml # 主配置文件
多环境配置技巧
yaml复制# application-dev.yml
spring:
profiles:
active: dev
datasource:
dynamic:
datasource:
master:
url: jdbc:mysql://dev-db:3306/tenant1
日志配置建议
yaml复制logging:
level:
root: info
com.example: debug
file:
name: logs/app.log
pattern:
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
在项目初期,我花了大量时间在技术选型上,后来发现过度设计反而拖慢了进度。最终采用"先跑通再优化"的策略,用最简单的实现验证核心需求,再逐步迭代完善。