feat(tenant): 实现多租户功能支持

This commit is contained in:
Ray.Hao
2025-12-10 21:14:37 +08:00
parent f16c1e6227
commit 329b3551f7
17 changed files with 1787 additions and 0 deletions

186
sql/mysql/tenant_add.sql Normal file
View File

@@ -0,0 +1,186 @@
-- ============================================
-- 多租户支持 SQL 脚本(为现有系统添加多租户功能)
-- ============================================
-- 说明:此脚本用于为现有表添加 tenant_id 字段,启用多租户功能
-- 适用场景:已有系统需要升级支持多租户
-- 执行前请确保已备份数据库!
-- ============================================
USE youlai_admin;
SET FOREIGN_KEY_CHECKS = 0;
-- ============================================
-- 1. 创建租户表(如果不存在)
-- ============================================
DROP TABLE IF EXISTS `sys_tenant`;
CREATE TABLE `sys_tenant` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '租户ID',
`name` varchar(100) NOT NULL COMMENT '租户名称',
`code` varchar(50) NOT NULL COMMENT '租户编码(唯一)',
`contact_name` varchar(50) DEFAULT NULL COMMENT '联系人姓名',
`contact_phone` varchar(20) DEFAULT NULL COMMENT '联系人电话',
`contact_email` varchar(100) DEFAULT NULL COMMENT '联系人邮箱',
`domain` varchar(100) DEFAULT NULL COMMENT '租户域名(用于域名识别)',
`logo` varchar(255) DEFAULT NULL COMMENT '租户Logo',
`status` tinyint DEFAULT '1' COMMENT '状态(1-正常 0-禁用)',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`expire_time` datetime DEFAULT NULL COMMENT '过期时间NULL表示永不过期',
`create_time` datetime COMMENT '创建时间',
`update_time` datetime COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code` (`code`),
UNIQUE KEY `uk_domain` (`domain`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='系统租户表';
-- 插入默认租户
INSERT INTO `sys_tenant` (`id`, `name`, `code`, `status`, `create_time`) VALUES
(1, '默认租户', 'DEFAULT', 1, NOW());
-- ============================================
-- 2. 创建用户租户关联表(支持一个用户属于多个租户)
-- ============================================
DROP TABLE IF EXISTS `sys_user_tenant`;
CREATE TABLE `sys_user_tenant` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`tenant_id` bigint NOT NULL COMMENT '租户ID',
`is_default` tinyint DEFAULT '0' COMMENT '是否默认租户(1-是 0-否)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_tenant` (`user_id`, `tenant_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户租户关联表(多租户模式)';
-- ============================================
-- 3. 为业务表添加 tenant_id 字段
-- ============================================
-- 注意MySQL 5.7 不支持 IF NOT EXISTS如果字段已存在会报错
-- 建议先检查字段是否存在,或使用 MySQL 8.0+
-- 用户表
ALTER TABLE `sys_user`
ADD COLUMN `tenant_id` bigint DEFAULT 1 COMMENT '租户ID' AFTER `id`,
ADD INDEX `idx_tenant_id` (`tenant_id`);
-- 更新现有数据的 tenant_id设置为默认租户
UPDATE `sys_user` SET `tenant_id` = 1 WHERE `tenant_id` IS NULL;
-- 角色表
ALTER TABLE `sys_role`
ADD COLUMN `tenant_id` bigint DEFAULT 1 COMMENT '租户ID' AFTER `id`,
ADD INDEX `idx_tenant_id` (`tenant_id`);
UPDATE `sys_role` SET `tenant_id` = 1 WHERE `tenant_id` IS NULL;
-- 部门表
ALTER TABLE `sys_dept`
ADD COLUMN `tenant_id` bigint DEFAULT 1 COMMENT '租户ID' AFTER `id`,
ADD INDEX `idx_tenant_id` (`tenant_id`);
UPDATE `sys_dept` SET `tenant_id` = 1 WHERE `tenant_id` IS NULL;
-- 通知公告表
ALTER TABLE `sys_notice`
ADD COLUMN `tenant_id` bigint DEFAULT 1 COMMENT '租户ID' AFTER `id`,
ADD INDEX `idx_tenant_id` (`tenant_id`);
UPDATE `sys_notice` SET `tenant_id` = 1 WHERE `tenant_id` IS NULL;
-- 系统日志表
ALTER TABLE `sys_log`
ADD COLUMN `tenant_id` bigint DEFAULT 1 COMMENT '租户ID' AFTER `id`,
ADD INDEX `idx_tenant_id` (`tenant_id`);
UPDATE `sys_log` SET `tenant_id` = 1 WHERE `tenant_id` IS NULL;
-- AI 命令记录表
ALTER TABLE `ai_command_log`
ADD COLUMN `tenant_id` bigint DEFAULT 1 COMMENT '租户ID' AFTER `id`,
ADD INDEX `idx_tenant_id` (`tenant_id`);
UPDATE `ai_command_log` SET `tenant_id` = 1 WHERE `tenant_id` IS NULL;
-- 代码生成配置表(如果存在)
-- ALTER TABLE `gen_config`
-- ADD COLUMN `tenant_id` bigint DEFAULT 1 COMMENT '租户ID' AFTER `id`,
-- ADD INDEX `idx_tenant_id` (`tenant_id`);
-- UPDATE `gen_config` SET `tenant_id` = 1 WHERE `tenant_id` IS NULL;
-- 代码生成字段配置表(如果存在)
-- ALTER TABLE `gen_field_config`
-- ADD COLUMN `tenant_id` bigint DEFAULT 1 COMMENT '租户ID' AFTER `id`,
-- ADD INDEX `idx_tenant_id` (`tenant_id`);
-- UPDATE `gen_field_config` SET `tenant_id` = 1 WHERE `tenant_id` IS NULL;
-- ============================================
-- 4. 初始化现有用户的租户关联(默认租户)
-- ============================================
INSERT INTO `sys_user_tenant` (`user_id`, `tenant_id`, `is_default`)
SELECT `id`, 1, 1 FROM `sys_user` WHERE `is_deleted` = 0
ON DUPLICATE KEY UPDATE `is_default` = 1;
-- ============================================
-- 5. 添加租户管理菜单和权限(仅在菜单不存在时添加)
-- ============================================
-- 租户管理主菜单放在部门管理之后字典管理之前ID=6
INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES (6, 1, '0,1', '租户管理', 1, 'Tenant', 'tenant', 'system/tenant/index', NULL, NULL, NULL, 1, 5, 'el-icon-OfficeBuilding', NULL, NOW(), NOW(), NULL)
ON DUPLICATE KEY UPDATE `name` = '租户管理';
-- 调整字典管理的排序从6改为7
UPDATE `sys_menu` SET `sort` = 7 WHERE `id` = 7 AND `sort` = 6;
-- 调整字典项的排序从7改为8
UPDATE `sys_menu` SET `sort` = 8 WHERE `id` = 8 AND `sort` = 7;
-- 调整系统日志的排序从8改为9
UPDATE `sys_menu` SET `sort` = 9 WHERE `id` = 9 AND `sort` = 8;
-- 调整系统配置的排序从9改为10
UPDATE `sys_menu` SET `sort` = 10 WHERE `id` = 10 AND `sort` = 9;
-- 调整通知公告的排序从10改为11
UPDATE `sys_menu` SET `sort` = 11 WHERE `id` = 11 AND `sort` = 10;
-- 租户管理权限按钮ID: 141-145
INSERT INTO `sys_menu` (`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(141, 6, '0,1,6', '租户查询', 4, NULL, '', NULL, 'sys:tenant:query', NULL, NULL, 1, 1, '', NULL, NOW(), NOW(), NULL),
(142, 6, '0,1,6', '租户新增', 4, NULL, '', NULL, 'sys:tenant:add', NULL, NULL, 1, 2, '', NULL, NOW(), NOW(), NULL),
(143, 6, '0,1,6', '租户编辑', 4, NULL, '', NULL, 'sys:tenant:edit', NULL, NULL, 1, 3, '', NULL, NOW(), NOW(), NULL),
(144, 6, '0,1,6', '租户删除', 4, NULL, '', NULL, 'sys:tenant:delete', NULL, NULL, 1, 4, '', NULL, NOW(), NOW(), NULL),
(145, 6, '0,1,6', '租户启用/禁用', 4, NULL, '', NULL, 'sys:tenant:status', NULL, NULL, 1, 5, '', NULL, NOW(), NOW(), NULL)
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`);
-- 为系统管理员角色role_id=2分配租户管理菜单权限
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
VALUES
(2, 6),
(2, 141),
(2, 142),
(2, 143),
(2, 144),
(2, 145)
ON DUPLICATE KEY UPDATE `role_id` = VALUES(`role_id`);
SET FOREIGN_KEY_CHECKS = 1;
-- ============================================
-- 脚本执行完成
-- ============================================
-- 执行完成后,请在 application.yml 中配置:
-- youlai:
-- tenant:
-- enabled: true
-- column: tenant_id
-- default-tenant-id: 1
-- header-name: tenant-id
-- ignore-tables:
-- - sys_tenant
-- - sys_dict
-- - sys_dict_item
-- - sys_config
-- ============================================

111
sql/mysql/tenant_remove.sql Normal file
View File

@@ -0,0 +1,111 @@
-- ============================================
-- 多租户移除脚本(移除多租户功能)
-- ============================================
-- 说明:此脚本用于移除多租户功能,删除 tenant_id 字段和相关表
-- 适用场景:不再需要多租户功能,需要回退到单租户模式
-- 执行前请确保已备份数据库!
-- 警告:此操作不可逆,请谨慎执行!
-- ============================================
USE youlai_admin;
SET FOREIGN_KEY_CHECKS = 0;
-- ============================================
-- 1. 删除用户租户关联表
-- ============================================
DROP TABLE IF EXISTS `sys_user_tenant`;
-- ============================================
-- 2. 删除租户表(可选)
-- ============================================
-- 注意:如果将来可能再次启用多租户,建议保留此表
-- 如需删除,取消下面的注释
-- DROP TABLE IF EXISTS `sys_tenant`;
-- ============================================
-- 3. 移除业务表的 tenant_id 字段和索引
-- ============================================
-- 注意:如果字段不存在会报错,请根据实际情况调整
-- 用户表
ALTER TABLE `sys_user` DROP INDEX IF EXISTS `idx_tenant_id`;
ALTER TABLE `sys_user` DROP COLUMN IF EXISTS `tenant_id`;
-- 角色表
ALTER TABLE `sys_role` DROP INDEX IF EXISTS `idx_tenant_id`;
ALTER TABLE `sys_role` DROP COLUMN IF EXISTS `tenant_id`;
-- 部门表
ALTER TABLE `sys_dept` DROP INDEX IF EXISTS `idx_tenant_id`;
ALTER TABLE `sys_dept` DROP COLUMN IF EXISTS `tenant_id`;
-- 通知公告表
ALTER TABLE `sys_notice` DROP INDEX IF EXISTS `idx_tenant_id`;
ALTER TABLE `sys_notice` DROP COLUMN IF EXISTS `tenant_id`;
-- 系统日志表
ALTER TABLE `sys_log` DROP INDEX IF EXISTS `idx_tenant_id`;
ALTER TABLE `sys_log` DROP COLUMN IF EXISTS `tenant_id`;
-- AI 命令记录表
ALTER TABLE `ai_command_log` DROP INDEX IF EXISTS `idx_tenant_id`;
ALTER TABLE `ai_command_log` DROP COLUMN IF EXISTS `tenant_id`;
-- 代码生成配置表(如果存在)
-- ALTER TABLE `gen_config` DROP INDEX IF EXISTS `idx_tenant_id`;
-- ALTER TABLE `gen_config` DROP COLUMN IF EXISTS `tenant_id`;
-- 代码生成字段配置表(如果存在)
-- ALTER TABLE `gen_field_config` DROP INDEX IF EXISTS `idx_tenant_id`;
-- ALTER TABLE `gen_field_config` DROP COLUMN IF EXISTS `tenant_id`;
-- 菜单表(如果之前添加了)
-- ALTER TABLE `sys_menu` DROP INDEX IF EXISTS `idx_tenant_id`;
-- ALTER TABLE `sys_menu` DROP COLUMN IF EXISTS `tenant_id`;
-- ============================================
-- 4. 删除租户管理菜单和权限
-- ============================================
-- 删除角色菜单关联
DELETE FROM `sys_role_menu` WHERE `menu_id` IN (6, 141, 142, 143, 144, 145);
-- 删除租户管理权限按钮
DELETE FROM `sys_menu` WHERE `id` IN (141, 142, 143, 144, 145);
-- 删除租户管理主菜单
DELETE FROM `sys_menu` WHERE `id` = 6;
-- 恢复字典管理的排序从7改回6
UPDATE `sys_menu` SET `sort` = 6 WHERE `id` = 7 AND `sort` = 7;
-- 恢复字典项的排序从8改回7
UPDATE `sys_menu` SET `sort` = 7 WHERE `id` = 8 AND `sort` = 8;
-- 恢复系统日志的排序从9改回8
UPDATE `sys_menu` SET `sort` = 8 WHERE `id` = 9 AND `sort` = 9;
-- 恢复系统配置的排序从10改回9
UPDATE `sys_menu` SET `sort` = 9 WHERE `id` = 10 AND `sort` = 10;
-- 恢复通知公告的排序从11改回10
UPDATE `sys_menu` SET `sort` = 10 WHERE `id` = 11 AND `sort` = 11;
SET FOREIGN_KEY_CHECKS = 1;
-- ============================================
-- 脚本执行完成
-- ============================================
-- 执行完成后,请执行以下操作:
-- 1. 在 application.yml 中配置:
-- youlai:
-- tenant:
-- enabled: false
-- 2. 更新 BaseEntity.java将 tenantId 字段的 exist 设置为 false
-- 或移除 tenantId 字段(如果确定不再使用)
-- ============================================
-- 注意:
-- 1. MySQL 5.7 不支持 IF EXISTS 语法,如果执行报错,请手动检查字段是否存在
-- 2. 对于 MySQL 8.0+,可以使用上面的语法
-- 3. 如果使用 MySQL 5.7,请先检查字段是否存在,再执行删除操作
-- ============================================

648
sql/mysql/youlai_admin.sql Normal file
View File

@@ -0,0 +1,648 @@
# YouLai_Admin 数据库(MySQL 5.7 ~ MySQL 8.x)
# Copyright (c) 2021-present, youlai.tech
-- ----------------------------
-- 1. 创建数据库
-- ----------------------------
CREATE DATABASE IF NOT EXISTS youlai_admin CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
-- ----------------------------
-- 2. 创建表 && 数据初始化
-- ----------------------------
USE youlai_admin;
SET NAMES utf8mb4; # 设置字符集
SET FOREIGN_KEY_CHECKS = 0; # 关闭外键检查,加快导入速度
-- ----------------------------
-- Table structure for sys_dept
-- ----------------------------
DROP TABLE IF EXISTS `sys_dept`;
CREATE TABLE `sys_dept` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(100) NOT NULL COMMENT '部门名称',
`code` varchar(100) NOT NULL COMMENT '部门编号',
`parent_id` bigint DEFAULT 0 COMMENT '父节点id',
`tree_path` varchar(255) NOT NULL COMMENT '父节点id路径',
`sort` smallint DEFAULT 0 COMMENT '显示顺序',
`status` tinyint DEFAULT 1 COMMENT '状态(1-正常 0-禁用)',
`create_by` bigint NULL COMMENT '创建人ID',
`create_time` datetime NULL COMMENT '创建时间',
`update_by` bigint NULL COMMENT '修改人ID',
`update_time` datetime NULL COMMENT '更新时间',
`is_deleted` tinyint DEFAULT 0 COMMENT '逻辑删除标识(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_code`(`code` ASC) USING BTREE COMMENT '部门编号唯一索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '部门管理表';
-- ----------------------------
-- Records of sys_dept
-- ----------------------------
INSERT INTO `sys_dept` VALUES (1, '有来技术', 'YOULAI', 0, '0', 1, 1, 1, NULL, 1, now(), 0);
INSERT INTO `sys_dept` VALUES (2, '研发部门', 'RD001', 1, '0,1', 1, 1, 2, NULL, 2, now(), 0);
INSERT INTO `sys_dept` VALUES (3, '测试部门', 'QA001', 1, '0,1', 1, 1, 2, NULL, 2, now(), 0);
-- ----------------------------
-- Table structure for sys_dict
-- ----------------------------
DROP TABLE IF EXISTS `sys_dict`;
CREATE TABLE `sys_dict` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键 ',
`dict_code` varchar(50) COMMENT '类型编码',
`name` varchar(50) COMMENT '类型名称',
`status` tinyint(1) DEFAULT '0' COMMENT '状态(0:正常;1:禁用)',
`remark` varchar(255) COMMENT '备注',
`create_time` datetime COMMENT '创建时间',
`create_by` bigint COMMENT '创建人ID',
`update_time` datetime COMMENT '更新时间',
`update_by` bigint COMMENT '修改人ID',
`is_deleted` tinyint DEFAULT '0' COMMENT '是否删除(1-删除0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_dict_code` (`dict_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据字典类型表';
-- ----------------------------
-- Records of sys_dict
-- ----------------------------
INSERT INTO `sys_dict` VALUES (1, 'gender', '性别', 1, NULL, now() , 1,now(), 1,0);
INSERT INTO `sys_dict` VALUES (2, 'notice_type', '通知类型', 1, NULL, now(), 1,now(), 1,0);
INSERT INTO `sys_dict` VALUES (3, 'notice_level', '通知级别', 1, NULL, now(), 1,now(), 1,0);
-- ----------------------------
-- Table structure for sys_dict_item
-- ----------------------------
DROP TABLE IF EXISTS `sys_dict_item`;
CREATE TABLE `sys_dict_item` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`dict_code` varchar(50) COMMENT '关联字典编码与sys_dict表中的dict_code对应',
`value` varchar(50) COMMENT '字典项值',
`label` varchar(100) COMMENT '字典项标签',
`tag_type` varchar(50) COMMENT '标签类型用于前端样式展示如success、warning等',
`status` tinyint DEFAULT '0' COMMENT '状态1-正常0-禁用)',
`sort` int DEFAULT '0' COMMENT '排序',
`remark` varchar(255) COMMENT '备注',
`create_time` datetime COMMENT '创建时间',
`create_by` bigint COMMENT '创建人ID',
`update_time` datetime COMMENT '更新时间',
`update_by` bigint COMMENT '修改人ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据字典项表';
-- ----------------------------
-- Records of sys_dict_item
-- ----------------------------
INSERT INTO `sys_dict_item` VALUES (1, 'gender', '1', '', 'primary', 1, 1, NULL, now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (2, 'gender', '2', '', 'danger', 1, 2, NULL, now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (3, 'gender', '0', '保密', 'info', 1, 3, NULL, now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (4, 'notice_type', '1', '系统升级', 'success', 1, 1, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (5, 'notice_type', '2', '系统维护', 'primary', 1, 2, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (6, 'notice_type', '3', '安全警告', 'danger', 1, 3, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (7, 'notice_type', '4', '假期通知', 'success', 1, 4, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (8, 'notice_type', '5', '公司新闻', 'primary', 1, 5, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (9, 'notice_type', '99', '其他', 'info', 1, 99, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (10, 'notice_level', 'L', '', 'info', 1, 1, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (11, 'notice_level', 'M', '', 'warning', 1, 2, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (12, 'notice_level', 'H', '', 'danger', 1, 3, '', now(), 1,now(),1);
-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
`parent_id` bigint NOT NULL COMMENT '父菜单ID',
`tree_path` varchar(255) COMMENT '父节点ID路径',
`name` varchar(64) NOT NULL COMMENT '菜单名称',
`type` char(1) NOT NULL COMMENT '菜单类型C-目录 M-菜单 B-按钮)',
`route_name` varchar(255) COMMENT '路由名称Vue Router 中用于命名路由)',
`route_path` varchar(128) COMMENT '路由路径Vue Router 中定义的 URL 路径)',
`component` varchar(128) COMMENT '组件路径(组件页面完整路径,相对于 src/views/,缺省后缀 .vue',
`perm` varchar(128) COMMENT '【按钮】权限标识',
`always_show` tinyint DEFAULT 0 COMMENT '【目录】只有一个子路由是否始终显示1-是 0-否)',
`keep_alive` tinyint DEFAULT 0 COMMENT '【菜单】是否开启页面缓存1-是 0-否)',
`visible` tinyint(1) DEFAULT 1 COMMENT '显示状态1-显示 0-隐藏)',
`sort` int DEFAULT 0 COMMENT '排序',
`icon` varchar(64) COMMENT '菜单图标',
`redirect` varchar(128) COMMENT '跳转路径',
`create_time` datetime NULL COMMENT '创建时间',
`update_time` datetime NULL COMMENT '更新时间',
`params` varchar(255) NULL COMMENT '路由参数',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统菜单表';
-- ----------------------------
-- Records of sys_menu
-- ----------------------------
-- 顶级目录1-10平台/系统/代码生成/AI助手/文档/接口文档/组件/演示/多级/路由
INSERT INTO `sys_menu` VALUES (1, 0, '0', '平台管理', 'C', '', '/platform', 'Layout', NULL, NULL, NULL, 1, 1, 'platform', '/platform/tenant', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2, 0, '0', '系统管理', 'C', '', '/system', 'Layout', NULL, NULL, NULL, 1, 2, 'system', '/system/user', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (3, 0, '0', '代码生成', 'C', '', '/gen', 'Layout', NULL, NULL, NULL, 1, 3, 'code', '/gen/index', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (4, 0, '0', 'AI助手', 'C', '', '/ai', 'Layout', NULL, NULL, NULL, 1, 4, 'platform', '/ai/command-record', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (5, 0, '0', '平台文档', 'C', '', '/doc', 'Layout', NULL, NULL, NULL, 1, 5, 'document', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (6, 0, '0', '接口文档', 'C', '', '/api', 'Layout', NULL, NULL, NULL, 1, 6, 'api', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (7, 0, '0', '组件封装', 'C', '', '/component', 'Layout', NULL, NULL, NULL, 1, 7, 'menu', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (8, 0, '0', '功能演示', 'C', '', '/function', 'Layout', NULL, NULL, NULL, 1, 8, 'menu', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (9, 0, '0', '多级菜单', 'C', NULL, '/multi-level', 'Layout', NULL, 1, NULL, 1, 9, 'cascader', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (10, 0, '0', '路由参数', 'C', '', '/route-param', 'Layout', NULL, NULL, NULL, 1, 10, 'el-icon-ElementPlus', '', now(), now(), NULL);
-- 平台管理(平台方)
INSERT INTO `sys_menu` VALUES (110, 1, '0,1', '租户管理', 'M', 'Tenant', 'tenant', 'system/tenant/index', NULL, NULL, 1, 1, 1, 'el-icon-OfficeBuilding', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (1101, 110, '0,1,110', '租户查询', 'B', NULL, '', NULL, 'sys:tenant:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (1102, 110, '0,1,110', '租户新增', 'B', NULL, '', NULL, 'sys:tenant:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (1103, 110, '0,1,110', '租户编辑', 'B', NULL, '', NULL, 'sys:tenant:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (1104, 110, '0,1,110', '租户删除', 'B', NULL, '', NULL, 'sys:tenant:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (1105, 110, '0,1,110', '租户启用/禁用', 'B', NULL, '', NULL, 'sys:tenant:change-status', NULL, NULL, 1, 5, '', NULL, now(), now(), NULL);
-- 系统管理(租户侧)
INSERT INTO `sys_menu` VALUES (210, 2, '0,2', '用户管理', 'M', 'User', 'user', 'system/user/index', NULL, NULL, 1, 1, 1, 'el-icon-User', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2101, 210, '0,2,210', '用户查询', 'B', NULL, '', NULL, 'sys:user:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2102, 210, '0,2,210', '用户新增', 'B', NULL, '', NULL, 'sys:user:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2103, 210, '0,2,210', '用户编辑', 'B', NULL, '', NULL, 'sys:user:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2104, 210, '0,2,210', '用户删除', 'B', NULL, '', NULL, 'sys:user:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2105, 210, '0,2,210', '重置密码', 'B', NULL, '', NULL, 'sys:user:reset-password', NULL, NULL, 1, 5, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2106, 210, '0,2,210', '用户导入', 'B', NULL, '', NULL, 'sys:user:import', NULL, NULL, 1, 6, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2107, 210, '0,2,210', '用户导出', 'B', NULL, '', NULL, 'sys:user:export', NULL, NULL, 1, 7, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (220, 2, '0,2', '角色管理', 'M', 'Role', 'role', 'system/role/index', NULL, NULL, 1, 1, 2, 'role', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2201, 220, '0,2,220', '角色查询', 'B', NULL, '', NULL, 'sys:role:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2202, 220, '0,2,220', '角色新增', 'B', NULL, '', NULL, 'sys:role:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2203, 220, '0,2,220', '角色编辑', 'B', NULL, '', NULL, 'sys:role:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2204, 220, '0,2,220', '角色删除', 'B', NULL, '', NULL, 'sys:role:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (230, 2, '0,2', '菜单管理', 'M', 'SysMenu', 'menu', 'system/menu/index', NULL, NULL, 1, 1, 3, 'menu', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2301, 230, '0,2,230', '菜单查询', 'B', NULL, '', NULL, 'sys:menu:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2302, 230, '0,2,230', '菜单新增', 'B', NULL, '', NULL, 'sys:menu:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2303, 230, '0,2,230', '菜单编辑', 'B', NULL, '', NULL, 'sys:menu:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2304, 230, '0,2,230', '菜单删除', 'B', NULL, '', NULL, 'sys:menu:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (240, 2, '0,2', '部门管理', 'M', 'Dept', 'dept', 'system/dept/index', NULL, NULL, 1, 1, 4, 'tree', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2401, 240, '0,2,240', '部门查询', 'B', NULL, '', NULL, 'sys:dept:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2402, 240, '0,2,240', '部门新增', 'B', NULL, '', NULL, 'sys:dept:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2403, 240, '0,2,240', '部门编辑', 'B', NULL, '', NULL, 'sys:dept:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2404, 240, '0,2,240', '部门删除', 'B', NULL, '', NULL, 'sys:dept:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (250, 2, '0,2', '字典管理', 'M', 'Dict', 'dict', 'system/dict/index', NULL, NULL, 1, 1, 5, 'dict', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2501, 250, '0,2,250', '字典查询', 'B', NULL, '', NULL, 'sys:dict:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2502, 250, '0,2,250', '字典新增', 'B', NULL, '', NULL, 'sys:dict:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2503, 250, '0,2,250', '字典编辑', 'B', NULL, '', NULL, 'sys:dict:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2504, 250, '0,2,250', '字典删除', 'B', NULL, '', NULL, 'sys:dict:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (251, 250, '0,2,250,251', '字典项', 'M', 'DictItem', 'dict-item', 'system/dict/dict-item', NULL, 0, 1, 0, 6, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2511, 251, '0,2,250,251', '字典项查询', 'B', NULL, '', NULL, 'sys:dict-item:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2512, 251, '0,2,250,251', '字典项新增', 'B', NULL, '', NULL, 'sys:dict-item:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2513, 251, '0,2,250,251', '字典项编辑', 'B', NULL, '', NULL, 'sys:dict-item:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2514, 251, '0,2,250,251', '字典项删除', 'B', NULL, '', NULL, 'sys:dict-item:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (260, 2, '0,2', '系统日志', 'M', 'Log', 'log', 'system/log/index', NULL, 0, 1, 1, 7, 'document', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (270, 2, '0,2', '系统配置', 'M', 'Config', 'config', 'system/config/index', NULL, 0, 1, 1, 8, 'setting', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2701, 270, '0,2,270', '系统配置查询', 'B', NULL, '', NULL, 'sys:config:list', 0, 1, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2702, 270, '0,2,270', '系统配置新增', 'B', NULL, '', NULL, 'sys:config:create', 0, 1, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2703, 270, '0,2,270', '系统配置修改', 'B', NULL, '', NULL, 'sys:config:update', 0, 1, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2704, 270, '0,2,270', '系统配置删除', 'B', NULL, '', NULL, 'sys:config:delete', 0, 1, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2705, 270, '0,2,270', '系统配置刷新', 'B', NULL, '', NULL, 'sys:config:refresh', 0, 1, 1, 5, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (280, 2, '0,2', '通知公告', 'M', 'Notice', 'notice', 'system/notice/index', NULL, NULL, NULL, 1, 9, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2801, 280, '0,2,280', '通知查询', 'B', NULL, '', NULL, 'sys:notice:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2802, 280, '0,2,280', '通知新增', 'B', NULL, '', NULL, 'sys:notice:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2803, 280, '0,2,280', '通知编辑', 'B', NULL, '', NULL, 'sys:notice:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2804, 280, '0,2,280', '通知删除', 'B', NULL, '', NULL, 'sys:notice:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2805, 280, '0,2,280', '通知发布', 'B', NULL, '', NULL, 'sys:notice:publish', 0, 1, 1, 5, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2806, 280, '0,2,280', '通知撤回', 'B', NULL, '', NULL, 'sys:notice:revoke', 0, 1, 1, 6, '', NULL, now(), now(), NULL);
-- 代码生成
INSERT INTO `sys_menu` VALUES (310, 3, '0,3', '代码生成', 'M', 'Gen', 'gen', 'gen/index', NULL, NULL, 1, 1, 1, 'code', NULL, now(), now(), NULL);
-- AI 助手
INSERT INTO `sys_menu` VALUES (401, 4, '0,4', 'AI命令记录', 'M', 'AiCommandRecord', 'command-record', 'ai/command-record/index', NULL, NULL, 1, 1, 1, 'document', NULL, now(), now(), NULL);
-- 平台文档(外链通过 route_path 识别)
INSERT INTO `sys_menu` VALUES (501, 5, '0,5', '平台文档(外链)', 'M', NULL, 'https://juejin.cn/post/7228990409909108793', '', NULL, NULL, NULL, 1, 1, 'document', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (502, 5, '0,5', '后端文档', 'M', NULL, 'https://youlai.blog.csdn.net/article/details/145178880', '', NULL, NULL, NULL, 1, 2, 'document', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (503, 5, '0,5', '移动端文档', 'M', NULL, 'https://youlai.blog.csdn.net/article/details/143222890', '', NULL, NULL, NULL, 1, 3, 'document', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (504, 5, '0,5', '内部文档', 'M', NULL, 'internal-doc', 'demo/internal-doc', NULL, NULL, NULL, 1, 4, 'document', '', now(), now(), NULL);
-- 接口文档
INSERT INTO `sys_menu` VALUES (601, 6, '0,6', 'Apifox', 'M', 'Apifox', 'apifox', 'demo/api/apifox', NULL, NULL, 1, 1, 1, 'api', '', now(), now(), NULL);
-- 组件封装
INSERT INTO `sys_menu` VALUES (701, 7, '0,7', '富文本编辑器', 'M', 'WangEditor', 'wang-editor', 'demo/wang-editor', NULL, NULL, 1, 1, 2, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (702, 7, '0,7', '图片上传', 'M', 'Upload', 'upload', 'demo/upload', NULL, NULL, 1, 1, 3, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (703, 7, '0,7', '图标选择器', 'M', 'IconSelect', 'icon-select', 'demo/icon-select', NULL, NULL, 1, 1, 4, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (704, 7, '0,7', '字典组件', 'M', 'DictDemo', 'dict-demo', 'demo/dictionary', NULL, NULL, 1, 1, 4, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (705, 7, '0,7', '增删改查', 'M', 'Curd', 'curd', 'demo/curd/index', NULL, NULL, 1, 1, 0, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (706, 7, '0,7', '列表选择器', 'M', 'TableSelect', 'table-select', 'demo/table-select/index', NULL, NULL, 1, 1, 1, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (707, 7, '0,7', '拖拽组件', 'M', 'Drag', 'drag', 'demo/drag', NULL, NULL, NULL, 1, 5, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (708, 7, '0,7', '滚动文本', 'M', 'TextScroll', 'text-scroll', 'demo/text-scroll', NULL, NULL, NULL, 1, 6, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (709, 7, '0,7', '自适应表格操作列', 'M', 'AutoOperationColumn', 'operation-column', 'demo/auto-operation-column', NULL, NULL, 1, 1, 1, '', '', now(), now(), NULL);
-- 功能演示
INSERT INTO `sys_menu` VALUES (801, 8, '0,8', 'Websocket', 'M', 'WebSocket', '/function/websocket', 'demo/websocket', NULL, NULL, 1, 1, 1, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (802, 8, '0,8', 'Icons', 'M', 'IconDemo', 'icon-demo', 'demo/icons', NULL, NULL, 1, 1, 2, 'el-icon-Notification', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (803, 8, '0,8', '字典实时同步', 'M', 'DictSync', 'dict-sync', 'demo/dict-sync', NULL, NULL, NULL, 1, 3, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (804, 8, '0,8', 'VxeTable', 'M', 'VxeTable', 'vxe-table', 'demo/vxe-table/index', NULL, NULL, 1, 1, 4, 'el-icon-MagicStick', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (805, 8, '0,8', 'CURD单文件', 'M', 'CurdSingle', 'curd-single', 'demo/curd-single', NULL, NULL, 1, 1, 5, 'el-icon-Reading', '', now(), now(), NULL);
-- 多级菜单示例
INSERT INTO `sys_menu` VALUES (910, 9, '0,9', '菜单一级', 'C', NULL, 'multi-level1', 'Layout', NULL, 1, NULL, 1, 1, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (911, 910, '0,9,910', '菜单二级', 'C', NULL, 'multi-level2', 'Layout', NULL, 0, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (912, 911, '0,9,910,911', '菜单三级-1', 'M', NULL, 'multi-level3-1', 'demo/multi-level/children/children/level3-1', NULL, 0, 1, 1, 1, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (913, 911, '0,9,910,911', '菜单三级-2', 'M', NULL, 'multi-level3-2', 'demo/multi-level/children/children/level3-2', NULL, 0, 1, 1, 2, '', '', now(), now(), NULL);
-- 路由参数
INSERT INTO `sys_menu` VALUES (1001, 10, '0,10', '参数(type=1)', 'M', 'RouteParamType1', 'route-param-type1', 'demo/route-param', NULL, 0, 1, 1, 1, 'el-icon-Star', NULL, now(), now(), '{\"type\": \"1\"}');
INSERT INTO `sys_menu` VALUES (1002, 10, '0,10', '参数(type=2)', 'M', 'RouteParamType2', 'route-param-type2', 'demo/route-param', NULL, 0, 1, 1, 2, 'el-icon-StarFilled', NULL, now(), now(), '{\"type\": \"2\"}');
-- ============================================
--- 系统配置权限按钮ID: 901-905
--- 字典项权限按钮ID: 701-704
-- ============================================
-- 通知公告权限按钮ID: 1101-1106
-- ============================================
-- ============================================
-- 字典项权限按钮ID: 701-704
-- ============================================
-- ============================================
-- 租户管理权限按钮ID: 501-505
-- ============================================
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL COMMENT '角色名称',
`code` varchar(32) NOT NULL COMMENT '角色编码',
`sort` int NULL COMMENT '显示顺序',
`status` tinyint(1) DEFAULT 1 COMMENT '角色状态(1-正常 0-停用)',
`data_scope` tinyint NULL COMMENT '数据权限(1-所有数据 2-部门及子部门数据 3-本部门数据 4-本人数据)',
`create_by` bigint NULL COMMENT '创建人 ID',
`create_time` datetime NULL COMMENT '创建时间',
`update_by` bigint NULL COMMENT '更新人ID',
`update_time` datetime NULL COMMENT '更新时间',
`is_deleted` tinyint(1) DEFAULT 0 COMMENT '逻辑删除标识(0-未删除 1-已删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_name`(`name` ASC) USING BTREE COMMENT '角色名称唯一索引',
UNIQUE INDEX `uk_code`(`code` ASC) USING BTREE COMMENT '角色编码唯一索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统角色表';
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, '超级管理员', 'ROOT', 1, 1, 1, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (2, '系统管理员', 'ADMIN', 2, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (3, '访问游客', 'GUEST', 3, 1, 3, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (4, '系统管理员1', 'ADMIN1', 4, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (5, '系统管理员2', 'ADMIN2', 5, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (6, '系统管理员3', 'ADMIN3', 6, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (7, '系统管理员4', 'ADMIN4', 7, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (8, '系统管理员5', 'ADMIN5', 8, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (9, '系统管理员6', 'ADMIN6', 9, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (10, '系统管理员7', 'ADMIN7', 10, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (11, '系统管理员8', 'ADMIN8', 11, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (12, '系统管理员9', 'ADMIN9', 12, 1, 1, NULL, now(), NULL, NULL, 0);
-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`role_id` bigint NOT NULL COMMENT '角色ID',
`menu_id` bigint NOT NULL COMMENT '菜单ID',
UNIQUE INDEX `uk_roleid_menuid`(`role_id` ASC, `menu_id` ASC) USING BTREE COMMENT '角色菜单唯一索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '角色菜单关联表';
-- ============================================
-- 系统管理员角色菜单权限role_id=2
-- 顶级目录
INSERT INTO `sys_role_menu` VALUES (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7), (2, 8), (2, 9), (2, 10);
-- 平台管理
INSERT INTO `sys_role_menu` VALUES (2, 110), (2, 1101), (2, 1102), (2, 1103), (2, 1104), (2, 1105);
-- 系统管理
INSERT INTO `sys_role_menu` VALUES (2, 210), (2, 2101), (2, 2102), (2, 2103), (2, 2104), (2, 2105), (2, 2106), (2, 2107);
INSERT INTO `sys_role_menu` VALUES (2, 220), (2, 2201), (2, 2202), (2, 2203), (2, 2204);
INSERT INTO `sys_role_menu` VALUES (2, 230), (2, 2301), (2, 2302), (2, 2303), (2, 2304);
INSERT INTO `sys_role_menu` VALUES (2, 240), (2, 2401), (2, 2402), (2, 2403), (2, 2404);
INSERT INTO `sys_role_menu` VALUES (2, 250), (2, 2501), (2, 2502), (2, 2503), (2, 2504);
INSERT INTO `sys_role_menu` VALUES (2, 251), (2, 2511), (2, 2512), (2, 2513), (2, 2514);
INSERT INTO `sys_role_menu` VALUES (2, 260);
INSERT INTO `sys_role_menu` VALUES (2, 270), (2, 2701), (2, 2702), (2, 2703), (2, 2704), (2, 2705);
INSERT INTO `sys_role_menu` VALUES (2, 280), (2, 2801), (2, 2802), (2, 2803), (2, 2804), (2, 2805), (2, 2806);
-- 代码生成
INSERT INTO `sys_role_menu` VALUES (2, 310);
-- AI 助手
INSERT INTO `sys_role_menu` VALUES (2, 401);
-- 平台文档
INSERT INTO `sys_role_menu` VALUES (2, 501), (2, 502), (2, 503), (2, 504);
-- 接口文档
INSERT INTO `sys_role_menu` VALUES (2, 601);
-- 组件封装
INSERT INTO `sys_role_menu` VALUES (2, 701), (2, 702), (2, 703), (2, 704), (2, 705), (2, 706), (2, 707), (2, 708), (2, 709);
-- 功能演示 / 多级菜单
INSERT INTO `sys_role_menu` VALUES (2, 801), (2, 802), (2, 803), (2, 804), (2, 805), (2, 910), (2, 911), (2, 912), (2, 913);
-- 路由参数
INSERT INTO `sys_role_menu` VALUES (2, 1001), (2, 1002);
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(64) COMMENT '用户名',
`nickname` varchar(64) COMMENT '昵称',
`gender` tinyint(1) DEFAULT 1 COMMENT '性别((1-男 2-女 0-保密)',
`password` varchar(100) COMMENT '密码',
`dept_id` int COMMENT '部门ID',
`avatar` varchar(255) COMMENT '用户头像',
`mobile` varchar(20) COMMENT '联系方式',
`status` tinyint(1) DEFAULT 1 COMMENT '状态(1-正常 0-禁用)',
`email` varchar(128) COMMENT '用户邮箱',
`create_time` datetime COMMENT '创建时间',
`create_by` bigint COMMENT '创建人ID',
`update_time` datetime COMMENT '更新时间',
`update_by` bigint COMMENT '修改人ID',
`is_deleted` tinyint(1) DEFAULT 0 COMMENT '逻辑删除标识(0-未删除 1-已删除)',
`openid` char(28) COMMENT '微信 openid',
PRIMARY KEY (`id`) USING BTREE,
KEY `login_name` (`username`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统用户表';
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'root', '有来技术', 0, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', NULL, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345677', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0,NULL);
INSERT INTO `sys_user` VALUES (2, 'admin', '系统管理员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345678', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0,NULL);
INSERT INTO `sys_user` VALUES (3, 'test', '测试小用户', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 3, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345679', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0,NULL);
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`user_id` bigint NOT NULL COMMENT '用户ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
PRIMARY KEY (`user_id`, `role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '用户角色关联表';
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1);
INSERT INTO `sys_user_role` VALUES (2, 2);
INSERT INTO `sys_user_role` VALUES (3, 3);
-- ----------------------------
-- Table structure for sys_log
-- ----------------------------
DROP TABLE IF EXISTS `sys_log`;
CREATE TABLE `sys_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`module` varchar(50) NOT NULL COMMENT '日志模块',
`request_method` varchar(64) NOT NULL COMMENT '请求方式',
`request_params` text COMMENT '请求参数(批量请求参数可能会超过text)',
`response_content` mediumtext COMMENT '返回参数',
`content` varchar(255) NOT NULL COMMENT '日志内容',
`request_uri` varchar(255) COMMENT '请求路径',
`method` varchar(255) COMMENT '方法名',
`ip` varchar(45) COMMENT 'IP地址',
`province` varchar(100) COMMENT '省份',
`city` varchar(100) COMMENT '城市',
`execution_time` bigint COMMENT '执行时间(ms)',
`browser` varchar(100) COMMENT '浏览器',
`browser_version` varchar(100) COMMENT '浏览器版本',
`os` varchar(100) COMMENT '终端系统',
`create_by` bigint COMMENT '创建人ID',
`create_time` datetime COMMENT '创建时间',
`is_deleted` tinyint DEFAULT '0' COMMENT '逻辑删除标识(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_create_time` (`create_time`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表';
-- ----------------------------
-- Table structure for gen_config
-- ----------------------------
DROP TABLE IF EXISTS `gen_config`;
CREATE TABLE `gen_config` (
`id` bigint NOT NULL AUTO_INCREMENT,
`table_name` varchar(100) NOT NULL COMMENT '表名',
`module_name` varchar(100) COMMENT '模块名',
`package_name` varchar(255) NOT NULL COMMENT '包名',
`business_name` varchar(100) NOT NULL COMMENT '业务名',
`entity_name` varchar(100) NOT NULL COMMENT '实体类名',
`author` varchar(50) NOT NULL COMMENT '作者',
`parent_menu_id` bigint COMMENT '上级菜单ID对应sys_menu的id ',
`remove_table_prefix` varchar(20) COMMENT '要移除的表前缀,如: sys_',
`page_type` varchar(20) COMMENT '页面类型(classic|curd)',
`create_time` datetime COMMENT '创建时间',
`update_time` datetime COMMENT '更新时间',
`is_deleted` tinyint(4) DEFAULT 0 COMMENT '是否删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tablename` (`table_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代码生成配置表';
-- ----------------------------
-- Table structure for gen_field_config
-- ----------------------------
DROP TABLE IF EXISTS `gen_field_config`;
CREATE TABLE `gen_field_config` (
`id` bigint NOT NULL AUTO_INCREMENT,
`config_id` bigint NOT NULL COMMENT '关联的配置ID',
`column_name` varchar(100) ,
`column_type` varchar(50) ,
`column_length` int ,
`field_name` varchar(100) NOT NULL COMMENT '字段名称',
`field_type` varchar(100) COMMENT '字段类型',
`field_sort` int COMMENT '字段排序',
`field_comment` varchar(255) COMMENT '字段描述',
`max_length` int ,
`is_required` tinyint(1) COMMENT '是否必填',
`is_show_in_list` tinyint(1) DEFAULT '0' COMMENT '是否在列表显示',
`is_show_in_form` tinyint(1) DEFAULT '0' COMMENT '是否在表单显示',
`is_show_in_query` tinyint(1) DEFAULT '0' COMMENT '是否在查询条件显示',
`query_type` tinyint COMMENT '查询方式',
`form_type` tinyint COMMENT '表单类型',
`dict_type` varchar(50) COMMENT '字典类型',
`create_time` datetime COMMENT '创建时间',
`update_time` datetime COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `config_id` (`config_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代码生成字段配置表';
-- ----------------------------
-- 系统配置表
-- ----------------------------
DROP TABLE IF EXISTS `sys_config`;
CREATE TABLE `sys_config` (
`id` bigint NOT NULL AUTO_INCREMENT,
`config_name` varchar(50) NOT NULL COMMENT '配置名称',
`config_key` varchar(50) NOT NULL COMMENT '配置key',
`config_value` varchar(100) NOT NULL COMMENT '配置值',
`remark` varchar(255) COMMENT '备注',
`create_time` datetime COMMENT '创建时间',
`create_by` bigint COMMENT '创建人ID',
`update_time` datetime COMMENT '更新时间',
`update_by` bigint COMMENT '更新人ID',
`is_deleted` tinyint(4) DEFAULT '0' NOT NULL COMMENT '逻辑删除标识(0-未删除 1-已删除)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='系统配置表';
INSERT INTO `sys_config` VALUES (1, '系统限流QPS', 'IP_QPS_THRESHOLD_LIMIT', '10', '单个IP请求的最大每秒查询数QPS阈值Key', now(), 1, NULL, NULL, 0);
-- ----------------------------
-- 通知公告表
-- ----------------------------
DROP TABLE IF EXISTS `sys_notice`;
CREATE TABLE `sys_notice` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(50) COMMENT '通知标题',
`content` text COMMENT '通知内容',
`type` tinyint NOT NULL COMMENT '通知类型关联字典编码notice_type',
`level` varchar(5) NOT NULL COMMENT '通知等级字典codenotice_level',
`target_type` tinyint NOT NULL COMMENT '目标类型1: 全体, 2: 指定)',
`target_user_ids` varchar(255) COMMENT '目标人ID集合多个使用英文逗号,分割)',
`publisher_id` bigint COMMENT '发布人ID',
`publish_status` tinyint DEFAULT '0' COMMENT '发布状态0: 未发布, 1: 已发布, -1: 已撤回)',
`publish_time` datetime COMMENT '发布时间',
`revoke_time` datetime COMMENT '撤回时间',
`create_by` bigint NOT NULL COMMENT '创建人ID',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_by` bigint COMMENT '更新人ID',
`update_time` datetime COMMENT '更新时间',
`is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除0: 未删除, 1: 已删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统通知公告表';
INSERT INTO `sys_notice` VALUES (1, 'v3.0.0 版本发布 - 多租户功能上线', '<p>🎉 新版本发布,主要更新内容:</p><p>1. 新增多租户功能,支持租户隔离和数据管理</p><p>2. 优化系统性能,提升响应速度</p><p>3. 完善权限管理,增强安全性</p><p>4. 修复已知问题,提升系统稳定性</p>', 1, 'H', 1, NULL, 1, 1, '2024-12-15 10:00:00', NULL, 1, '2024-12-15 10:00:00', 1, '2024-12-15 10:00:00', 0);
INSERT INTO `sys_notice` VALUES (2, '系统维护通知 - 2024年12月20日', '<p>⏰ 系统维护通知</p><p>系统将于 <strong>2024年12月20日本周五凌晨 2:00-4:00</strong> 进行例行维护升级。</p><p>维护期间系统将暂停服务,请提前做好数据备份工作。</p><p>给您带来的不便,敬请谅解!</p>', 2, 'H', 1, NULL, 1, 1, '2024-12-18 14:30:00', NULL, 1, '2024-12-18 14:30:00', 1, '2024-12-18 14:30:00', 0);
INSERT INTO `sys_notice` VALUES (3, '安全提醒 - 防范钓鱼邮件', '<p>⚠️ 安全提醒</p><p>近期发现有不法分子通过钓鱼邮件进行网络攻击,请大家提高警惕:</p><p>1. 不要点击来源不明的邮件链接</p><p>2. 不要下载可疑附件</p><p>3. 遇到可疑邮件请及时联系IT部门</p><p>4. 定期修改密码,使用强密码策略</p>', 3, 'H', 1, NULL, 1, 1, '2024-12-10 09:00:00', NULL, 1, '2024-12-10 09:00:00', 1, '2024-12-10 09:00:00', 0);
INSERT INTO `sys_notice` VALUES (4, '元旦假期安排通知', '<p>📅 元旦假期安排</p><p>根据国家法定节假日安排,公司元旦假期时间为:</p><p><strong>2024年12月30日周一至 2025年1月1日周三</strong>共3天。</p><p>2024年12月29日周日正常上班。</p><p>祝大家元旦快乐,假期愉快!</p>', 4, 'M', 1, NULL, 1, 1, '2024-12-25 16:00:00', NULL, 1, '2024-12-25 16:00:00', 1, '2024-12-25 16:00:00', 0);
INSERT INTO `sys_notice` VALUES (5, '新产品发布会邀请', '<p>🎊 新产品发布会邀请</p><p>公司将于 <strong>2025年1月15日下午14:00</strong> 在总部会议室举办新产品发布会。</p><p>届时将展示最新研发的产品和技术成果,欢迎全体员工参加。</p><p>请各部门提前安排好工作,准时参加。</p>', 5, 'M', 1, NULL, 1, 1, '2024-12-28 11:00:00', NULL, 1, '2024-12-28 11:00:00', 1, '2024-12-28 11:00:00', 0);
INSERT INTO `sys_notice` VALUES (6, 'v2.16.1 版本更新', '<p>✨ 版本更新</p><p>v2.16.1 版本已发布,主要修复内容:</p><p>1. 修复 WebSocket 重复连接导致的后台线程阻塞问题</p><p>2. 优化通知公告功能,提升用户体验</p><p>3. 修复部分已知bug</p><p>建议尽快更新到最新版本。</p>', 1, 'M', 1, NULL, 1, 1, '2024-12-05 15:30:00', NULL, 1, '2024-12-05 15:30:00', 1, '2024-12-05 15:30:00', 0);
INSERT INTO `sys_notice` VALUES (7, '年终总结会议通知', '<p>📋 年终总结会议通知</p><p>各部门年终总结会议将于 <strong>2024年12月30日上午9:00</strong> 召开。</p><p>请各部门负责人提前准备好年度工作总结和下年度工作计划。</p><p>会议地点:总部大会议室</p>', 5, 'M', 2, '1,2', 1, 1, '2024-12-22 10:00:00', NULL, 1, '2024-12-22 10:00:00', 1, '2024-12-22 10:00:00', 0);
INSERT INTO `sys_notice` VALUES (8, '系统功能优化完成', '<p>✅ 系统功能优化</p><p>已完成以下功能优化:</p><p>1. 优化用户管理界面,提升操作体验</p><p>2. 增强数据导出功能,支持更多格式</p><p>3. 优化搜索功能,提升查询效率</p><p>4. 修复部分界面显示问题</p>', 1, 'L', 1, NULL, 1, 1, '2024-12-12 14:20:00', NULL, 1, '2024-12-12 14:20:00', 1, '2024-12-12 14:20:00', 0);
INSERT INTO `sys_notice` VALUES (9, '员工培训计划', '<p>📚 员工培训计划</p><p>为提升员工专业技能,公司将于 <strong>2025年1月8日-10日</strong> 组织技术培训。</p><p>培训内容:</p><p>1. 新技术框架应用</p><p>2. 代码规范与最佳实践</p><p>3. 系统架构设计</p><p>请各部门合理安排工作,确保培训顺利进行。</p>', 5, 'M', 1, NULL, 1, 1, '2024-12-20 09:30:00', NULL, 1, '2024-12-20 09:30:00', 1, '2024-12-20 09:30:00', 0);
INSERT INTO `sys_notice` VALUES (10, '数据备份提醒', '<p>💾 数据备份提醒</p><p>请各部门注意定期备份重要数据,建议每周至少备份一次。</p><p>备份方式:</p><p>1. 使用系统自带备份功能</p><p>2. 手动导出重要数据</p><p>3. 联系IT部门协助备份</p><p>数据安全,人人有责!</p>', 3, 'L', 1, NULL, 1, 1, '2024-12-08 08:00:00', NULL, 1, '2024-12-08 08:00:00', 1, '2024-12-08 08:00:00', 0);
-- ----------------------------
-- 用户通知公告表
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_notice`;
CREATE TABLE `sys_user_notice` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`notice_id` bigint NOT NULL COMMENT '公共通知id',
`user_id` bigint NOT NULL COMMENT '用户id',
`is_read` bigint DEFAULT '0' COMMENT '读取状态0: 未读, 1: 已读)',
`read_time` datetime COMMENT '阅读时间',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime COMMENT '更新时间',
`is_deleted` tinyint DEFAULT '0' COMMENT '逻辑删除(0: 未删除, 1: 已删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户通知公告关联表';
INSERT INTO `sys_user_notice` VALUES (1, 1, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (2, 2, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (3, 3, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (4, 4, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (5, 5, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (6, 6, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (7, 7, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (8, 8, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (9, 9, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (10, 10, 2, 1, NULL, now(), now(), 0);
-- ----------------------------
-- AI 命令记录表
-- ----------------------------
DROP TABLE IF EXISTS `ai_command_log`;
CREATE TABLE `ai_command_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint DEFAULT NULL COMMENT '用户ID',
`username` varchar(64) DEFAULT NULL COMMENT '用户名',
`original_command` text COMMENT '原始命令',
`ai_provider` varchar(32) DEFAULT NULL COMMENT 'AI 供应商(qwen/openai/deepseek/gemini等)',
`ai_model` varchar(64) DEFAULT NULL COMMENT 'AI 模型名称(qwen-plus/qwen-max/gpt-4-turbo等)',
`parse_status` tinyint DEFAULT '0' COMMENT '解析是否成功(0-失败, 1-成功)',
`function_calls` text COMMENT '解析出的函数调用列表(JSON)',
`explanation` varchar(500) DEFAULT NULL COMMENT 'AI的理解说明',
`confidence` decimal(3,2) DEFAULT NULL COMMENT '置信度(0.00-1.00)',
`parse_error_message` text COMMENT '解析错误信息',
`input_tokens` int DEFAULT NULL COMMENT '输入Token数量',
`output_tokens` int DEFAULT NULL COMMENT '输出Token数量',
`parse_duration_ms` int DEFAULT NULL COMMENT '解析耗时(毫秒)',
`function_name` varchar(255) DEFAULT NULL COMMENT '执行的函数名称',
`function_arguments` text COMMENT '函数参数(JSON)',
`execute_status` tinyint(1) DEFAULT NULL COMMENT '执行状态(0-待执行, 1-成功, -1-失败)',
`execute_error_message` text COMMENT '执行错误信息',
`ip_address` varchar(128) DEFAULT NULL COMMENT 'IP地址',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_create_time` (`create_time`),
KEY `idx_provider` (`ai_provider`),
KEY `idx_model` (`ai_model`),
KEY `idx_parse_success` (`parse_status`),
KEY `idx_execute_status` (`execute_status`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='AI 命令日志表';
-- ----------------------------
-- 租户表(多租户模式)
-- ----------------------------
DROP TABLE IF EXISTS `sys_tenant`;
CREATE TABLE `sys_tenant` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '租户ID',
`name` varchar(100) NOT NULL COMMENT '租户名称',
`code` varchar(50) NOT NULL COMMENT '租户编码(唯一)',
`contact_name` varchar(50) DEFAULT NULL COMMENT '联系人姓名',
`contact_phone` varchar(20) DEFAULT NULL COMMENT '联系人电话',
`contact_email` varchar(100) DEFAULT NULL COMMENT '联系人邮箱',
`domain` varchar(100) DEFAULT NULL COMMENT '租户域名(用于域名识别)',
`logo` varchar(255) DEFAULT NULL COMMENT '租户Logo',
`status` tinyint DEFAULT '1' COMMENT '状态(1-正常 0-禁用)',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`expire_time` datetime DEFAULT NULL COMMENT '过期时间NULL表示永不过期',
`create_time` datetime COMMENT '创建时间',
`update_time` datetime COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code` (`code`),
UNIQUE KEY `uk_domain` (`domain`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='系统租户表';
-- ----------------------------
-- Records of sys_tenant
-- ----------------------------
INSERT INTO `sys_tenant` VALUES (1, '默认租户', 'DEFAULT', '系统管理员', '18812345678', 'admin@youlai.tech', NULL, NULL, 1, '系统默认租户', NULL, now(), now());
INSERT INTO `sys_tenant` VALUES (2, '演示租户', 'DEMO', '演示用户', '18812345679', 'demo@youlai.tech', 'demo.youlai.tech', NULL, 1, '演示租户', NULL, now(), now());
-- ----------------------------
-- 用户租户关联表(多租户模式)
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_tenant`;
CREATE TABLE `sys_user_tenant` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`tenant_id` bigint NOT NULL COMMENT '租户ID',
`is_default` tinyint DEFAULT '0' COMMENT '是否默认租户(1-是 0-否)',
`create_time` datetime COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_tenant` (`user_id`, `tenant_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户租户关联表(多租户模式)';
-- ----------------------------
-- Records of sys_user_tenant
-- ----------------------------
INSERT INTO `sys_user_tenant` VALUES (1, 1, 1, 1, now());
INSERT INTO `sys_user_tenant` VALUES (2, 2, 1, 1, now());
INSERT INTO `sys_user_tenant` VALUES (3, 2, 2, 0, now());
SET FOREIGN_KEY_CHECKS = 1;

View File

@@ -0,0 +1,22 @@
package com.youlai.boot.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 忽略多租户注解
* <p>
* 标注在方法或类上,表示该方法或类下的所有方法忽略多租户过滤
* 适用于系统管理、租户管理等不需要租户隔离的场景
* </p>
*
* @author Ray.Hao
* @since 3.0.0
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreTenant {
}

View File

@@ -0,0 +1,80 @@
package com.youlai.boot.common.tenant;
import lombok.extern.slf4j.Slf4j;
/**
* 租户上下文工具类
* <p>
* 使用 ThreadLocal 存储当前线程的租户ID确保线程安全
* </p>
*
* @author Ray.Hao
* @since 3.0.0
*/
@Slf4j
public class TenantContextHolder {
/**
* 租户ID线程本地变量
*/
private static final ThreadLocal<Long> TENANT_ID_HOLDER = new ThreadLocal<>();
/**
* 忽略租户标志(用于某些场景下临时跳过租户过滤)
*/
private static final ThreadLocal<Boolean> IGNORE_TENANT_HOLDER = new ThreadLocal<>();
/**
* 设置当前租户ID
*
* @param tenantId 租户ID
*/
public static void setTenantId(Long tenantId) {
if (tenantId != null) {
TENANT_ID_HOLDER.set(tenantId);
log.debug("设置当前租户ID: {}", tenantId);
}
}
/**
* 获取当前租户ID
*
* @return 租户ID如果未设置则返回 null
*/
public static Long getTenantId() {
return TENANT_ID_HOLDER.get();
}
/**
* 设置忽略租户标志
*
* @param ignore 是否忽略
*/
public static void setIgnoreTenant(boolean ignore) {
IGNORE_TENANT_HOLDER.set(ignore);
log.debug("设置忽略租户标志: {}", ignore);
}
/**
* 是否忽略租户
*
* @return true-忽略false-不忽略
*/
public static boolean isIgnoreTenant() {
Boolean ignore = IGNORE_TENANT_HOLDER.get();
return ignore != null && ignore;
}
/**
* 清除当前线程的租户上下文
* <p>
* 必须在请求结束时调用,避免线程池复用导致的数据泄露
* </p>
*/
public static void clear() {
TENANT_ID_HOLDER.remove();
IGNORE_TENANT_HOLDER.remove();
log.debug("清除租户上下文");
}
}

View File

@@ -0,0 +1,62 @@
package com.youlai.boot.config.property;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 多租户配置属性
*
* @author Ray.Hao
* @since 3.0.0
*/
@Data
@Component
@ConfigurationProperties(prefix = "youlai.tenant")
public class TenantProperties {
/**
* 是否启用多租户功能
* 默认false不启用
*/
private Boolean enabled = false;
/**
* 租户字段名
* 默认tenant_id
*/
private String column = "tenant_id";
/**
* 默认租户ID用于兼容旧数据tenant_id 为 NULL 时使用)
* 默认1
*/
private Long defaultTenantId = 1L;
/**
* 忽略多租户过滤的表名列表
* 系统表、租户表等不需要租户隔离的表
*/
private List<String> ignoreTables = new ArrayList<>();
/**
* 请求头中的租户ID字段名
* 默认tenant-id
*/
private String headerName = "tenant-id";
/**
* 初始化默认忽略的表
*/
public TenantProperties() {
// 系统表默认忽略多租户
ignoreTables.add("sys_tenant");
ignoreTables.add("sys_dict");
ignoreTables.add("sys_dict_item");
ignoreTables.add("sys_config");
}
}

View File

@@ -0,0 +1,46 @@
package com.youlai.boot.core.aspect;
import com.youlai.boot.common.annotation.IgnoreTenant;
import com.youlai.boot.common.tenant.TenantContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 多租户切面
* <p>
* 处理 @IgnoreTenant 注解,临时跳过租户过滤
* </p>
*
* @author Ray.Hao
* @since 3.0.0
*/
@Aspect
@Component
@Order(1)
@Slf4j
@ConditionalOnProperty(prefix = "youlai.tenant", name = "enabled", havingValue = "true", matchIfMissing = false)
public class TenantAspect {
/**
* 环绕通知:处理 @IgnoreTenant 注解
*/
@Around("@annotation(ignoreTenant) || @within(ignoreTenant)")
public Object around(ProceedingJoinPoint joinPoint, IgnoreTenant ignoreTenant) throws Throwable {
try {
// 设置忽略租户标志
TenantContextHolder.setIgnoreTenant(true);
log.debug("方法 {} 忽略多租户过滤", joinPoint.getSignature().getName());
// 执行原方法
return joinPoint.proceed();
} finally {
// 恢复租户过滤
TenantContextHolder.setIgnoreTenant(false);
}
}
}

View File

@@ -0,0 +1,72 @@
package com.youlai.boot.core.filter;
import com.youlai.boot.common.tenant.TenantContextHolder;
import com.youlai.boot.config.property.TenantProperties;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
/**
* 租户上下文过滤器
* <p>
* 从请求头中获取租户ID设置到线程上下文
* 请求结束时自动清除上下文,避免线程池复用导致的数据泄露
* </p>
*
* @author Ray.Hao
* @since 3.0.0
*/
@Slf4j
@Component
@Order(1) // 确保在其他过滤器之前执行
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "youlai.tenant", name = "enabled", havingValue = "true", matchIfMissing = false)
public class TenantContextFilter extends OncePerRequestFilter {
private final TenantProperties tenantProperties;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
// 从请求头获取租户ID
String tenantIdStr = request.getHeader(tenantProperties.getHeaderName());
if (StringUtils.hasText(tenantIdStr)) {
try {
Long tenantId = Long.parseLong(tenantIdStr);
TenantContextHolder.setTenantId(tenantId);
log.debug("从请求头获取租户ID: {}", tenantId);
} catch (NumberFormatException e) {
log.warn("租户ID格式错误: {}", tenantIdStr);
}
} else {
// 如果未提供租户ID使用默认租户ID
Long defaultTenantId = tenantProperties.getDefaultTenantId();
if (defaultTenantId != null) {
TenantContextHolder.setTenantId(defaultTenantId);
log.debug("使用默认租户ID: {}", defaultTenantId);
}
}
// 继续执行过滤器链
filterChain.doFilter(request, response);
} finally {
// 请求结束时清除租户上下文,避免线程池复用导致的数据泄露
TenantContextHolder.clear();
}
}
}

View File

@@ -0,0 +1,90 @@
package com.youlai.boot.plugin.mybatis;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.youlai.boot.common.tenant.TenantContextHolder;
import com.youlai.boot.config.property.TenantProperties;
import lombok.RequiredArgsConstructor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.NullValue;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* MyBatis-Plus 多租户处理器
* <p>
* 实现 TenantLineHandler 接口,自动为 SQL 添加租户过滤条件
* 仅在启用多租户时注册(通过 @ConditionalOnProperty 控制)
* </p>
*
* @author Ray.Hao
* @since 3.0.0
*/
@Component
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "youlai.tenant", name = "enabled", havingValue = "true", matchIfMissing = false)
public class TenantLineHandler implements com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler {
private final TenantProperties tenantProperties;
/**
* 获取租户ID表达式
* <p>
* 从 TenantContextHolder 获取当前租户ID
* 如果未设置或忽略租户,返回 NULL不添加租户条件
* </p>
*
* @return 租户ID表达式
*/
@Override
public Expression getTenantId() {
// 如果设置了忽略租户标志,返回 NULL不添加租户条件
if (TenantContextHolder.isIgnoreTenant()) {
return new NullValue();
}
// 获取当前租户ID
Long tenantId = TenantContextHolder.getTenantId();
// 如果未设置租户ID使用默认租户ID
if (tenantId == null) {
tenantId = tenantProperties.getDefaultTenantId();
}
return new LongValue(tenantId);
}
/**
* 获取租户字段名
*
* @return 租户字段名
*/
@Override
public String getTenantIdColumn() {
return tenantProperties.getColumn();
}
/**
* 判断表是否忽略多租户过滤
* <p>
* 系统表、租户表等不需要租户隔离的表应返回 true
* </p>
*
* @param tableName 表名
* @return true-忽略false-不忽略
*/
@Override
public boolean ignoreTable(String tableName) {
List<String> ignoreTables = tenantProperties.getIgnoreTables();
if (ignoreTables == null || ignoreTables.isEmpty()) {
return false;
}
// 忽略表名匹配(不区分大小写)
return ignoreTables.stream()
.anyMatch(ignoreTable -> ignoreTable.equalsIgnoreCase(tableName));
}
}

View File

@@ -0,0 +1,110 @@
package com.youlai.boot.system.controller;
import com.youlai.boot.common.tenant.TenantContextHolder;
import com.youlai.boot.core.web.Result;
import com.youlai.boot.security.util.SecurityUtils;
import com.youlai.boot.system.model.vo.TenantVO;
import com.youlai.boot.system.service.TenantService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 租户管理控制器
* <p>
* 提供租户切换、查询等功能
* </p>
*
* @author Ray.Hao
* @since 3.0.0
*/
@Tag(name = "租户管理接口")
@RestController
@RequestMapping("/api/v1/tenant")
@RequiredArgsConstructor
@Slf4j
@ConditionalOnProperty(prefix = "youlai.tenant", name = "enabled", havingValue = "true", matchIfMissing = false)
public class TenantController {
private final TenantService tenantService;
/**
* 获取当前用户的租户列表
* <p>
* 根据当前登录用户查询其所属的所有租户
* </p>
*
* @return 租户列表
*/
@Operation(summary = "获取当前用户的租户列表")
@GetMapping("/list")
public Result<List<TenantVO>> getTenantList() {
Long userId = SecurityUtils.getUserId();
List<TenantVO> tenantList = tenantService.getTenantListByUserId(userId);
log.info("获取用户 {} 的租户列表,共 {} 个租户", userId, tenantList.size());
return Result.success(tenantList);
}
/**
* 获取当前租户信息
*
* @return 当前租户信息
*/
@Operation(summary = "获取当前租户信息")
@GetMapping("/current")
public Result<TenantVO> getCurrentTenant() {
Long tenantId = TenantContextHolder.getTenantId();
if (tenantId == null) {
return Result.success(null);
}
TenantVO tenant = tenantService.getTenantById(tenantId);
return Result.success(tenant);
}
/**
* 切换租户
* <p>
* 切换当前用户的租户上下文,需要验证用户是否有权限访问该租户
* </p>
*
* @param tenantId 目标租户ID
* @return 切换结果
*/
@Operation(summary = "切换租户")
@PostMapping("/switch/{tenantId}")
public Result<TenantVO> switchTenant(
@Parameter(description = "租户ID") @PathVariable Long tenantId
) {
Long userId = SecurityUtils.getUserId();
log.info("用户 {} 请求切换租户到 {}", userId, tenantId);
// 验证用户是否有权限访问该租户
boolean hasPermission = tenantService.hasTenantPermission(userId, tenantId);
if (!hasPermission) {
log.warn("用户 {} 无权限访问租户 {}", userId, tenantId);
return Result.failed("无权限访问该租户");
}
// 验证租户是否存在且正常
TenantVO tenant = tenantService.getTenantById(tenantId);
if (tenant == null) {
return Result.failed("租户不存在");
}
if (tenant.getStatus() == null || tenant.getStatus() != 1) {
return Result.failed("租户已禁用");
}
// 设置新的租户上下文
TenantContextHolder.setTenantId(tenantId);
log.info("用户 {} 成功切换租户到 {}", userId, tenantId);
return Result.success(tenant);
}
}

View File

@@ -0,0 +1,16 @@
package com.youlai.boot.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.system.model.entity.Tenant;
import org.apache.ibatis.annotations.Mapper;
/**
* 租户 Mapper
*
* @author Ray.Hao
* @since 3.0.0
*/
@Mapper
public interface TenantMapper extends BaseMapper<Tenant> {
}

View File

@@ -0,0 +1,16 @@
package com.youlai.boot.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.system.model.entity.UserTenant;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户租户关联 Mapper
*
* @author Ray.Hao
* @since 3.0.0
*/
@Mapper
public interface UserTenantMapper extends BaseMapper<UserTenant> {
}

View File

@@ -0,0 +1,71 @@
package com.youlai.boot.system.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.youlai.boot.common.base.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* 租户实体
*
* @author Ray.Hao
* @since 3.0.0
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_tenant")
public class Tenant extends BaseEntity {
/**
* 租户名称
*/
private String name;
/**
* 租户编码(唯一)
*/
private String code;
/**
* 联系人姓名
*/
private String contactName;
/**
* 联系人电话
*/
private String contactPhone;
/**
* 联系人邮箱
*/
private String contactEmail;
/**
* 租户域名(用于域名识别)
*/
private String domain;
/**
* 租户Logo
*/
private String logo;
/**
* 状态(1-正常 0-禁用)
*/
private Integer status;
/**
* 备注
*/
private String remark;
/**
* 过期时间NULL表示永不过期
*/
private LocalDateTime expireTime;
}

View File

@@ -0,0 +1,34 @@
package com.youlai.boot.system.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.youlai.boot.common.base.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 用户租户关联实体
*
* @author Ray.Hao
* @since 3.0.0
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_user_tenant")
public class UserTenant extends BaseEntity {
/**
* 用户ID
*/
private Long userId;
/**
* 租户ID
*/
private Long tenantId;
/**
* 是否默认租户(1-是 0-否)
*/
private Integer isDefault;
}

View File

@@ -0,0 +1,48 @@
package com.youlai.boot.system.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 租户VO
*
* @author Ray.Hao
* @since 3.0.0
*/
@Data
@Schema(description = "租户信息")
public class TenantVO implements Serializable {
@Schema(description = "租户ID")
private Long id;
@Schema(description = "租户名称")
private String name;
@Schema(description = "租户编码")
private String code;
@Schema(description = "租户状态(1-正常 0-禁用)")
private Integer status;
@Schema(description = "联系人姓名")
private String contactName;
@Schema(description = "联系人电话")
private String contactPhone;
@Schema(description = "联系人邮箱")
private String contactEmail;
@Schema(description = "租户域名")
private String domain;
@Schema(description = "租户Logo")
private String logo;
@Schema(description = "是否默认租户")
private Boolean isDefault;
}

View File

@@ -0,0 +1,50 @@
package com.youlai.boot.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.boot.system.model.entity.Tenant;
import com.youlai.boot.system.model.vo.TenantVO;
import java.util.List;
/**
* 租户服务接口
*
* @author Ray.Hao
* @since 3.0.0
*/
public interface TenantService extends IService<Tenant> {
/**
* 根据用户ID查询用户所属的租户列表
*
* @param userId 用户ID
* @return 租户列表
*/
List<TenantVO> getTenantListByUserId(Long userId);
/**
* 根据租户ID查询租户信息
*
* @param tenantId 租户ID
* @return 租户信息
*/
TenantVO getTenantById(Long tenantId);
/**
* 根据域名查询租户ID
*
* @param domain 域名
* @return 租户ID
*/
Long getTenantIdByDomain(String domain);
/**
* 验证用户是否有权限访问指定租户
*
* @param userId 用户ID
* @param tenantId 租户ID
* @return true-有权限false-无权限
*/
boolean hasTenantPermission(Long userId, Long tenantId);
}

View File

@@ -0,0 +1,125 @@
package com.youlai.boot.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.boot.common.tenant.TenantContextHolder;
import com.youlai.boot.system.mapper.TenantMapper;
import com.youlai.boot.system.mapper.UserTenantMapper;
import com.youlai.boot.system.model.entity.Tenant;
import com.youlai.boot.system.model.entity.UserTenant;
import com.youlai.boot.system.model.vo.TenantVO;
import com.youlai.boot.system.service.TenantService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
/**
* 租户服务实现类
*
* @author Ray.Hao
* @since 3.0.0
*/
@Service
@Slf4j
@RequiredArgsConstructor
public class TenantServiceImpl extends ServiceImpl<TenantMapper, Tenant> implements TenantService {
private final UserTenantMapper userTenantMapper;
@Override
public List<TenantVO> getTenantListByUserId(Long userId) {
// 临时忽略租户过滤,查询所有租户
TenantContextHolder.setIgnoreTenant(true);
try {
// 查询用户关联的租户ID列表
List<UserTenant> userTenants = userTenantMapper.selectList(
new LambdaQueryWrapper<UserTenant>()
.eq(UserTenant::getUserId, userId)
);
if (userTenants.isEmpty()) {
return List.of();
}
// 提取租户ID列表
List<Long> tenantIds = userTenants.stream()
.map(UserTenant::getTenantId)
.collect(Collectors.toList());
// 查询租户信息
List<Tenant> tenants = this.list(
new LambdaQueryWrapper<Tenant>()
.in(Tenant::getId, tenantIds)
.eq(Tenant::getStatus, 1) // 只查询正常状态的租户
.orderByDesc(Tenant::getId)
);
// 转换为VO并标记默认租户
return tenants.stream().map(tenant -> {
TenantVO vo = new TenantVO();
BeanUtils.copyProperties(tenant, vo);
// 查找是否为默认租户
userTenants.stream()
.filter(ut -> ut.getTenantId().equals(tenant.getId()) && ut.getIsDefault() == 1)
.findFirst()
.ifPresent(ut -> vo.setIsDefault(true));
return vo;
}).collect(Collectors.toList());
} finally {
TenantContextHolder.setIgnoreTenant(false);
}
}
@Override
public TenantVO getTenantById(Long tenantId) {
TenantContextHolder.setIgnoreTenant(true);
try {
Tenant tenant = this.getById(tenantId);
if (tenant == null) {
return null;
}
TenantVO vo = new TenantVO();
BeanUtils.copyProperties(tenant, vo);
return vo;
} finally {
TenantContextHolder.setIgnoreTenant(false);
}
}
@Override
public Long getTenantIdByDomain(String domain) {
TenantContextHolder.setIgnoreTenant(true);
try {
Tenant tenant = this.getOne(
new LambdaQueryWrapper<Tenant>()
.eq(Tenant::getDomain, domain)
.eq(Tenant::getStatus, 1)
.last("LIMIT 1")
);
return tenant != null ? tenant.getId() : null;
} finally {
TenantContextHolder.setIgnoreTenant(false);
}
}
@Override
public boolean hasTenantPermission(Long userId, Long tenantId) {
TenantContextHolder.setIgnoreTenant(true);
try {
UserTenant userTenant = userTenantMapper.selectOne(
new LambdaQueryWrapper<UserTenant>()
.eq(UserTenant::getUserId, userId)
.eq(UserTenant::getTenantId, tenantId)
.last("LIMIT 1")
);
return userTenant != null;
} finally {
TenantContextHolder.setIgnoreTenant(false);
}
}
}