From 329b3551f77a393e22b190b103eb77863137c482 Mon Sep 17 00:00:00 2001 From: "Ray.Hao" <1490493387@qq.com> Date: Wed, 10 Dec 2025 21:14:37 +0800 Subject: [PATCH] =?UTF-8?q?feat(tenant):=20=E5=AE=9E=E7=8E=B0=E5=A4=9A?= =?UTF-8?q?=E7=A7=9F=E6=88=B7=E5=8A=9F=E8=83=BD=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/mysql/tenant_add.sql | 186 +++++ sql/mysql/tenant_remove.sql | 111 +++ sql/mysql/youlai_admin.sql | 648 ++++++++++++++++++ .../boot/common/annotation/IgnoreTenant.java | 22 + .../common/tenant/TenantContextHolder.java | 80 +++ .../config/property/TenantProperties.java | 62 ++ .../youlai/boot/core/aspect/TenantAspect.java | 46 ++ .../boot/core/filter/TenantContextFilter.java | 72 ++ .../plugin/mybatis/TenantLineHandler.java | 90 +++ .../system/controller/TenantController.java | 110 +++ .../boot/system/mapper/TenantMapper.java | 16 + .../boot/system/mapper/UserTenantMapper.java | 16 + .../boot/system/model/entity/Tenant.java | 71 ++ .../boot/system/model/entity/UserTenant.java | 34 + .../youlai/boot/system/model/vo/TenantVO.java | 48 ++ .../boot/system/service/TenantService.java | 50 ++ .../service/impl/TenantServiceImpl.java | 125 ++++ 17 files changed, 1787 insertions(+) create mode 100644 sql/mysql/tenant_add.sql create mode 100644 sql/mysql/tenant_remove.sql create mode 100644 sql/mysql/youlai_admin.sql create mode 100644 src/main/java/com/youlai/boot/common/annotation/IgnoreTenant.java create mode 100644 src/main/java/com/youlai/boot/common/tenant/TenantContextHolder.java create mode 100644 src/main/java/com/youlai/boot/config/property/TenantProperties.java create mode 100644 src/main/java/com/youlai/boot/core/aspect/TenantAspect.java create mode 100644 src/main/java/com/youlai/boot/core/filter/TenantContextFilter.java create mode 100644 src/main/java/com/youlai/boot/plugin/mybatis/TenantLineHandler.java create mode 100644 src/main/java/com/youlai/boot/system/controller/TenantController.java create mode 100644 src/main/java/com/youlai/boot/system/mapper/TenantMapper.java create mode 100644 src/main/java/com/youlai/boot/system/mapper/UserTenantMapper.java create mode 100644 src/main/java/com/youlai/boot/system/model/entity/Tenant.java create mode 100644 src/main/java/com/youlai/boot/system/model/entity/UserTenant.java create mode 100644 src/main/java/com/youlai/boot/system/model/vo/TenantVO.java create mode 100644 src/main/java/com/youlai/boot/system/service/TenantService.java create mode 100644 src/main/java/com/youlai/boot/system/service/impl/TenantServiceImpl.java diff --git a/sql/mysql/tenant_add.sql b/sql/mysql/tenant_add.sql new file mode 100644 index 00000000..6e6a4f3d --- /dev/null +++ b/sql/mysql/tenant_add.sql @@ -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 +-- ============================================ diff --git a/sql/mysql/tenant_remove.sql b/sql/mysql/tenant_remove.sql new file mode 100644 index 00000000..d247a4f7 --- /dev/null +++ b/sql/mysql/tenant_remove.sql @@ -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,请先检查字段是否存在,再执行删除操作 +-- ============================================ diff --git a/sql/mysql/youlai_admin.sql b/sql/mysql/youlai_admin.sql new file mode 100644 index 00000000..9a50c036 --- /dev/null +++ b/sql/mysql/youlai_admin.sql @@ -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 '通知等级(字典code:notice_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 版本发布 - 多租户功能上线', '

🎉 新版本发布,主要更新内容:

1. 新增多租户功能,支持租户隔离和数据管理

2. 优化系统性能,提升响应速度

3. 完善权限管理,增强安全性

4. 修复已知问题,提升系统稳定性

