Pre Merge pull request !1126 from W-yf/cherry-pick-tenant
commit
8279759dda
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
# Don't index SpecStory auto-save files, but allow explicit context inclusion via @ references
|
||||
.specstory/**
|
||||
|
|
@ -0,0 +1,355 @@
|
|||
################################################################################
|
||||
# Hair-Link 美发预约管理系统 - 环境变量配置模板
|
||||
#
|
||||
# 使用说明:
|
||||
# 1. 复制此文件为 .env(开发环境)或 .env.production(生产环境)
|
||||
# 2. 根据实际环境修改配置值
|
||||
# 3. 确保 .env 文件已添加到 .gitignore,不要提交到版本控制
|
||||
# 4. 所有配置项都有默认值,可选择性覆盖
|
||||
################################################################################
|
||||
|
||||
#==============================================================================
|
||||
# 数据库配置 (Database Configuration)
|
||||
#==============================================================================
|
||||
|
||||
# 主数据库主机地址
|
||||
# 默认: localhost
|
||||
HAIRLINK_DB_HOST=localhost
|
||||
|
||||
# 主数据库端口
|
||||
# 默认: 3306
|
||||
HAIRLINK_DB_PORT=3306
|
||||
|
||||
# 主数据库名称
|
||||
# 默认: ry-vue
|
||||
HAIRLINK_DB_NAME=ry-vue
|
||||
|
||||
# 主数据库连接参数
|
||||
# 默认: useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
HAIRLINK_DB_PARAMS=useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
|
||||
# 主数据库用户名
|
||||
# 默认: root
|
||||
HAIRLINK_DB_USERNAME=root
|
||||
|
||||
# 主数据库密码
|
||||
# 默认: password
|
||||
# ⚠️ 生产环境请务必修改为强密码
|
||||
HAIRLINK_DB_PASSWORD=password
|
||||
|
||||
# 从数据库开关(读写分离)
|
||||
# 可选值: true/false
|
||||
# 默认: false
|
||||
HAIRLINK_DB_SLAVE_ENABLED=false
|
||||
|
||||
# 从数据库主机地址(仅在启用从库时需要配置)
|
||||
# 默认: (空)
|
||||
# HAIRLINK_DB_SLAVE_HOST=slave-host
|
||||
|
||||
# 从数据库端口(仅在启用从库时需要配置)
|
||||
# 默认: (空)
|
||||
# HAIRLINK_DB_SLAVE_PORT=3306
|
||||
|
||||
# 从数据库名称(仅在启用从库时需要配置)
|
||||
# 默认: (空)
|
||||
# HAIRLINK_DB_SLAVE_NAME=ry-vue
|
||||
|
||||
# 从数据库连接参数(仅在启用从库时需要配置)
|
||||
# 默认: (空)
|
||||
# HAIRLINK_DB_SLAVE_PARAMS=useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
|
||||
# 从数据库用户名(仅在启用从库时需要配置)
|
||||
# 默认: (空)
|
||||
# HAIRLINK_DB_SLAVE_USERNAME=root
|
||||
|
||||
# 从数据库密码(仅在启用从库时需要配置)
|
||||
# 默认: (空)
|
||||
# HAIRLINK_DB_SLAVE_PASSWORD=password
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# 数据库连接池配置 (Database Connection Pool)
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# 初始连接数
|
||||
# 默认: 5
|
||||
HAIRLINK_DB_INITIAL_SIZE=5
|
||||
|
||||
# 最小空闲连接数
|
||||
# 默认: 10
|
||||
HAIRLINK_DB_MIN_IDLE=10
|
||||
|
||||
# 最大活动连接数
|
||||
# 默认: 20
|
||||
HAIRLINK_DB_MAX_ACTIVE=20
|
||||
|
||||
# 获取连接等待超时时间(毫秒)
|
||||
# 默认: 60000 (60秒)
|
||||
HAIRLINK_DB_MAX_WAIT=60000
|
||||
|
||||
# 连接超时时间(毫秒)
|
||||
# 默认: 30000 (30秒)
|
||||
HAIRLINK_DB_CONNECT_TIMEOUT=30000
|
||||
|
||||
# 网络超时时间(毫秒)
|
||||
# 默认: 60000 (60秒)
|
||||
HAIRLINK_DB_SOCKET_TIMEOUT=60000
|
||||
|
||||
# 空闲连接检测间隔(毫秒)
|
||||
# 默认: 60000 (60秒)
|
||||
HAIRLINK_DB_TIME_BETWEEN_EVICTION=60000
|
||||
|
||||
# 连接在池中最小空闲时间(毫秒)
|
||||
# 默认: 300000 (5分钟)
|
||||
HAIRLINK_DB_MIN_EVICTABLE_IDLE_TIME=300000
|
||||
|
||||
# 连接在池中最大空闲时间(毫秒)
|
||||
# 默认: 900000 (15分钟)
|
||||
HAIRLINK_DB_MAX_EVICTABLE_IDLE_TIME=900000
|
||||
|
||||
#==============================================================================
|
||||
# Redis 配置 (Redis Configuration)
|
||||
#==============================================================================
|
||||
|
||||
# Redis 服务器地址
|
||||
# 默认: localhost
|
||||
HAIRLINK_REDIS_HOST=localhost
|
||||
|
||||
# Redis 服务器端口
|
||||
# 默认: 6379
|
||||
HAIRLINK_REDIS_PORT=6379
|
||||
|
||||
# Redis 数据库索引(0-15)
|
||||
# 默认: 0
|
||||
HAIRLINK_REDIS_DATABASE=0
|
||||
|
||||
# Redis 密码(如果Redis未设置密码,留空即可)
|
||||
# 默认: (空)
|
||||
HAIRLINK_REDIS_PASSWORD=
|
||||
|
||||
# Redis 连接超时时间
|
||||
# 默认: 10s
|
||||
HAIRLINK_REDIS_TIMEOUT=10s
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Redis 连接池配置 (Redis Connection Pool)
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# 连接池中的最小空闲连接
|
||||
# 默认: 0
|
||||
HAIRLINK_REDIS_MIN_IDLE=0
|
||||
|
||||
# 连接池中的最大空闲连接
|
||||
# 默认: 8
|
||||
HAIRLINK_REDIS_MAX_IDLE=8
|
||||
|
||||
# 连接池的最大数据库连接数
|
||||
# 默认: 8
|
||||
HAIRLINK_REDIS_MAX_ACTIVE=8
|
||||
|
||||
# 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
# 默认: -1ms
|
||||
HAIRLINK_REDIS_MAX_WAIT=-1ms
|
||||
|
||||
#==============================================================================
|
||||
# 应用服务配置 (Application Server Configuration)
|
||||
#==============================================================================
|
||||
|
||||
# 应用服务器端口
|
||||
# 默认: 8080
|
||||
HAIRLINK_SERVER_PORT=8080
|
||||
|
||||
# 应用上下文路径
|
||||
# 默认: /
|
||||
# 示例: /api 则访问地址为 http://localhost:8080/api
|
||||
HAIRLINK_CONTEXT_PATH=/
|
||||
|
||||
# Tomcat URI 编码
|
||||
# 默认: UTF-8
|
||||
HAIRLINK_SERVER_URI_ENCODING=UTF-8
|
||||
|
||||
# 连接数满后的排队数
|
||||
# 默认: 1000
|
||||
HAIRLINK_SERVER_ACCEPT_COUNT=1000
|
||||
|
||||
# Tomcat 最大线程数
|
||||
# 默认: 800
|
||||
HAIRLINK_SERVER_MAX_THREADS=800
|
||||
|
||||
# Tomcat 启动初始化的线程数
|
||||
# 默认: 100
|
||||
HAIRLINK_SERVER_MIN_SPARE_THREADS=100
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# 文件上传配置 (File Upload Configuration)
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# 文件上传存储路径
|
||||
# 默认: ./upload (相对路径,自动适配操作系统)
|
||||
# Windows示例: D:/hairlink/upload
|
||||
# Linux示例: /home/hairlink/upload
|
||||
HAIRLINK_UPLOAD_PATH=./upload
|
||||
|
||||
# 单个文件大小限制
|
||||
# 默认: 10MB
|
||||
HAIRLINK_MAX_FILE_SIZE=10MB
|
||||
|
||||
# 总上传文件大小限制
|
||||
# 默认: 20MB
|
||||
HAIRLINK_MAX_REQUEST_SIZE=20MB
|
||||
|
||||
#==============================================================================
|
||||
# Token 安全配置 (Token Security Configuration)
|
||||
#==============================================================================
|
||||
|
||||
# JWT Token 请求头名称
|
||||
# 默认: Authorization
|
||||
HAIRLINK_TOKEN_HEADER=Authorization
|
||||
|
||||
# JWT Token 密钥
|
||||
# 默认: abcdefghijklmnopqrstuvwxyz
|
||||
# ⚠️ 生产环境请务必修改为复杂的随机字符串(建议32位以上)
|
||||
# 生成方法: openssl rand -base64 32
|
||||
HAIRLINK_TOKEN_SECRET=abcdefghijklmnopqrstuvwxyz
|
||||
|
||||
# Token 过期时间(单位:分钟)
|
||||
# 默认: 30
|
||||
HAIRLINK_TOKEN_EXPIRE_TIME=30
|
||||
|
||||
#==============================================================================
|
||||
# 用户密码策略配置 (Password Policy Configuration)
|
||||
#==============================================================================
|
||||
|
||||
# 密码最大错误尝试次数
|
||||
# 超过此次数将锁定账户
|
||||
# 默认: 5
|
||||
HAIRLINK_PASSWORD_MAX_RETRY=5
|
||||
|
||||
# 密码错误锁定时间(单位:分钟)
|
||||
# 默认: 10
|
||||
HAIRLINK_PASSWORD_LOCK_TIME=10
|
||||
|
||||
#==============================================================================
|
||||
# 应用功能配置 (Application Features Configuration)
|
||||
#==============================================================================
|
||||
|
||||
# 验证码类型
|
||||
# 可选值: math(数学计算) / char(字符验证)
|
||||
# 默认: math
|
||||
HAIRLINK_CAPTCHA_TYPE=math
|
||||
|
||||
# IP地址获取开关
|
||||
# 是否启用IP地址解析功能
|
||||
# 可选值: true/false
|
||||
# 默认: false
|
||||
HAIRLINK_ADDRESS_ENABLED=false
|
||||
|
||||
# 热部署开关
|
||||
# 开发环境建议开启,生产环境建议关闭
|
||||
# 可选值: true/false
|
||||
# 默认: true
|
||||
HAIRLINK_DEVTOOLS_ENABLED=true
|
||||
|
||||
#==============================================================================
|
||||
# XSS 防护配置 (XSS Protection Configuration)
|
||||
#==============================================================================
|
||||
|
||||
# XSS 过滤开关
|
||||
# 可选值: true/false
|
||||
# 默认: true
|
||||
HAIRLINK_XSS_ENABLED=true
|
||||
|
||||
# XSS 排除链接(多个用逗号分隔)
|
||||
# 这些链接不进行XSS过滤
|
||||
# 默认: /system/notice
|
||||
HAIRLINK_XSS_EXCLUDES=/system/notice
|
||||
|
||||
# XSS 匹配链接(多个用逗号分隔)
|
||||
# 只对这些链接进行XSS过滤
|
||||
# 默认: /system/*,/monitor/*,/tool/*
|
||||
HAIRLINK_XSS_URL_PATTERNS=/system/*,/monitor/*,/tool/*
|
||||
|
||||
#==============================================================================
|
||||
# 防盗链配置 (Referer Protection Configuration)
|
||||
#==============================================================================
|
||||
|
||||
# 防盗链开关
|
||||
# 可选值: true/false
|
||||
# 默认: false
|
||||
HAIRLINK_REFERER_ENABLED=false
|
||||
|
||||
# 允许的域名列表(多个用逗号分隔)
|
||||
# 默认: localhost,127.0.0.1
|
||||
HAIRLINK_REFERER_ALLOWED_DOMAINS=localhost,127.0.0.1
|
||||
|
||||
#==============================================================================
|
||||
# Druid 数据库监控配置 (Druid Monitor Configuration)
|
||||
#==============================================================================
|
||||
|
||||
# Druid 监控后台管理员用户名
|
||||
# 访问地址: http://localhost:8080/druid
|
||||
# 默认: admin
|
||||
# ⚠️ 生产环境请务必修改
|
||||
HAIRLINK_DRUID_USERNAME=admin
|
||||
|
||||
# Druid 监控后台管理员密码
|
||||
# 默认: 123456
|
||||
# ⚠️ 生产环境请务必修改为强密码
|
||||
HAIRLINK_DRUID_PASSWORD=123456
|
||||
|
||||
# 慢 SQL 记录阈值(单位:毫秒)
|
||||
# 超过此时间的SQL将被记录为慢SQL
|
||||
# 默认: 1000 (1秒)
|
||||
HAIRLINK_DRUID_SLOW_SQL_MILLIS=1000
|
||||
|
||||
#==============================================================================
|
||||
# 日志配置 (Logging Configuration)
|
||||
#==============================================================================
|
||||
|
||||
# 日志文件存储路径
|
||||
# 默认: ./logs (相对路径,自动适配操作系统)
|
||||
# Windows示例: D:/hairlink/logs
|
||||
# Linux示例: /home/hairlink/logs
|
||||
HAIRLINK_LOG_PATH=./logs
|
||||
|
||||
# 日志文件保留天数
|
||||
# 默认: 60
|
||||
HAIRLINK_LOG_MAX_HISTORY=60
|
||||
|
||||
# com.ruoyi 包的日志级别
|
||||
# 可选值: trace/debug/info/warn/error
|
||||
# 默认: debug
|
||||
HAIRLINK_LOG_LEVEL_RUOYI=debug
|
||||
|
||||
# Spring 框架的日志级别
|
||||
# 可选值: trace/debug/info/warn/error
|
||||
# 默认: warn
|
||||
HAIRLINK_LOG_LEVEL_SPRING=warn
|
||||
|
||||
#==============================================================================
|
||||
# Swagger API 文档配置 (Swagger Configuration)
|
||||
#==============================================================================
|
||||
|
||||
# Swagger 开关
|
||||
# 开发环境建议开启,生产环境建议关闭
|
||||
# 可选值: true/false
|
||||
# 默认: true
|
||||
HAIRLINK_SWAGGER_ENABLED=true
|
||||
|
||||
# Swagger 请求前缀
|
||||
# 默认: /dev-api
|
||||
HAIRLINK_SWAGGER_PATH_MAPPING=/dev-api
|
||||
|
||||
################################################################################
|
||||
# 配置优先级说明:
|
||||
# 1. 环境变量 > YAML配置文件
|
||||
# 2. 如果环境变量未设置,将使用YAML中的默认值
|
||||
# 3. 建议生产环境通过环境变量覆盖敏感配置
|
||||
#
|
||||
# 生产环境必须修改的配置项:
|
||||
# - HAIRLINK_DB_PASSWORD: 数据库密码
|
||||
# - HAIRLINK_TOKEN_SECRET: Token密钥(建议32位以上随机字符串)
|
||||
# - HAIRLINK_DRUID_PASSWORD: Druid监控密码
|
||||
# - HAIRLINK_REDIS_PASSWORD: Redis密码(如果Redis设置了密码)
|
||||
# - HAIRLINK_DEVTOOLS_ENABLED: 建议设置为false
|
||||
# - HAIRLINK_SWAGGER_ENABLED: 建议设置为false
|
||||
################################################################################
|
||||
|
|
@ -45,3 +45,47 @@ nbdist/
|
|||
!*/build/*.java
|
||||
!*/build/*.html
|
||||
!*/build/*.xml
|
||||
|
||||
# 灵妙AI开发助手自动添加
|
||||
.specstory/
|
||||
log/
|
||||
logs/
|
||||
**/AGENTS.md
|
||||
**/WARP.md
|
||||
**/GEMINI.md
|
||||
.bmad/
|
||||
.claude/commands/bmad/
|
||||
.cursor/rules/bmad/
|
||||
.cursor/rules/root-prompt.mdc
|
||||
|
||||
# MCP反馈端口文件(灵妙AI开发助手自动添加)
|
||||
.mcp-feedback-port
|
||||
|
||||
# 点思工坊工作流目录(灵妙AI开发助手自动添加)
|
||||
.diansi/workflows
|
||||
|
||||
# 数据库数据目录(灵妙AI开发助手自动添加)
|
||||
mysql_data/
|
||||
redis_data/
|
||||
pgvector_data/
|
||||
|
||||
# 进程ID文件目录(灵妙AI开发助手自动添加)
|
||||
.pids/
|
||||
|
||||
# 本地设置文件(灵妙AI开发助手自动添加)
|
||||
.claude/settings.local.json
|
||||
|
||||
######################################################################
|
||||
# Environment Variables & Application Data
|
||||
|
||||
# 环境变量配置文件(包含敏感信息,不提交到版本控制)
|
||||
.env
|
||||
.env.local
|
||||
.env.development
|
||||
.env.test
|
||||
.env.production
|
||||
.env.*.local
|
||||
|
||||
# 上传文件目录
|
||||
upload/
|
||||
uploadPath/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
# 开发文档索引
|
||||
|
||||
本目录包含项目的开发相关文档。
|
||||
|
||||
## 文档列表
|
||||
|
||||
### 架构设计文档
|
||||
- [TENANT_GUIDE.md](./TENANT_GUIDE.md) - 若依多租户架构实现指南
|
||||
- 多租户SaaS架构概述与方案对比
|
||||
- 核心组件实现原理(TenantContext、拦截器、SQL改写)
|
||||
- 快速集成指南(依赖配置、代码集成、数据库改造)
|
||||
- 使用指南与开发注意事项
|
||||
- 最佳实践与性能优化
|
||||
- 常见问题FAQ
|
||||
|
||||
## 文档规范
|
||||
|
||||
所有开发文档遵循以下规范:
|
||||
- 文档命名格式:`{分类}_{英文大写}.md`,单词用 `_` 隔开
|
||||
- 文档使用简体中文编写
|
||||
- 言简意赅,避免长篇大论
|
||||
- 及时更新本索引文件
|
||||
File diff suppressed because it is too large
Load Diff
23
pom.xml
23
pom.xml
|
|
@ -8,9 +8,9 @@
|
|||
<artifactId>ruoyi</artifactId>
|
||||
<version>3.9.1</version>
|
||||
|
||||
<name>ruoyi</name>
|
||||
<url>http://www.ruoyi.vip</url>
|
||||
<description>若依管理系统</description>
|
||||
<name>hair-link</name>
|
||||
<url>http://www.example.com</url>
|
||||
<description>Hair-Link 美发预约管理系统</description>
|
||||
|
||||
<properties>
|
||||
<ruoyi.version>3.9.1</ruoyi.version>
|
||||
|
|
@ -30,6 +30,9 @@
|
|||
<poi.version>4.1.2</poi.version>
|
||||
<velocity.version>2.3</velocity.version>
|
||||
<jwt.version>0.9.1</jwt.version>
|
||||
<!-- 多租户相关依赖版本 -->
|
||||
<transmittable-thread-local.version>2.14.3</transmittable-thread-local.version>
|
||||
<jsqlparser.version>4.6</jsqlparser.version>
|
||||
<!-- override dependency version -->
|
||||
<tomcat.version>9.0.112</tomcat.version>
|
||||
<logback.version>1.2.13</logback.version>
|
||||
|
|
@ -218,6 +221,20 @@
|
|||
<version>${ruoyi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 多租户支持 - TransmittableThreadLocal -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
<version>${transmittable-thread-local.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- SQL解析器 - JSqlParser -->
|
||||
<dependency>
|
||||
<groupId>com.github.jsqlparser</groupId>
|
||||
<artifactId>jsqlparser</artifactId>
|
||||
<version>${jsqlparser.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<artifactId>hair-link</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.9.1</version>
|
||||
</parent>
|
||||
|
|
|
|||
|
|
@ -16,15 +16,14 @@ public class RuoYiApplication
|
|||
{
|
||||
// System.setProperty("spring.devtools.restart.enabled", "false");
|
||||
SpringApplication.run(RuoYiApplication.class, args);
|
||||
System.out.println("(♥◠‿◠)ノ゙ 若依启动成功 ლ(´ڡ`ლ)゙ \n" +
|
||||
" .-------. ____ __ \n" +
|
||||
" | _ _ \\ \\ \\ / / \n" +
|
||||
" | ( ' ) | \\ _. / ' \n" +
|
||||
" |(_ o _) / _( )_ .' \n" +
|
||||
" | (_,_).' __ ___(_ o _)' \n" +
|
||||
" | |\\ \\ | || |(_,_)' \n" +
|
||||
" | | \\ `' /| `-' / \n" +
|
||||
" | | \\ / \\ / \n" +
|
||||
" ''-' `'-' `-..-' ");
|
||||
System.out.println("\n" +
|
||||
" ███████╗██╗ ██╗██╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗ ██████╗ \n" +
|
||||
" ██╔════╝██║ ██║██║██╔════╝ ██║ ██║██╔══██╗████╗ ██║██╔════╝ \n" +
|
||||
" ███████╗███████║██║██║ ███╗██║ ██║███████║██╔██╗ ██║██║ ███╗\n" +
|
||||
" ╚════██║██╔══██║██║██║ ██║██║ ██║██╔══██║██║╚██╗██║██║ ██║\n" +
|
||||
" ███████║██║ ██║██║╚██████╔╝╚██████╔╝██║ ██║██║ ╚████║╚██████╔╝\n" +
|
||||
" ╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ \n" +
|
||||
"\n" +
|
||||
" 【 时 光 】 启动成功!\n");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ public class SysLoginController
|
|||
|
||||
/**
|
||||
* 登录方法
|
||||
*
|
||||
*
|
||||
* @param loginBody 登录信息
|
||||
* @return 结果
|
||||
*/
|
||||
|
|
@ -59,7 +59,7 @@ public class SysLoginController
|
|||
AjaxResult ajax = AjaxResult.success();
|
||||
// 生成令牌
|
||||
String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
|
||||
loginBody.getUuid());
|
||||
loginBody.getUuid(), loginBody.getTenantCode());
|
||||
ajax.put(Constants.TOKEN, token);
|
||||
return ajax;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
package com.ruoyi.web.controller.system;
|
||||
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.ruoyi.system.domain.SysTenant;
|
||||
import com.ruoyi.system.service.ISysTenantService;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
|
||||
/**
|
||||
* 租户信息Controller
|
||||
*
|
||||
* @author W-yf
|
||||
* @date 2025-12-19
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/link/tenant")
|
||||
public class SysTenantController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private ISysTenantService sysTenantService;
|
||||
|
||||
/**
|
||||
* 查询租户信息列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('link:tenant:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(
|
||||
SysTenant sysTenant)
|
||||
{
|
||||
startPage();
|
||||
List<SysTenant> list = sysTenantService.selectSysTenantList(sysTenant);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出租户信息列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('link:tenant:export')")
|
||||
@Log(title = "租户信息", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, SysTenant sysTenant)
|
||||
{
|
||||
List<SysTenant> list = sysTenantService.selectSysTenantList(sysTenant);
|
||||
ExcelUtil<SysTenant> util = new ExcelUtil<SysTenant>(SysTenant.class);
|
||||
util.exportExcel(response, list, "租户信息数据");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户信息详细信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('link:tenant:query')")
|
||||
@GetMapping(value = "/{tenantId}")
|
||||
public AjaxResult getInfo(@PathVariable("tenantId") Long tenantId)
|
||||
{
|
||||
return success(sysTenantService.selectSysTenantByTenantId(tenantId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增租户信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('link:tenant:add')")
|
||||
@Log(title = "租户信息", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody SysTenant sysTenant)
|
||||
{
|
||||
return toAjax(sysTenantService.insertSysTenant(sysTenant));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改租户信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('link:tenant:edit')")
|
||||
@Log(title = "租户信息", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody SysTenant sysTenant)
|
||||
{
|
||||
return toAjax(sysTenantService.updateSysTenant(sysTenant));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除租户信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('link:tenant:remove')")
|
||||
@Log(title = "租户信息", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{tenantIds}")
|
||||
public AjaxResult remove(@PathVariable Long[] tenantIds)
|
||||
{
|
||||
return toAjax(sysTenantService.deleteSysTenantByTenantIds(tenantIds));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package com.ruoyi.web.controller.system;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.system.domain.SysTenantPackage;
|
||||
import com.ruoyi.system.service.ISysTenantPackageService;
|
||||
|
||||
/**
|
||||
* 租户套餐Controller
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-12-19
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/system/package")
|
||||
public class SysTenantPackageController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private ISysTenantPackageService packageService;
|
||||
|
||||
/**
|
||||
* 查询租户套餐列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:package:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(SysTenantPackage sysTenantPackage)
|
||||
{
|
||||
startPage();
|
||||
List<SysTenantPackage> list = packageService.selectPackageList(sysTenantPackage);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户套餐详细信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:package:query')")
|
||||
@GetMapping(value = "/{packageId}")
|
||||
public AjaxResult getInfo(@PathVariable("packageId") Long packageId)
|
||||
{
|
||||
return success(packageService.selectPackageById(packageId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增租户套餐
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:package:add')")
|
||||
@Log(title = "租户套餐", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@Validated @RequestBody SysTenantPackage sysTenantPackage)
|
||||
{
|
||||
return toAjax(packageService.insertPackage(sysTenantPackage));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改租户套餐
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:package:edit')")
|
||||
@Log(title = "租户套餐", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@Validated @RequestBody SysTenantPackage sysTenantPackage)
|
||||
{
|
||||
return toAjax(packageService.updatePackage(sysTenantPackage));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除租户套餐
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:package:remove')")
|
||||
@Log(title = "租户套餐", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{packageIds}")
|
||||
public AjaxResult remove(@PathVariable Long[] packageIds)
|
||||
{
|
||||
return toAjax(packageService.deletePackageByIds(packageIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询套餐关联的菜单ID列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:package:query')")
|
||||
@GetMapping(value = "/{packageId}/menus")
|
||||
public AjaxResult getPackageMenus(@PathVariable("packageId") Long packageId)
|
||||
{
|
||||
List<Long> menuIds = packageService.selectMenuIdsByPackageId(packageId);
|
||||
return success(menuIds);
|
||||
}
|
||||
}
|
||||
|
|
@ -27,9 +27,11 @@ import com.ruoyi.common.enums.BusinessType;
|
|||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.system.domain.SysTenant;
|
||||
import com.ruoyi.system.service.ISysDeptService;
|
||||
import com.ruoyi.system.service.ISysPostService;
|
||||
import com.ruoyi.system.service.ISysRoleService;
|
||||
import com.ruoyi.system.service.ISysTenantService;
|
||||
import com.ruoyi.system.service.ISysUserService;
|
||||
|
||||
/**
|
||||
|
|
@ -53,6 +55,9 @@ public class SysUserController extends BaseController
|
|||
@Autowired
|
||||
private ISysPostService postService;
|
||||
|
||||
@Autowired
|
||||
private ISysTenantService tenantService;
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
*/
|
||||
|
|
@ -113,6 +118,13 @@ public class SysUserController extends BaseController
|
|||
List<SysRole> roles = roleService.selectRoleAll();
|
||||
ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
|
||||
ajax.put("posts", postService.selectPostAll());
|
||||
// 如果是超级管理员,返回租户列表
|
||||
if (SysUser.isAdmin(SecurityUtils.getUserId()))
|
||||
{
|
||||
SysTenant query = new SysTenant();
|
||||
query.setStatus("0");
|
||||
ajax.put("tenants", tenantService.selectSysTenantList(query));
|
||||
}
|
||||
return ajax;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,34 +6,34 @@ spring:
|
|||
druid:
|
||||
# 主库数据源
|
||||
master:
|
||||
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: password
|
||||
url: jdbc:mysql://${HAIRLINK_DB_HOST:localhost}:${HAIRLINK_DB_PORT:3306}/${HAIRLINK_DB_NAME:ry-vue}?${HAIRLINK_DB_PARAMS:useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8}
|
||||
username: ${HAIRLINK_DB_USERNAME:root}
|
||||
password: ${HAIRLINK_DB_PASSWORD:password}
|
||||
# 从库数据源
|
||||
slave:
|
||||
# 从数据源开关/默认关闭
|
||||
enabled: false
|
||||
url:
|
||||
username:
|
||||
password:
|
||||
enabled: ${HAIRLINK_DB_SLAVE_ENABLED:false}
|
||||
url: jdbc:mysql://${HAIRLINK_DB_SLAVE_HOST:}:${HAIRLINK_DB_SLAVE_PORT:}/${HAIRLINK_DB_SLAVE_NAME:}?${HAIRLINK_DB_SLAVE_PARAMS:}
|
||||
username: ${HAIRLINK_DB_SLAVE_USERNAME:}
|
||||
password: ${HAIRLINK_DB_SLAVE_PASSWORD:}
|
||||
# 初始连接数
|
||||
initialSize: 5
|
||||
initialSize: ${HAIRLINK_DB_INITIAL_SIZE:5}
|
||||
# 最小连接池数量
|
||||
minIdle: 10
|
||||
minIdle: ${HAIRLINK_DB_MIN_IDLE:10}
|
||||
# 最大连接池数量
|
||||
maxActive: 20
|
||||
maxActive: ${HAIRLINK_DB_MAX_ACTIVE:20}
|
||||
# 配置获取连接等待超时的时间
|
||||
maxWait: 60000
|
||||
maxWait: ${HAIRLINK_DB_MAX_WAIT:60000}
|
||||
# 配置连接超时时间
|
||||
connectTimeout: 30000
|
||||
connectTimeout: ${HAIRLINK_DB_CONNECT_TIMEOUT:30000}
|
||||
# 配置网络超时时间
|
||||
socketTimeout: 60000
|
||||
socketTimeout: ${HAIRLINK_DB_SOCKET_TIMEOUT:60000}
|
||||
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
|
||||
timeBetweenEvictionRunsMillis: 60000
|
||||
timeBetweenEvictionRunsMillis: ${HAIRLINK_DB_TIME_BETWEEN_EVICTION:60000}
|
||||
# 配置一个连接在池中最小生存的时间,单位是毫秒
|
||||
minEvictableIdleTimeMillis: 300000
|
||||
minEvictableIdleTimeMillis: ${HAIRLINK_DB_MIN_EVICTABLE_IDLE_TIME:300000}
|
||||
# 配置一个连接在池中最大生存的时间,单位是毫秒
|
||||
maxEvictableIdleTimeMillis: 900000
|
||||
maxEvictableIdleTimeMillis: ${HAIRLINK_DB_MAX_EVICTABLE_IDLE_TIME:900000}
|
||||
# 配置检测连接是否有效
|
||||
validationQuery: SELECT 1 FROM DUAL
|
||||
testWhileIdle: true
|
||||
|
|
@ -47,14 +47,14 @@ spring:
|
|||
allow:
|
||||
url-pattern: /druid/*
|
||||
# 控制台管理用户名和密码
|
||||
login-username: ruoyi
|
||||
login-password: 123456
|
||||
login-username: ${HAIRLINK_DRUID_USERNAME:admin}
|
||||
login-password: ${HAIRLINK_DRUID_PASSWORD:123456}
|
||||
filter:
|
||||
stat:
|
||||
enabled: true
|
||||
# 慢SQL记录
|
||||
log-slow-sql: true
|
||||
slow-sql-millis: 1000
|
||||
slow-sql-millis: ${HAIRLINK_DRUID_SLOW_SQL_MILLIS:1000}
|
||||
merge-sql: true
|
||||
wall:
|
||||
config:
|
||||
|
|
|
|||
|
|
@ -1,49 +1,49 @@
|
|||
# 项目相关配置
|
||||
ruoyi:
|
||||
hairlink:
|
||||
# 名称
|
||||
name: RuoYi
|
||||
name: Hair-Link
|
||||
# 版本
|
||||
version: 3.9.1
|
||||
# 版权年份
|
||||
copyrightYear: 2025
|
||||
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
|
||||
profile: D:/ruoyi/uploadPath
|
||||
# 文件路径 示例( Windows配置D:/hairlink/uploadPath,Linux配置 /home/hairlink/uploadPath)
|
||||
profile: ${HAIRLINK_UPLOAD_PATH:./upload}
|
||||
# 获取ip地址开关
|
||||
addressEnabled: false
|
||||
addressEnabled: ${HAIRLINK_ADDRESS_ENABLED:false}
|
||||
# 验证码类型 math 数字计算 char 字符验证
|
||||
captchaType: math
|
||||
captchaType: ${HAIRLINK_CAPTCHA_TYPE:math}
|
||||
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080
|
||||
port: 8080
|
||||
port: ${HAIRLINK_SERVER_PORT:8080}
|
||||
servlet:
|
||||
# 应用的访问路径
|
||||
context-path: /
|
||||
context-path: ${HAIRLINK_CONTEXT_PATH:/}
|
||||
tomcat:
|
||||
# tomcat的URI编码
|
||||
uri-encoding: UTF-8
|
||||
uri-encoding: ${HAIRLINK_SERVER_URI_ENCODING:UTF-8}
|
||||
# 连接数满后的排队数,默认为100
|
||||
accept-count: 1000
|
||||
accept-count: ${HAIRLINK_SERVER_ACCEPT_COUNT:1000}
|
||||
threads:
|
||||
# tomcat最大线程数,默认为200
|
||||
max: 800
|
||||
max: ${HAIRLINK_SERVER_MAX_THREADS:800}
|
||||
# Tomcat启动初始化的线程数,默认值10
|
||||
min-spare: 100
|
||||
min-spare: ${HAIRLINK_SERVER_MIN_SPARE_THREADS:100}
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
com.ruoyi: debug
|
||||
org.springframework: warn
|
||||
com.ruoyi: ${HAIRLINK_LOG_LEVEL_RUOYI:debug}
|
||||
org.springframework: ${HAIRLINK_LOG_LEVEL_SPRING:warn}
|
||||
|
||||
# 用户配置
|
||||
user:
|
||||
password:
|
||||
# 密码最大错误次数
|
||||
maxRetryCount: 5
|
||||
maxRetryCount: ${HAIRLINK_PASSWORD_MAX_RETRY:5}
|
||||
# 密码锁定时间(默认10分钟)
|
||||
lockTime: 10
|
||||
lockTime: ${HAIRLINK_PASSWORD_LOCK_TIME:10}
|
||||
|
||||
# Spring配置
|
||||
spring:
|
||||
|
|
@ -57,45 +57,45 @@ spring:
|
|||
servlet:
|
||||
multipart:
|
||||
# 单个文件大小
|
||||
max-file-size: 10MB
|
||||
max-file-size: ${HAIRLINK_MAX_FILE_SIZE:10MB}
|
||||
# 设置总上传的文件大小
|
||||
max-request-size: 20MB
|
||||
max-request-size: ${HAIRLINK_MAX_REQUEST_SIZE:20MB}
|
||||
# 服务模块
|
||||
devtools:
|
||||
restart:
|
||||
# 热部署开关
|
||||
enabled: true
|
||||
enabled: ${HAIRLINK_DEVTOOLS_ENABLED:true}
|
||||
# redis 配置
|
||||
redis:
|
||||
# 地址
|
||||
host: localhost
|
||||
host: ${HAIRLINK_REDIS_HOST:localhost}
|
||||
# 端口,默认为6379
|
||||
port: 6379
|
||||
port: ${HAIRLINK_REDIS_PORT:6379}
|
||||
# 数据库索引
|
||||
database: 0
|
||||
database: ${HAIRLINK_REDIS_DATABASE:0}
|
||||
# 密码
|
||||
password:
|
||||
password: ${HAIRLINK_REDIS_PASSWORD:}
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
timeout: ${HAIRLINK_REDIS_TIMEOUT:10s}
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
min-idle: ${HAIRLINK_REDIS_MIN_IDLE:0}
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 8
|
||||
max-idle: ${HAIRLINK_REDIS_MAX_IDLE:8}
|
||||
# 连接池的最大数据库连接数
|
||||
max-active: 8
|
||||
max-active: ${HAIRLINK_REDIS_MAX_ACTIVE:8}
|
||||
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
max-wait: ${HAIRLINK_REDIS_MAX_WAIT:-1ms}
|
||||
|
||||
# token配置
|
||||
token:
|
||||
# 令牌自定义标识
|
||||
header: Authorization
|
||||
header: ${HAIRLINK_TOKEN_HEADER:Authorization}
|
||||
# 令牌密钥
|
||||
secret: abcdefghijklmnopqrstuvwxyz
|
||||
secret: ${HAIRLINK_TOKEN_SECRET:abcdefghijklmnopqrstuvwxyz}
|
||||
# 令牌有效期(默认30分钟)
|
||||
expireTime: 30
|
||||
expireTime: ${HAIRLINK_TOKEN_EXPIRE_TIME:30}
|
||||
|
||||
# MyBatis配置
|
||||
mybatis:
|
||||
|
|
@ -115,22 +115,22 @@ pagehelper:
|
|||
# Swagger配置
|
||||
swagger:
|
||||
# 是否开启swagger
|
||||
enabled: true
|
||||
enabled: ${HAIRLINK_SWAGGER_ENABLED:true}
|
||||
# 请求前缀
|
||||
pathMapping: /dev-api
|
||||
pathMapping: ${HAIRLINK_SWAGGER_PATH_MAPPING:/dev-api}
|
||||
|
||||
# 防盗链配置
|
||||
referer:
|
||||
# 防盗链开关
|
||||
enabled: false
|
||||
enabled: ${HAIRLINK_REFERER_ENABLED:false}
|
||||
# 允许的域名列表
|
||||
allowed-domains: localhost,127.0.0.1,ruoyi.vip,www.ruoyi.vip
|
||||
allowed-domains: ${HAIRLINK_REFERER_ALLOWED_DOMAINS:localhost,127.0.0.1}
|
||||
|
||||
# 防止XSS攻击
|
||||
xss:
|
||||
# 过滤开关
|
||||
enabled: true
|
||||
enabled: ${HAIRLINK_XSS_ENABLED:true}
|
||||
# 排除链接(多个用逗号分隔)
|
||||
excludes: /system/notice
|
||||
excludes: ${HAIRLINK_XSS_EXCLUDES:/system/notice}
|
||||
# 匹配链接
|
||||
urlPatterns: /system/*,/monitor/*,/tool/*
|
||||
urlPatterns: ${HAIRLINK_XSS_URL_PATTERNS:/system/*,/monitor/*,/tool/*}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,30 @@
|
|||
Application Version: ${ruoyi.version}
|
||||
Application Version: ${hairlink.version}
|
||||
Spring Boot Version: ${spring-boot.version}
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// _ooOoo_ //
|
||||
// o8888888o //
|
||||
// 88" . "88 //
|
||||
// (| ^_^ |) //
|
||||
// O\ = /O //
|
||||
// ____/`---'\____ //
|
||||
// .' \\| |// `. //
|
||||
// / \\||| : |||// \ //
|
||||
// / _||||| -:- |||||- \ //
|
||||
// | | \\\ - /// | | //
|
||||
// | \_| ''\---/'' | | //
|
||||
// \ .-\__ `-` ___/-. / //
|
||||
// ___`. .' /--.--\ `. . ___ //
|
||||
// ."" '< `.___\_<|>_/___.' >'"". //
|
||||
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
|
||||
// \ \ `-. \_ __\ /__ _/ .-` / / //
|
||||
// ========`-.____`-.___\_____/___.-`____.-'======== //
|
||||
// `=---=' //
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
|
||||
// 佛祖保佑 永不宕机 永无BUG //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
╔═══════════════════════╗
|
||||
║ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ║
|
||||
║ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ║
|
||||
║ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ║
|
||||
╚══╗ ▓▓▓▓▓▓▓▓▓ ╔══╝
|
||||
╲ ▓▓▓▓▓▓▓ ╱
|
||||
╲ ▓▓▓▓▓ ╱
|
||||
╲ ▓▓▓ ╱
|
||||
╲ ▓ ╱
|
||||
╲ ╱
|
||||
╲ ╱
|
||||
╲ ╱
|
||||
V
|
||||
╱ ╲
|
||||
╱ ╲
|
||||
╱ ╲
|
||||
╱ ░ ╲
|
||||
╱ ░░░ ╲
|
||||
╱ ░░░░░ ╲
|
||||
╱ ░░░░░░░ ╲
|
||||
╔══╝ ░░░░░░░░░ ╚══╗
|
||||
║ ░░░░░░░░░░░░░ ║
|
||||
║ ░░░░░░░░░░░░░░░░░ ║
|
||||
║ ░░░░░░░░░░░░░░░░░░░ ║
|
||||
╚═══════════════════════╝
|
||||
|
||||
═══【时 光】═══
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<!-- 日志存放路径 -->
|
||||
<property name="log.path" value="/home/ruoyi/logs" />
|
||||
<property name="log.path" value="${HAIRLINK_LOG_PATH:-./logs}" />
|
||||
<!-- 日志输出格式 -->
|
||||
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
|
||||
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>60</maxHistory>
|
||||
<maxHistory>${HAIRLINK_LOG_MAX_HISTORY:-60}</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>60</maxHistory>
|
||||
<maxHistory>${HAIRLINK_LOG_MAX_HISTORY:-60}</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
<!-- 按天回滚 daily -->
|
||||
<fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>60</maxHistory>
|
||||
<maxHistory>${HAIRLINK_LOG_MAX_HISTORY:-60}</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<artifactId>hair-link</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.9.1</version>
|
||||
</parent>
|
||||
|
|
@ -113,6 +113,12 @@
|
|||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 多租户支持 - TransmittableThreadLocal -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -5,11 +5,11 @@ import org.springframework.stereotype.Component;
|
|||
|
||||
/**
|
||||
* 读取项目相关配置
|
||||
*
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "ruoyi")
|
||||
@ConfigurationProperties(prefix = "hairlink")
|
||||
public class RuoYiConfig
|
||||
{
|
||||
/** 项目名称 */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
package com.ruoyi.common.config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 租户配置类
|
||||
*
|
||||
* Reason: 集中管理租户相关配置,包括超级管理员列表
|
||||
*/
|
||||
public class TenantConfig {
|
||||
|
||||
/**
|
||||
* 超级管理员用户ID列表
|
||||
* Reason: 使用固定用户ID判断,简单可靠,避免角色权限体系的复杂度
|
||||
*
|
||||
* 超级管理员拥有以下特权:
|
||||
* 1. 登录后自动进入全局模式
|
||||
* 2. 可以查看所有租户的数据
|
||||
* 3. 所有操作都会记录审计日志
|
||||
*/
|
||||
public static final List<Long> SUPER_ADMIN_USER_IDS = Arrays.asList(
|
||||
1L // admin 用户
|
||||
);
|
||||
|
||||
/**
|
||||
* 忽略租户过滤的URL路径(不需要租户隔离的接口)
|
||||
* Reason: 登录、验证码等公共接口不需要租户上下文
|
||||
*/
|
||||
public static final List<String> IGNORE_URLS = Arrays.asList(
|
||||
"/login",
|
||||
"/captchaImage",
|
||||
"/logout",
|
||||
"/register"
|
||||
);
|
||||
|
||||
/**
|
||||
* 需要进行租户隔离的表名列表
|
||||
* Reason: 明确定义需要拦截的表,避免误拦截
|
||||
*/
|
||||
public static final List<String> TENANT_TABLES = Arrays.asList(
|
||||
// 若依框架表
|
||||
"sys_user",
|
||||
"sys_dept",
|
||||
"sys_role",
|
||||
"sys_post",
|
||||
"sys_notice",
|
||||
// HairLink业务表
|
||||
"hl_customer",
|
||||
"hl_member_card",
|
||||
"hl_service",
|
||||
"hl_staff",
|
||||
"hl_order"
|
||||
);
|
||||
|
||||
/**
|
||||
* 混合模式表(系统级 + 租户级共存)
|
||||
* Reason: 这些表同时支持系统级数据(tenant_id=0)和租户级数据
|
||||
* 查询时使用 WHERE (tenant_id = ? OR tenant_id = 0)
|
||||
*/
|
||||
public static final List<String> HYBRID_TABLES = Arrays.asList(
|
||||
"sys_dict_type",
|
||||
"sys_dict_data"
|
||||
);
|
||||
|
||||
/**
|
||||
* 不需要租户隔离的表(白名单)
|
||||
* Reason: 这些表是全局共享的,所有租户共用
|
||||
*/
|
||||
public static final List<String> IGNORE_TABLES = Arrays.asList(
|
||||
"sys_menu",
|
||||
"sys_config",
|
||||
"sys_user_role",
|
||||
"sys_role_menu",
|
||||
"sys_role_dept",
|
||||
"sys_tenant",
|
||||
"sys_logininfor",
|
||||
"sys_oper_log",
|
||||
"sys_job",
|
||||
"sys_job_log"
|
||||
);
|
||||
|
||||
/**
|
||||
* 判断是否是超级管理员
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return true-超级管理员,false-普通用户
|
||||
*/
|
||||
public static boolean isSuperAdmin(Long userId) {
|
||||
if (userId == null) {
|
||||
return false;
|
||||
}
|
||||
return SUPER_ADMIN_USER_IDS.contains(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断URL是否需要忽略租户过滤
|
||||
*
|
||||
* @param url 请求URL
|
||||
* @return true-忽略,false-需要过滤
|
||||
*/
|
||||
public static boolean isIgnoreUrl(String url) {
|
||||
if (url == null) {
|
||||
return false;
|
||||
}
|
||||
return IGNORE_URLS.stream().anyMatch(url::contains);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断表是否需要租户隔离
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @return true-需要隔离,false-不需要
|
||||
*/
|
||||
public static boolean needTenantFilter(String tableName) {
|
||||
if (tableName == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String lowerTableName = tableName.toLowerCase();
|
||||
|
||||
// 白名单表不需要过滤
|
||||
if (IGNORE_TABLES.stream().anyMatch(lowerTableName::equals)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 租户表需要过滤
|
||||
return TENANT_TABLES.stream().anyMatch(lowerTableName::equals);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断表是否为混合模式表
|
||||
* Reason: 混合模式表同时支持系统级数据(tenant_id=0)和租户级数据
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @return true-混合模式表,false-非混合模式表
|
||||
*/
|
||||
public static boolean isHybridTable(String tableName) {
|
||||
if (tableName == null) {
|
||||
return false;
|
||||
}
|
||||
return HYBRID_TABLES.stream().anyMatch(tableName.toLowerCase()::equals);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package com.ruoyi.common.core.context;
|
||||
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 租户上下文 - 基于ThreadLocal实现租户ID的传递
|
||||
*
|
||||
* Reason: 使用TransmittableThreadLocal支持线程池场景下的上下文传递
|
||||
*/
|
||||
public class TenantContext {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TenantContext.class);
|
||||
|
||||
/**
|
||||
* 支持父子线程传递的ThreadLocal
|
||||
* Reason: 若依框架使用了异步任务和线程池,普通ThreadLocal会丢失上下文
|
||||
*/
|
||||
private static final TransmittableThreadLocal<Long> TENANT_ID_HOLDER = new TransmittableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 忽略租户过滤的标志(用于超级管理员或系统任务)
|
||||
*/
|
||||
private static final TransmittableThreadLocal<Boolean> IGNORE_TENANT = new TransmittableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 设置当前租户ID
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
public static void setTenantId(Long tenantId) {
|
||||
if (tenantId == null) {
|
||||
log.warn("尝试设置NULL租户ID,已拒绝");
|
||||
return;
|
||||
}
|
||||
TENANT_ID_HOLDER.set(tenantId);
|
||||
log.debug("TenantContext设置租户ID: {}", tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前租户ID
|
||||
*
|
||||
* @return 租户ID
|
||||
*/
|
||||
public static Long getTenantId() {
|
||||
return TENANT_ID_HOLDER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除租户上下文
|
||||
* Reason: 请求结束后必须清除,避免线程池复用时的上下文污染
|
||||
*/
|
||||
public static void clear() {
|
||||
TENANT_ID_HOLDER.remove();
|
||||
IGNORE_TENANT.remove();
|
||||
log.trace("TenantContext已清除");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置忽略租户过滤(超级管理员专用)
|
||||
*
|
||||
* @param ignore 是否忽略租户过滤
|
||||
*/
|
||||
public static void setIgnore(boolean ignore) {
|
||||
IGNORE_TENANT.set(ignore);
|
||||
if (ignore) {
|
||||
log.warn("租户过滤已禁用,当前处于全局模式!请谨慎操作");
|
||||
} else {
|
||||
log.info("租户过滤已启用");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否忽略租户过滤
|
||||
*
|
||||
* @return true-忽略(全局模式),false-正常过滤
|
||||
*/
|
||||
public static boolean isIgnore() {
|
||||
Boolean ignore = IGNORE_TENANT.get();
|
||||
return ignore != null && ignore;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前租户ID(如果不存在则抛出异常)
|
||||
*
|
||||
* @return 租户ID
|
||||
* @throws com.ruoyi.common.exception.ServiceException 租户ID不存在时抛出
|
||||
*/
|
||||
public static Long getRequiredTenantId() {
|
||||
Long tenantId = getTenantId();
|
||||
if (tenantId == null && !isIgnore()) {
|
||||
log.error("【安全警告】租户上下文丢失,当前操作被拦截");
|
||||
throw new RuntimeException("租户上下文丢失,请重新登录");
|
||||
}
|
||||
return tenantId;
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,10 @@ public class BaseEntity implements Serializable
|
|||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 租户ID */
|
||||
@JsonIgnore
|
||||
private Long tenantId;
|
||||
|
||||
/** 请求参数 */
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
private Map<String, Object> params;
|
||||
|
|
@ -115,4 +119,14 @@ public class BaseEntity implements Serializable
|
|||
{
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public Long getTenantId()
|
||||
{
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void setTenantId(Long tenantId)
|
||||
{
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ public class SysDictData extends BaseEntity
|
|||
@Excel(name = "字典编码", cellType = ColumnType.NUMERIC)
|
||||
private Long dictCode;
|
||||
|
||||
/** 租户ID(0=系统级) */
|
||||
private Long tenantId;
|
||||
|
||||
/** 字典排序 */
|
||||
@Excel(name = "字典排序", cellType = ColumnType.NUMERIC)
|
||||
private Long dictSort;
|
||||
|
|
@ -62,6 +65,16 @@ public class SysDictData extends BaseEntity
|
|||
this.dictCode = dictCode;
|
||||
}
|
||||
|
||||
public Long getTenantId()
|
||||
{
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void setTenantId(Long tenantId)
|
||||
{
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
public Long getDictSort()
|
||||
{
|
||||
return dictSort;
|
||||
|
|
@ -158,6 +171,7 @@ public class SysDictData extends BaseEntity
|
|||
public String toString() {
|
||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("dictCode", getDictCode())
|
||||
.append("tenantId", getTenantId())
|
||||
.append("dictSort", getDictSort())
|
||||
.append("dictLabel", getDictLabel())
|
||||
.append("dictValue", getDictValue())
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ public class SysDictType extends BaseEntity
|
|||
@Excel(name = "字典主键", cellType = ColumnType.NUMERIC)
|
||||
private Long dictId;
|
||||
|
||||
/** 租户ID(0=系统级) */
|
||||
private Long tenantId;
|
||||
|
||||
/** 字典名称 */
|
||||
@Excel(name = "字典名称")
|
||||
private String dictName;
|
||||
|
|
@ -44,6 +47,16 @@ public class SysDictType extends BaseEntity
|
|||
this.dictId = dictId;
|
||||
}
|
||||
|
||||
public Long getTenantId()
|
||||
{
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void setTenantId(Long tenantId)
|
||||
{
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
@NotBlank(message = "字典名称不能为空")
|
||||
@Size(min = 0, max = 100, message = "字典类型名称长度不能超过100个字符")
|
||||
public String getDictName()
|
||||
|
|
@ -83,6 +96,7 @@ public class SysDictType extends BaseEntity
|
|||
public String toString() {
|
||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("dictId", getDictId())
|
||||
.append("tenantId", getTenantId())
|
||||
.append("dictName", getDictName())
|
||||
.append("dictType", getDictType())
|
||||
.append("status", getStatus())
|
||||
|
|
|
|||
|
|
@ -93,6 +93,9 @@ public class SysUser extends BaseEntity
|
|||
/** 角色ID */
|
||||
private Long roleId;
|
||||
|
||||
/** 租户ID */
|
||||
private Long tenantId;
|
||||
|
||||
public SysUser()
|
||||
{
|
||||
|
||||
|
|
@ -312,6 +315,16 @@ public class SysUser extends BaseEntity
|
|||
this.roleId = roleId;
|
||||
}
|
||||
|
||||
public Long getTenantId()
|
||||
{
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void setTenantId(Long tenantId)
|
||||
{
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ public class LoginBody
|
|||
*/
|
||||
private String uuid;
|
||||
|
||||
/**
|
||||
* 租户编码
|
||||
*/
|
||||
private String tenantCode;
|
||||
|
||||
public String getUsername()
|
||||
{
|
||||
return username;
|
||||
|
|
@ -66,4 +71,14 @@ public class LoginBody
|
|||
{
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public String getTenantCode()
|
||||
{
|
||||
return tenantCode;
|
||||
}
|
||||
|
||||
public void setTenantCode(String tenantCode)
|
||||
{
|
||||
this.tenantCode = tenantCode;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ public class LoginUser implements UserDetails
|
|||
*/
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 用户唯一标识
|
||||
*/
|
||||
|
|
@ -258,6 +263,16 @@ public class LoginUser implements UserDetails
|
|||
this.user = user;
|
||||
}
|
||||
|
||||
public Long getTenantId()
|
||||
{
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void setTenantId(Long tenantId)
|
||||
{
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.alibaba.fastjson2.JSONArray;
|
|||
import com.ruoyi.common.constant.CacheConstants;
|
||||
import com.ruoyi.common.core.domain.entity.SysDictData;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.common.core.context.TenantContext;
|
||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -206,12 +207,24 @@ public class DictUtils
|
|||
|
||||
/**
|
||||
* 设置cache key
|
||||
*
|
||||
* Reason: 字典数据支持混合模式(系统级tenant_id=0 + 租户级tenant_id=租户ID)
|
||||
* 每个租户的缓存包含:系统级字典 + 该租户的字典,需要按租户隔离缓存
|
||||
*
|
||||
* @param configKey 参数键
|
||||
* @return 缓存键key
|
||||
*/
|
||||
public static String getCacheKey(String configKey)
|
||||
{
|
||||
return CacheConstants.SYS_DICT_KEY + configKey;
|
||||
// 超级管理员或全局模式使用全局缓存
|
||||
if (TenantContext.isIgnore()) {
|
||||
return CacheConstants.SYS_DICT_KEY + "global:" + configKey;
|
||||
}
|
||||
// 租户使用租户级缓存
|
||||
Long tenantId = TenantContext.getTenantId();
|
||||
if (tenantId != null) {
|
||||
return CacheConstants.SYS_DICT_KEY + "tenant_" + tenantId + ":" + configKey;
|
||||
}
|
||||
// 兜底:无租户上下文时使用全局缓存
|
||||
return CacheConstants.SYS_DICT_KEY + "global:" + configKey;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<artifactId>hair-link</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.9.1</version>
|
||||
</parent>
|
||||
|
|
@ -59,6 +59,12 @@
|
|||
<artifactId>ruoyi-system</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- SQL解析器 - 用于MyBatis租户拦截器 -->
|
||||
<dependency>
|
||||
<groupId>com.github.jsqlparser</groupId>
|
||||
<artifactId>jsqlparser</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -23,10 +23,11 @@ import org.springframework.core.type.classreading.MetadataReader;
|
|||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.framework.interceptor.TenantSqlInterceptor;
|
||||
|
||||
/**
|
||||
* Mybatis支持*匹配扫描包
|
||||
*
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Configuration
|
||||
|
|
@ -35,6 +36,9 @@ public class MyBatisConfig
|
|||
@Autowired
|
||||
private Environment env;
|
||||
|
||||
@Autowired
|
||||
private TenantSqlInterceptor tenantSqlInterceptor;
|
||||
|
||||
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
|
||||
|
||||
public static String setTypeAliasesPackage(String typeAliasesPackage)
|
||||
|
|
@ -127,6 +131,10 @@ public class MyBatisConfig
|
|||
sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
|
||||
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
|
||||
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
|
||||
|
||||
// 【多租户】注册MyBatis租户SQL拦截器
|
||||
sessionFactory.setPlugins(tenantSqlInterceptor);
|
||||
|
||||
return sessionFactory.getObject();
|
||||
}
|
||||
}
|
||||
|
|
@ -14,10 +14,11 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||
import com.ruoyi.common.config.RuoYiConfig;
|
||||
import com.ruoyi.common.constant.Constants;
|
||||
import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
|
||||
import com.ruoyi.framework.interceptor.TenantInterceptor;
|
||||
|
||||
/**
|
||||
* 通用配置
|
||||
*
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Configuration
|
||||
|
|
@ -26,6 +27,9 @@ public class ResourcesConfig implements WebMvcConfigurer
|
|||
@Autowired
|
||||
private RepeatSubmitInterceptor repeatSubmitInterceptor;
|
||||
|
||||
@Autowired
|
||||
private TenantInterceptor tenantInterceptor;
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry)
|
||||
{
|
||||
|
|
@ -45,6 +49,10 @@ public class ResourcesConfig implements WebMvcConfigurer
|
|||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry)
|
||||
{
|
||||
// 【多租户】租户拦截器 - 必须在第一位,优先设置租户上下文
|
||||
registry.addInterceptor(tenantInterceptor).addPathPatterns("/**");
|
||||
|
||||
// 防重复提交拦截器
|
||||
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
package com.ruoyi.framework.interceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import com.ruoyi.common.config.TenantConfig;
|
||||
import com.ruoyi.common.core.context.TenantContext;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
|
||||
/**
|
||||
* 租户拦截器 - 从LoginUser中提取tenant_id并设置到TenantContext
|
||||
*
|
||||
* Reason: 每个HTTP请求都需要识别当前租户,并将租户信息存入ThreadLocal
|
||||
*
|
||||
* 工作流程:
|
||||
* 1. 请求进入 → preHandle()
|
||||
* 2. 从SecurityContext获取LoginUser
|
||||
* 3. 判断是否是超级管理员
|
||||
* - 是:设置忽略租户过滤(全局模式)
|
||||
* - 否:提取tenant_id并设置到TenantContext
|
||||
* 4. 请求结束 → afterCompletion() → 清除TenantContext
|
||||
*/
|
||||
@Component
|
||||
public class TenantInterceptor implements HandlerInterceptor {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TenantInterceptor.class);
|
||||
|
||||
/**
|
||||
* 请求处理前的拦截
|
||||
*/
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||
throws Exception {
|
||||
|
||||
String requestUri = request.getRequestURI();
|
||||
|
||||
// 判断是否是忽略URL(如登录、验证码接口)
|
||||
if (TenantConfig.isIgnoreUrl(requestUri)) {
|
||||
log.trace("忽略URL租户过滤: {}", requestUri);
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// 从SecurityContext获取当前登录用户
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
|
||||
if (loginUser != null) {
|
||||
Long userId = loginUser.getUserId();
|
||||
|
||||
// 判断是否是超级管理员
|
||||
if (TenantConfig.isSuperAdmin(userId)) {
|
||||
// 超级管理员:忽略租户过滤(全局模式)
|
||||
TenantContext.setIgnore(true);
|
||||
log.warn("【超级管理员】用户 {} (ID:{}) 进入全局模式,可查看所有租户数据",
|
||||
loginUser.getUsername(), userId);
|
||||
}
|
||||
else if (loginUser.getTenantId() != null) {
|
||||
// 普通用户:设置租户ID
|
||||
TenantContext.setTenantId(loginUser.getTenantId());
|
||||
log.debug("【租户用户】用户 {} (ID:{}) 进入租户 {} 模式",
|
||||
loginUser.getUsername(), userId, loginUser.getTenantId());
|
||||
}
|
||||
else {
|
||||
// 未分配租户的普通用户(不允许访问)
|
||||
log.error("【安全警告】用户 {} (ID:{}) 未分配租户且非超级管理员,拒绝访问",
|
||||
loginUser.getUsername(), userId);
|
||||
throw new ServiceException("用户未分配租户,请联系管理员");
|
||||
}
|
||||
} else {
|
||||
// 未登录状态(可能是匿名访问或登录接口)
|
||||
log.trace("未获取到LoginUser,跳过租户拦截: {}", requestUri);
|
||||
}
|
||||
|
||||
} catch (ServiceException e) {
|
||||
// 业务异常直接抛出
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
// 其他异常(可能是登录接口或匿名访问)
|
||||
log.trace("获取租户上下文失败: {}, URI: {}", e.getMessage(), requestUri);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求完成后的清理
|
||||
* Reason: 必须清除ThreadLocal,避免线程池复用时的上下文污染
|
||||
*/
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler, Exception ex) throws Exception {
|
||||
TenantContext.clear();
|
||||
log.trace("请求完成,TenantContext已清除");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,450 @@
|
|||
package com.ruoyi.framework.interceptor;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import net.sf.jsqlparser.expression.LongValue;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
|
||||
import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
|
||||
import net.sf.jsqlparser.expression.Parenthesis;
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.Statement;
|
||||
import net.sf.jsqlparser.statement.delete.Delete;
|
||||
import net.sf.jsqlparser.statement.insert.Insert;
|
||||
import net.sf.jsqlparser.statement.select.FromItem;
|
||||
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||
import net.sf.jsqlparser.statement.select.Select;
|
||||
import net.sf.jsqlparser.statement.select.SubSelect;
|
||||
import net.sf.jsqlparser.statement.update.Update;
|
||||
import org.apache.ibatis.executor.statement.StatementHandler;
|
||||
import org.apache.ibatis.mapping.BoundSql;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.SqlCommandType;
|
||||
import org.apache.ibatis.plugin.Interceptor;
|
||||
import org.apache.ibatis.plugin.Intercepts;
|
||||
import org.apache.ibatis.plugin.Invocation;
|
||||
import org.apache.ibatis.plugin.Plugin;
|
||||
import org.apache.ibatis.plugin.Signature;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.apache.ibatis.reflection.SystemMetaObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.ruoyi.common.config.TenantConfig;
|
||||
import com.ruoyi.common.core.context.TenantContext;
|
||||
|
||||
/**
|
||||
* MyBatis租户SQL拦截器 - 自动在SQL中添加 tenant_id 过滤条件
|
||||
*
|
||||
* Reason: 借鉴若依DataScope模式,通过SQL解析实现租户隔离
|
||||
*
|
||||
* 工作原理:
|
||||
* 1. 拦截MyBatis的StatementHandler.prepare()方法
|
||||
* 2. 获取原始SQL和命令类型
|
||||
* 3. 判断是否需要拦截(SELECT/UPDATE/DELETE + 租户表)
|
||||
* 4. 使用JSQLParser解析SQL
|
||||
* 5. 在WHERE子句中添加 tenant_id = ? 条件
|
||||
* 6. 将改写后的SQL设置回BoundSql
|
||||
*/
|
||||
@Component
|
||||
@Intercepts({
|
||||
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
|
||||
})
|
||||
public class TenantSqlInterceptor implements Interceptor {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TenantSqlInterceptor.class);
|
||||
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
|
||||
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
|
||||
|
||||
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
|
||||
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
|
||||
|
||||
BoundSql boundSql = statementHandler.getBoundSql();
|
||||
String originalSql = boundSql.getSql();
|
||||
|
||||
// 判断是否需要拦截
|
||||
if (!shouldIntercept(sqlCommandType, originalSql)) {
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
// 超级管理员忽略租户过滤
|
||||
if (TenantContext.isIgnore()) {
|
||||
log.debug("【全局模式】跳过租户过滤: {}", originalSql.substring(0, Math.min(100, originalSql.length())));
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
// 获取租户ID
|
||||
Long tenantId = TenantContext.getTenantId();
|
||||
if (tenantId == null) {
|
||||
log.error("【安全警告】租户ID为空且非全局模式,SQL被拦截: {}",
|
||||
originalSql.substring(0, Math.min(100, originalSql.length())));
|
||||
throw new ServiceException("租户上下文丢失,请重新登录");
|
||||
}
|
||||
|
||||
// 解析并修改SQL
|
||||
try {
|
||||
String modifiedSql = processSql(originalSql, tenantId, sqlCommandType);
|
||||
if (!modifiedSql.equals(originalSql)) {
|
||||
metaObject.setValue("delegate.boundSql.sql", modifiedSql);
|
||||
log.debug("【租户SQL拦截】租户ID: {}, 原SQL: {}", tenantId,
|
||||
originalSql.substring(0, Math.min(100, originalSql.length())));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("租户SQL解析失败: {}, 原SQL: {}", e.getMessage(),
|
||||
originalSql.substring(0, Math.min(100, originalSql.length())));
|
||||
// SQL解析失败不影响执行,记录日志后继续
|
||||
}
|
||||
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要拦截
|
||||
*
|
||||
* 核心逻辑:通过SQL解析获取主表名,只有主表是租户表或混合模式表时才拦截
|
||||
* 这样可以避免:
|
||||
* 1. 字符串子串匹配导致的误判(如 sys_role_menu 误匹配 sys_role)
|
||||
* 2. JOIN表被错误过滤(只对主表添加租户条件)
|
||||
*
|
||||
* @param sqlCommandType SQL命令类型
|
||||
* @param sql 原始SQL
|
||||
* @return true-需要拦截,false-不需要
|
||||
*/
|
||||
private boolean shouldIntercept(SqlCommandType sqlCommandType, String sql) {
|
||||
// 拦截 SELECT、UPDATE、DELETE、INSERT
|
||||
if (sqlCommandType != SqlCommandType.SELECT &&
|
||||
sqlCommandType != SqlCommandType.UPDATE &&
|
||||
sqlCommandType != SqlCommandType.DELETE &&
|
||||
sqlCommandType != SqlCommandType.INSERT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 解析SQL获取主表名
|
||||
Statement statement = CCJSqlParserUtil.parse(sql);
|
||||
String mainTableName = getMainTableName(statement);
|
||||
|
||||
// 租户表或混合模式表都需要拦截
|
||||
return mainTableName != null && (isTenantTable(mainTableName) || isHybridTable(mainTableName));
|
||||
} catch (Exception e) {
|
||||
// SQL解析失败,不拦截(避免影响正常业务)
|
||||
log.debug("SQL解析失败,跳过租户拦截: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从SQL语句中获取主表名
|
||||
*
|
||||
* @param statement SQL语句
|
||||
* @return 主表名,解析失败返回null
|
||||
*/
|
||||
private String getMainTableName(Statement statement) {
|
||||
if (statement instanceof Select) {
|
||||
PlainSelect plainSelect = (PlainSelect) ((Select) statement).getSelectBody();
|
||||
FromItem fromItem = plainSelect.getFromItem();
|
||||
if (fromItem instanceof Table) {
|
||||
return ((Table) fromItem).getName();
|
||||
}
|
||||
} else if (statement instanceof Update) {
|
||||
return ((Update) statement).getTable().getName();
|
||||
} else if (statement instanceof Delete) {
|
||||
return ((Delete) statement).getTable().getName();
|
||||
} else if (statement instanceof Insert) {
|
||||
return ((Insert) statement).getTable().getName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断表名是否是租户表
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @return true-是租户表,false-不是
|
||||
*/
|
||||
private boolean isTenantTable(String tableName) {
|
||||
return TenantConfig.needTenantFilter(tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断表名是否是混合模式表
|
||||
* Reason: 混合模式表同时支持系统级数据(tenant_id=0)和租户级数据
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @return true-是混合模式表,false-不是
|
||||
*/
|
||||
private boolean isHybridTable(String tableName) {
|
||||
return TenantConfig.isHybridTable(tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理SQL - 添加租户过滤条件
|
||||
*
|
||||
* @param sql 原始SQL
|
||||
* @param tenantId 租户ID
|
||||
* @param sqlCommandType SQL类型
|
||||
* @return 改写后的SQL
|
||||
*/
|
||||
private String processSql(String sql, Long tenantId, SqlCommandType sqlCommandType) throws Exception {
|
||||
Statement statement = CCJSqlParserUtil.parse(sql);
|
||||
String tableName = getMainTableName(statement);
|
||||
boolean isHybrid = isHybridTable(tableName);
|
||||
|
||||
if (statement instanceof Select) {
|
||||
// 处理 SELECT 语句
|
||||
Select selectStatement = (Select) statement;
|
||||
PlainSelect plainSelect = (PlainSelect) selectStatement.getSelectBody();
|
||||
|
||||
// 获取主表别名,避免多表JOIN时tenant_id歧义
|
||||
String tableAlias = getMainTableAlias(plainSelect);
|
||||
|
||||
// 根据表类型构造不同的租户条件
|
||||
net.sf.jsqlparser.expression.Expression tenantCondition;
|
||||
if (isHybrid) {
|
||||
// 混合模式:(tenant_id = ? OR tenant_id = 0)
|
||||
tenantCondition = buildHybridTenantCondition(tenantId, tableAlias);
|
||||
} else {
|
||||
// 普通租户表:tenant_id = ?
|
||||
tenantCondition = buildTenantCondition(tenantId, tableAlias);
|
||||
}
|
||||
|
||||
// 合并WHERE条件
|
||||
if (plainSelect.getWhere() != null) {
|
||||
AndExpression andExpression = new AndExpression(tenantCondition, plainSelect.getWhere());
|
||||
plainSelect.setWhere(andExpression);
|
||||
} else {
|
||||
plainSelect.setWhere(tenantCondition);
|
||||
}
|
||||
|
||||
return selectStatement.toString();
|
||||
}
|
||||
else if (statement instanceof Update) {
|
||||
// 处理 UPDATE 语句
|
||||
Update updateStatement = (Update) statement;
|
||||
|
||||
// 获取表别名
|
||||
String tableAlias = getUpdateTableAlias(updateStatement);
|
||||
|
||||
// 根据表类型构造不同的租户条件
|
||||
// Reason: 混合模式表的UPDATE只能操作租户自己的数据,不能修改系统级数据(tenant_id=0)
|
||||
net.sf.jsqlparser.expression.Expression tenantCondition;
|
||||
tenantCondition = buildTenantCondition(tenantId, tableAlias);
|
||||
|
||||
if (updateStatement.getWhere() != null) {
|
||||
AndExpression andExpression = new AndExpression(tenantCondition, updateStatement.getWhere());
|
||||
updateStatement.setWhere(andExpression);
|
||||
} else {
|
||||
updateStatement.setWhere(tenantCondition);
|
||||
}
|
||||
|
||||
return updateStatement.toString();
|
||||
}
|
||||
else if (statement instanceof Delete) {
|
||||
// 处理 DELETE 语句
|
||||
Delete deleteStatement = (Delete) statement;
|
||||
|
||||
// 获取表别名
|
||||
String tableAlias = getDeleteTableAlias(deleteStatement);
|
||||
|
||||
// 根据表类型构造不同的租户条件
|
||||
// Reason: 混合模式表的DELETE只能操作租户自己的数据,不能删除系统级数据(tenant_id=0)
|
||||
net.sf.jsqlparser.expression.Expression tenantCondition;
|
||||
tenantCondition = buildTenantCondition(tenantId, tableAlias);
|
||||
|
||||
if (deleteStatement.getWhere() != null) {
|
||||
AndExpression andExpression = new AndExpression(tenantCondition, deleteStatement.getWhere());
|
||||
deleteStatement.setWhere(andExpression);
|
||||
} else {
|
||||
deleteStatement.setWhere(tenantCondition);
|
||||
}
|
||||
|
||||
return deleteStatement.toString();
|
||||
}
|
||||
else if (statement instanceof Insert) {
|
||||
// 处理 INSERT 语句
|
||||
return processInsert((Insert) statement, tenantId);
|
||||
}
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SELECT语句主表的别名或表名
|
||||
*
|
||||
* @param plainSelect SELECT语句
|
||||
* @return 表别名或表名,用于构造 tenant_id 条件
|
||||
*/
|
||||
private String getMainTableAlias(PlainSelect plainSelect) {
|
||||
FromItem fromItem = plainSelect.getFromItem();
|
||||
if (fromItem instanceof Table) {
|
||||
Table table = (Table) fromItem;
|
||||
if (table.getAlias() != null) {
|
||||
return table.getAlias().getName();
|
||||
}
|
||||
return table.getName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取UPDATE语句的表别名或表名
|
||||
*
|
||||
* @param updateStatement UPDATE语句
|
||||
* @return 表别名或表名
|
||||
*/
|
||||
private String getUpdateTableAlias(Update updateStatement) {
|
||||
Table table = updateStatement.getTable();
|
||||
if (table != null) {
|
||||
if (table.getAlias() != null) {
|
||||
return table.getAlias().getName();
|
||||
}
|
||||
return table.getName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取DELETE语句的表别名或表名
|
||||
*
|
||||
* @param deleteStatement DELETE语句
|
||||
* @return 表别名或表名
|
||||
*/
|
||||
private String getDeleteTableAlias(Delete deleteStatement) {
|
||||
Table table = deleteStatement.getTable();
|
||||
if (table != null) {
|
||||
if (table.getAlias() != null) {
|
||||
return table.getAlias().getName();
|
||||
}
|
||||
return table.getName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造租户过滤条件:[表别名.]tenant_id = ?
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param tableAlias 表别名(可为null)
|
||||
* @return EqualsTo 表达式
|
||||
*/
|
||||
private EqualsTo buildTenantCondition(Long tenantId, String tableAlias) {
|
||||
EqualsTo equalsTo = new EqualsTo();
|
||||
if (tableAlias != null) {
|
||||
equalsTo.setLeftExpression(new Column(new Table(tableAlias), "tenant_id"));
|
||||
} else {
|
||||
equalsTo.setLeftExpression(new Column("tenant_id"));
|
||||
}
|
||||
equalsTo.setRightExpression(new LongValue(tenantId));
|
||||
return equalsTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造混合模式租户过滤条件:([表别名.]tenant_id = ? OR [表别名.]tenant_id = 0)
|
||||
* Reason: 混合模式表同时支持系统级数据(tenant_id=0)和租户级数据
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param tableAlias 表别名(可为null)
|
||||
* @return Parenthesis 表达式(带括号的OR条件)
|
||||
*/
|
||||
private Parenthesis buildHybridTenantCondition(Long tenantId, String tableAlias) {
|
||||
// 构造 tenant_id = ?
|
||||
EqualsTo tenantCondition = new EqualsTo();
|
||||
if (tableAlias != null) {
|
||||
tenantCondition.setLeftExpression(new Column(new Table(tableAlias), "tenant_id"));
|
||||
} else {
|
||||
tenantCondition.setLeftExpression(new Column("tenant_id"));
|
||||
}
|
||||
tenantCondition.setRightExpression(new LongValue(tenantId));
|
||||
|
||||
// 构造 tenant_id = 0
|
||||
EqualsTo systemCondition = new EqualsTo();
|
||||
if (tableAlias != null) {
|
||||
systemCondition.setLeftExpression(new Column(new Table(tableAlias), "tenant_id"));
|
||||
} else {
|
||||
systemCondition.setLeftExpression(new Column("tenant_id"));
|
||||
}
|
||||
systemCondition.setRightExpression(new LongValue(0));
|
||||
|
||||
// 构造 (tenant_id = ? OR tenant_id = 0)
|
||||
OrExpression orExpression = new OrExpression(tenantCondition, systemCondition);
|
||||
return new Parenthesis(orExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理INSERT语句 - 自动填充tenant_id
|
||||
*
|
||||
* Reason: 防止开发人员忘记手动设置tenant_id导致孤儿数据
|
||||
*
|
||||
* @param insertStatement INSERT语句
|
||||
* @param tenantId 租户ID
|
||||
* @return 改写后的SQL
|
||||
*/
|
||||
private String processInsert(Insert insertStatement, Long tenantId) {
|
||||
// 1. 获取表名
|
||||
String tableName = insertStatement.getTable().getName();
|
||||
|
||||
// 2. 获取列定义
|
||||
List<Column> columns = insertStatement.getColumns();
|
||||
if (columns == null || columns.isEmpty()) {
|
||||
// 无列名INSERT: INSERT INTO table VALUES (...)
|
||||
log.warn("【租户INSERT】无列名语句无法自动填充tenant_id: {}", tableName);
|
||||
return insertStatement.toString();
|
||||
}
|
||||
|
||||
// 3. 检查是否已有tenant_id
|
||||
boolean hasTenantId = columns.stream()
|
||||
.anyMatch(col -> "tenant_id".equalsIgnoreCase(col.getColumnName()));
|
||||
|
||||
if (hasTenantId) {
|
||||
log.debug("【租户INSERT】已包含tenant_id,跳过自动填充: {}", tableName);
|
||||
return insertStatement.toString();
|
||||
}
|
||||
|
||||
// 4. 自动添加tenant_id列
|
||||
columns.add(new Column("tenant_id"));
|
||||
|
||||
// 5. 处理VALUES
|
||||
ItemsList itemsList = insertStatement.getItemsList();
|
||||
if (itemsList instanceof ExpressionList) {
|
||||
// 单行INSERT
|
||||
ExpressionList expressionList = (ExpressionList) itemsList;
|
||||
expressionList.getExpressions().add(new LongValue(tenantId));
|
||||
}
|
||||
else if (itemsList instanceof MultiExpressionList) {
|
||||
// 批量INSERT
|
||||
MultiExpressionList multiList = (MultiExpressionList) itemsList;
|
||||
for (ExpressionList expList : multiList.getExpressionLists()) {
|
||||
expList.getExpressions().add(new LongValue(tenantId));
|
||||
}
|
||||
}
|
||||
else if (itemsList instanceof SubSelect) {
|
||||
// INSERT SELECT(暂不支持)
|
||||
log.warn("【租户INSERT】INSERT SELECT语句暂不支持自动填充: {}", tableName);
|
||||
return insertStatement.toString();
|
||||
}
|
||||
|
||||
log.info("【租户INSERT拦截】自动填充tenant_id={}: {}", tenantId, tableName);
|
||||
return insertStatement.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
return Plugin.wrap(target, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperties(Properties properties) {
|
||||
// 可以从配置文件读取租户表配置
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.ruoyi.framework.web.service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
|
|
@ -22,20 +24,24 @@ import com.ruoyi.common.utils.DateUtils;
|
|||
import com.ruoyi.common.utils.MessageUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.ip.IpUtils;
|
||||
import com.ruoyi.common.core.context.TenantContext;
|
||||
import com.ruoyi.framework.manager.AsyncManager;
|
||||
import com.ruoyi.framework.manager.factory.AsyncFactory;
|
||||
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import com.ruoyi.system.service.ISysUserService;
|
||||
import com.ruoyi.system.service.ISysTenantService;
|
||||
import com.ruoyi.system.domain.SysTenant;
|
||||
|
||||
/**
|
||||
* 登录校验方法
|
||||
*
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class SysLoginService
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(SysLoginService.class);
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
|
|
@ -51,52 +57,106 @@ public class SysLoginService
|
|||
@Autowired
|
||||
private ISysConfigService configService;
|
||||
|
||||
@Autowired
|
||||
private ISysTenantService tenantService;
|
||||
|
||||
/**
|
||||
* 登录验证
|
||||
*
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @param code 验证码
|
||||
* @param uuid 唯一标识
|
||||
* @param tenantCode 租户编码
|
||||
* @return 结果
|
||||
*/
|
||||
public String login(String username, String password, String code, String uuid)
|
||||
public String login(String username, String password, String code, String uuid, String tenantCode)
|
||||
{
|
||||
// 验证码校验
|
||||
validateCaptcha(username, code, uuid);
|
||||
// 登录前置校验
|
||||
loginPreCheck(username, password);
|
||||
// 用户验证
|
||||
Authentication authentication = null;
|
||||
// 【多租户】登录阶段:临时忽略租户过滤
|
||||
// Reason: 登录时需要查询用户表获取tenant_id,形成先有鸡还是先有蛋的问题
|
||||
// 因此登录阶段临时设置全局模式,允许查询和更新用户表
|
||||
try
|
||||
{
|
||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
|
||||
AuthenticationContextHolder.setContext(authenticationToken);
|
||||
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
|
||||
authentication = authenticationManager.authenticate(authenticationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e instanceof BadCredentialsException)
|
||||
TenantContext.setIgnore(true);
|
||||
log.debug("【多租户】登录阶段:临时设置全局模式");
|
||||
|
||||
// 验证码校验
|
||||
validateCaptcha(username, code, uuid);
|
||||
// 登录前置校验
|
||||
loginPreCheck(username, password);
|
||||
|
||||
// 【多租户】通过租户编码查询租户ID并设置到上下文
|
||||
Long tenantId = null;
|
||||
if (StringUtils.isNotEmpty(tenantCode))
|
||||
{
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
|
||||
throw new UserPasswordNotMatchException();
|
||||
SysTenant tenant = tenantService.selectSysTenantByTenantCode(tenantCode);
|
||||
if (tenant == null)
|
||||
{
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, "租户不存在或已停用"));
|
||||
throw new ServiceException("租户不存在或已停用");
|
||||
}
|
||||
tenantId = tenant.getTenantId();
|
||||
// 将租户ID设置到上下文,供 UserDetailsServiceImpl 使用
|
||||
TenantContext.setTenantId(tenantId);
|
||||
log.info("【多租户】登录租户编码: {}, 租户ID: {}", tenantCode, tenantId);
|
||||
}
|
||||
|
||||
// 用户验证
|
||||
Authentication authentication = null;
|
||||
try
|
||||
{
|
||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
|
||||
AuthenticationContextHolder.setContext(authenticationToken);
|
||||
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
|
||||
authentication = authenticationManager.authenticate(authenticationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e instanceof BadCredentialsException)
|
||||
{
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
|
||||
throw new UserPasswordNotMatchException();
|
||||
}
|
||||
else
|
||||
{
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
AuthenticationContextHolder.clearContext();
|
||||
}
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
|
||||
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
|
||||
recordLoginInfo(loginUser.getUserId());
|
||||
|
||||
// 【多租户】从SysUser获取tenant_id并设置到LoginUser
|
||||
if (loginUser.getUser() != null && loginUser.getUser().getTenantId() != null)
|
||||
{
|
||||
loginUser.setTenantId(loginUser.getUser().getTenantId());
|
||||
log.info("【多租户】用户 {} 登录成功,租户ID: {}", username, loginUser.getTenantId());
|
||||
}
|
||||
else if (!com.ruoyi.common.config.TenantConfig.isSuperAdmin(loginUser.getUserId()))
|
||||
{
|
||||
// 非超级管理员必须有租户ID
|
||||
log.error("用户 {} 未分配租户且非超级管理员,拒绝登录", username);
|
||||
throw new ServiceException("用户未分配租户,请联系管理员");
|
||||
}
|
||||
else
|
||||
{
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
|
||||
throw new ServiceException(e.getMessage());
|
||||
log.warn("【多租户】超级管理员 {} 登录,无需租户ID", username);
|
||||
}
|
||||
|
||||
// 生成token
|
||||
return tokenService.createToken(loginUser);
|
||||
}
|
||||
finally
|
||||
{
|
||||
AuthenticationContextHolder.clearContext();
|
||||
// 【多租户】登录完成,清除全局模式标记
|
||||
TenantContext.clear();
|
||||
log.debug("【多租户】登录流程结束,清除全局模式");
|
||||
}
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
|
||||
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
|
||||
recordLoginInfo(loginUser.getUserId());
|
||||
// 生成token
|
||||
return tokenService.createToken(loginUser);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public class TokenService
|
|||
|
||||
/**
|
||||
* 获取用户身份信息
|
||||
*
|
||||
*
|
||||
* @return 用户信息
|
||||
*/
|
||||
public LoginUser getLoginUser(HttpServletRequest request)
|
||||
|
|
@ -72,6 +72,15 @@ public class TokenService
|
|||
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
|
||||
String userKey = getTokenKey(uuid);
|
||||
LoginUser user = redisCache.getCacheObject(userKey);
|
||||
|
||||
// 【多租户】从JWT中提取tenant_id并设置到LoginUser
|
||||
if (user != null && claims.get("tenant_id") != null)
|
||||
{
|
||||
Long tenantId = claims.get("tenant_id", Long.class);
|
||||
user.setTenantId(tenantId);
|
||||
log.debug("从JWT中提取租户ID: {}", tenantId);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
@ -107,7 +116,7 @@ public class TokenService
|
|||
|
||||
/**
|
||||
* 创建令牌
|
||||
*
|
||||
*
|
||||
* @param loginUser 用户信息
|
||||
* @return 令牌
|
||||
*/
|
||||
|
|
@ -121,6 +130,14 @@ public class TokenService
|
|||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put(Constants.LOGIN_USER_KEY, token);
|
||||
claims.put(Constants.JWT_USERNAME, loginUser.getUsername());
|
||||
|
||||
// 【多租户】将tenant_id存入JWT
|
||||
if (loginUser.getTenantId() != null)
|
||||
{
|
||||
claims.put("tenant_id", loginUser.getTenantId());
|
||||
log.debug("JWT中存储租户ID: {}", loginUser.getTenantId());
|
||||
}
|
||||
|
||||
return createToken(claims);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import org.springframework.security.core.userdetails.UserDetails;
|
|||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.common.core.context.TenantContext;
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.common.enums.UserStatus;
|
||||
|
|
@ -27,7 +28,7 @@ public class UserDetailsServiceImpl implements UserDetailsService
|
|||
|
||||
@Autowired
|
||||
private ISysUserService userService;
|
||||
|
||||
|
||||
@Autowired
|
||||
private SysPasswordService passwordService;
|
||||
|
||||
|
|
@ -37,7 +38,22 @@ public class UserDetailsServiceImpl implements UserDetailsService
|
|||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
|
||||
{
|
||||
SysUser user = userService.selectUserByUserName(username);
|
||||
// 【多租户】从上下文获取租户ID,用于查询指定租户的用户
|
||||
Long tenantId = TenantContext.getTenantId();
|
||||
SysUser user;
|
||||
if (tenantId != null)
|
||||
{
|
||||
// 有租户ID时,查询指定租户的用户
|
||||
user = userService.selectUserByUserNameAndTenantId(username, tenantId);
|
||||
log.debug("【多租户】查询用户: {}, 租户ID: {}", username, tenantId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 无租户ID时(超级管理员登录),查询所有用户
|
||||
user = userService.selectUserByUserName(username);
|
||||
log.debug("【多租户】超级管理员登录,查询用户: {}", username);
|
||||
}
|
||||
|
||||
if (StringUtils.isNull(user))
|
||||
{
|
||||
log.info("登录用户:{} 不存在.", username);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<artifactId>hair-link</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.9.1</version>
|
||||
</parent>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<artifactId>hair-link</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.9.1</version>
|
||||
</parent>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<artifactId>hair-link</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.9.1</version>
|
||||
</parent>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,163 @@
|
|||
package com.ruoyi.system.domain;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
/**
|
||||
* 租户信息对象 sys_tenant
|
||||
*
|
||||
* @author W-yf
|
||||
* @date 2025-12-19
|
||||
*/
|
||||
public class SysTenant extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 租户ID */
|
||||
private Long tenantId;
|
||||
|
||||
/** 租户名称 */
|
||||
@Excel(name = "租户名称")
|
||||
private String tenantName;
|
||||
|
||||
/** 租户编码(唯一标识) */
|
||||
@Excel(name = "租户编码", readConverterExp = "唯=一标识")
|
||||
private String tenantCode;
|
||||
|
||||
/** 联系人 */
|
||||
@Excel(name = "联系人")
|
||||
private String contactName;
|
||||
|
||||
/** 联系电话 */
|
||||
@Excel(name = "联系电话")
|
||||
private String contactPhone;
|
||||
|
||||
/** 过期时间(NULL表示永久) */
|
||||
@Excel(name = "过期时间", readConverterExp = "N=ULL表示永久")
|
||||
private Date expireTime;
|
||||
|
||||
/** 套餐ID(预留) */
|
||||
@Excel(name = "套餐ID", readConverterExp = "预=留")
|
||||
private Long packageId;
|
||||
|
||||
/** 状态(0正常 1停用) */
|
||||
@Excel(name = "状态", readConverterExp = "0=正常,1=停用")
|
||||
private String status;
|
||||
|
||||
/** 删除标志(0存在 2删除) */
|
||||
private String delFlag;
|
||||
|
||||
public void setTenantId(Long tenantId)
|
||||
{
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
public Long getTenantId()
|
||||
{
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void setTenantName(String tenantName)
|
||||
{
|
||||
this.tenantName = tenantName;
|
||||
}
|
||||
|
||||
public String getTenantName()
|
||||
{
|
||||
return tenantName;
|
||||
}
|
||||
|
||||
public void setTenantCode(String tenantCode)
|
||||
{
|
||||
this.tenantCode = tenantCode;
|
||||
}
|
||||
|
||||
public String getTenantCode()
|
||||
{
|
||||
return tenantCode;
|
||||
}
|
||||
|
||||
public void setContactName(String contactName)
|
||||
{
|
||||
this.contactName = contactName;
|
||||
}
|
||||
|
||||
public String getContactName()
|
||||
{
|
||||
return contactName;
|
||||
}
|
||||
|
||||
public void setContactPhone(String contactPhone)
|
||||
{
|
||||
this.contactPhone = contactPhone;
|
||||
}
|
||||
|
||||
public String getContactPhone()
|
||||
{
|
||||
return contactPhone;
|
||||
}
|
||||
|
||||
public void setExpireTime(Date expireTime)
|
||||
{
|
||||
this.expireTime = expireTime;
|
||||
}
|
||||
|
||||
public Date getExpireTime()
|
||||
{
|
||||
return expireTime;
|
||||
}
|
||||
|
||||
public void setPackageId(Long packageId)
|
||||
{
|
||||
this.packageId = packageId;
|
||||
}
|
||||
|
||||
public Long getPackageId()
|
||||
{
|
||||
return packageId;
|
||||
}
|
||||
|
||||
public void setStatus(String status)
|
||||
{
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setDelFlag(String delFlag)
|
||||
{
|
||||
this.delFlag = delFlag;
|
||||
}
|
||||
|
||||
public String getDelFlag()
|
||||
{
|
||||
return delFlag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("tenantId", getTenantId())
|
||||
.append("tenantName", getTenantName())
|
||||
.append("tenantCode", getTenantCode())
|
||||
.append("contactName", getContactName())
|
||||
.append("contactPhone", getContactPhone())
|
||||
.append("expireTime", getExpireTime())
|
||||
.append("packageId", getPackageId())
|
||||
.append("status", getStatus())
|
||||
.append("delFlag", getDelFlag())
|
||||
.append("createBy", getCreateBy())
|
||||
.append("createTime", getCreateTime())
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("remark", getRemark())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.ruoyi.system.domain;
|
||||
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
/**
|
||||
* 租户套餐对象 sys_tenant_package
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-12-19
|
||||
*/
|
||||
public class SysTenantPackage extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 套餐ID */
|
||||
private Long packageId;
|
||||
|
||||
/** 套餐名称 */
|
||||
private String packageName;
|
||||
|
||||
/** 套餐编码 */
|
||||
private String packageCode;
|
||||
|
||||
/** 状态(0正常 1停用) */
|
||||
private String status;
|
||||
|
||||
/** 删除标志(0存在 2删除) */
|
||||
private String delFlag;
|
||||
|
||||
/** 菜单ID列表(非数据库字段) */
|
||||
private Long[] menuIds;
|
||||
|
||||
public void setPackageId(Long packageId)
|
||||
{
|
||||
this.packageId = packageId;
|
||||
}
|
||||
|
||||
public Long getPackageId()
|
||||
{
|
||||
return packageId;
|
||||
}
|
||||
|
||||
public void setPackageName(String packageName)
|
||||
{
|
||||
this.packageName = packageName;
|
||||
}
|
||||
|
||||
public String getPackageName()
|
||||
{
|
||||
return packageName;
|
||||
}
|
||||
|
||||
public void setPackageCode(String packageCode)
|
||||
{
|
||||
this.packageCode = packageCode;
|
||||
}
|
||||
|
||||
public String getPackageCode()
|
||||
{
|
||||
return packageCode;
|
||||
}
|
||||
|
||||
public void setStatus(String status)
|
||||
{
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setDelFlag(String delFlag)
|
||||
{
|
||||
this.delFlag = delFlag;
|
||||
}
|
||||
|
||||
public String getDelFlag()
|
||||
{
|
||||
return delFlag;
|
||||
}
|
||||
|
||||
public Long[] getMenuIds()
|
||||
{
|
||||
return menuIds;
|
||||
}
|
||||
|
||||
public void setMenuIds(Long[] menuIds)
|
||||
{
|
||||
this.menuIds = menuIds;
|
||||
}
|
||||
}
|
||||
|
|
@ -86,10 +86,20 @@ public interface SysDictDataMapper
|
|||
|
||||
/**
|
||||
* 同步修改字典类型
|
||||
*
|
||||
*
|
||||
* @param oldDictType 旧字典类型
|
||||
* @param newDictType 新旧字典类型
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateDictDataType(@Param("oldDictType") String oldDictType, @Param("newDictType") String newDictType);
|
||||
|
||||
/**
|
||||
* 混合模式查询:根据字典类型和租户ID查询字典数据
|
||||
* Reason: 合并系统字典(tenant_id=0)和租户字典,租户优先
|
||||
*
|
||||
* @param dictType 字典类型
|
||||
* @param tenantId 租户ID
|
||||
* @return 字典数据集合信息
|
||||
*/
|
||||
public List<SysDictData> selectDictDataByTypeWithTenant(@Param("dictType") String dictType, @Param("tenantId") Long tenantId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
package com.ruoyi.system.mapper;
|
||||
|
||||
import com.ruoyi.system.domain.SysTenant;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* 租户信息Mapper接口
|
||||
*
|
||||
* @author W-yf
|
||||
* @date 2025-12-19
|
||||
*/
|
||||
public interface SysTenantMapper
|
||||
{
|
||||
/**
|
||||
* 查询租户信息
|
||||
*
|
||||
* @param tenantId 租户信息主键
|
||||
* @return 租户信息
|
||||
*/
|
||||
public SysTenant selectSysTenantByTenantId(Long tenantId);
|
||||
|
||||
/**
|
||||
* 通过租户编码查询租户信息
|
||||
*
|
||||
* @param tenantCode 租户编码
|
||||
* @return 租户信息
|
||||
*/
|
||||
public SysTenant selectSysTenantByTenantCode(String tenantCode);
|
||||
|
||||
/**
|
||||
* 查询租户信息列表
|
||||
*
|
||||
* @param sysTenant 租户信息
|
||||
* @return 租户信息集合
|
||||
*/
|
||||
public List<SysTenant> selectSysTenantList(SysTenant sysTenant);
|
||||
|
||||
/**
|
||||
* 新增租户信息
|
||||
*
|
||||
* @param sysTenant 租户信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertSysTenant(SysTenant sysTenant);
|
||||
|
||||
/**
|
||||
* 修改租户信息
|
||||
*
|
||||
* @param sysTenant 租户信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateSysTenant(SysTenant sysTenant);
|
||||
|
||||
/**
|
||||
* 删除租户信息
|
||||
*
|
||||
* @param tenantId 租户信息主键
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteSysTenantByTenantId(Long tenantId);
|
||||
|
||||
/**
|
||||
* 批量删除租户信息
|
||||
*
|
||||
* @param tenantIds 需要删除的数据主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteSysTenantByTenantIds(Long[] tenantIds);
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package com.ruoyi.system.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.system.domain.SysTenantPackage;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 租户套餐Mapper接口
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-12-19
|
||||
*/
|
||||
public interface SysTenantPackageMapper
|
||||
{
|
||||
/**
|
||||
* 查询租户套餐
|
||||
*
|
||||
* @param packageId 租户套餐主键
|
||||
* @return 租户套餐
|
||||
*/
|
||||
public SysTenantPackage selectPackageById(Long packageId);
|
||||
|
||||
/**
|
||||
* 查询租户套餐列表
|
||||
*
|
||||
* @param sysTenantPackage 租户套餐
|
||||
* @return 租户套餐集合
|
||||
*/
|
||||
public List<SysTenantPackage> selectPackageList(SysTenantPackage sysTenantPackage);
|
||||
|
||||
/**
|
||||
* 新增租户套餐
|
||||
*
|
||||
* @param sysTenantPackage 租户套餐
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertPackage(SysTenantPackage sysTenantPackage);
|
||||
|
||||
/**
|
||||
* 修改租户套餐
|
||||
*
|
||||
* @param sysTenantPackage 租户套餐
|
||||
* @return 结果
|
||||
*/
|
||||
public int updatePackage(SysTenantPackage sysTenantPackage);
|
||||
|
||||
/**
|
||||
* 删除租户套餐
|
||||
*
|
||||
* @param packageId 租户套餐主键
|
||||
* @return 结果
|
||||
*/
|
||||
public int deletePackageById(Long packageId);
|
||||
|
||||
/**
|
||||
* 批量删除租户套餐
|
||||
*
|
||||
* @param packageIds 需要删除的数据主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
public int deletePackageByIds(Long[] packageIds);
|
||||
|
||||
/**
|
||||
* 查询套餐包含的菜单ID列表
|
||||
*
|
||||
* @param packageId 套餐ID
|
||||
* @return 菜单ID列表
|
||||
*/
|
||||
public List<Long> selectMenuIdsByPackageId(Long packageId);
|
||||
|
||||
/**
|
||||
* 批量新增套餐菜单关联
|
||||
*
|
||||
* @param packageId 套餐ID
|
||||
* @param menuIds 菜单ID列表
|
||||
* @return 结果
|
||||
*/
|
||||
public int batchInsertPackageMenu(@Param("packageId") Long packageId, @Param("menuIds") Long[] menuIds);
|
||||
|
||||
/**
|
||||
* 删除套餐菜单关联
|
||||
*
|
||||
* @param packageId 套餐ID
|
||||
* @return 结果
|
||||
*/
|
||||
public int deletePackageMenuByPackageId(Long packageId);
|
||||
}
|
||||
|
|
@ -38,12 +38,21 @@ public interface SysUserMapper
|
|||
|
||||
/**
|
||||
* 通过用户名查询用户
|
||||
*
|
||||
*
|
||||
* @param userName 用户名
|
||||
* @return 用户对象信息
|
||||
*/
|
||||
public SysUser selectUserByUserName(String userName);
|
||||
|
||||
/**
|
||||
* 通过用户名和租户ID查询用户
|
||||
*
|
||||
* @param userName 用户名
|
||||
* @param tenantId 租户ID
|
||||
* @return 用户对象信息
|
||||
*/
|
||||
public SysUser selectUserByUserNameAndTenantId(@Param("userName") String userName, @Param("tenantId") Long tenantId);
|
||||
|
||||
/**
|
||||
* 通过用户ID查询用户
|
||||
*
|
||||
|
|
@ -123,25 +132,28 @@ public interface SysUserMapper
|
|||
|
||||
/**
|
||||
* 校验用户名称是否唯一
|
||||
*
|
||||
*
|
||||
* @param userName 用户名称
|
||||
* @param tenantId 租户ID
|
||||
* @return 结果
|
||||
*/
|
||||
public SysUser checkUserNameUnique(String userName);
|
||||
public SysUser checkUserNameUnique(@Param("userName") String userName, @Param("tenantId") Long tenantId);
|
||||
|
||||
/**
|
||||
* 校验手机号码是否唯一
|
||||
*
|
||||
* @param phonenumber 手机号码
|
||||
* @param tenantId 租户ID
|
||||
* @return 结果
|
||||
*/
|
||||
public SysUser checkPhoneUnique(String phonenumber);
|
||||
public SysUser checkPhoneUnique(@Param("phonenumber") String phonenumber, @Param("tenantId") Long tenantId);
|
||||
|
||||
/**
|
||||
* 校验email是否唯一
|
||||
*
|
||||
* @param email 用户邮箱
|
||||
* @param tenantId 租户ID
|
||||
* @return 结果
|
||||
*/
|
||||
public SysUser checkEmailUnique(String email);
|
||||
public SysUser checkEmailUnique(@Param("email") String email, @Param("tenantId") Long tenantId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
package com.ruoyi.system.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.system.domain.SysTenantPackage;
|
||||
|
||||
/**
|
||||
* 租户套餐Service接口
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-12-19
|
||||
*/
|
||||
public interface ISysTenantPackageService
|
||||
{
|
||||
/**
|
||||
* 查询租户套餐
|
||||
*
|
||||
* @param packageId 租户套餐主键
|
||||
* @return 租户套餐
|
||||
*/
|
||||
public SysTenantPackage selectPackageById(Long packageId);
|
||||
|
||||
/**
|
||||
* 查询租户套餐列表
|
||||
*
|
||||
* @param sysTenantPackage 租户套餐
|
||||
* @return 租户套餐集合
|
||||
*/
|
||||
public List<SysTenantPackage> selectPackageList(SysTenantPackage sysTenantPackage);
|
||||
|
||||
/**
|
||||
* 新增租户套餐
|
||||
*
|
||||
* @param sysTenantPackage 租户套餐
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertPackage(SysTenantPackage sysTenantPackage);
|
||||
|
||||
/**
|
||||
* 修改租户套餐
|
||||
*
|
||||
* @param sysTenantPackage 租户套餐
|
||||
* @return 结果
|
||||
*/
|
||||
public int updatePackage(SysTenantPackage sysTenantPackage);
|
||||
|
||||
/**
|
||||
* 批量删除租户套餐
|
||||
*
|
||||
* @param packageIds 需要删除的租户套餐主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
public int deletePackageByIds(Long[] packageIds);
|
||||
|
||||
/**
|
||||
* 删除租户套餐信息
|
||||
*
|
||||
* @param packageId 租户套餐主键
|
||||
* @return 结果
|
||||
*/
|
||||
public int deletePackageById(Long packageId);
|
||||
|
||||
/**
|
||||
* 查询套餐包含的菜单ID列表
|
||||
*
|
||||
* Reason: 用于套餐菜单过滤,返回空列表表示不限制
|
||||
*
|
||||
* @param packageId 套餐ID
|
||||
* @return 菜单ID列表,空列表表示高级套餐(不限制)
|
||||
*/
|
||||
public List<Long> selectMenuIdsByPackageId(Long packageId);
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.ruoyi.system.service;
|
||||
|
||||
import com.ruoyi.system.domain.SysTenant;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 租户信息Service接口
|
||||
*
|
||||
* @author W-yf
|
||||
* @date 2025-12-19
|
||||
*/
|
||||
public interface ISysTenantService
|
||||
{
|
||||
/**
|
||||
* 查询租户信息
|
||||
*
|
||||
* @param tenantId 租户信息主键
|
||||
* @return 租户信息
|
||||
*/
|
||||
public SysTenant selectSysTenantByTenantId(Long tenantId);
|
||||
|
||||
/**
|
||||
* 通过租户编码查询租户信息
|
||||
*
|
||||
* @param tenantCode 租户编码
|
||||
* @return 租户信息
|
||||
*/
|
||||
public SysTenant selectSysTenantByTenantCode(String tenantCode);
|
||||
|
||||
/**
|
||||
* 查询租户信息列表
|
||||
*
|
||||
* @param sysTenant 租户信息
|
||||
* @return 租户信息集合
|
||||
*/
|
||||
public List<SysTenant> selectSysTenantList(SysTenant sysTenant);
|
||||
|
||||
/**
|
||||
* 新增租户信息
|
||||
*
|
||||
* @param sysTenant 租户信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertSysTenant(SysTenant sysTenant);
|
||||
|
||||
/**
|
||||
* 修改租户信息
|
||||
*
|
||||
* @param sysTenant 租户信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateSysTenant(SysTenant sysTenant);
|
||||
|
||||
/**
|
||||
* 批量删除租户信息
|
||||
*
|
||||
* @param tenantIds 需要删除的租户信息主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteSysTenantByTenantIds(Long[] tenantIds);
|
||||
|
||||
/**
|
||||
* 删除租户信息信息
|
||||
*
|
||||
* @param tenantId 租户信息主键
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteSysTenantByTenantId(Long tenantId);
|
||||
}
|
||||
|
|
@ -37,12 +37,21 @@ public interface ISysUserService
|
|||
|
||||
/**
|
||||
* 通过用户名查询用户
|
||||
*
|
||||
*
|
||||
* @param userName 用户名
|
||||
* @return 用户对象信息
|
||||
*/
|
||||
public SysUser selectUserByUserName(String userName);
|
||||
|
||||
/**
|
||||
* 通过用户名和租户ID查询用户
|
||||
*
|
||||
* @param userName 用户名
|
||||
* @param tenantId 租户ID
|
||||
* @return 用户对象信息
|
||||
*/
|
||||
public SysUser selectUserByUserNameAndTenantId(String userName, Long tenantId);
|
||||
|
||||
/**
|
||||
* 通过用户ID查询用户
|
||||
*
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.ruoyi.system.service.impl;
|
|||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.common.core.context.TenantContext;
|
||||
import com.ruoyi.common.core.domain.entity.SysDictData;
|
||||
import com.ruoyi.common.utils.DictUtils;
|
||||
import com.ruoyi.system.mapper.SysDictDataMapper;
|
||||
|
|
@ -75,13 +76,17 @@ public class SysDictDataServiceImpl implements ISysDictDataService
|
|||
|
||||
/**
|
||||
* 新增保存字典数据信息
|
||||
*
|
||||
*
|
||||
* @param data 字典数据信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int insertDictData(SysDictData data)
|
||||
{
|
||||
// 自动填充 tenant_id(混合模式:普通租户填充租户ID,超级管理员填充0表示系统级)
|
||||
Long tenantId = TenantContext.getTenantId();
|
||||
data.setTenantId(tenantId != null ? tenantId : 0L);
|
||||
|
||||
int row = dictDataMapper.insertDictData(data);
|
||||
if (row > 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.ruoyi.common.constant.UserConstants;
|
||||
import com.ruoyi.common.core.context.TenantContext;
|
||||
import com.ruoyi.common.core.domain.entity.SysDictData;
|
||||
import com.ruoyi.common.core.domain.entity.SysDictType;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
|
|
@ -133,16 +134,26 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
|
|||
|
||||
/**
|
||||
* 加载字典缓存数据
|
||||
* Reason: 启动时设置全局模式,跳过租户过滤
|
||||
*/
|
||||
@Override
|
||||
public void loadingDictCache()
|
||||
{
|
||||
SysDictData dictData = new SysDictData();
|
||||
dictData.setStatus("0");
|
||||
Map<String, List<SysDictData>> dictDataMap = dictDataMapper.selectDictDataList(dictData).stream().collect(Collectors.groupingBy(SysDictData::getDictType));
|
||||
for (Map.Entry<String, List<SysDictData>> entry : dictDataMap.entrySet())
|
||||
// 启动时加载缓存,设置全局模式跳过租户过滤
|
||||
TenantContext.setIgnore(true);
|
||||
try
|
||||
{
|
||||
DictUtils.setDictCache(entry.getKey(), entry.getValue().stream().sorted(Comparator.comparing(SysDictData::getDictSort)).collect(Collectors.toList()));
|
||||
SysDictData dictData = new SysDictData();
|
||||
dictData.setStatus("0");
|
||||
Map<String, List<SysDictData>> dictDataMap = dictDataMapper.selectDictDataList(dictData).stream().collect(Collectors.groupingBy(SysDictData::getDictType));
|
||||
for (Map.Entry<String, List<SysDictData>> entry : dictDataMap.entrySet())
|
||||
{
|
||||
DictUtils.setDictCache(entry.getKey(), entry.getValue().stream().sorted(Comparator.comparing(SysDictData::getDictSort)).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
TenantContext.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,13 +178,17 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
|
|||
|
||||
/**
|
||||
* 新增保存字典类型信息
|
||||
*
|
||||
*
|
||||
* @param dict 字典类型信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int insertDictType(SysDictType dict)
|
||||
{
|
||||
// 自动填充 tenant_id(混合模式:普通租户填充租户ID,超级管理员填充0表示系统级)
|
||||
Long tenantId = TenantContext.getTenantId();
|
||||
dict.setTenantId(tenantId != null ? tenantId : 0L);
|
||||
|
||||
int row = dictTypeMapper.insertDictType(dict);
|
||||
if (row > 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import java.util.LinkedList;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.common.constant.Constants;
|
||||
|
|
@ -16,14 +18,18 @@ import com.ruoyi.common.core.domain.TreeSelect;
|
|||
import com.ruoyi.common.core.domain.entity.SysMenu;
|
||||
import com.ruoyi.common.core.domain.entity.SysRole;
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.system.domain.SysTenant;
|
||||
import com.ruoyi.system.domain.vo.MetaVo;
|
||||
import com.ruoyi.system.domain.vo.RouterVo;
|
||||
import com.ruoyi.system.mapper.SysMenuMapper;
|
||||
import com.ruoyi.system.mapper.SysRoleMapper;
|
||||
import com.ruoyi.system.mapper.SysRoleMenuMapper;
|
||||
import com.ruoyi.system.service.ISysMenuService;
|
||||
import com.ruoyi.system.service.ISysTenantService;
|
||||
import com.ruoyi.system.service.ISysTenantPackageService;
|
||||
|
||||
/**
|
||||
* 菜单 业务层处理
|
||||
|
|
@ -33,6 +39,8 @@ import com.ruoyi.system.service.ISysMenuService;
|
|||
@Service
|
||||
public class SysMenuServiceImpl implements ISysMenuService
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(SysMenuServiceImpl.class);
|
||||
|
||||
public static final String PREMISSION_STRING = "perms[\"{0}\"]";
|
||||
|
||||
@Autowired
|
||||
|
|
@ -44,6 +52,12 @@ public class SysMenuServiceImpl implements ISysMenuService
|
|||
@Autowired
|
||||
private SysRoleMenuMapper roleMenuMapper;
|
||||
|
||||
@Autowired
|
||||
private ISysTenantService tenantService;
|
||||
|
||||
@Autowired
|
||||
private ISysTenantPackageService packageService;
|
||||
|
||||
/**
|
||||
* 根据用户查询系统菜单列表
|
||||
*
|
||||
|
|
@ -58,7 +72,7 @@ public class SysMenuServiceImpl implements ISysMenuService
|
|||
|
||||
/**
|
||||
* 查询系统菜单列表
|
||||
*
|
||||
*
|
||||
* @param menu 菜单信息
|
||||
* @return 菜单列表
|
||||
*/
|
||||
|
|
@ -75,6 +89,8 @@ public class SysMenuServiceImpl implements ISysMenuService
|
|||
{
|
||||
menu.getParams().put("userId", userId);
|
||||
menuList = menuMapper.selectMenuListByUserId(menu);
|
||||
// 应用租户套餐过滤
|
||||
menuList = applyPackageFilter(menuList);
|
||||
}
|
||||
return menuList;
|
||||
}
|
||||
|
|
@ -123,7 +139,7 @@ public class SysMenuServiceImpl implements ISysMenuService
|
|||
|
||||
/**
|
||||
* 根据用户ID查询菜单
|
||||
*
|
||||
*
|
||||
* @param userId 用户名称
|
||||
* @return 菜单列表
|
||||
*/
|
||||
|
|
@ -138,6 +154,8 @@ public class SysMenuServiceImpl implements ISysMenuService
|
|||
else
|
||||
{
|
||||
menus = menuMapper.selectMenuTreeByUserId(userId);
|
||||
// 应用租户套餐过滤
|
||||
menus = applyPackageFilter(menus);
|
||||
}
|
||||
return getChildPerms(menus, 0);
|
||||
}
|
||||
|
|
@ -532,7 +550,7 @@ public class SysMenuServiceImpl implements ISysMenuService
|
|||
|
||||
/**
|
||||
* 内链域名特殊字符替换
|
||||
*
|
||||
*
|
||||
* @return 替换后的内链域名
|
||||
*/
|
||||
public String innerLinkReplaceEach(String path)
|
||||
|
|
@ -540,4 +558,61 @@ public class SysMenuServiceImpl implements ISysMenuService
|
|||
return StringUtils.replaceEach(path, new String[] { Constants.HTTP, Constants.HTTPS, Constants.WWW, ".", ":" },
|
||||
new String[] { "", "", "", "/", "/" });
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用租户套餐菜单过滤
|
||||
*
|
||||
* Reason: 实现租户套餐功能,通过套餐控制租户的菜单权限
|
||||
* 逻辑:角色菜单 ∩ 套餐菜单
|
||||
*
|
||||
* @param menus 角色菜单列表
|
||||
* @return 过滤后的菜单列表
|
||||
*/
|
||||
private List<SysMenu> applyPackageFilter(List<SysMenu> menus)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. 获取当前租户
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser == null || loginUser.getTenantId() == null)
|
||||
{
|
||||
return menus; // 无租户不过滤
|
||||
}
|
||||
|
||||
// 2. 获取租户信息
|
||||
SysTenant tenant = tenantService.selectSysTenantByTenantId(loginUser.getTenantId());
|
||||
if (tenant == null || tenant.getPackageId() == null)
|
||||
{
|
||||
log.warn("租户 {} 未配置套餐,不限制菜单", loginUser.getTenantId());
|
||||
return menus;
|
||||
}
|
||||
|
||||
// 3. 获取套餐菜单ID集合
|
||||
List<Long> packageMenuIds = packageService.selectMenuIdsByPackageId(tenant.getPackageId());
|
||||
|
||||
// 4. 空列表表示高级套餐(不限制)
|
||||
if (packageMenuIds == null || packageMenuIds.isEmpty())
|
||||
{
|
||||
log.debug("租户 {} 使用高级套餐,不限制菜单", loginUser.getTenantId());
|
||||
return menus;
|
||||
}
|
||||
|
||||
// 5. 过滤:只保留套餐内菜单
|
||||
Set<Long> packageMenuIdSet = new HashSet<>(packageMenuIds);
|
||||
List<SysMenu> filteredMenus = menus.stream()
|
||||
.filter(menu -> packageMenuIdSet.contains(menu.getMenuId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
log.debug("租户 {} 套餐过滤: {} → {} 个菜单",
|
||||
loginUser.getTenantId(), menus.size(), filteredMenus.size());
|
||||
|
||||
return filteredMenus;
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("应用租户套餐过滤失败,降级为不过滤", e);
|
||||
return menus; // 容错:失败不影响正常功能
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
package com.ruoyi.system.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.ruoyi.system.mapper.SysTenantPackageMapper;
|
||||
import com.ruoyi.system.domain.SysTenantPackage;
|
||||
import com.ruoyi.system.service.ISysTenantPackageService;
|
||||
|
||||
/**
|
||||
* 租户套餐Service业务层处理
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-12-19
|
||||
*/
|
||||
@Service
|
||||
public class SysTenantPackageServiceImpl implements ISysTenantPackageService
|
||||
{
|
||||
@Autowired
|
||||
private SysTenantPackageMapper tenantPackageMapper;
|
||||
|
||||
/**
|
||||
* 查询租户套餐
|
||||
*
|
||||
* @param packageId 租户套餐主键
|
||||
* @return 租户套餐
|
||||
*/
|
||||
@Override
|
||||
public SysTenantPackage selectPackageById(Long packageId)
|
||||
{
|
||||
return tenantPackageMapper.selectPackageById(packageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询租户套餐列表
|
||||
*
|
||||
* @param sysTenantPackage 租户套餐
|
||||
* @return 租户套餐
|
||||
*/
|
||||
@Override
|
||||
public List<SysTenantPackage> selectPackageList(SysTenantPackage sysTenantPackage)
|
||||
{
|
||||
return tenantPackageMapper.selectPackageList(sysTenantPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增租户套餐
|
||||
*
|
||||
* @param sysTenantPackage 租户套餐
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public int insertPackage(SysTenantPackage sysTenantPackage)
|
||||
{
|
||||
sysTenantPackage.setCreateTime(DateUtils.getNowDate());
|
||||
int rows = tenantPackageMapper.insertPackage(sysTenantPackage);
|
||||
// 保存菜单关联
|
||||
if (sysTenantPackage.getMenuIds() != null && sysTenantPackage.getMenuIds().length > 0)
|
||||
{
|
||||
tenantPackageMapper.batchInsertPackageMenu(sysTenantPackage.getPackageId(), sysTenantPackage.getMenuIds());
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改租户套餐
|
||||
*
|
||||
* @param sysTenantPackage 租户套餐
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public int updatePackage(SysTenantPackage sysTenantPackage)
|
||||
{
|
||||
sysTenantPackage.setUpdateTime(DateUtils.getNowDate());
|
||||
// 先删除旧的菜单关联
|
||||
tenantPackageMapper.deletePackageMenuByPackageId(sysTenantPackage.getPackageId());
|
||||
// 再插入新的菜单关联
|
||||
if (sysTenantPackage.getMenuIds() != null && sysTenantPackage.getMenuIds().length > 0)
|
||||
{
|
||||
tenantPackageMapper.batchInsertPackageMenu(sysTenantPackage.getPackageId(), sysTenantPackage.getMenuIds());
|
||||
}
|
||||
return tenantPackageMapper.updatePackage(sysTenantPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除租户套餐
|
||||
*
|
||||
* @param packageIds 需要删除的租户套餐主键
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deletePackageByIds(Long[] packageIds)
|
||||
{
|
||||
return tenantPackageMapper.deletePackageByIds(packageIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除租户套餐信息
|
||||
*
|
||||
* @param packageId 租户套餐主键
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deletePackageById(Long packageId)
|
||||
{
|
||||
return tenantPackageMapper.deletePackageById(packageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询套餐包含的菜单ID列表
|
||||
*
|
||||
* @param packageId 套餐ID
|
||||
* @return 菜单ID列表,空列表表示高级套餐(不限制)
|
||||
*/
|
||||
@Override
|
||||
public List<Long> selectMenuIdsByPackageId(Long packageId)
|
||||
{
|
||||
return tenantPackageMapper.selectMenuIdsByPackageId(packageId);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
package com.ruoyi.system.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.system.domain.SysTenant;
|
||||
import com.ruoyi.system.mapper.SysTenantMapper;
|
||||
import com.ruoyi.system.service.ISysTenantService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 租户信息Service业务层处理
|
||||
*
|
||||
* @author W-yf
|
||||
* @date 2025-12-19
|
||||
*/
|
||||
@Service
|
||||
public class SysTenantServiceImpl implements ISysTenantService
|
||||
{
|
||||
@Autowired
|
||||
private SysTenantMapper sysTenantMapper;
|
||||
|
||||
/**
|
||||
* 查询租户信息
|
||||
*
|
||||
* @param tenantId 租户信息主键
|
||||
* @return 租户信息
|
||||
*/
|
||||
@Override
|
||||
public SysTenant selectSysTenantByTenantId(Long tenantId)
|
||||
{
|
||||
return sysTenantMapper.selectSysTenantByTenantId(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过租户编码查询租户信息
|
||||
*
|
||||
* @param tenantCode 租户编码
|
||||
* @return 租户信息
|
||||
*/
|
||||
@Override
|
||||
public SysTenant selectSysTenantByTenantCode(String tenantCode)
|
||||
{
|
||||
return sysTenantMapper.selectSysTenantByTenantCode(tenantCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询租户信息列表
|
||||
*
|
||||
* @param sysTenant 租户信息
|
||||
* @return 租户信息
|
||||
*/
|
||||
@Override
|
||||
public List<SysTenant> selectSysTenantList(SysTenant sysTenant)
|
||||
{
|
||||
return sysTenantMapper.selectSysTenantList(sysTenant);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增租户信息
|
||||
*
|
||||
* @param sysTenant 租户信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int insertSysTenant(SysTenant sysTenant)
|
||||
{
|
||||
sysTenant.setCreateTime(DateUtils.getNowDate());
|
||||
return sysTenantMapper.insertSysTenant(sysTenant);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改租户信息
|
||||
*
|
||||
* @param sysTenant 租户信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int updateSysTenant(SysTenant sysTenant)
|
||||
{
|
||||
sysTenant.setUpdateTime(DateUtils.getNowDate());
|
||||
return sysTenantMapper.updateSysTenant(sysTenant);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除租户信息
|
||||
*
|
||||
* @param tenantIds 需要删除的租户信息主键
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteSysTenantByTenantIds(Long[] tenantIds)
|
||||
{
|
||||
return sysTenantMapper.deleteSysTenantByTenantIds(tenantIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除租户信息信息
|
||||
*
|
||||
* @param tenantId 租户信息主键
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteSysTenantByTenantId(Long tenantId)
|
||||
{
|
||||
return sysTenantMapper.deleteSysTenantByTenantId(tenantId);
|
||||
}
|
||||
}
|
||||
|
|
@ -107,7 +107,7 @@ public class SysUserServiceImpl implements ISysUserService
|
|||
|
||||
/**
|
||||
* 通过用户名查询用户
|
||||
*
|
||||
*
|
||||
* @param userName 用户名
|
||||
* @return 用户对象信息
|
||||
*/
|
||||
|
|
@ -117,6 +117,19 @@ public class SysUserServiceImpl implements ISysUserService
|
|||
return userMapper.selectUserByUserName(userName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过用户名和租户ID查询用户
|
||||
*
|
||||
* @param userName 用户名
|
||||
* @param tenantId 租户ID
|
||||
* @return 用户对象信息
|
||||
*/
|
||||
@Override
|
||||
public SysUser selectUserByUserNameAndTenantId(String userName, Long tenantId)
|
||||
{
|
||||
return userMapper.selectUserByUserNameAndTenantId(userName, tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过用户ID查询用户
|
||||
*
|
||||
|
|
@ -165,7 +178,8 @@ public class SysUserServiceImpl implements ISysUserService
|
|||
|
||||
/**
|
||||
* 校验用户名称是否唯一
|
||||
*
|
||||
* Reason: Bug Fix - 添加 tenant_id 参数,支持不同租户创建同名用户
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 结果
|
||||
*/
|
||||
|
|
@ -173,7 +187,8 @@ public class SysUserServiceImpl implements ISysUserService
|
|||
public boolean checkUserNameUnique(SysUser user)
|
||||
{
|
||||
Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId();
|
||||
SysUser info = userMapper.checkUserNameUnique(user.getUserName());
|
||||
Long tenantId = user.getTenantId();
|
||||
SysUser info = userMapper.checkUserNameUnique(user.getUserName(), tenantId);
|
||||
if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue())
|
||||
{
|
||||
return UserConstants.NOT_UNIQUE;
|
||||
|
|
@ -183,6 +198,7 @@ public class SysUserServiceImpl implements ISysUserService
|
|||
|
||||
/**
|
||||
* 校验手机号码是否唯一
|
||||
* Reason: Bug Fix - 添加 tenant_id 参数,支持不同租户使用同一手机号
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return
|
||||
|
|
@ -191,7 +207,8 @@ public class SysUserServiceImpl implements ISysUserService
|
|||
public boolean checkPhoneUnique(SysUser user)
|
||||
{
|
||||
Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId();
|
||||
SysUser info = userMapper.checkPhoneUnique(user.getPhonenumber());
|
||||
Long tenantId = user.getTenantId();
|
||||
SysUser info = userMapper.checkPhoneUnique(user.getPhonenumber(), tenantId);
|
||||
if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue())
|
||||
{
|
||||
return UserConstants.NOT_UNIQUE;
|
||||
|
|
@ -201,6 +218,7 @@ public class SysUserServiceImpl implements ISysUserService
|
|||
|
||||
/**
|
||||
* 校验email是否唯一
|
||||
* Reason: Bug Fix - 添加 tenant_id 参数,支持不同租户使用同一邮箱
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return
|
||||
|
|
@ -209,7 +227,8 @@ public class SysUserServiceImpl implements ISysUserService
|
|||
public boolean checkEmailUnique(SysUser user)
|
||||
{
|
||||
Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId();
|
||||
SysUser info = userMapper.checkEmailUnique(user.getEmail());
|
||||
Long tenantId = user.getTenantId();
|
||||
SysUser info = userMapper.checkEmailUnique(user.getEmail(), tenantId);
|
||||
if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue())
|
||||
{
|
||||
return UserConstants.NOT_UNIQUE;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
|
||||
<resultMap type="SysDictData" id="SysDictDataResult">
|
||||
<id property="dictCode" column="dict_code" />
|
||||
<result property="tenantId" column="tenant_id" />
|
||||
<result property="dictSort" column="dict_sort" />
|
||||
<result property="dictLabel" column="dict_label" />
|
||||
<result property="dictValue" column="dict_value" />
|
||||
|
|
@ -19,9 +20,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
</resultMap>
|
||||
|
||||
|
||||
<sql id="selectDictDataVo">
|
||||
select dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark
|
||||
select dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark
|
||||
from sys_dict_data
|
||||
</sql>
|
||||
|
||||
|
|
@ -95,6 +96,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
|
||||
<insert id="insertDictData" parameterType="SysDictData">
|
||||
insert into sys_dict_data(
|
||||
<if test="tenantId != null">tenant_id,</if>
|
||||
<if test="dictSort != null">dict_sort,</if>
|
||||
<if test="dictLabel != null and dictLabel != ''">dict_label,</if>
|
||||
<if test="dictValue != null and dictValue != ''">dict_value,</if>
|
||||
|
|
@ -107,6 +109,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="createBy != null and createBy != ''">create_by,</if>
|
||||
create_time
|
||||
)values(
|
||||
<if test="tenantId != null">#{tenantId},</if>
|
||||
<if test="dictSort != null">#{dictSort},</if>
|
||||
<if test="dictLabel != null and dictLabel != ''">#{dictLabel},</if>
|
||||
<if test="dictValue != null and dictValue != ''">#{dictValue},</if>
|
||||
|
|
@ -120,5 +123,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
||||
|
||||
<!-- 混合模式查询:合并系统字典和租户字典,租户优先 -->
|
||||
<select id="selectDictDataByTypeWithTenant" resultMap="SysDictDataResult">
|
||||
SELECT d1.* FROM sys_dict_data d1
|
||||
WHERE d1.dict_type = #{dictType}
|
||||
AND d1.status = '0'
|
||||
AND d1.tenant_id IN (0, #{tenantId})
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_dict_data d2
|
||||
WHERE d2.dict_type = d1.dict_type
|
||||
AND d2.dict_value = d1.dict_value
|
||||
AND d2.tenant_id = #{tenantId}
|
||||
AND d1.tenant_id = 0
|
||||
)
|
||||
ORDER BY d1.dict_sort ASC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
|
@ -6,6 +6,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
|
||||
<resultMap type="SysDictType" id="SysDictTypeResult">
|
||||
<id property="dictId" column="dict_id" />
|
||||
<result property="tenantId" column="tenant_id" />
|
||||
<result property="dictName" column="dict_name" />
|
||||
<result property="dictType" column="dict_type" />
|
||||
<result property="status" column="status" />
|
||||
|
|
@ -14,9 +15,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
</resultMap>
|
||||
|
||||
|
||||
<sql id="selectDictTypeVo">
|
||||
select dict_id, dict_name, dict_type, status, create_by, create_time, remark
|
||||
select dict_id, tenant_id, dict_name, dict_type, status, create_by, create_time, remark
|
||||
from sys_dict_type
|
||||
</sql>
|
||||
|
||||
|
|
@ -39,6 +40,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
and date_format(create_time,'%Y%m%d') <= date_format(#{params.endTime},'%Y%m%d')
|
||||
</if>
|
||||
</where>
|
||||
order by create_time desc
|
||||
</select>
|
||||
|
||||
<select id="selectDictTypeAll" resultMap="SysDictTypeResult">
|
||||
|
|
@ -86,6 +88,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
|
||||
<insert id="insertDictType" parameterType="SysDictType">
|
||||
insert into sys_dict_type(
|
||||
<if test="tenantId != null">tenant_id,</if>
|
||||
<if test="dictName != null and dictName != ''">dict_name,</if>
|
||||
<if test="dictType != null and dictType != ''">dict_type,</if>
|
||||
<if test="status != null">status,</if>
|
||||
|
|
@ -93,6 +96,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="createBy != null and createBy != ''">create_by,</if>
|
||||
create_time
|
||||
)values(
|
||||
<if test="tenantId != null">#{tenantId},</if>
|
||||
<if test="dictName != null and dictName != ''">#{dictName},</if>
|
||||
<if test="dictType != null and dictType != ''">#{dictType},</if>
|
||||
<if test="status != null">#{status},</if>
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="status != null and status != ''">status,</if>
|
||||
<if test="remark != null and remark != ''">remark,</if>
|
||||
<if test="createBy != null and createBy != ''">create_by,</if>
|
||||
<if test="tenantId != null">tenant_id,</if>
|
||||
create_time
|
||||
)values(
|
||||
<if test="roleId != null and roleId != 0">#{roleId},</if>
|
||||
|
|
@ -117,6 +118,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="status != null and status != ''">#{status},</if>
|
||||
<if test="remark != null and remark != ''">#{remark},</if>
|
||||
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
||||
<if test="tenantId != null">#{tenantId},</if>
|
||||
sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.system.mapper.SysTenantMapper">
|
||||
|
||||
<resultMap type="SysTenant" id="SysTenantResult">
|
||||
<result property="tenantId" column="tenant_id" />
|
||||
<result property="tenantName" column="tenant_name" />
|
||||
<result property="tenantCode" column="tenant_code" />
|
||||
<result property="contactName" column="contact_name" />
|
||||
<result property="contactPhone" column="contact_phone" />
|
||||
<result property="expireTime" column="expire_time" />
|
||||
<result property="packageId" column="package_id" />
|
||||
<result property="status" column="status" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectSysTenantVo">
|
||||
select tenant_id, tenant_name, tenant_code, contact_name, contact_phone, expire_time, package_id, status, del_flag, create_by, create_time, update_by, update_time, remark from sys_tenant
|
||||
</sql>
|
||||
|
||||
<select id="selectSysTenantList" parameterType="SysTenant" resultMap="SysTenantResult">
|
||||
<include refid="selectSysTenantVo"/>
|
||||
<where>
|
||||
<if test="tenantName != null and tenantName != ''"> and tenant_name like concat('%', #{tenantName}, '%')</if>
|
||||
<if test="tenantCode != null and tenantCode != ''"> and tenant_code = #{tenantCode}</if>
|
||||
<if test="contactName != null and contactName != ''"> and contact_name like concat('%', #{contactName}, '%')</if>
|
||||
<if test="contactPhone != null and contactPhone != ''"> and contact_phone = #{contactPhone}</if>
|
||||
<if test="expireTime != null "> and expire_time = #{expireTime}</if>
|
||||
<if test="packageId != null "> and package_id = #{packageId}</if>
|
||||
<if test="status != null and status != ''"> and status = #{status}</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<select id="selectSysTenantByTenantId" parameterType="Long" resultMap="SysTenantResult">
|
||||
<include refid="selectSysTenantVo"/>
|
||||
where tenant_id = #{tenantId}
|
||||
</select>
|
||||
|
||||
<select id="selectSysTenantByTenantCode" parameterType="String" resultMap="SysTenantResult">
|
||||
<include refid="selectSysTenantVo"/>
|
||||
where tenant_code = #{tenantCode} and del_flag = '0' and status = '0'
|
||||
</select>
|
||||
|
||||
<insert id="insertSysTenant" parameterType="SysTenant" useGeneratedKeys="true" keyProperty="tenantId">
|
||||
insert into sys_tenant
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="tenantName != null and tenantName != ''">tenant_name,</if>
|
||||
<if test="tenantCode != null and tenantCode != ''">tenant_code,</if>
|
||||
<if test="contactName != null">contact_name,</if>
|
||||
<if test="contactPhone != null">contact_phone,</if>
|
||||
<if test="expireTime != null">expire_time,</if>
|
||||
<if test="packageId != null">package_id,</if>
|
||||
<if test="status != null">status,</if>
|
||||
<if test="delFlag != null">del_flag,</if>
|
||||
<if test="createBy != null">create_by,</if>
|
||||
<if test="createTime != null">create_time,</if>
|
||||
<if test="updateBy != null">update_by,</if>
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="tenantName != null and tenantName != ''">#{tenantName},</if>
|
||||
<if test="tenantCode != null and tenantCode != ''">#{tenantCode},</if>
|
||||
<if test="contactName != null">#{contactName},</if>
|
||||
<if test="contactPhone != null">#{contactPhone},</if>
|
||||
<if test="expireTime != null">#{expireTime},</if>
|
||||
<if test="packageId != null">#{packageId},</if>
|
||||
<if test="status != null">#{status},</if>
|
||||
<if test="delFlag != null">#{delFlag},</if>
|
||||
<if test="createBy != null">#{createBy},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
<if test="updateBy != null">#{updateBy},</if>
|
||||
<if test="updateTime != null">#{updateTime},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<update id="updateSysTenant" parameterType="SysTenant">
|
||||
update sys_tenant
|
||||
<trim prefix="SET" suffixOverrides=",">
|
||||
<if test="tenantName != null and tenantName != ''">tenant_name = #{tenantName},</if>
|
||||
<if test="tenantCode != null and tenantCode != ''">tenant_code = #{tenantCode},</if>
|
||||
<if test="contactName != null">contact_name = #{contactName},</if>
|
||||
<if test="contactPhone != null">contact_phone = #{contactPhone},</if>
|
||||
<if test="expireTime != null">expire_time = #{expireTime},</if>
|
||||
<if test="packageId != null">package_id = #{packageId},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
<if test="delFlag != null">del_flag = #{delFlag},</if>
|
||||
<if test="createBy != null">create_by = #{createBy},</if>
|
||||
<if test="createTime != null">create_time = #{createTime},</if>
|
||||
<if test="updateBy != null">update_by = #{updateBy},</if>
|
||||
<if test="updateTime != null">update_time = #{updateTime},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
</trim>
|
||||
where tenant_id = #{tenantId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteSysTenantByTenantId" parameterType="Long">
|
||||
delete from sys_tenant where tenant_id = #{tenantId}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteSysTenantByTenantIds" parameterType="String">
|
||||
delete from sys_tenant where tenant_id in
|
||||
<foreach item="tenantId" collection="array" open="(" separator="," close=")">
|
||||
#{tenantId}
|
||||
</foreach>
|
||||
</delete>
|
||||
</mapper>
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.system.mapper.SysTenantPackageMapper">
|
||||
|
||||
<resultMap type="SysTenantPackage" id="SysTenantPackageResult">
|
||||
<result property="packageId" column="package_id" />
|
||||
<result property="packageName" column="package_name" />
|
||||
<result property="packageCode" column="package_code" />
|
||||
<result property="status" column="status" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectPackageVo">
|
||||
select package_id, package_name, package_code, status, del_flag, create_by, create_time, update_by, update_time, remark from sys_tenant_package
|
||||
</sql>
|
||||
|
||||
<select id="selectPackageList" parameterType="SysTenantPackage" resultMap="SysTenantPackageResult">
|
||||
<include refid="selectPackageVo"/>
|
||||
<where>
|
||||
del_flag = '0'
|
||||
<if test="packageName != null and packageName != ''"> and package_name like concat('%', #{packageName}, '%')</if>
|
||||
<if test="packageCode != null and packageCode != ''"> and package_code = #{packageCode}</if>
|
||||
<if test="status != null and status != ''"> and status = #{status}</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<select id="selectPackageById" parameterType="Long" resultMap="SysTenantPackageResult">
|
||||
<include refid="selectPackageVo"/>
|
||||
where package_id = #{packageId} and del_flag = '0'
|
||||
</select>
|
||||
|
||||
<insert id="insertPackage" parameterType="SysTenantPackage" useGeneratedKeys="true" keyProperty="packageId">
|
||||
insert into sys_tenant_package
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="packageName != null and packageName != ''">package_name,</if>
|
||||
<if test="packageCode != null and packageCode != ''">package_code,</if>
|
||||
<if test="status != null">status,</if>
|
||||
<if test="delFlag != null">del_flag,</if>
|
||||
<if test="createBy != null">create_by,</if>
|
||||
<if test="createTime != null">create_time,</if>
|
||||
<if test="updateBy != null">update_by,</if>
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="packageName != null and packageName != ''">#{packageName},</if>
|
||||
<if test="packageCode != null and packageCode != ''">#{packageCode},</if>
|
||||
<if test="status != null">#{status},</if>
|
||||
<if test="delFlag != null">#{delFlag},</if>
|
||||
<if test="createBy != null">#{createBy},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
<if test="updateBy != null">#{updateBy},</if>
|
||||
<if test="updateTime != null">#{updateTime},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<update id="updatePackage" parameterType="SysTenantPackage">
|
||||
update sys_tenant_package
|
||||
<trim prefix="SET" suffixOverrides=",">
|
||||
<if test="packageName != null and packageName != ''">package_name = #{packageName},</if>
|
||||
<if test="packageCode != null and packageCode != ''">package_code = #{packageCode},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
<if test="delFlag != null">del_flag = #{delFlag},</if>
|
||||
<if test="createBy != null">create_by = #{createBy},</if>
|
||||
<if test="createTime != null">create_time = #{createTime},</if>
|
||||
<if test="updateBy != null">update_by = #{updateBy},</if>
|
||||
<if test="updateTime != null">update_time = #{updateTime},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
</trim>
|
||||
where package_id = #{packageId}
|
||||
</update>
|
||||
|
||||
<update id="deletePackageById" parameterType="Long">
|
||||
update sys_tenant_package set del_flag = '2' where package_id = #{packageId}
|
||||
</update>
|
||||
|
||||
<update id="deletePackageByIds" parameterType="Long">
|
||||
update sys_tenant_package set del_flag = '2' where package_id in
|
||||
<foreach item="packageId" collection="array" open="(" separator="," close=")">
|
||||
#{packageId}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
<!-- 查询套餐包含的菜单ID列表 -->
|
||||
<select id="selectMenuIdsByPackageId" parameterType="Long" resultType="Long">
|
||||
select menu_id from sys_package_menu where package_id = #{packageId}
|
||||
</select>
|
||||
|
||||
<!-- 批量新增套餐菜单关联 -->
|
||||
<insert id="batchInsertPackageMenu">
|
||||
insert into sys_package_menu(package_id, menu_id) values
|
||||
<foreach item="menuId" collection="menuIds" separator=",">
|
||||
(#{packageId}, #{menuId})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<!-- 删除套餐菜单关联 -->
|
||||
<delete id="deletePackageMenuByPackageId" parameterType="Long">
|
||||
delete from sys_package_menu where package_id = #{packageId}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
|
|
@ -7,6 +7,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<resultMap type="SysUser" id="SysUserResult">
|
||||
<id property="userId" column="user_id" />
|
||||
<result property="deptId" column="dept_id" />
|
||||
<result property="tenantId" column="tenant_id" />
|
||||
<result property="userName" column="user_name" />
|
||||
<result property="nickName" column="nick_name" />
|
||||
<result property="email" column="email" />
|
||||
|
|
@ -48,7 +49,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
</resultMap>
|
||||
|
||||
<sql id="selectUserVo">
|
||||
select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.pwd_update_date, u.create_by, u.create_time, u.remark,
|
||||
select u.user_id, u.dept_id, u.tenant_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.pwd_update_date, u.create_by, u.create_time, u.remark,
|
||||
d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status,
|
||||
r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
|
||||
from sys_user u
|
||||
|
|
@ -58,7 +59,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
</sql>
|
||||
|
||||
<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
|
||||
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u
|
||||
select u.user_id, u.dept_id, u.tenant_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u
|
||||
left join sys_dept d on u.dept_id = d.dept_id
|
||||
where u.del_flag = '0'
|
||||
<if test="userId != null and userId != 0">
|
||||
|
|
@ -125,22 +126,27 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<include refid="selectUserVo"/>
|
||||
where u.user_name = #{userName} and u.del_flag = '0'
|
||||
</select>
|
||||
|
||||
|
||||
<select id="selectUserByUserNameAndTenantId" resultMap="SysUserResult">
|
||||
<include refid="selectUserVo"/>
|
||||
where u.user_name = #{userName} and u.tenant_id = #{tenantId} and u.del_flag = '0'
|
||||
</select>
|
||||
|
||||
<select id="selectUserById" parameterType="Long" resultMap="SysUserResult">
|
||||
<include refid="selectUserVo"/>
|
||||
where u.user_id = #{userId}
|
||||
</select>
|
||||
|
||||
<select id="checkUserNameUnique" parameterType="String" resultMap="SysUserResult">
|
||||
select user_id, user_name from sys_user where user_name = #{userName} and del_flag = '0' limit 1
|
||||
<select id="checkUserNameUnique" resultMap="SysUserResult">
|
||||
select user_id, user_name from sys_user where user_name = #{userName} and tenant_id = #{tenantId} and del_flag = '0' limit 1
|
||||
</select>
|
||||
|
||||
<select id="checkPhoneUnique" parameterType="String" resultMap="SysUserResult">
|
||||
select user_id, phonenumber from sys_user where phonenumber = #{phonenumber} and del_flag = '0' limit 1
|
||||
|
||||
<select id="checkPhoneUnique" resultMap="SysUserResult">
|
||||
select user_id, phonenumber from sys_user where phonenumber = #{phonenumber} and tenant_id = #{tenantId} and del_flag = '0' limit 1
|
||||
</select>
|
||||
|
||||
<select id="checkEmailUnique" parameterType="String" resultMap="SysUserResult">
|
||||
select user_id, email from sys_user where email = #{email} and del_flag = '0' limit 1
|
||||
|
||||
<select id="checkEmailUnique" resultMap="SysUserResult">
|
||||
select user_id, email from sys_user where email = #{email} and tenant_id = #{tenantId} and del_flag = '0' limit 1
|
||||
</select>
|
||||
|
||||
<insert id="insertUser" parameterType="SysUser" useGeneratedKeys="true" keyProperty="userId">
|
||||
|
|
@ -158,6 +164,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="pwdUpdateDate != null">pwd_update_date,</if>
|
||||
<if test="createBy != null and createBy != ''">create_by,</if>
|
||||
<if test="remark != null and remark != ''">remark,</if>
|
||||
<if test="tenantId != null">tenant_id,</if>
|
||||
create_time
|
||||
)values(
|
||||
<if test="userId != null and userId != ''">#{userId},</if>
|
||||
|
|
@ -173,6 +180,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="pwdUpdateDate != null">#{pwdUpdateDate},</if>
|
||||
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
||||
<if test="remark != null and remark != ''">#{remark},</if>
|
||||
<if test="tenantId != null">#{tenantId},</if>
|
||||
sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# 页面标题
|
||||
VUE_APP_TITLE = 若依管理系统
|
||||
VUE_APP_TITLE = Hair-Link
|
||||
|
||||
# 开发环境配置
|
||||
ENV = 'development'
|
||||
|
||||
# 若依管理系统/开发环境
|
||||
# Hair-Link/开发环境
|
||||
VUE_APP_BASE_API = '/dev-api'
|
||||
|
||||
# 路由懒加载
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# 页面标题
|
||||
VUE_APP_TITLE = 若依管理系统
|
||||
VUE_APP_TITLE = Hair-Link
|
||||
|
||||
# 生产环境配置
|
||||
ENV = 'production'
|
||||
|
||||
# 若依管理系统/生产环境
|
||||
# Hair-Link/生产环境
|
||||
VUE_APP_BASE_API = '/prod-api'
|
||||
|
|
|
|||
|
|
@ -19,10 +19,6 @@
|
|||
"admin-template",
|
||||
"management-system"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://gitee.com/y_project/RuoYi-Vue.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@riophae/vue-treeselect": "0.4.0",
|
||||
"axios": "0.28.1",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 登录方法
|
||||
export function login(username, password, code, uuid) {
|
||||
export function login(username, password, code, uuid, tenantCode) {
|
||||
const data = {
|
||||
username,
|
||||
password,
|
||||
code,
|
||||
uuid
|
||||
uuid,
|
||||
tenantCode
|
||||
}
|
||||
return request({
|
||||
url: '/login',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 查询套餐列表
|
||||
export function listPackage(query) {
|
||||
return request({
|
||||
url: '/system/package/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询套餐详细
|
||||
export function getPackage(packageId) {
|
||||
return request({
|
||||
url: '/system/package/' + packageId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增套餐
|
||||
export function addPackage(data) {
|
||||
return request({
|
||||
url: '/system/package',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改套餐
|
||||
export function updatePackage(data) {
|
||||
return request({
|
||||
url: '/system/package',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除套餐
|
||||
export function delPackage(packageId) {
|
||||
return request({
|
||||
url: '/system/package/' + packageId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 查询套餐关联的菜单ID列表
|
||||
export function getPackageMenus(packageId) {
|
||||
return request({
|
||||
url: '/system/package/' + packageId + '/menus',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 查询租户信息列表
|
||||
export function listTenant(query) {
|
||||
return request({
|
||||
url: '/link/tenant/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询租户信息详细
|
||||
export function getTenant(tenantId) {
|
||||
return request({
|
||||
url: '/link/tenant/' + tenantId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增租户信息
|
||||
export function addTenant(data) {
|
||||
return request({
|
||||
url: '/link/tenant',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改租户信息
|
||||
export function updateTenant(data) {
|
||||
return request({
|
||||
url: '/link/tenant',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除租户信息
|
||||
export function delTenant(tenantId) {
|
||||
return request({
|
||||
url: '/link/tenant/' + tenantId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
|
@ -134,3 +134,6 @@ export function deptTreeSelect() {
|
|||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 导出租户列表查询方法
|
||||
export { listTenant } from '@/api/system/tenant'
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<svg-icon icon-class="question" @click="goto" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'RuoYiDoc',
|
||||
data() {
|
||||
return {
|
||||
url: 'http://doc.ruoyi.vip/ruoyi-vue'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goto() {
|
||||
window.open(this.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<svg-icon icon-class="github" @click="goto" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'RuoYiGit',
|
||||
data() {
|
||||
return {
|
||||
url: 'https://gitee.com/y_project/RuoYi-Vue'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goto() {
|
||||
window.open(this.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -12,14 +12,6 @@
|
|||
<template v-if="device!=='mobile'">
|
||||
<search id="header-search" class="right-menu-item" />
|
||||
|
||||
<el-tooltip content="源码地址" effect="dark" placement="bottom">
|
||||
<ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip content="文档地址" effect="dark" placement="bottom">
|
||||
<ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
|
||||
</el-tooltip>
|
||||
|
||||
<screenfull id="screenfull" class="right-menu-item hover-effect" />
|
||||
|
||||
<el-tooltip content="布局大小" effect="dark" placement="bottom">
|
||||
|
|
@ -59,8 +51,6 @@ import Hamburger from '@/components/Hamburger'
|
|||
import Screenfull from '@/components/Screenfull'
|
||||
import SizeSelect from '@/components/SizeSelect'
|
||||
import Search from '@/components/HeaderSearch'
|
||||
import RuoYiGit from '@/components/RuoYi/Git'
|
||||
import RuoYiDoc from '@/components/RuoYi/Doc'
|
||||
|
||||
export default {
|
||||
emits: ['setLayout'],
|
||||
|
|
@ -72,9 +62,7 @@ export default {
|
|||
Hamburger,
|
||||
Screenfull,
|
||||
SizeSelect,
|
||||
Search,
|
||||
RuoYiGit,
|
||||
RuoYiDoc
|
||||
Search
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
|
|
|
|||
|
|
@ -52,5 +52,5 @@ module.exports = {
|
|||
/**
|
||||
* 底部版权文本内容
|
||||
*/
|
||||
footerContent: 'Copyright © 2018-2025 RuoYi. All Rights Reserved.'
|
||||
footerContent: 'Copyright © 2025 时光. All Rights Reserved.'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,8 +47,9 @@ const user = {
|
|||
const password = userInfo.password
|
||||
const code = userInfo.code
|
||||
const uuid = userInfo.uuid
|
||||
const tenantCode = userInfo.tenantCode
|
||||
return new Promise((resolve, reject) => {
|
||||
login(username, password, code, uuid).then(res => {
|
||||
login(username, password, code, uuid, tenantCode).then(res => {
|
||||
setToken(res.token)
|
||||
commit('SET_TOKEN', res.token)
|
||||
resolve()
|
||||
|
|
@ -67,9 +68,13 @@ const user = {
|
|||
if (!isHttp(avatar)) {
|
||||
avatar = (isEmpty(avatar)) ? defAva : process.env.VUE_APP_BASE_API + avatar
|
||||
}
|
||||
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
|
||||
commit('SET_ROLES', res.roles)
|
||||
// 设置权限(独立于角色,解决跨租户角色关联时permissions无法设置的问题)
|
||||
if (res.permissions && res.permissions.length > 0) {
|
||||
commit('SET_PERMISSIONS', res.permissions)
|
||||
}
|
||||
// 设置角色
|
||||
if (res.roles && res.roles.length > 0) {
|
||||
commit('SET_ROLES', res.roles)
|
||||
} else {
|
||||
commit('SET_ROLES', ['ROLE_DEFAULT'])
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -2,6 +2,16 @@
|
|||
<div class="login">
|
||||
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
|
||||
<h3 class="title">{{title}}</h3>
|
||||
<el-form-item prop="tenantCode">
|
||||
<el-input
|
||||
v-model="loginForm.tenantCode"
|
||||
type="text"
|
||||
auto-complete="off"
|
||||
placeholder="租户编码(管理员留空)"
|
||||
>
|
||||
<svg-icon slot="prefix" icon-class="tree" class="el-input__icon input-icon" />
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
|
|
@ -56,7 +66,7 @@
|
|||
</el-form>
|
||||
<!-- 底部 -->
|
||||
<div class="el-login-footer">
|
||||
<span>{{ footerContent }}</span>
|
||||
<span>Copyright © 2025 时光. All Rights Reserved.</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -75,6 +85,7 @@ export default {
|
|||
footerContent: defaultSettings.footerContent,
|
||||
codeUrl: "",
|
||||
loginForm: {
|
||||
tenantCode: "",
|
||||
username: "admin",
|
||||
password: "admin123",
|
||||
rememberMe: false,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,376 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="套餐名称" prop="packageName">
|
||||
<el-input
|
||||
v-model="queryParams.packageName"
|
||||
placeholder="请输入套餐名称"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="套餐编码" prop="packageCode">
|
||||
<el-input
|
||||
v-model="queryParams.packageCode"
|
||||
placeholder="请输入套餐编码"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-option label="正常" value="0" />
|
||||
<el-option label="停用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['system:package:add']"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
v-hasPermi="['system:package:edit']"
|
||||
>修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['system:package:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="packageList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="套餐ID" align="center" prop="packageId" width="80" />
|
||||
<el-table-column label="套餐名称" align="center" prop="packageName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="套餐编码" align="center" prop="packageCode" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
|
||||
{{ scope.row.status === '0' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['system:package:edit']"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['system:package:remove']"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 添加或修改套餐对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="套餐名称" prop="packageName">
|
||||
<el-input v-model="form.packageName" placeholder="请输入套餐名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="套餐编码" prop="packageCode">
|
||||
<el-input v-model="form.packageCode" placeholder="请输入套餐编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio label="0">正常</el-radio>
|
||||
<el-radio label="1">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单权限">
|
||||
<el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
|
||||
<el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
|
||||
<el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox>
|
||||
<el-tree
|
||||
class="tree-border"
|
||||
:data="menuOptions"
|
||||
show-checkbox
|
||||
ref="menu"
|
||||
node-key="id"
|
||||
:check-strictly="!form.menuCheckStrictly"
|
||||
empty-text="加载中,请稍候"
|
||||
:props="defaultProps"
|
||||
></el-tree>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listPackage, getPackage, addPackage, updatePackage, delPackage, getPackageMenus } from "@/api/system/package"
|
||||
import { treeselect as menuTreeselect } from "@/api/system/menu"
|
||||
|
||||
export default {
|
||||
name: "Package",
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 套餐表格数据
|
||||
packageList: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 菜单列表
|
||||
menuOptions: [],
|
||||
// 菜单树展开
|
||||
menuExpand: false,
|
||||
// 菜单树全选
|
||||
menuNodeAll: false,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
packageName: null,
|
||||
packageCode: null,
|
||||
status: null
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 默认树属性
|
||||
defaultProps: {
|
||||
children: "children",
|
||||
label: "label"
|
||||
},
|
||||
// 表单校验
|
||||
rules: {
|
||||
packageName: [
|
||||
{ required: true, message: "套餐名称不能为空", trigger: "blur" }
|
||||
],
|
||||
packageCode: [
|
||||
{ required: true, message: "套餐编码不能为空", trigger: "blur" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
/** 查询套餐列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
listPackage(this.queryParams).then(response => {
|
||||
this.packageList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/** 查询菜单树结构 */
|
||||
getMenuTreeselect() {
|
||||
menuTreeselect().then(response => {
|
||||
this.menuOptions = response.data
|
||||
})
|
||||
},
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.open = false
|
||||
this.reset()
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
if (this.$refs.menu != undefined) {
|
||||
this.$refs.menu.setCheckedKeys([])
|
||||
}
|
||||
this.menuExpand = false
|
||||
this.menuNodeAll = false
|
||||
this.form = {
|
||||
packageId: null,
|
||||
packageName: null,
|
||||
packageCode: null,
|
||||
status: "0",
|
||||
menuCheckStrictly: true,
|
||||
remark: null
|
||||
}
|
||||
this.resetForm("form")
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm")
|
||||
this.handleQuery()
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.packageId)
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
// 树权限(展开/折叠)
|
||||
handleCheckedTreeExpand(value, type) {
|
||||
if (type == 'menu') {
|
||||
let treeList = this.menuOptions
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
this.$refs.menu.store.nodesMap[treeList[i].id].expanded = value
|
||||
}
|
||||
}
|
||||
},
|
||||
// 树权限(全选/全不选)
|
||||
handleCheckedTreeNodeAll(value, type) {
|
||||
if (type == 'menu') {
|
||||
this.$refs.menu.setCheckedNodes(value ? this.menuOptions : [])
|
||||
}
|
||||
},
|
||||
// 树权限(父子联动)
|
||||
handleCheckedTreeConnect(value, type) {
|
||||
if (type == 'menu') {
|
||||
this.form.menuCheckStrictly = value ? true : false
|
||||
}
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.getMenuTreeselect()
|
||||
this.open = true
|
||||
this.title = "添加套餐"
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
this.getMenuTreeselect()
|
||||
const packageId = row.packageId || this.ids
|
||||
getPackage(packageId).then(response => {
|
||||
this.form = response.data
|
||||
this.form.menuCheckStrictly = true
|
||||
this.open = true
|
||||
this.title = "修改套餐"
|
||||
// 获取套餐关联的菜单
|
||||
this.$nextTick(() => {
|
||||
getPackageMenus(packageId).then(res => {
|
||||
let checkedKeys = res.data
|
||||
checkedKeys.forEach((v) => {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.menu.setChecked(v, true, false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
// 获取选中的菜单ID
|
||||
this.form.menuIds = this.getMenuAllCheckedKeys()
|
||||
if (this.form.packageId != null) {
|
||||
updatePackage(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
} else {
|
||||
addPackage(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const packageIds = row.packageId || this.ids
|
||||
this.$modal.confirm('是否确认删除套餐编号为"' + packageIds + '"的数据项?').then(function() {
|
||||
return delPackage(packageIds)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
// 所有菜单节点数据
|
||||
getMenuAllCheckedKeys() {
|
||||
// 目前被选中的菜单节点
|
||||
let checkedKeys = this.$refs.menu.getCheckedKeys()
|
||||
// 半选中的菜单节点
|
||||
let halfCheckedKeys = this.$refs.menu.getHalfCheckedKeys()
|
||||
checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys)
|
||||
return checkedKeys
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tree-border {
|
||||
margin-top: 5px;
|
||||
border: 1px solid #e5e6e7;
|
||||
background: #FFFFFF none;
|
||||
border-radius: 4px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -158,6 +158,16 @@
|
|||
<!-- 添加或修改角色配置对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="所属租户" prop="tenantId" v-if="isSuperAdmin">
|
||||
<el-select v-model="form.tenantId" placeholder="请选择所属租户" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in tenantList"
|
||||
:key="item.tenantId"
|
||||
:label="item.tenantName"
|
||||
:value="item.tenantId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色名称" prop="roleName">
|
||||
<el-input v-model="form.roleName" placeholder="请输入角色名称" />
|
||||
</el-form-item>
|
||||
|
|
@ -254,6 +264,7 @@
|
|||
<script>
|
||||
import { listRole, getRole, delRole, addRole, updateRole, dataScope, changeRoleStatus, deptTreeSelect } from "@/api/system/role"
|
||||
import { treeselect as menuTreeselect, roleMenuTreeselect } from "@/api/system/menu"
|
||||
import { listTenant } from "@/api/system/tenant"
|
||||
|
||||
export default {
|
||||
name: "Role",
|
||||
|
|
@ -313,6 +324,8 @@ export default {
|
|||
menuOptions: [],
|
||||
// 部门列表
|
||||
deptOptions: [],
|
||||
// 租户列表
|
||||
tenantList: [],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
|
|
@ -344,6 +357,13 @@ export default {
|
|||
created() {
|
||||
this.getList()
|
||||
},
|
||||
computed: {
|
||||
// 判断是否为超级管理员(拥有*:*:*权限)
|
||||
isSuperAdmin() {
|
||||
const permissions = this.$store.getters && this.$store.getters.permissions
|
||||
return permissions && permissions.includes('*:*:*')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 查询角色列表 */
|
||||
getList() {
|
||||
|
|
@ -361,6 +381,12 @@ export default {
|
|||
this.menuOptions = response.data
|
||||
})
|
||||
},
|
||||
/** 查询租户列表 */
|
||||
getTenantList() {
|
||||
listTenant({ status: '0' }).then(response => {
|
||||
this.tenantList = response.rows
|
||||
})
|
||||
},
|
||||
// 所有菜单节点数据
|
||||
getMenuAllCheckedKeys() {
|
||||
// 目前被选中的菜单节点
|
||||
|
|
@ -433,7 +459,8 @@ export default {
|
|||
deptIds: [],
|
||||
menuCheckStrictly: true,
|
||||
deptCheckStrictly: true,
|
||||
remark: undefined
|
||||
remark: undefined,
|
||||
tenantId: undefined
|
||||
}
|
||||
this.resetForm("form")
|
||||
},
|
||||
|
|
@ -501,6 +528,10 @@ export default {
|
|||
handleAdd() {
|
||||
this.reset()
|
||||
this.getMenuTreeselect()
|
||||
// 超级管理员获取租户列表
|
||||
if (this.isSuperAdmin) {
|
||||
this.getTenantList()
|
||||
}
|
||||
this.open = true
|
||||
this.title = "添加角色"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,375 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="租户名称" prop="tenantName">
|
||||
<el-input
|
||||
v-model="queryParams.tenantName"
|
||||
placeholder="请输入租户名称"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="租户编码" prop="tenantCode">
|
||||
<el-input
|
||||
v-model="queryParams.tenantCode"
|
||||
placeholder="请输入租户编码"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人" prop="contactName">
|
||||
<el-input
|
||||
v-model="queryParams.contactName"
|
||||
placeholder="请输入联系人"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话" prop="contactPhone">
|
||||
<el-input
|
||||
v-model="queryParams.contactPhone"
|
||||
placeholder="请输入联系电话"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="过期时间" prop="expireTime">
|
||||
<el-date-picker clearable
|
||||
v-model="queryParams.expireTime"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="请选择过期时间">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="租户套餐" prop="packageId">
|
||||
<el-select v-model="queryParams.packageId" placeholder="请选择套餐" clearable>
|
||||
<el-option
|
||||
v-for="pkg in packageList"
|
||||
:key="pkg.packageId"
|
||||
:label="pkg.packageName"
|
||||
:value="pkg.packageId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['link:tenant:add']"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
v-hasPermi="['link:tenant:edit']"
|
||||
>修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['link:tenant:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['link:tenant:export']"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="tenantList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="租户ID" align="center" prop="tenantId" />
|
||||
<el-table-column label="租户名称" align="center" prop="tenantName" />
|
||||
<el-table-column label="租户编码" align="center" prop="tenantCode" />
|
||||
<el-table-column label="联系人" align="center" prop="contactName" />
|
||||
<el-table-column label="联系电话" align="center" prop="contactPhone" />
|
||||
<el-table-column label="过期时间" align="center" prop="expireTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.expireTime, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="租户套餐" align="center" prop="packageId">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ getPackageName(scope.row.packageId) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['link:tenant:edit']"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['link:tenant:remove']"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 添加或修改租户信息对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="租户名称" prop="tenantName">
|
||||
<el-input v-model="form.tenantName" placeholder="请输入租户名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="租户编码" prop="tenantCode">
|
||||
<el-input v-model="form.tenantCode" placeholder="请输入租户编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人" prop="contactName">
|
||||
<el-input v-model="form.contactName" placeholder="请输入联系人" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话" prop="contactPhone">
|
||||
<el-input v-model="form.contactPhone" placeholder="请输入联系电话" />
|
||||
</el-form-item>
|
||||
<el-form-item label="过期时间" prop="expireTime">
|
||||
<el-date-picker clearable
|
||||
v-model="form.expireTime"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="请选择过期时间">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="租户套餐" prop="packageId">
|
||||
<el-select v-model="form.packageId" placeholder="请选择套餐" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="pkg in packageList"
|
||||
:key="pkg.packageId"
|
||||
:label="pkg.packageName"
|
||||
:value="pkg.packageId"
|
||||
>
|
||||
<span>{{ pkg.packageName }}</span>
|
||||
<span style="color: #8492a6; font-size: 12px; margin-left: 10px">{{ pkg.remark }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listTenant, getTenant, delTenant, addTenant, updateTenant } from "@/api/system/tenant"
|
||||
import { listPackage } from "@/api/system/package"
|
||||
|
||||
export default {
|
||||
name: "Tenant",
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 租户信息表格数据
|
||||
tenantList: [],
|
||||
// 套餐列表
|
||||
packageList: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
tenantName: null,
|
||||
tenantCode: null,
|
||||
contactName: null,
|
||||
contactPhone: null,
|
||||
expireTime: null,
|
||||
packageId: null,
|
||||
status: null,
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
tenantName: [
|
||||
{ required: true, message: "租户名称不能为空", trigger: "blur" }
|
||||
],
|
||||
tenantCode: [
|
||||
{ required: true, message: "租户编码不能为空", trigger: "blur" }
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.getPackageList()
|
||||
},
|
||||
methods: {
|
||||
/** 查询租户信息列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
listTenant(this.queryParams).then(response => {
|
||||
this.tenantList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/** 查询套餐列表 */
|
||||
getPackageList() {
|
||||
listPackage({ status: '0' }).then(response => {
|
||||
this.packageList = response.rows
|
||||
})
|
||||
},
|
||||
/** 根据套餐ID获取套餐名称 */
|
||||
getPackageName(packageId) {
|
||||
if (!packageId) return '-'
|
||||
const pkg = this.packageList.find(p => p.packageId === packageId)
|
||||
return pkg ? pkg.packageName : '-'
|
||||
},
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.open = false
|
||||
this.reset()
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.form = {
|
||||
tenantId: null,
|
||||
tenantName: null,
|
||||
tenantCode: null,
|
||||
contactName: null,
|
||||
contactPhone: null,
|
||||
expireTime: null,
|
||||
packageId: null,
|
||||
status: null,
|
||||
delFlag: null,
|
||||
createBy: null,
|
||||
createTime: null,
|
||||
updateBy: null,
|
||||
updateTime: null,
|
||||
remark: null
|
||||
}
|
||||
this.resetForm("form")
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm")
|
||||
this.handleQuery()
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.tenantId)
|
||||
this.single = selection.length!==1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.open = true
|
||||
this.title = "添加租户信息"
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
const tenantId = row.tenantId || this.ids
|
||||
getTenant(tenantId).then(response => {
|
||||
this.form = response.data
|
||||
this.open = true
|
||||
this.title = "修改租户信息"
|
||||
})
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
if (this.form.tenantId != null) {
|
||||
updateTenant(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
} else {
|
||||
addTenant(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const tenantIds = row.tenantId || this.ids
|
||||
this.$modal.confirm('是否确认删除租户信息编号为"' + tenantIds + '"的数据项?').then(function() {
|
||||
return delTenant(tenantIds)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('link/tenant/export', {
|
||||
...this.queryParams
|
||||
}, `tenant_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -165,6 +165,15 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-if="isSuperAdmin">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="所属租户" prop="tenantId">
|
||||
<el-select v-model="form.tenantId" placeholder="请选择租户" clearable>
|
||||
<el-option v-for="item in tenantOptions" :key="item.tenantId" :label="item.tenantName" :value="item.tenantId" :disabled="item.status == '1'"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注">
|
||||
|
|
@ -246,6 +255,8 @@ export default {
|
|||
postOptions: [],
|
||||
// 角色选项
|
||||
roleOptions: [],
|
||||
// 租户选项
|
||||
tenantOptions: [],
|
||||
// 表单参数
|
||||
form: {},
|
||||
defaultProps: {
|
||||
|
|
@ -317,6 +328,12 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 判断当前用户是否为超级管理员
|
||||
isSuperAdmin() {
|
||||
return this.$store.getters.id === 1
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 根据名称筛选部门树
|
||||
deptName(val) {
|
||||
|
|
@ -400,7 +417,8 @@ export default {
|
|||
status: "0",
|
||||
remark: undefined,
|
||||
postIds: [],
|
||||
roleIds: []
|
||||
roleIds: [],
|
||||
tenantId: undefined
|
||||
}
|
||||
this.resetForm("form")
|
||||
},
|
||||
|
|
@ -442,6 +460,9 @@ export default {
|
|||
getUser().then(response => {
|
||||
this.postOptions = response.posts
|
||||
this.roleOptions = response.roles
|
||||
if (response.tenants) {
|
||||
this.tenantOptions = response.tenants
|
||||
}
|
||||
this.open = true
|
||||
this.title = "添加用户"
|
||||
this.form.password = this.initPassword
|
||||
|
|
@ -455,6 +476,9 @@ export default {
|
|||
this.form = response.data
|
||||
this.postOptions = response.posts
|
||||
this.roleOptions = response.roles
|
||||
if (response.tenants) {
|
||||
this.tenantOptions = response.tenants
|
||||
}
|
||||
this.$set(this.form, "postIds", response.postIds)
|
||||
this.$set(this.form, "roleIds", response.roleIds)
|
||||
this.open = true
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ function resolve(dir) {
|
|||
|
||||
const CompressionPlugin = require('compression-webpack-plugin')
|
||||
|
||||
const name = process.env.VUE_APP_TITLE || '若依管理系统' // 网页标题
|
||||
const name = process.env.VUE_APP_TITLE || 'Hair-Link' // 网页标题
|
||||
|
||||
const baseUrl = 'http://localhost:8080' // 后端接口
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ const port = process.env.port || process.env.npm_config_port || 80 // 端口
|
|||
module.exports = {
|
||||
// 部署生产环境和开发环境下的URL。
|
||||
// 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
|
||||
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
|
||||
// 例如 https://www.example.com/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.example.com/admin/,则设置 baseUrl 为 /admin/。
|
||||
publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
|
||||
// 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist)
|
||||
outputDir: 'dist',
|
||||
|
|
@ -65,7 +65,7 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
plugins: [
|
||||
// http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
|
||||
// 使用gzip解压缩静态文件
|
||||
new CompressionPlugin({
|
||||
cache: false, // 不启用文件缓存
|
||||
test: /\.(js|css|html|jpe?g|png|gif|svg)?$/i, // 压缩文件格式
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
-- ============================================
|
||||
-- Bug 修复脚本 - 2025-12-20
|
||||
-- ============================================
|
||||
|
||||
-- Bug 1: 修复基础套餐菜单配置(添加字典管理菜单)
|
||||
-- 原因:基础套餐缺少字典管理菜单(ID=105)及其子菜单权限
|
||||
|
||||
-- 1. 添加字典管理主菜单
|
||||
INSERT IGNORE INTO sys_package_menu (package_id, menu_id) VALUES (1, 105);
|
||||
|
||||
-- 2. 添加字典管理子菜单权限(1025-1029)
|
||||
INSERT IGNORE INTO sys_package_menu (package_id, menu_id) VALUES (1, 1025); -- 字典查询
|
||||
INSERT IGNORE INTO sys_package_menu (package_id, menu_id) VALUES (1, 1026); -- 字典新增
|
||||
INSERT IGNORE INTO sys_package_menu (package_id, menu_id) VALUES (1, 1027); -- 字典修改
|
||||
INSERT IGNORE INTO sys_package_menu (package_id, menu_id) VALUES (1, 1028); -- 字典删除
|
||||
INSERT IGNORE INTO sys_package_menu (package_id, menu_id) VALUES (1, 1029); -- 字典导出
|
||||
|
||||
-- 验证修复结果
|
||||
SELECT pm.package_id, pm.menu_id, m.menu_name
|
||||
FROM sys_package_menu pm
|
||||
LEFT JOIN sys_menu m ON pm.menu_id = m.menu_id
|
||||
WHERE pm.package_id = 1 AND pm.menu_id IN (105, 1025, 1026, 1027, 1028, 1029);
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
-- =============================================
|
||||
-- 字典表混合模式租户隔离迁移脚本
|
||||
-- 执行前请备份数据库
|
||||
-- =============================================
|
||||
|
||||
-- 1. 为 sys_dict_type 添加 tenant_id 字段
|
||||
ALTER TABLE sys_dict_type
|
||||
ADD COLUMN tenant_id BIGINT(20) NOT NULL DEFAULT 0 COMMENT '租户ID(0=系统级)' AFTER dict_id;
|
||||
|
||||
-- 2. 为 sys_dict_data 添加 tenant_id 字段
|
||||
ALTER TABLE sys_dict_data
|
||||
ADD COLUMN tenant_id BIGINT(20) NOT NULL DEFAULT 0 COMMENT '租户ID(0=系统级)' AFTER dict_code;
|
||||
|
||||
-- 3. 删除旧的唯一约束
|
||||
ALTER TABLE sys_dict_type DROP INDEX dict_type;
|
||||
|
||||
-- 4. 创建新的复合唯一约束(包含 tenant_id)
|
||||
ALTER TABLE sys_dict_type ADD UNIQUE INDEX uk_tenant_dict_type (tenant_id, dict_type);
|
||||
ALTER TABLE sys_dict_data ADD UNIQUE INDEX uk_tenant_dict_data (tenant_id, dict_type, dict_value);
|
||||
|
||||
-- 5. 添加索引优化查询
|
||||
CREATE INDEX idx_dict_type_tenant ON sys_dict_type(tenant_id);
|
||||
CREATE INDEX idx_dict_data_tenant ON sys_dict_data(tenant_id);
|
||||
|
|
@ -0,0 +1,312 @@
|
|||
-- ============================
|
||||
-- HairLink Phase 1 数据库初始化脚本
|
||||
-- 版本:1.0.0
|
||||
-- 创建日期:2025-12-19
|
||||
-- 说明:包含P1阶段核心业务表(顾客管理、订单管理、基础配置)
|
||||
-- 表数量:6个核心业务表
|
||||
-- 依赖:需先执行 ry_20250522.sql(若依框架基础表)
|
||||
-- ============================
|
||||
|
||||
-- ----------------------------
|
||||
-- 1、顾客档案表
|
||||
-- ----------------------------
|
||||
drop table if exists hl_customer;
|
||||
create table hl_customer (
|
||||
customer_id bigint(20) not null auto_increment comment '顾客ID',
|
||||
tenant_id bigint(20) default null comment '租户ID',
|
||||
dept_id bigint(20) default null comment '归属门店',
|
||||
|
||||
-- [P1] 基础信息
|
||||
customer_name varchar(50) default '散客' comment '姓名',
|
||||
phone varchar(11) default null comment '手机号(兼容散客为空)',
|
||||
gender char(1) default '2' comment '性别 0男 1女 2未知',
|
||||
|
||||
-- [P2] 资产快照(预留)
|
||||
member_level char(1) default '0' comment '会员等级 0普通 1银卡 2金卡',
|
||||
balance decimal(10,2) default 0.00 comment '储值余额',
|
||||
|
||||
-- [P3] C端连接(预留)
|
||||
wx_openid varchar(64) default null comment '小程序OpenID',
|
||||
points int(11) default 0 comment '积分',
|
||||
|
||||
-- 标准字段
|
||||
create_by varchar(64) default '',
|
||||
create_time datetime default null,
|
||||
update_by varchar(64) default '',
|
||||
update_time datetime default null,
|
||||
remark varchar(500) default null,
|
||||
|
||||
primary key (customer_id),
|
||||
index idx_tenant (tenant_id),
|
||||
index idx_phone (phone),
|
||||
index idx_openid (wx_openid)
|
||||
) engine=innodb auto_increment=100 comment = '顾客档案表';
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 2、会员权益卡表
|
||||
-- ----------------------------
|
||||
drop table if exists hl_member_card;
|
||||
create table hl_member_card (
|
||||
card_id bigint(20) not null auto_increment comment '卡ID',
|
||||
tenant_id bigint(20) default null comment '租户ID',
|
||||
customer_id bigint(20) not null comment '顾客ID',
|
||||
|
||||
-- [P1] 计次卡逻辑
|
||||
card_name varchar(100) default '' comment '卡名称',
|
||||
total_count int(11) default 0 comment '总次数',
|
||||
remain_count int(11) default 0 comment '剩余次数',
|
||||
|
||||
-- [P2] 储值卡/期限卡逻辑(预留)
|
||||
card_type char(1) default '0' comment '卡类型 0计次 1储值 2期限',
|
||||
total_amount decimal(10,2) default 0.00 comment '总充值金额',
|
||||
remain_amount decimal(10,2) default 0.00 comment '剩余金额',
|
||||
gift_amount decimal(10,2) default 0.00 comment '赠送金额',
|
||||
|
||||
expire_time datetime default null comment '过期时间',
|
||||
status char(1) default '0' comment '状态 0正常 1停用 2耗尽',
|
||||
|
||||
create_by varchar(64) default '',
|
||||
create_time datetime default null,
|
||||
update_by varchar(64) default '',
|
||||
update_time datetime default null,
|
||||
|
||||
primary key (card_id),
|
||||
index idx_tenant (tenant_id),
|
||||
index idx_customer (customer_id)
|
||||
) engine=innodb auto_increment=100 comment = '会员权益卡表';
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 3、服务项目表
|
||||
-- ----------------------------
|
||||
drop table if exists hl_service;
|
||||
create table hl_service (
|
||||
service_id bigint(20) not null auto_increment comment '服务ID',
|
||||
tenant_id bigint(20) default null comment '租户ID',
|
||||
|
||||
service_name varchar(100) not null comment '服务名称',
|
||||
service_code varchar(50) default null comment '服务编码',
|
||||
price decimal(10,2) default 0.00 comment '标准价格',
|
||||
duration int(11) default 30 comment '服务时长(分钟)',
|
||||
category varchar(50) default null comment '服务分类',
|
||||
status char(1) default '0' comment '状态 0正常 1停用',
|
||||
sort_order int(11) default 0 comment '排序',
|
||||
|
||||
create_by varchar(64) default '',
|
||||
create_time datetime default null,
|
||||
update_by varchar(64) default '',
|
||||
update_time datetime default null,
|
||||
remark varchar(500) default null,
|
||||
|
||||
primary key (service_id),
|
||||
index idx_tenant (tenant_id),
|
||||
unique key uk_code (service_code)
|
||||
) engine=innodb auto_increment=100 comment = '服务项目表';
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 4、员工扩展表
|
||||
-- ----------------------------
|
||||
drop table if exists hl_staff;
|
||||
create table hl_staff (
|
||||
staff_id bigint(20) not null auto_increment comment '员工ID',
|
||||
user_id bigint(20) not null comment '关联sys_user用户ID',
|
||||
tenant_id bigint(20) default null comment '租户ID',
|
||||
dept_id bigint(20) default null comment '所属门店',
|
||||
|
||||
staff_name varchar(50) not null comment '员工姓名',
|
||||
phone varchar(11) default null comment '手机号',
|
||||
position varchar(50) default '理发师' comment '职位',
|
||||
level char(1) default '1' comment '级别 1初级 2中级 3高级',
|
||||
|
||||
-- [P2] 业绩相关(预留)
|
||||
commission_rate decimal(5,2) default 30.00 comment '提成比例',
|
||||
base_salary decimal(10,2) default 0.00 comment '底薪',
|
||||
|
||||
status char(1) default '0' comment '状态 0在职 1离职',
|
||||
entry_date date default null comment '入职日期',
|
||||
|
||||
create_by varchar(64) default '',
|
||||
create_time datetime default null,
|
||||
update_by varchar(64) default '',
|
||||
update_time datetime default null,
|
||||
|
||||
primary key (staff_id),
|
||||
index idx_user (user_id),
|
||||
index idx_dept (dept_id)
|
||||
) engine=innodb auto_increment=100 comment = '员工扩展表';
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 5、业务订单表
|
||||
-- ----------------------------
|
||||
drop table if exists hl_order;
|
||||
create table hl_order (
|
||||
order_id bigint(20) not null auto_increment comment '订单ID',
|
||||
order_no varchar(32) not null comment '订单编号',
|
||||
tenant_id bigint(20) default null comment '租户ID',
|
||||
dept_id bigint(20) default null comment '门店ID',
|
||||
customer_id bigint(20) not null comment '顾客ID',
|
||||
|
||||
-- [P1] 基础交易
|
||||
pay_amount decimal(10,2) default 0.00 comment '实收/实扣金额',
|
||||
pay_method char(1) default '0' comment '支付方式 0现金 1微信 2卡扣次',
|
||||
pay_status char(1) default '1' comment '支付状态 1已付',
|
||||
|
||||
-- [P2] 复杂交易(预留)
|
||||
order_type char(1) default '0' comment '订单类型 0服务 1办卡 2零售',
|
||||
total_amount decimal(10,2) default 0.00 comment '原价总额',
|
||||
discount_amount decimal(10,2) default 0.00 comment '优惠金额',
|
||||
|
||||
-- [P3] 在线支付(预留)
|
||||
transaction_id varchar(64) default null comment '微信支付流水号',
|
||||
|
||||
create_by varchar(64) default '',
|
||||
create_time datetime default null,
|
||||
|
||||
primary key (order_id),
|
||||
unique key uk_order_no (order_no),
|
||||
index idx_tenant (tenant_id),
|
||||
index idx_customer (customer_id),
|
||||
index idx_create_time (create_time)
|
||||
) engine=innodb auto_increment=1000 comment = '业务订单表';
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 6、订单明细表
|
||||
-- ----------------------------
|
||||
drop table if exists hl_order_detail;
|
||||
create table hl_order_detail (
|
||||
detail_id bigint(20) not null auto_increment comment '明细ID',
|
||||
order_id bigint(20) not null comment '订单ID',
|
||||
|
||||
-- [P1] 核心内容
|
||||
item_id bigint(20) not null comment '项目ID',
|
||||
item_name varchar(100) not null comment '项目名称',
|
||||
price decimal(10,2) default 0.00 comment '单价',
|
||||
quantity int(11) default 1 comment '数量',
|
||||
staff_id bigint(20) default null comment '主理发师ID',
|
||||
deduct_card_id bigint(20) default null comment '关联扣次卡ID',
|
||||
|
||||
-- [P2] 类型扩展(预留)
|
||||
item_type char(1) default '0' comment '项目类型 0服务 1产品',
|
||||
is_stock_deducted char(1) default '0' comment '是否已扣库 0否 1是',
|
||||
|
||||
primary key (detail_id),
|
||||
index idx_order (order_id)
|
||||
) engine=innodb auto_increment=1000 comment = '订单明细表';
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 外键约束
|
||||
-- ----------------------------
|
||||
|
||||
-- 会员卡 -> 顾客
|
||||
ALTER TABLE hl_member_card
|
||||
ADD CONSTRAINT fk_card_customer
|
||||
FOREIGN KEY (customer_id) REFERENCES hl_customer(customer_id);
|
||||
|
||||
-- 订单 -> 顾客
|
||||
ALTER TABLE hl_order
|
||||
ADD CONSTRAINT fk_order_customer
|
||||
FOREIGN KEY (customer_id) REFERENCES hl_customer(customer_id);
|
||||
|
||||
-- 订单明细 -> 订单(级联删除)
|
||||
ALTER TABLE hl_order_detail
|
||||
ADD CONSTRAINT fk_detail_order
|
||||
FOREIGN KEY (order_id) REFERENCES hl_order(order_id) ON DELETE CASCADE;
|
||||
|
||||
-- 订单明细 -> 会员卡(可选)
|
||||
ALTER TABLE hl_order_detail
|
||||
ADD CONSTRAINT fk_detail_card
|
||||
FOREIGN KEY (deduct_card_id) REFERENCES hl_member_card(card_id);
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化-服务项目表数据
|
||||
-- ----------------------------
|
||||
INSERT INTO hl_service (service_name, service_code, price, duration, category, status, sort_order, create_by, create_time) VALUES
|
||||
('男士剪发', 'MAN_CUT', 38.00, 30, '基础服务', '0', 1, 'admin', sysdate()),
|
||||
('女士剪发', 'WOMAN_CUT', 58.00, 45, '基础服务', '0', 2, 'admin', sysdate()),
|
||||
('烫发', 'PERM', 288.00, 120, '造型服务', '0', 3, 'admin', sysdate()),
|
||||
('染发', 'DYE', 268.00, 120, '造型服务', '0', 4, 'admin', sysdate()),
|
||||
('洗剪吹', 'WASH_CUT_BLOW', 68.00, 60, '套餐服务', '0', 5, 'admin', sysdate());
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 测试数据-顾客档案
|
||||
-- ----------------------------
|
||||
INSERT INTO hl_customer (customer_name, phone, gender, member_level, balance, create_by, create_time, remark) VALUES
|
||||
('张三', '13800138001', '0', '0', 0.00, 'admin', sysdate(), '测试会员顾客1'),
|
||||
('李四', '13800138002', '1', '0', 0.00, 'admin', sysdate(), '测试会员顾客2'),
|
||||
('散客', NULL, '2', '0', 0.00, 'admin', sysdate(), '测试散客');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 测试数据-员工扩展表
|
||||
-- ----------------------------
|
||||
INSERT INTO hl_staff (user_id, staff_name, phone, position, level, commission_rate, status, entry_date, create_by, create_time) VALUES
|
||||
(1, '王师傅', '13900139001', '高级理发师', '3', 30.00, '0', '2024-01-01', 'admin', sysdate()),
|
||||
(2, '赵师傅', '13900139002', '理发师', '2', 30.00, '0', '2024-06-01', 'admin', sysdate());
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 测试数据-会员卡
|
||||
-- ----------------------------
|
||||
-- 为张三创建一张10次理发卡
|
||||
INSERT INTO hl_member_card (customer_id, card_name, card_type, total_count, remain_count, status, create_by, create_time) VALUES
|
||||
(100, '理发计次卡', '0', 10, 10, '0', 'admin', sysdate());
|
||||
|
||||
-- 为李四创建一张20次理发卡
|
||||
INSERT INTO hl_member_card (customer_id, card_name, card_type, total_count, remain_count, status, create_by, create_time) VALUES
|
||||
(101, '理发计次卡', '0', 20, 19, '0', 'admin', sysdate());
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 测试数据-订单及明细
|
||||
-- ----------------------------
|
||||
-- 订单1:散客现金支付剪发
|
||||
INSERT INTO hl_order (order_no, customer_id, pay_amount, pay_method, pay_status, order_type, total_amount, create_by, create_time) VALUES
|
||||
('20251219000001', 102, 38.00, '0', '1', '0', 38.00, 'admin', sysdate());
|
||||
|
||||
INSERT INTO hl_order_detail (order_id, item_id, item_name, price, quantity, staff_id, item_type) VALUES
|
||||
(1000, 100, '男士剪发', 38.00, 1, 100, '0');
|
||||
|
||||
|
||||
-- 订单2:李四会员卡扣次烫发
|
||||
INSERT INTO hl_order (order_no, customer_id, pay_amount, pay_method, pay_status, order_type, total_amount, create_by, create_time) VALUES
|
||||
('20251219000002', 101, 288.00, '2', '1', '0', 288.00, 'admin', sysdate());
|
||||
|
||||
INSERT INTO hl_order_detail (order_id, item_id, item_name, price, quantity, staff_id, deduct_card_id, item_type) VALUES
|
||||
(1001, 102, '烫发', 288.00, 1, 100, 101, '0');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化完成提示
|
||||
-- ----------------------------
|
||||
-- ============================
|
||||
-- HairLink Phase 1 数据库初始化完成!
|
||||
--
|
||||
-- 已创建表:
|
||||
-- 1. hl_customer (顾客档案表)
|
||||
-- 2. hl_member_card (会员权益卡表)
|
||||
-- 3. hl_service (服务项目表)
|
||||
-- 4. hl_staff (员工扩展表)
|
||||
-- 5. hl_order (业务订单表)
|
||||
-- 6. hl_order_detail (订单明细表)
|
||||
--
|
||||
-- 已添加外键约束:4个
|
||||
--
|
||||
-- 已初始化数据:
|
||||
-- - 服务项目:5条
|
||||
-- - 测试顾客:3条
|
||||
-- - 测试员工:2条
|
||||
-- - 测试会员卡:2条
|
||||
-- - 测试订单:2条(含订单明细)
|
||||
--
|
||||
-- 下一步:
|
||||
-- 1. 在MySQL中执行本脚本
|
||||
-- 2. 验证表结构和数据
|
||||
-- 3. 开始后端代码开发
|
||||
-- ============================
|
||||
|
|
@ -0,0 +1,792 @@
|
|||
-- ============================
|
||||
-- 若依框架基础表 + 多租户支持
|
||||
-- 版本:1.0.0_with_tenant
|
||||
-- 创建日期:2025-12-19
|
||||
-- 说明:在若依框架基础表创建时就包含多租户支持,无需后续ALTER TABLE
|
||||
-- 改动说明:
|
||||
-- 1. 新增 sys_tenant 租户管理表
|
||||
-- 2. 为 sys_user, sys_dept, sys_role, sys_post, sys_notice, sys_config 添加 tenant_id 字段
|
||||
-- 3. 为 tenant_id 字段创建索引
|
||||
-- 4. 初始化数据设置默认租户ID
|
||||
-- ============================
|
||||
|
||||
-- ----------------------------
|
||||
-- 0、租户信息表 (新增)
|
||||
-- ----------------------------
|
||||
drop table if exists sys_tenant;
|
||||
create table sys_tenant (
|
||||
tenant_id bigint(20) not null auto_increment comment '租户ID',
|
||||
tenant_name varchar(50) not null comment '租户名称',
|
||||
tenant_code varchar(50) not null comment '租户编码(唯一标识)',
|
||||
contact_name varchar(50) default null comment '联系人',
|
||||
contact_phone varchar(20) default null comment '联系电话',
|
||||
expire_time datetime default null comment '过期时间(NULL表示永久)',
|
||||
package_id bigint(20) default null comment '套餐ID(预留)',
|
||||
status char(1) default '0' comment '状态(0正常 1停用)',
|
||||
del_flag char(1) default '0' comment '删除标志(0存在 2删除)',
|
||||
create_by varchar(64) default '' comment '创建者',
|
||||
create_time datetime default null comment '创建时间',
|
||||
update_by varchar(64) default '' comment '更新者',
|
||||
update_time datetime default null comment '更新时间',
|
||||
remark varchar(500) default null comment '备注',
|
||||
primary key (tenant_id),
|
||||
unique key uk_tenant_code (tenant_code)
|
||||
) engine=innodb auto_increment=1000 comment = '租户信息表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化-租户信息表数据
|
||||
-- ----------------------------
|
||||
insert into sys_tenant (tenant_name, tenant_code, contact_name, contact_phone, status, create_by, create_time, remark) values
|
||||
('默认租户', 'DEFAULT', '系统管理员', '13800138000', '0', 'admin', sysdate(), '系统默认租户,现有数据归属此租户'),
|
||||
('测试租户A', 'TENANT_A', '张三', '13800138001', '0', 'admin', sysdate(), '测试租户A,用于多租户隔离测试'),
|
||||
('测试租户B', 'TENANT_B', '李四', '13800138002', '0', 'admin', sysdate(), '测试租户B,用于跨租户访问测试');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 1、部门表 (已添加 tenant_id)
|
||||
-- ----------------------------
|
||||
drop table if exists sys_dept;
|
||||
create table sys_dept (
|
||||
dept_id bigint(20) not null auto_increment comment '部门id',
|
||||
tenant_id bigint(20) default null comment '租户ID',
|
||||
parent_id bigint(20) default 0 comment '父部门id',
|
||||
ancestors varchar(50) default '' comment '祖级列表',
|
||||
dept_name varchar(30) default '' comment '部门名称',
|
||||
order_num int(4) default 0 comment '显示顺序',
|
||||
leader varchar(20) default null comment '负责人',
|
||||
phone varchar(11) default null comment '联系电话',
|
||||
email varchar(50) default null comment '邮箱',
|
||||
status char(1) default '0' comment '部门状态(0正常 1停用)',
|
||||
del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)',
|
||||
create_by varchar(64) default '' comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by varchar(64) default '' comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
primary key (dept_id),
|
||||
index idx_tenant_id (tenant_id)
|
||||
) engine=innodb auto_increment=200 comment = '部门表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化-部门表数据
|
||||
-- ----------------------------
|
||||
set @default_tenant_id = (select tenant_id from sys_tenant where tenant_code = 'DEFAULT');
|
||||
insert into sys_dept values(100, @default_tenant_id, 0, '0', '若依科技', 0, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
|
||||
insert into sys_dept values(101, @default_tenant_id, 100, '0,100', '深圳总公司', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
|
||||
insert into sys_dept values(102, @default_tenant_id, 100, '0,100', '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
|
||||
insert into sys_dept values(103, @default_tenant_id, 101, '0,100,101', '研发部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
|
||||
insert into sys_dept values(104, @default_tenant_id, 101, '0,100,101', '市场部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
|
||||
insert into sys_dept values(105, @default_tenant_id, 101, '0,100,101', '测试部门', 3, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
|
||||
insert into sys_dept values(106, @default_tenant_id, 101, '0,100,101', '财务部门', 4, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
|
||||
insert into sys_dept values(107, @default_tenant_id, 101, '0,100,101', '运维部门', 5, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
|
||||
insert into sys_dept values(108, @default_tenant_id, 102, '0,100,102', '市场部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
|
||||
insert into sys_dept values(109, @default_tenant_id, 102, '0,100,102', '财务部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 2、用户信息表 (已添加 tenant_id)
|
||||
-- ----------------------------
|
||||
drop table if exists sys_user;
|
||||
create table sys_user (
|
||||
user_id bigint(20) not null auto_increment comment '用户ID',
|
||||
tenant_id bigint(20) default null comment '租户ID',
|
||||
dept_id bigint(20) default null comment '部门ID',
|
||||
user_name varchar(30) not null comment '用户账号',
|
||||
nick_name varchar(30) not null comment '用户昵称',
|
||||
user_type varchar(2) default '00' comment '用户类型(00系统用户)',
|
||||
email varchar(50) default '' comment '用户邮箱',
|
||||
phonenumber varchar(11) default '' comment '手机号码',
|
||||
sex char(1) default '0' comment '用户性别(0男 1女 2未知)',
|
||||
avatar varchar(100) default '' comment '头像地址',
|
||||
password varchar(100) default '' comment '密码',
|
||||
status char(1) default '0' comment '账号状态(0正常 1停用)',
|
||||
del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)',
|
||||
login_ip varchar(128) default '' comment '最后登录IP',
|
||||
login_date datetime comment '最后登录时间',
|
||||
pwd_update_date datetime comment '密码最后更新时间',
|
||||
create_by varchar(64) default '' comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by varchar(64) default '' comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
remark varchar(500) default null comment '备注',
|
||||
primary key (user_id),
|
||||
index idx_tenant_id (tenant_id),
|
||||
index idx_tenant_user (tenant_id, user_name)
|
||||
) engine=innodb auto_increment=100 comment = '用户信息表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化-用户信息表数据
|
||||
-- ----------------------------
|
||||
insert into sys_user values(1, @default_tenant_id, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), sysdate(), 'admin', sysdate(), '', null, '管理员');
|
||||
insert into sys_user values(2, @default_tenant_id, 105, 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), sysdate(), 'admin', sysdate(), '', null, '测试员');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 3、岗位信息表 (已添加 tenant_id)
|
||||
-- ----------------------------
|
||||
drop table if exists sys_post;
|
||||
create table sys_post
|
||||
(
|
||||
post_id bigint(20) not null auto_increment comment '岗位ID',
|
||||
tenant_id bigint(20) default null comment '租户ID',
|
||||
post_code varchar(64) not null comment '岗位编码',
|
||||
post_name varchar(50) not null comment '岗位名称',
|
||||
post_sort int(4) not null comment '显示顺序',
|
||||
status char(1) not null comment '状态(0正常 1停用)',
|
||||
create_by varchar(64) default '' comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by varchar(64) default '' comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
remark varchar(500) default null comment '备注',
|
||||
primary key (post_id),
|
||||
index idx_tenant_id (tenant_id)
|
||||
) engine=innodb comment = '岗位信息表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化-岗位信息表数据
|
||||
-- ----------------------------
|
||||
insert into sys_post values(1, @default_tenant_id, 'ceo', '董事长', 1, '0', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_post values(2, @default_tenant_id, 'se', '项目经理', 2, '0', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_post values(3, @default_tenant_id, 'hr', '人力资源', 3, '0', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_post values(4, @default_tenant_id, 'user', '普通员工', 4, '0', 'admin', sysdate(), '', null, '');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 4、角色信息表 (已添加 tenant_id)
|
||||
-- ----------------------------
|
||||
drop table if exists sys_role;
|
||||
create table sys_role (
|
||||
role_id bigint(20) not null auto_increment comment '角色ID',
|
||||
tenant_id bigint(20) default null comment '租户ID',
|
||||
role_name varchar(30) not null comment '角色名称',
|
||||
role_key varchar(100) not null comment '角色权限字符串',
|
||||
role_sort int(4) not null comment '显示顺序',
|
||||
data_scope char(1) default '1' comment '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)',
|
||||
menu_check_strictly tinyint(1) default 1 comment '菜单树选择项是否关联显示',
|
||||
dept_check_strictly tinyint(1) default 1 comment '部门树选择项是否关联显示',
|
||||
status char(1) not null comment '角色状态(0正常 1停用)',
|
||||
del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)',
|
||||
create_by varchar(64) default '' comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by varchar(64) default '' comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
remark varchar(500) default null comment '备注',
|
||||
primary key (role_id),
|
||||
index idx_tenant_id (tenant_id)
|
||||
) engine=innodb auto_increment=100 comment = '角色信息表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化-角色信息表数据
|
||||
-- ----------------------------
|
||||
insert into sys_role values('1', @default_tenant_id, '超级管理员', 'admin', 1, 1, 1, 1, '0', '0', 'admin', sysdate(), '', null, '超级管理员');
|
||||
insert into sys_role values('2', @default_tenant_id, '普通角色', 'common', 2, 2, 1, 1, '0', '0', 'admin', sysdate(), '', null, '普通角色');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 5、菜单权限表
|
||||
-- ----------------------------
|
||||
drop table if exists sys_menu;
|
||||
create table sys_menu (
|
||||
menu_id bigint(20) not null auto_increment comment '菜单ID',
|
||||
menu_name varchar(50) not null comment '菜单名称',
|
||||
parent_id bigint(20) default 0 comment '父菜单ID',
|
||||
order_num int(4) default 0 comment '显示顺序',
|
||||
path varchar(200) default '' comment '路由地址',
|
||||
component varchar(255) default null comment '组件路径',
|
||||
query varchar(255) default null comment '路由参数',
|
||||
route_name varchar(50) default '' comment '路由名称',
|
||||
is_frame int(1) default 1 comment '是否为外链(0是 1否)',
|
||||
is_cache int(1) default 0 comment '是否缓存(0缓存 1不缓存)',
|
||||
menu_type char(1) default '' comment '菜单类型(M目录 C菜单 F按钮)',
|
||||
visible char(1) default 0 comment '菜单状态(0显示 1隐藏)',
|
||||
status char(1) default 0 comment '菜单状态(0正常 1停用)',
|
||||
perms varchar(100) default null comment '权限标识',
|
||||
icon varchar(100) default '#' comment '菜单图标',
|
||||
create_by varchar(64) default '' comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by varchar(64) default '' comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
remark varchar(500) default '' comment '备注',
|
||||
primary key (menu_id)
|
||||
) engine=innodb auto_increment=2000 comment = '菜单权限表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化-菜单信息表数据
|
||||
-- ----------------------------
|
||||
-- 一级菜单
|
||||
insert into sys_menu values('1', '系统管理', '0', '1', 'system', null, '', '', 1, 0, 'M', '0', '0', '', 'system', 'admin', sysdate(), '', null, '系统管理目录');
|
||||
insert into sys_menu values('2', '系统监控', '0', '2', 'monitor', null, '', '', 1, 0, 'M', '0', '0', '', 'monitor', 'admin', sysdate(), '', null, '系统监控目录');
|
||||
insert into sys_menu values('3', '系统工具', '0', '3', 'tool', null, '', '', 1, 0, 'M', '0', '0', '', 'tool', 'admin', sysdate(), '', null, '系统工具目录');
|
||||
insert into sys_menu values('4', '若依官网', '0', '4', 'http://ruoyi.vip', null, '', '', 0, 0, 'M', '0', '0', '', 'guide', 'admin', sysdate(), '', null, '若依官网地址');
|
||||
-- 二级菜单
|
||||
insert into sys_menu values('100', '用户管理', '1', '1', 'user', 'system/user/index', '', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', sysdate(), '', null, '用户管理菜单');
|
||||
insert into sys_menu values('101', '角色管理', '1', '2', 'role', 'system/role/index', '', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', sysdate(), '', null, '角色管理菜单');
|
||||
insert into sys_menu values('102', '菜单管理', '1', '3', 'menu', 'system/menu/index', '', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', sysdate(), '', null, '菜单管理菜单');
|
||||
insert into sys_menu values('103', '部门管理', '1', '4', 'dept', 'system/dept/index', '', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', sysdate(), '', null, '部门管理菜单');
|
||||
insert into sys_menu values('104', '岗位管理', '1', '5', 'post', 'system/post/index', '', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', sysdate(), '', null, '岗位管理菜单');
|
||||
insert into sys_menu values('105', '字典管理', '1', '6', 'dict', 'system/dict/index', '', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', sysdate(), '', null, '字典管理菜单');
|
||||
insert into sys_menu values('106', '参数设置', '1', '7', 'config', 'system/config/index', '', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', sysdate(), '', null, '参数设置菜单');
|
||||
insert into sys_menu values('107', '通知公告', '1', '8', 'notice', 'system/notice/index', '', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', sysdate(), '', null, '通知公告菜单');
|
||||
insert into sys_menu values('108', '日志管理', '1', '9', 'log', '', '', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', sysdate(), '', null, '日志管理菜单');
|
||||
insert into sys_menu values('109', '在线用户', '2', '1', 'online', 'monitor/online/index', '', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', sysdate(), '', null, '在线用户菜单');
|
||||
insert into sys_menu values('110', '定时任务', '2', '2', 'job', 'monitor/job/index', '', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', sysdate(), '', null, '定时任务菜单');
|
||||
insert into sys_menu values('111', '数据监控', '2', '3', 'druid', 'monitor/druid/index', '', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', sysdate(), '', null, '数据监控菜单');
|
||||
insert into sys_menu values('112', '服务监控', '2', '4', 'server', 'monitor/server/index', '', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', sysdate(), '', null, '服务监控菜单');
|
||||
insert into sys_menu values('113', '缓存监控', '2', '5', 'cache', 'monitor/cache/index', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', sysdate(), '', null, '缓存监控菜单');
|
||||
insert into sys_menu values('114', '缓存列表', '2', '6', 'cacheList', 'monitor/cache/list', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', 'admin', sysdate(), '', null, '缓存列表菜单');
|
||||
insert into sys_menu values('115', '表单构建', '3', '1', 'build', 'tool/build/index', '', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', sysdate(), '', null, '表单构建菜单');
|
||||
insert into sys_menu values('116', '代码生成', '3', '2', 'gen', 'tool/gen/index', '', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', sysdate(), '', null, '代码生成菜单');
|
||||
insert into sys_menu values('117', '系统接口', '3', '3', 'swagger', 'tool/swagger/index', '', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', sysdate(), '', null, '系统接口菜单');
|
||||
-- 三级菜单
|
||||
insert into sys_menu values('500', '操作日志', '108', '1', 'operlog', 'monitor/operlog/index', '', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', sysdate(), '', null, '操作日志菜单');
|
||||
insert into sys_menu values('501', '登录日志', '108', '2', 'logininfor', 'monitor/logininfor/index', '', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', sysdate(), '', null, '登录日志菜单');
|
||||
-- 用户管理按钮
|
||||
insert into sys_menu values('1000', '用户查询', '100', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1001', '用户新增', '100', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1002', '用户修改', '100', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1003', '用户删除', '100', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1004', '用户导出', '100', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1005', '用户导入', '100', '6', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1006', '重置密码', '100', '7', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', sysdate(), '', null, '');
|
||||
-- 角色管理按钮
|
||||
insert into sys_menu values('1007', '角色查询', '101', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1008', '角色新增', '101', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1009', '角色修改', '101', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1010', '角色删除', '101', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1011', '角色导出', '101', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', sysdate(), '', null, '');
|
||||
-- 菜单管理按钮
|
||||
insert into sys_menu values('1012', '菜单查询', '102', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1013', '菜单新增', '102', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1014', '菜单修改', '102', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1015', '菜单删除', '102', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', sysdate(), '', null, '');
|
||||
-- 部门管理按钮
|
||||
insert into sys_menu values('1016', '部门查询', '103', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1017', '部门新增', '103', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1018', '部门修改', '103', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1019', '部门删除', '103', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', sysdate(), '', null, '');
|
||||
-- 岗位管理按钮
|
||||
insert into sys_menu values('1020', '岗位查询', '104', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1021', '岗位新增', '104', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1022', '岗位修改', '104', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1023', '岗位删除', '104', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1024', '岗位导出', '104', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', sysdate(), '', null, '');
|
||||
-- 字典管理按钮
|
||||
insert into sys_menu values('1025', '字典查询', '105', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1026', '字典新增', '105', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1027', '字典修改', '105', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1028', '字典删除', '105', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1029', '字典导出', '105', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', sysdate(), '', null, '');
|
||||
-- 参数设置按钮
|
||||
insert into sys_menu values('1030', '参数查询', '106', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1031', '参数新增', '106', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1032', '参数修改', '106', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1033', '参数删除', '106', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1034', '参数导出', '106', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', sysdate(), '', null, '');
|
||||
-- 通知公告按钮
|
||||
insert into sys_menu values('1035', '公告查询', '107', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1036', '公告新增', '107', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1037', '公告修改', '107', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1038', '公告删除', '107', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', sysdate(), '', null, '');
|
||||
-- 操作日志按钮
|
||||
insert into sys_menu values('1039', '操作查询', '500', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1040', '操作删除', '500', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1041', '日志导出', '500', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', sysdate(), '', null, '');
|
||||
-- 登录日志按钮
|
||||
insert into sys_menu values('1042', '登录查询', '501', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1043', '登录删除', '501', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1044', '日志导出', '501', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1045', '账户解锁', '501', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 'admin', sysdate(), '', null, '');
|
||||
-- 在线用户按钮
|
||||
insert into sys_menu values('1046', '在线查询', '109', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1047', '批量强退', '109', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1048', '单条强退', '109', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', sysdate(), '', null, '');
|
||||
-- 定时任务按钮
|
||||
insert into sys_menu values('1049', '任务查询', '110', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1050', '任务新增', '110', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1051', '任务修改', '110', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1052', '任务删除', '110', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1053', '状态修改', '110', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1054', '任务导出', '110', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', sysdate(), '', null, '');
|
||||
-- 代码生成按钮
|
||||
insert into sys_menu values('1055', '生成查询', '116', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1056', '生成修改', '116', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1057', '生成删除', '116', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1058', '导入代码', '116', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1059', '预览代码', '116', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_menu values('1060', '生成代码', '116', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', sysdate(), '', null, '');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 6、用户和角色关联表 用户N-1角色
|
||||
-- ----------------------------
|
||||
drop table if exists sys_user_role;
|
||||
create table sys_user_role (
|
||||
user_id bigint(20) not null comment '用户ID',
|
||||
role_id bigint(20) not null comment '角色ID',
|
||||
primary key(user_id, role_id)
|
||||
) engine=innodb comment = '用户和角色关联表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化-用户和角色关联表数据
|
||||
-- ----------------------------
|
||||
insert into sys_user_role values ('1', '1');
|
||||
insert into sys_user_role values ('2', '2');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 7、角色和菜单关联表 角色1-N菜单
|
||||
-- ----------------------------
|
||||
drop table if exists sys_role_menu;
|
||||
create table sys_role_menu (
|
||||
role_id bigint(20) not null comment '角色ID',
|
||||
menu_id bigint(20) not null comment '菜单ID',
|
||||
primary key(role_id, menu_id)
|
||||
) engine=innodb comment = '角色和菜单关联表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化-角色和菜单关联表数据
|
||||
-- ----------------------------
|
||||
insert into sys_role_menu values ('2', '1');
|
||||
insert into sys_role_menu values ('2', '2');
|
||||
insert into sys_role_menu values ('2', '3');
|
||||
insert into sys_role_menu values ('2', '4');
|
||||
insert into sys_role_menu values ('2', '100');
|
||||
insert into sys_role_menu values ('2', '101');
|
||||
insert into sys_role_menu values ('2', '102');
|
||||
insert into sys_role_menu values ('2', '103');
|
||||
insert into sys_role_menu values ('2', '104');
|
||||
insert into sys_role_menu values ('2', '105');
|
||||
insert into sys_role_menu values ('2', '106');
|
||||
insert into sys_role_menu values ('2', '107');
|
||||
insert into sys_role_menu values ('2', '108');
|
||||
insert into sys_role_menu values ('2', '109');
|
||||
insert into sys_role_menu values ('2', '110');
|
||||
insert into sys_role_menu values ('2', '111');
|
||||
insert into sys_role_menu values ('2', '112');
|
||||
insert into sys_role_menu values ('2', '113');
|
||||
insert into sys_role_menu values ('2', '114');
|
||||
insert into sys_role_menu values ('2', '115');
|
||||
insert into sys_role_menu values ('2', '116');
|
||||
insert into sys_role_menu values ('2', '117');
|
||||
insert into sys_role_menu values ('2', '500');
|
||||
insert into sys_role_menu values ('2', '501');
|
||||
insert into sys_role_menu values ('2', '1000');
|
||||
insert into sys_role_menu values ('2', '1001');
|
||||
insert into sys_role_menu values ('2', '1002');
|
||||
insert into sys_role_menu values ('2', '1003');
|
||||
insert into sys_role_menu values ('2', '1004');
|
||||
insert into sys_role_menu values ('2', '1005');
|
||||
insert into sys_role_menu values ('2', '1006');
|
||||
insert into sys_role_menu values ('2', '1007');
|
||||
insert into sys_role_menu values ('2', '1008');
|
||||
insert into sys_role_menu values ('2', '1009');
|
||||
insert into sys_role_menu values ('2', '1010');
|
||||
insert into sys_role_menu values ('2', '1011');
|
||||
insert into sys_role_menu values ('2', '1012');
|
||||
insert into sys_role_menu values ('2', '1013');
|
||||
insert into sys_role_menu values ('2', '1014');
|
||||
insert into sys_role_menu values ('2', '1015');
|
||||
insert into sys_role_menu values ('2', '1016');
|
||||
insert into sys_role_menu values ('2', '1017');
|
||||
insert into sys_role_menu values ('2', '1018');
|
||||
insert into sys_role_menu values ('2', '1019');
|
||||
insert into sys_role_menu values ('2', '1020');
|
||||
insert into sys_role_menu values ('2', '1021');
|
||||
insert into sys_role_menu values ('2', '1022');
|
||||
insert into sys_role_menu values ('2', '1023');
|
||||
insert into sys_role_menu values ('2', '1024');
|
||||
insert into sys_role_menu values ('2', '1025');
|
||||
insert into sys_role_menu values ('2', '1026');
|
||||
insert into sys_role_menu values ('2', '1027');
|
||||
insert into sys_role_menu values ('2', '1028');
|
||||
insert into sys_role_menu values ('2', '1029');
|
||||
insert into sys_role_menu values ('2', '1030');
|
||||
insert into sys_role_menu values ('2', '1031');
|
||||
insert into sys_role_menu values ('2', '1032');
|
||||
insert into sys_role_menu values ('2', '1033');
|
||||
insert into sys_role_menu values ('2', '1034');
|
||||
insert into sys_role_menu values ('2', '1035');
|
||||
insert into sys_role_menu values ('2', '1036');
|
||||
insert into sys_role_menu values ('2', '1037');
|
||||
insert into sys_role_menu values ('2', '1038');
|
||||
insert into sys_role_menu values ('2', '1039');
|
||||
insert into sys_role_menu values ('2', '1040');
|
||||
insert into sys_role_menu values ('2', '1041');
|
||||
insert into sys_role_menu values ('2', '1042');
|
||||
insert into sys_role_menu values ('2', '1043');
|
||||
insert into sys_role_menu values ('2', '1044');
|
||||
insert into sys_role_menu values ('2', '1045');
|
||||
insert into sys_role_menu values ('2', '1046');
|
||||
insert into sys_role_menu values ('2', '1047');
|
||||
insert into sys_role_menu values ('2', '1048');
|
||||
insert into sys_role_menu values ('2', '1049');
|
||||
insert into sys_role_menu values ('2', '1050');
|
||||
insert into sys_role_menu values ('2', '1051');
|
||||
insert into sys_role_menu values ('2', '1052');
|
||||
insert into sys_role_menu values ('2', '1053');
|
||||
insert into sys_role_menu values ('2', '1054');
|
||||
insert into sys_role_menu values ('2', '1055');
|
||||
insert into sys_role_menu values ('2', '1056');
|
||||
insert into sys_role_menu values ('2', '1057');
|
||||
insert into sys_role_menu values ('2', '1058');
|
||||
insert into sys_role_menu values ('2', '1059');
|
||||
insert into sys_role_menu values ('2', '1060');
|
||||
|
||||
-- ----------------------------
|
||||
-- 8、角色和部门关联表 角色1-N部门
|
||||
-- ----------------------------
|
||||
drop table if exists sys_role_dept;
|
||||
create table sys_role_dept (
|
||||
role_id bigint(20) not null comment '角色ID',
|
||||
dept_id bigint(20) not null comment '部门ID',
|
||||
primary key(role_id, dept_id)
|
||||
) engine=innodb comment = '角色和部门关联表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化-角色和部门关联表数据
|
||||
-- ----------------------------
|
||||
insert into sys_role_dept values ('2', '100');
|
||||
insert into sys_role_dept values ('2', '101');
|
||||
insert into sys_role_dept values ('2', '105');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 9、用户与岗位关联表 用户1-N岗位
|
||||
-- ----------------------------
|
||||
drop table if exists sys_user_post;
|
||||
create table sys_user_post
|
||||
(
|
||||
user_id bigint(20) not null comment '用户ID',
|
||||
post_id bigint(20) not null comment '岗位ID',
|
||||
primary key (user_id, post_id)
|
||||
) engine=innodb comment = '用户与岗位关联表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化-用户与岗位关联表数据
|
||||
-- ----------------------------
|
||||
insert into sys_user_post values ('1', '1');
|
||||
insert into sys_user_post values ('2', '2');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 10、操作日志记录
|
||||
-- ----------------------------
|
||||
drop table if exists sys_oper_log;
|
||||
create table sys_oper_log (
|
||||
oper_id bigint(20) not null auto_increment comment '日志主键',
|
||||
title varchar(50) default '' comment '模块标题',
|
||||
business_type int(2) default 0 comment '业务类型(0其它 1新增 2修改 3删除)',
|
||||
method varchar(200) default '' comment '方法名称',
|
||||
request_method varchar(10) default '' comment '请求方式',
|
||||
operator_type int(1) default 0 comment '操作类别(0其它 1后台用户 2手机端用户)',
|
||||
oper_name varchar(50) default '' comment '操作人员',
|
||||
dept_name varchar(50) default '' comment '部门名称',
|
||||
oper_url varchar(255) default '' comment '请求URL',
|
||||
oper_ip varchar(128) default '' comment '主机地址',
|
||||
oper_location varchar(255) default '' comment '操作地点',
|
||||
oper_param varchar(2000) default '' comment '请求参数',
|
||||
json_result varchar(2000) default '' comment '返回参数',
|
||||
status int(1) default 0 comment '操作状态(0正常 1异常)',
|
||||
error_msg varchar(2000) default '' comment '错误消息',
|
||||
oper_time datetime comment '操作时间',
|
||||
cost_time bigint(20) default 0 comment '消耗时间',
|
||||
primary key (oper_id),
|
||||
key idx_sys_oper_log_bt (business_type),
|
||||
key idx_sys_oper_log_s (status),
|
||||
key idx_sys_oper_log_ot (oper_time)
|
||||
) engine=innodb auto_increment=100 comment = '操作日志记录';
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 11、字典类型表
|
||||
-- ----------------------------
|
||||
drop table if exists sys_dict_type;
|
||||
create table sys_dict_type
|
||||
(
|
||||
dict_id bigint(20) not null auto_increment comment '字典主键',
|
||||
dict_name varchar(100) default '' comment '字典名称',
|
||||
dict_type varchar(100) default '' comment '字典类型',
|
||||
status char(1) default '0' comment '状态(0正常 1停用)',
|
||||
create_by varchar(64) default '' comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by varchar(64) default '' comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
remark varchar(500) default null comment '备注',
|
||||
primary key (dict_id),
|
||||
unique (dict_type)
|
||||
) engine=innodb auto_increment=100 comment = '字典类型表';
|
||||
|
||||
insert into sys_dict_type values(1, '用户性别', 'sys_user_sex', '0', 'admin', sysdate(), '', null, '用户性别列表');
|
||||
insert into sys_dict_type values(2, '菜单状态', 'sys_show_hide', '0', 'admin', sysdate(), '', null, '菜单状态列表');
|
||||
insert into sys_dict_type values(3, '系统开关', 'sys_normal_disable', '0', 'admin', sysdate(), '', null, '系统开关列表');
|
||||
insert into sys_dict_type values(4, '任务状态', 'sys_job_status', '0', 'admin', sysdate(), '', null, '任务状态列表');
|
||||
insert into sys_dict_type values(5, '任务分组', 'sys_job_group', '0', 'admin', sysdate(), '', null, '任务分组列表');
|
||||
insert into sys_dict_type values(6, '系统是否', 'sys_yes_no', '0', 'admin', sysdate(), '', null, '系统是否列表');
|
||||
insert into sys_dict_type values(7, '通知类型', 'sys_notice_type', '0', 'admin', sysdate(), '', null, '通知类型列表');
|
||||
insert into sys_dict_type values(8, '通知状态', 'sys_notice_status', '0', 'admin', sysdate(), '', null, '通知状态列表');
|
||||
insert into sys_dict_type values(9, '操作类型', 'sys_oper_type', '0', 'admin', sysdate(), '', null, '操作类型列表');
|
||||
insert into sys_dict_type values(10, '系统状态', 'sys_common_status', '0', 'admin', sysdate(), '', null, '登录状态列表');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 12、字典数据表
|
||||
-- ----------------------------
|
||||
drop table if exists sys_dict_data;
|
||||
create table sys_dict_data
|
||||
(
|
||||
dict_code bigint(20) not null auto_increment comment '字典编码',
|
||||
dict_sort int(4) default 0 comment '字典排序',
|
||||
dict_label varchar(100) default '' comment '字典标签',
|
||||
dict_value varchar(100) default '' comment '字典键值',
|
||||
dict_type varchar(100) default '' comment '字典类型',
|
||||
css_class varchar(100) default null comment '样式属性(其他样式扩展)',
|
||||
list_class varchar(100) default null comment '表格回显样式',
|
||||
is_default char(1) default 'N' comment '是否默认(Y是 N否)',
|
||||
status char(1) default '0' comment '状态(0正常 1停用)',
|
||||
create_by varchar(64) default '' comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by varchar(64) default '' comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
remark varchar(500) default null comment '备注',
|
||||
primary key (dict_code)
|
||||
) engine=innodb auto_increment=100 comment = '字典数据表';
|
||||
|
||||
insert into sys_dict_data values(1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', sysdate(), '', null, '性别男');
|
||||
insert into sys_dict_data values(2, 2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别女');
|
||||
insert into sys_dict_data values(3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别未知');
|
||||
insert into sys_dict_data values(4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '显示菜单');
|
||||
insert into sys_dict_data values(5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '隐藏菜单');
|
||||
insert into sys_dict_data values(6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态');
|
||||
insert into sys_dict_data values(7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态');
|
||||
insert into sys_dict_data values(8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态');
|
||||
insert into sys_dict_data values(9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态');
|
||||
insert into sys_dict_data values(10, 1, '默认', 'DEFAULT', 'sys_job_group', '', '', 'Y', '0', 'admin', sysdate(), '', null, '默认分组');
|
||||
insert into sys_dict_data values(11, 2, '系统', 'SYSTEM', 'sys_job_group', '', '', 'N', '0', 'admin', sysdate(), '', null, '系统分组');
|
||||
insert into sys_dict_data values(12, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '系统默认是');
|
||||
insert into sys_dict_data values(13, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '系统默认否');
|
||||
insert into sys_dict_data values(14, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', sysdate(), '', null, '通知');
|
||||
insert into sys_dict_data values(15, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '公告');
|
||||
insert into sys_dict_data values(16, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态');
|
||||
insert into sys_dict_data values(17, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '关闭状态');
|
||||
insert into sys_dict_data values(18, 99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '其他操作');
|
||||
insert into sys_dict_data values(19, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '新增操作');
|
||||
insert into sys_dict_data values(20, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '修改操作');
|
||||
insert into sys_dict_data values(21, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '删除操作');
|
||||
insert into sys_dict_data values(22, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '授权操作');
|
||||
insert into sys_dict_data values(23, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导出操作');
|
||||
insert into sys_dict_data values(24, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导入操作');
|
||||
insert into sys_dict_data values(25, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '强退操作');
|
||||
insert into sys_dict_data values(26, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '生成操作');
|
||||
insert into sys_dict_data values(27, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '清空操作');
|
||||
insert into sys_dict_data values(28, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '正常状态');
|
||||
insert into sys_dict_data values(29, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 13、参数配置表 (已添加 tenant_id)
|
||||
-- ----------------------------
|
||||
drop table if exists sys_config;
|
||||
create table sys_config (
|
||||
config_id int(5) not null auto_increment comment '参数主键',
|
||||
tenant_id bigint(20) default null comment '租户ID(NULL表示全局配置)',
|
||||
config_name varchar(100) default '' comment '参数名称',
|
||||
config_key varchar(100) default '' comment '参数键名',
|
||||
config_value varchar(500) default '' comment '参数键值',
|
||||
config_type char(1) default 'N' comment '系统内置(Y是 N否)',
|
||||
create_by varchar(64) default '' comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by varchar(64) default '' comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
remark varchar(500) default null comment '备注',
|
||||
primary key (config_id),
|
||||
index idx_tenant_id (tenant_id)
|
||||
) engine=innodb auto_increment=100 comment = '参数配置表';
|
||||
|
||||
-- 注意:sys_config 的初始化数据 tenant_id 保持 NULL,表示全局配置
|
||||
insert into sys_config values(1, NULL, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'admin', sysdate(), '', null, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow' );
|
||||
insert into sys_config values(2, NULL, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', sysdate(), '', null, '初始化密码 123456' );
|
||||
insert into sys_config values(3, NULL, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', sysdate(), '', null, '深色主题theme-dark,浅色主题theme-light' );
|
||||
insert into sys_config values(4, NULL, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', sysdate(), '', null, '是否开启验证码功能(true开启,false关闭)');
|
||||
insert into sys_config values(5, NULL, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', sysdate(), '', null, '是否开启注册用户功能(true开启,false关闭)');
|
||||
insert into sys_config values(6, NULL, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', sysdate(), '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)');
|
||||
insert into sys_config values(7, NULL, '用户管理-初始密码修改策略', 'sys.account.initPasswordModify', '1', 'Y', 'admin', sysdate(), '', null, '0:初始密码修改策略关闭,没有任何提示,1:提醒用户,如果未修改初始密码,则在登录时就会提醒修改密码对话框');
|
||||
insert into sys_config values(8, NULL, '用户管理-账号密码更新周期', 'sys.account.passwordValidateDays', '0', 'Y', 'admin', sysdate(), '', null, '密码更新周期(填写数字,数据初始化值为0不限制,若修改必须为大于0小于365的正整数),如果超过这个周期登录系统时,则在登录时就会提醒修改密码对话框');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 14、系统访问记录
|
||||
-- ----------------------------
|
||||
drop table if exists sys_logininfor;
|
||||
create table sys_logininfor (
|
||||
info_id bigint(20) not null auto_increment comment '访问ID',
|
||||
user_name varchar(50) default '' comment '用户账号',
|
||||
ipaddr varchar(128) default '' comment '登录IP地址',
|
||||
login_location varchar(255) default '' comment '登录地点',
|
||||
browser varchar(50) default '' comment '浏览器类型',
|
||||
os varchar(50) default '' comment '操作系统',
|
||||
status char(1) default '0' comment '登录状态(0成功 1失败)',
|
||||
msg varchar(255) default '' comment '提示消息',
|
||||
login_time datetime comment '访问时间',
|
||||
primary key (info_id),
|
||||
key idx_sys_logininfor_s (status),
|
||||
key idx_sys_logininfor_lt (login_time)
|
||||
) engine=innodb auto_increment=100 comment = '系统访问记录';
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 15、定时任务调度表
|
||||
-- ----------------------------
|
||||
drop table if exists sys_job;
|
||||
create table sys_job (
|
||||
job_id bigint(20) not null auto_increment comment '任务ID',
|
||||
job_name varchar(64) default '' comment '任务名称',
|
||||
job_group varchar(64) default 'DEFAULT' comment '任务组名',
|
||||
invoke_target varchar(500) not null comment '调用目标字符串',
|
||||
cron_expression varchar(255) default '' comment 'cron执行表达式',
|
||||
misfire_policy varchar(20) default '3' comment '计划执行错误策略(1立即执行 2执行一次 3放弃执行)',
|
||||
concurrent char(1) default '1' comment '是否并发执行(0允许 1禁止)',
|
||||
status char(1) default '0' comment '状态(0正常 1暂停)',
|
||||
create_by varchar(64) default '' comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by varchar(64) default '' comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
remark varchar(500) default '' comment '备注信息',
|
||||
primary key (job_id, job_name, job_group)
|
||||
) engine=innodb auto_increment=100 comment = '定时任务调度表';
|
||||
|
||||
insert into sys_job values(1, '系统默认(无参)', 'DEFAULT', 'ryTask.ryNoParams', '0/10 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_job values(2, '系统默认(有参)', 'DEFAULT', 'ryTask.ryParams(\'ry\')', '0/15 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, '');
|
||||
insert into sys_job values(3, '系统默认(多参)', 'DEFAULT', 'ryTask.ryMultipleParams(\'ry\', true, 2000L, 316.50D, 100)', '0/20 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, '');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 16、定时任务调度日志表
|
||||
-- ----------------------------
|
||||
drop table if exists sys_job_log;
|
||||
create table sys_job_log (
|
||||
job_log_id bigint(20) not null auto_increment comment '任务日志ID',
|
||||
job_name varchar(64) not null comment '任务名称',
|
||||
job_group varchar(64) not null comment '任务组名',
|
||||
invoke_target varchar(500) not null comment '调用目标字符串',
|
||||
job_message varchar(500) comment '日志信息',
|
||||
status char(1) default '0' comment '执行状态(0正常 1失败)',
|
||||
exception_info varchar(2000) default '' comment '异常信息',
|
||||
create_time datetime comment '创建时间',
|
||||
primary key (job_log_id)
|
||||
) engine=innodb comment = '定时任务调度日志表';
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 17、通知公告表 (已添加 tenant_id)
|
||||
-- ----------------------------
|
||||
drop table if exists sys_notice;
|
||||
create table sys_notice (
|
||||
notice_id int(4) not null auto_increment comment '公告ID',
|
||||
tenant_id bigint(20) default null comment '租户ID',
|
||||
notice_title varchar(50) not null comment '公告标题',
|
||||
notice_type char(1) not null comment '公告类型(1通知 2公告)',
|
||||
notice_content longblob default null comment '公告内容',
|
||||
status char(1) default '0' comment '公告状态(0正常 1关闭)',
|
||||
create_by varchar(64) default '' comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by varchar(64) default '' comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
remark varchar(255) default null comment '备注',
|
||||
primary key (notice_id),
|
||||
index idx_tenant_id (tenant_id)
|
||||
) engine=innodb auto_increment=10 comment = '通知公告表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 初始化-公告信息表数据
|
||||
-- ----------------------------
|
||||
insert into sys_notice values('1', @default_tenant_id, '温馨提醒:2018-07-01 若依新版本发布啦', '2', '新版本内容', '0', 'admin', sysdate(), '', null, '管理员');
|
||||
insert into sys_notice values('2', @default_tenant_id, '维护通知:2018-07-01 若依系统凌晨维护', '1', '维护内容', '0', 'admin', sysdate(), '', null, '管理员');
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 18、代码生成业务表
|
||||
-- ----------------------------
|
||||
drop table if exists gen_table;
|
||||
create table gen_table (
|
||||
table_id bigint(20) not null auto_increment comment '编号',
|
||||
table_name varchar(200) default '' comment '表名称',
|
||||
table_comment varchar(500) default '' comment '表描述',
|
||||
sub_table_name varchar(64) default null comment '关联子表的表名',
|
||||
sub_table_fk_name varchar(64) default null comment '子表关联的外键名',
|
||||
class_name varchar(100) default '' comment '实体类名称',
|
||||
tpl_category varchar(200) default 'crud' comment '使用的模板(crud单表操作 tree树表操作)',
|
||||
tpl_web_type varchar(30) default '' comment '前端模板类型(element-ui模版 element-plus模版)',
|
||||
package_name varchar(100) comment '生成包路径',
|
||||
module_name varchar(30) comment '生成模块名',
|
||||
business_name varchar(30) comment '生成业务名',
|
||||
function_name varchar(50) comment '生成功能名',
|
||||
function_author varchar(50) comment '生成功能作者',
|
||||
gen_type char(1) default '0' comment '生成代码方式(0zip压缩包 1自定义路径)',
|
||||
gen_path varchar(200) default '/' comment '生成路径(不填默认项目路径)',
|
||||
options varchar(1000) comment '其它生成选项',
|
||||
create_by varchar(64) default '' comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by varchar(64) default '' comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
remark varchar(500) default null comment '备注',
|
||||
primary key (table_id)
|
||||
) engine=innodb auto_increment=1 comment = '代码生成业务表';
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 19、代码生成业务表字段
|
||||
-- ----------------------------
|
||||
drop table if exists gen_table_column;
|
||||
create table gen_table_column (
|
||||
column_id bigint(20) not null auto_increment comment '编号',
|
||||
table_id bigint(20) comment '归属表编号',
|
||||
column_name varchar(200) comment '列名称',
|
||||
column_comment varchar(500) comment '列描述',
|
||||
column_type varchar(100) comment '列类型',
|
||||
java_type varchar(500) comment 'JAVA类型',
|
||||
java_field varchar(200) comment 'JAVA字段名',
|
||||
is_pk char(1) comment '是否主键(1是)',
|
||||
is_increment char(1) comment '是否自增(1是)',
|
||||
is_required char(1) comment '是否必填(1是)',
|
||||
is_insert char(1) comment '是否为插入字段(1是)',
|
||||
is_edit char(1) comment '是否编辑字段(1是)',
|
||||
is_list char(1) comment '是否列表字段(1是)',
|
||||
is_query char(1) comment '是否查询字段(1是)',
|
||||
query_type varchar(200) default 'EQ' comment '查询方式(等于、不等于、大于、小于、范围)',
|
||||
html_type varchar(200) comment '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)',
|
||||
dict_type varchar(200) default '' comment '字典类型',
|
||||
sort int comment '排序',
|
||||
create_by varchar(64) default '' comment '创建者',
|
||||
create_time datetime comment '创建时间',
|
||||
update_by varchar(64) default '' comment '更新者',
|
||||
update_time datetime comment '更新时间',
|
||||
primary key (column_id)
|
||||
) engine=innodb auto_increment=1 comment = '代码生成业务表字段';
|
||||
|
||||
|
||||
-- ============================
|
||||
-- 初始化完成提示
|
||||
-- ============================
|
||||
-- ============================
|
||||
-- 若依框架 + 多租户支持 初始化完成!
|
||||
--
|
||||
-- 已完成改造:
|
||||
-- 1. 新增 sys_tenant 租户管理表
|
||||
-- 2. 为 6 张表添加 tenant_id 字段:
|
||||
-- - sys_user (用户表)
|
||||
-- - sys_dept (部门表)
|
||||
-- - sys_role (角色表)
|
||||
-- - sys_post (岗位表)
|
||||
-- - sys_notice (通知公告表)
|
||||
-- - sys_config (参数配置表,NULL表示全局配置)
|
||||
-- 3. 创建必要的索引:
|
||||
-- - sys_user: idx_tenant_id, idx_tenant_user(tenant_id, user_name)
|
||||
-- - 其他表: idx_tenant_id
|
||||
-- 4. 初始化数据已设置默认租户ID
|
||||
--
|
||||
-- 下一步:
|
||||
-- 1. 执行 hairlink_phase1.sql (业务表)
|
||||
-- 2. 开发后端租户隔离逻辑:
|
||||
-- - TenantContext (租户上下文)
|
||||
-- - TenantInterceptor (HTTP拦截器)
|
||||
-- - TenantSqlInterceptor (MyBatis拦截器)
|
||||
-- ============================
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
-- ============================================
|
||||
-- 若依多租户 - 唯一索引改造脚本
|
||||
-- 版本: 1.0
|
||||
-- 日期: 2025-12-19
|
||||
-- 警告: 执行前务必备份数据库!
|
||||
-- ============================================
|
||||
|
||||
-- ============================================
|
||||
-- 第一步:检查是否存在重复数据(必须先执行)
|
||||
-- ============================================
|
||||
|
||||
-- 检查 sys_user 表重复数据
|
||||
SELECT tenant_id, user_name, COUNT(*)
|
||||
FROM sys_user
|
||||
WHERE del_flag = '0'
|
||||
GROUP BY tenant_id, user_name
|
||||
HAVING COUNT(*) > 1;
|
||||
-- 如有输出,需先处理重复数据
|
||||
|
||||
-- 检查 sys_role 表重复数据
|
||||
SELECT tenant_id, role_key, COUNT(*)
|
||||
FROM sys_role
|
||||
WHERE del_flag = '0'
|
||||
GROUP BY tenant_id, role_key
|
||||
HAVING COUNT(*) > 1;
|
||||
-- 如有输出,需先处理重复数据
|
||||
|
||||
-- 检查 sys_post 表重复数据
|
||||
-- 注意:sys_post 表没有 del_flag 字段
|
||||
SELECT tenant_id, post_code, COUNT(*)
|
||||
FROM sys_post
|
||||
GROUP BY tenant_id, post_code
|
||||
HAVING COUNT(*) > 1;
|
||||
-- 如有输出,需先处理重复数据
|
||||
|
||||
-- ============================================
|
||||
-- 第二步:执行唯一索引改造
|
||||
-- ============================================
|
||||
|
||||
-- 1. sys_user表:用户名改为租户内唯一
|
||||
-- 说明:不同租户可以有同名用户(如都有 admin)
|
||||
-- 安全删除旧索引(兼容MySQL 5.7)
|
||||
SET @index_exists = (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'sys_user' AND index_name = 'uk_user_name');
|
||||
SET @sql = IF(@index_exists > 0, 'ALTER TABLE sys_user DROP INDEX uk_user_name', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
-- 创建新的联合唯一索引
|
||||
ALTER TABLE sys_user ADD UNIQUE KEY uk_tenant_user_name (tenant_id, user_name);
|
||||
|
||||
-- 2. sys_role表:角色标识改为租户内唯一
|
||||
-- 说明:不同租户可以有同标识角色(如都有 role_admin)
|
||||
SET @index_exists = (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'sys_role' AND index_name = 'uk_role_key');
|
||||
SET @sql = IF(@index_exists > 0, 'ALTER TABLE sys_role DROP INDEX uk_role_key', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
ALTER TABLE sys_role ADD UNIQUE KEY uk_tenant_role_key (tenant_id, role_key);
|
||||
|
||||
-- 3. sys_post表:岗位编码改为租户内唯一
|
||||
-- 说明:不同租户可以有同编码岗位(如都有 ceo)
|
||||
SET @index_exists = (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'sys_post' AND index_name = 'uk_post_code');
|
||||
SET @sql = IF(@index_exists > 0, 'ALTER TABLE sys_post DROP INDEX uk_post_code', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
ALTER TABLE sys_post ADD UNIQUE KEY uk_tenant_post_code (tenant_id, post_code);
|
||||
|
||||
-- ============================================
|
||||
-- 第三步:验证索引创建
|
||||
-- ============================================
|
||||
|
||||
-- 验证 sys_user 索引
|
||||
SHOW INDEX FROM sys_user WHERE Key_name = 'uk_tenant_user_name';
|
||||
|
||||
-- 验证 sys_role 索引
|
||||
SHOW INDEX FROM sys_role WHERE Key_name = 'uk_tenant_role_key';
|
||||
|
||||
-- 验证 sys_post 索引
|
||||
SHOW INDEX FROM sys_post WHERE Key_name = 'uk_tenant_post_code';
|
||||
|
||||
-- ============================================
|
||||
-- 执行结果说明
|
||||
-- ============================================
|
||||
-- 每个表应该显示索引的两条记录(tenant_id 和对应的业务字段)
|
||||
-- Seq_in_index = 1: tenant_id
|
||||
-- Seq_in_index = 2: user_name / role_key / post_code
|
||||
|
||||
-- ============================================
|
||||
-- 回滚脚本(仅供紧急情况使用)
|
||||
-- ============================================
|
||||
/*
|
||||
-- 警告:回滚将恢复到单租户模式,可能导致数据冲突!
|
||||
|
||||
-- 回滚 sys_user 索引
|
||||
ALTER TABLE sys_user DROP INDEX IF EXISTS uk_tenant_user_name;
|
||||
ALTER TABLE sys_user ADD UNIQUE KEY uk_user_name (user_name);
|
||||
|
||||
-- 回滚 sys_role 索引
|
||||
ALTER TABLE sys_role DROP INDEX IF EXISTS uk_tenant_role_key;
|
||||
ALTER TABLE sys_role ADD UNIQUE KEY uk_role_key (role_key);
|
||||
|
||||
-- 回滚 sys_post 索引
|
||||
ALTER TABLE sys_post DROP INDEX IF EXISTS uk_tenant_post_code;
|
||||
ALTER TABLE sys_post ADD UNIQUE KEY uk_post_code (post_code);
|
||||
*/
|
||||
|
||||
-- ============================================
|
||||
-- 执行注意事项
|
||||
-- ============================================
|
||||
-- 1. 选择业务低峰期执行(预计停机10-30分钟)
|
||||
-- 2. 执行前完整备份数据库:mysqldump -u root -p database_name > backup.sql
|
||||
-- 3. 务必先执行第一步的重复数据检查
|
||||
-- 4. 如有重复数据,需先清理或合并
|
||||
-- 5. 大表索引创建较慢,耐心等待不要中断
|
||||
-- 6. 准备回滚脚本以备不时之需
|
||||
-- 7. 执行完毕后验证应用功能正常
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
-- ============================================
|
||||
-- 租户套餐功能 - 数据库脚本
|
||||
-- 版本: 1.0
|
||||
-- 日期: 2025-12-19
|
||||
-- 说明: 实现租户套餐功能,通过套餐控制租户的菜单权限
|
||||
-- ============================================
|
||||
|
||||
-- ============================================
|
||||
-- 第一步:创建租户套餐表
|
||||
-- ============================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sys_tenant_package (
|
||||
package_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '套餐ID',
|
||||
package_name VARCHAR(50) NOT NULL COMMENT '套餐名称',
|
||||
package_code VARCHAR(50) NOT NULL COMMENT '套餐编码',
|
||||
status CHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
|
||||
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0存在 2删除)',
|
||||
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
|
||||
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
|
||||
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
|
||||
PRIMARY KEY (package_id),
|
||||
UNIQUE KEY uk_package_code (package_code)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='租户套餐表';
|
||||
|
||||
-- ============================================
|
||||
-- 第二步:创建套餐菜单关联表
|
||||
-- ============================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sys_package_menu (
|
||||
package_id BIGINT(20) NOT NULL COMMENT '套餐ID',
|
||||
menu_id BIGINT(20) NOT NULL COMMENT '菜单ID',
|
||||
PRIMARY KEY (package_id, menu_id),
|
||||
INDEX idx_package_id (package_id) -- 为package_id添加索引(用户要求)
|
||||
) ENGINE=InnoDB COMMENT='套餐菜单关联表';
|
||||
|
||||
-- ============================================
|
||||
-- 第三步:修改sys_tenant表,增加套餐字段
|
||||
-- ============================================
|
||||
|
||||
-- 检查列是否存在,避免重复执行报错
|
||||
SELECT COUNT(*) INTO @col_exists
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'sys_tenant'
|
||||
AND column_name = 'package_id';
|
||||
|
||||
-- 只有列不存在时才添加
|
||||
SET @sql = IF(@col_exists = 0,
|
||||
'ALTER TABLE sys_tenant ADD COLUMN package_id BIGINT(20) DEFAULT NULL COMMENT ''套餐ID'' AFTER tenant_code',
|
||||
'SELECT ''Column package_id already exists'' AS message');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 添加索引(忽略已存在的情况)
|
||||
-- 注意:如果索引已存在会报错,可忽略该错误继续执行
|
||||
SET @index_exists = (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'sys_tenant' AND index_name = 'idx_package_id');
|
||||
SET @sql = IF(@index_exists = 0, 'ALTER TABLE sys_tenant ADD INDEX idx_package_id (package_id)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ============================================
|
||||
-- 第四步:初始化套餐数据
|
||||
-- ============================================
|
||||
|
||||
INSERT INTO sys_tenant_package (package_name, package_code, status, create_by, create_time, remark) VALUES
|
||||
('基础套餐', 'BASIC', '0', 'admin', NOW(), '用户、部门管理'),
|
||||
('标准套餐', 'STANDARD', '0', 'admin', NOW(), '增加角色、菜单、字典管理'),
|
||||
('高级套餐', 'PREMIUM', '0', 'admin', NOW(), '全部功能,不限制菜单');
|
||||
|
||||
-- ============================================
|
||||
-- 第五步:初始化套餐菜单关联(基础套餐)
|
||||
-- ============================================
|
||||
|
||||
-- 基础套餐:只包含用户、部门、岗位、字典管理
|
||||
INSERT INTO sys_package_menu (package_id, menu_id) VALUES
|
||||
-- 系统管理模块
|
||||
(1, 1), -- 系统管理
|
||||
-- 菜单项
|
||||
(1, 100), -- 用户管理
|
||||
(1, 101), -- 部门管理
|
||||
(1, 103), -- 岗位管理
|
||||
(1, 104), -- 字典管理
|
||||
-- 用户管理按钮
|
||||
(1, 1000), (1, 1001), (1, 1002), (1, 1003), (1, 1004), (1, 1005), (1, 1006),
|
||||
-- 部门管理按钮
|
||||
(1, 1016), (1, 1017), (1, 1018), (1, 1019),
|
||||
-- 岗位管理按钮
|
||||
(1, 1020), (1, 1021), (1, 1022), (1, 1023), (1, 1024),
|
||||
-- 字典管理按钮
|
||||
(1, 1025), (1, 1026), (1, 1027), (1, 1028), (1, 1029);
|
||||
|
||||
-- ============================================
|
||||
-- 第六步:初始化套餐菜单关联(标准套餐)
|
||||
-- ============================================
|
||||
|
||||
-- 标准套餐:增加角色、菜单管理
|
||||
INSERT INTO sys_package_menu (package_id, menu_id) VALUES
|
||||
-- 系统管理模块
|
||||
(2, 1), -- 系统管理
|
||||
-- 菜单项
|
||||
(2, 100), (2, 101), (2, 102), (2, 103), (2, 104), (2, 105), -- 用户/部门/角色/岗位/字典/菜单
|
||||
-- 用户管理按钮
|
||||
(2, 1000), (2, 1001), (2, 1002), (2, 1003), (2, 1004), (2, 1005), (2, 1006),
|
||||
-- 角色管理按钮
|
||||
(2, 1007), (2, 1008), (2, 1009), (2, 1010), (2, 1011),
|
||||
-- 菜单管理按钮
|
||||
(2, 1012), (2, 1013), (2, 1014), (2, 1015),
|
||||
-- 部门管理按钮
|
||||
(2, 1016), (2, 1017), (2, 1018), (2, 1019),
|
||||
-- 岗位管理按钮
|
||||
(2, 1020), (2, 1021), (2, 1022), (2, 1023), (2, 1024),
|
||||
-- 字典管理按钮
|
||||
(2, 1025), (2, 1026), (2, 1027), (2, 1028), (2, 1029);
|
||||
|
||||
-- ============================================
|
||||
-- 第七步:高级套餐不插入关联数据
|
||||
-- ============================================
|
||||
|
||||
-- 说明:高级套餐 (package_id=3) 不插入 sys_package_menu 数据
|
||||
-- 代码逻辑:查询结果为空列表时,表示"不限制菜单",用户可以看到所有菜单
|
||||
|
||||
-- ============================================
|
||||
-- 第八步:关联现有租户到套餐
|
||||
-- ============================================
|
||||
|
||||
-- 根据实际租户情况调整(使用安全的更新方式)
|
||||
UPDATE sys_tenant SET package_id = 1 WHERE tenant_code = 'DEFAULT' AND package_id IS NULL;
|
||||
UPDATE sys_tenant SET package_id = 2 WHERE tenant_code = 'TENANT_A' AND package_id IS NULL;
|
||||
UPDATE sys_tenant SET package_id = 3 WHERE tenant_code = 'TENANT_B' AND package_id IS NULL;
|
||||
|
||||
-- ============================================
|
||||
-- 验证数据
|
||||
-- ============================================
|
||||
|
||||
-- 验证套餐表
|
||||
SELECT * FROM sys_tenant_package;
|
||||
|
||||
-- 验证套餐菜单关联(基础套餐)
|
||||
SELECT COUNT(*) AS '基础套餐菜单数' FROM sys_package_menu WHERE package_id = 1;
|
||||
|
||||
-- 验证套餐菜单关联(标准套餐)
|
||||
SELECT COUNT(*) AS '标准套餐菜单数' FROM sys_package_menu WHERE package_id = 2;
|
||||
|
||||
-- 验证套餐菜单关联(高级套餐)
|
||||
SELECT COUNT(*) AS '高级套餐菜单数(应为0)' FROM sys_package_menu WHERE package_id = 3;
|
||||
|
||||
-- 验证租户套餐关联
|
||||
SELECT tenant_id, tenant_name, tenant_code, package_id
|
||||
FROM sys_tenant
|
||||
WHERE package_id IS NOT NULL;
|
||||
|
||||
-- ============================================
|
||||
-- 回滚脚本(仅供紧急情况使用)
|
||||
-- ============================================
|
||||
|
||||
/*
|
||||
-- 警告:回滚将删除所有套餐数据和配置!
|
||||
|
||||
-- 1. 清除租户的套餐关联
|
||||
UPDATE sys_tenant SET package_id = NULL;
|
||||
|
||||
-- 2. 删除套餐菜单关联表
|
||||
DROP TABLE IF EXISTS sys_package_menu;
|
||||
|
||||
-- 3. 删除套餐表
|
||||
DROP TABLE IF EXISTS sys_tenant_package;
|
||||
|
||||
-- 4. 删除租户表的套餐字段
|
||||
ALTER TABLE sys_tenant DROP COLUMN IF EXISTS package_id;
|
||||
ALTER TABLE sys_tenant DROP INDEX IF EXISTS idx_package_id;
|
||||
*/
|
||||
|
||||
-- ============================================
|
||||
-- 使用说明
|
||||
-- ============================================
|
||||
|
||||
/*
|
||||
套餐机制说明:
|
||||
|
||||
1. **基础套餐 (BASIC)**
|
||||
- 适用于小型租户
|
||||
- 只包含基础的用户、部门、岗位、字典管理
|
||||
- 不能分配角色和菜单权限
|
||||
|
||||
2. **标准套餐 (STANDARD)**
|
||||
- 适用于中型租户
|
||||
- 包含基础功能 + 角色管理 + 菜单管理
|
||||
- 可以自定义角色和权限
|
||||
|
||||
3. **高级套餐 (PREMIUM)**
|
||||
- 适用于大型租户
|
||||
- 不限制任何菜单功能
|
||||
- sys_package_menu 表中无记录表示"全部菜单可见"
|
||||
|
||||
菜单过滤逻辑:
|
||||
- 用户最终可见菜单 = 角色菜单 ∩ 套餐菜单
|
||||
- 超级管理员不受套餐限制
|
||||
- 未配置套餐的租户默认不限制菜单
|
||||
*/
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
-- ============================================
|
||||
-- 套餐管理菜单 - 插入脚本
|
||||
-- 版本: 1.0
|
||||
-- 日期: 2025-12-20
|
||||
-- ============================================
|
||||
|
||||
-- 1. 套餐管理菜单(放在系统管理下,parent_id = 1)
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES (2006, '套餐管理', 1, 10, 'package', 'system/package/index', NULL, 'Package', 1, 0, 'C', '0', '0', 'system:package:list', 'component', 'admin', NOW(), '', NULL, '租户套餐管理菜单');
|
||||
|
||||
-- 2. 套餐管理按钮权限
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES (2007, '套餐查询', 2006, 1, '', NULL, NULL, '', 1, 0, 'F', '0', '0', 'system:package:query', '#', 'admin', NOW(), '', NULL, '');
|
||||
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES (2008, '套餐新增', 2006, 2, '', NULL, NULL, '', 1, 0, 'F', '0', '0', 'system:package:add', '#', 'admin', NOW(), '', NULL, '');
|
||||
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES (2009, '套餐修改', 2006, 3, '', NULL, NULL, '', 1, 0, 'F', '0', '0', 'system:package:edit', '#', 'admin', NOW(), '', NULL, '');
|
||||
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES (2010, '套餐删除', 2006, 4, '', NULL, NULL, '', 1, 0, 'F', '0', '0', 'system:package:remove', '#', 'admin', NOW(), '', NULL, '');
|
||||
|
||||
-- 3. 给超级管理员角色分配权限(role_id = 1)
|
||||
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 2006);
|
||||
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 2007);
|
||||
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 2008);
|
||||
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 2009);
|
||||
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 2010);
|
||||
|
||||
-- 验证
|
||||
SELECT * FROM sys_menu WHERE menu_id >= 2006;
|
||||
Loading…
Reference in New Issue