', 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日', '

⏰ 系统维护通知

系统将于 2024年12月20日(本周五)凌晨 2:00-4:00 进行例行维护升级。

维护期间系统将暂停服务,请提前做好数据备份工作。

给您带来的不便,敬请谅解!

', 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, '安全提醒 - 防范钓鱼邮件', '

⚠️ 安全提醒

近期发现有不法分子通过钓鱼邮件进行网络攻击,请大家提高警惕:

1. 不要点击来源不明的邮件链接

2. 不要下载可疑附件

3. 遇到可疑邮件请及时联系IT部门

4. 定期修改密码,使用强密码策略

', 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, '元旦假期安排通知', '

📅 元旦假期安排

根据国家法定节假日安排,公司元旦假期时间为:

2024年12月30日(周一)至 2025年1月1日(周三),共3天。

2024年12月29日(周日)正常上班。

祝大家元旦快乐,假期愉快!

', 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, '新产品发布会邀请', '

🎊 新产品发布会邀请

公司将于 2025年1月15日下午14:00 在总部会议室举办新产品发布会。

届时将展示最新研发的产品和技术成果,欢迎全体员工参加。

请各部门提前安排好工作,准时参加。

', 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 版本更新', '

✨ 版本更新

v2.16.1 版本已发布,主要修复内容:

1. 修复 WebSocket 重复连接导致的后台线程阻塞问题

2. 优化通知公告功能,提升用户体验

3. 修复部分已知bug

建议尽快更新到最新版本。

', 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, '年终总结会议通知', '

📋 年终总结会议通知

各部门年终总结会议将于 2024年12月30日上午9:00 召开。

请各部门负责人提前准备好年度工作总结和下年度工作计划。

会议地点:总部大会议室

', 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, '系统功能优化完成', '

✅ 系统功能优化

已完成以下功能优化:

1. 优化用户管理界面,提升操作体验

2. 增强数据导出功能,支持更多格式

3. 优化搜索功能,提升查询效率

4. 修复部分界面显示问题

', 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, '员工培训计划', '

📚 员工培训计划

为提升员工专业技能,公司将于 2025年1月8日-10日 组织技术培训。

培训内容:

1. 新技术框架应用

2. 代码规范与最佳实践

3. 系统架构设计

请各部门合理安排工作,确保培训顺利进行。

', 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, '数据备份提醒', '

💾 数据备份提醒

请各部门注意定期备份重要数据,建议每周至少备份一次。

备份方式:

1. 使用系统自带备份功能

2. 手动导出重要数据

3. 联系IT部门协助备份

数据安全,人人有责!

', 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; diff --git a/src/main/java/com/youlai/boot/common/annotation/IgnoreTenant.java b/src/main/java/com/youlai/boot/common/annotation/IgnoreTenant.java new file mode 100644 index 00000000..a388c2e9 --- /dev/null +++ b/src/main/java/com/youlai/boot/common/annotation/IgnoreTenant.java @@ -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; + +/** + * 忽略多租户注解 + *

+ * 标注在方法或类上,表示该方法或类下的所有方法忽略多租户过滤 + * 适用于系统管理、租户管理等不需要租户隔离的场景 + *

+ * + * @author Ray.Hao + * @since 3.0.0 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface IgnoreTenant { +} + diff --git a/src/main/java/com/youlai/boot/common/tenant/TenantContextHolder.java b/src/main/java/com/youlai/boot/common/tenant/TenantContextHolder.java new file mode 100644 index 00000000..fd674b31 --- /dev/null +++ b/src/main/java/com/youlai/boot/common/tenant/TenantContextHolder.java @@ -0,0 +1,80 @@ +package com.youlai.boot.common.tenant; + +import lombok.extern.slf4j.Slf4j; + +/** + * 租户上下文工具类 + *

+ * 使用 ThreadLocal 存储当前线程的租户ID,确保线程安全 + *

+ * + * @author Ray.Hao + * @since 3.0.0 + */ +@Slf4j +public class TenantContextHolder { + + /** + * 租户ID线程本地变量 + */ + private static final ThreadLocal TENANT_ID_HOLDER = new ThreadLocal<>(); + + /** + * 忽略租户标志(用于某些场景下临时跳过租户过滤) + */ + private static final ThreadLocal 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; + } + + /** + * 清除当前线程的租户上下文 + *

+ * 必须在请求结束时调用,避免线程池复用导致的数据泄露 + *

+ */ + public static void clear() { + TENANT_ID_HOLDER.remove(); + IGNORE_TENANT_HOLDER.remove(); + log.debug("清除租户上下文"); + } +} + diff --git a/src/main/java/com/youlai/boot/config/property/TenantProperties.java b/src/main/java/com/youlai/boot/config/property/TenantProperties.java new file mode 100644 index 00000000..460c2068 --- /dev/null +++ b/src/main/java/com/youlai/boot/config/property/TenantProperties.java @@ -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 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"); + } +} + diff --git a/src/main/java/com/youlai/boot/core/aspect/TenantAspect.java b/src/main/java/com/youlai/boot/core/aspect/TenantAspect.java new file mode 100644 index 00000000..0b50eeb5 --- /dev/null +++ b/src/main/java/com/youlai/boot/core/aspect/TenantAspect.java @@ -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; + +/** + * 多租户切面 + *

+ * 处理 @IgnoreTenant 注解,临时跳过租户过滤 + *

+ * + * @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); + } + } +} + diff --git a/src/main/java/com/youlai/boot/core/filter/TenantContextFilter.java b/src/main/java/com/youlai/boot/core/filter/TenantContextFilter.java new file mode 100644 index 00000000..9e53282d --- /dev/null +++ b/src/main/java/com/youlai/boot/core/filter/TenantContextFilter.java @@ -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; + +/** + * 租户上下文过滤器 + *

+ * 从请求头中获取租户ID,设置到线程上下文 + * 请求结束时自动清除上下文,避免线程池复用导致的数据泄露 + *

+ * + * @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(); + } + } +} + diff --git a/src/main/java/com/youlai/boot/plugin/mybatis/TenantLineHandler.java b/src/main/java/com/youlai/boot/plugin/mybatis/TenantLineHandler.java new file mode 100644 index 00000000..1b33ba0c --- /dev/null +++ b/src/main/java/com/youlai/boot/plugin/mybatis/TenantLineHandler.java @@ -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 多租户处理器 + *

+ * 实现 TenantLineHandler 接口,自动为 SQL 添加租户过滤条件 + * 仅在启用多租户时注册(通过 @ConditionalOnProperty 控制) + *

+ * + * @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表达式 + *

+ * 从 TenantContextHolder 获取当前租户ID + * 如果未设置或忽略租户,返回 NULL(不添加租户条件) + *

+ * + * @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(); + } + + /** + * 判断表是否忽略多租户过滤 + *

+ * 系统表、租户表等不需要租户隔离的表应返回 true + *

+ * + * @param tableName 表名 + * @return true-忽略,false-不忽略 + */ + @Override + public boolean ignoreTable(String tableName) { + List ignoreTables = tenantProperties.getIgnoreTables(); + if (ignoreTables == null || ignoreTables.isEmpty()) { + return false; + } + + // 忽略表名匹配(不区分大小写) + return ignoreTables.stream() + .anyMatch(ignoreTable -> ignoreTable.equalsIgnoreCase(tableName)); + } +} + diff --git a/src/main/java/com/youlai/boot/system/controller/TenantController.java b/src/main/java/com/youlai/boot/system/controller/TenantController.java new file mode 100644 index 00000000..dfd0ff3a --- /dev/null +++ b/src/main/java/com/youlai/boot/system/controller/TenantController.java @@ -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; + +/** + * 租户管理控制器 + *

+ * 提供租户切换、查询等功能 + *

+ * + * @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; + + /** + * 获取当前用户的租户列表 + *

+ * 根据当前登录用户查询其所属的所有租户 + *

+ * + * @return 租户列表 + */ + @Operation(summary = "获取当前用户的租户列表") + @GetMapping("/list") + public Result> getTenantList() { + Long userId = SecurityUtils.getUserId(); + List tenantList = tenantService.getTenantListByUserId(userId); + log.info("获取用户 {} 的租户列表,共 {} 个租户", userId, tenantList.size()); + return Result.success(tenantList); + } + + /** + * 获取当前租户信息 + * + * @return 当前租户信息 + */ + @Operation(summary = "获取当前租户信息") + @GetMapping("/current") + public Result getCurrentTenant() { + Long tenantId = TenantContextHolder.getTenantId(); + if (tenantId == null) { + return Result.success(null); + } + TenantVO tenant = tenantService.getTenantById(tenantId); + return Result.success(tenant); + } + + /** + * 切换租户 + *

+ * 切换当前用户的租户上下文,需要验证用户是否有权限访问该租户 + *

+ * + * @param tenantId 目标租户ID + * @return 切换结果 + */ + @Operation(summary = "切换租户") + @PostMapping("/switch/{tenantId}") + public Result 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); + } +} + diff --git a/src/main/java/com/youlai/boot/system/mapper/TenantMapper.java b/src/main/java/com/youlai/boot/system/mapper/TenantMapper.java new file mode 100644 index 00000000..802e54ab --- /dev/null +++ b/src/main/java/com/youlai/boot/system/mapper/TenantMapper.java @@ -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 { +} + diff --git a/src/main/java/com/youlai/boot/system/mapper/UserTenantMapper.java b/src/main/java/com/youlai/boot/system/mapper/UserTenantMapper.java new file mode 100644 index 00000000..9be70dc6 --- /dev/null +++ b/src/main/java/com/youlai/boot/system/mapper/UserTenantMapper.java @@ -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 { +} + diff --git a/src/main/java/com/youlai/boot/system/model/entity/Tenant.java b/src/main/java/com/youlai/boot/system/model/entity/Tenant.java new file mode 100644 index 00000000..39cd414a --- /dev/null +++ b/src/main/java/com/youlai/boot/system/model/entity/Tenant.java @@ -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; +} + diff --git a/src/main/java/com/youlai/boot/system/model/entity/UserTenant.java b/src/main/java/com/youlai/boot/system/model/entity/UserTenant.java new file mode 100644 index 00000000..46becb9d --- /dev/null +++ b/src/main/java/com/youlai/boot/system/model/entity/UserTenant.java @@ -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; +} + diff --git a/src/main/java/com/youlai/boot/system/model/vo/TenantVO.java b/src/main/java/com/youlai/boot/system/model/vo/TenantVO.java new file mode 100644 index 00000000..884580cf --- /dev/null +++ b/src/main/java/com/youlai/boot/system/model/vo/TenantVO.java @@ -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; +} + diff --git a/src/main/java/com/youlai/boot/system/service/TenantService.java b/src/main/java/com/youlai/boot/system/service/TenantService.java new file mode 100644 index 00000000..36dc36f7 --- /dev/null +++ b/src/main/java/com/youlai/boot/system/service/TenantService.java @@ -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 { + + /** + * 根据用户ID查询用户所属的租户列表 + * + * @param userId 用户ID + * @return 租户列表 + */ + List 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); +} + diff --git a/src/main/java/com/youlai/boot/system/service/impl/TenantServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/TenantServiceImpl.java new file mode 100644 index 00000000..8d11aa49 --- /dev/null +++ b/src/main/java/com/youlai/boot/system/service/impl/TenantServiceImpl.java @@ -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 implements TenantService { + + private final UserTenantMapper userTenantMapper; + + @Override + public List getTenantListByUserId(Long userId) { + // 临时忽略租户过滤,查询所有租户 + TenantContextHolder.setIgnoreTenant(true); + try { + // 查询用户关联的租户ID列表 + List userTenants = userTenantMapper.selectList( + new LambdaQueryWrapper() + .eq(UserTenant::getUserId, userId) + ); + + if (userTenants.isEmpty()) { + return List.of(); + } + + // 提取租户ID列表 + List tenantIds = userTenants.stream() + .map(UserTenant::getTenantId) + .collect(Collectors.toList()); + + // 查询租户信息 + List tenants = this.list( + new LambdaQueryWrapper() + .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() + .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() + .eq(UserTenant::getUserId, userId) + .eq(UserTenant::getTenantId, tenantId) + .last("LIMIT 1") + ); + return userTenant != null; + } finally { + TenantContextHolder.setIgnoreTenant(false); + } + } +} +