!286 合并 多租户功能

* add 新增 ruoyi-common-tenant 多租户模块 全框架适配多租户改动
* update 优化 隐藏页面主键
* remove 移除 缓存列表功能(多租户缓存功能繁杂多样 没有办法在页面管理)
* update 重构 全局缓存KEY 与 常用缓存KEY做区分
* update 重构 OssFactory 加载方式 改为每次比对配置做实例更新
* update 优化 SaTokenDao 改为 Bean 注入 便于扩展
* update 重构 项目初始化数据改为懒加载 不提供热加载
* update 重构 验证码开关使用配置文件(经调查少有动态开启需求)
* update 优化 启用 sqlserver 高版本语法 简化sql脚本语法
* update 优化 DataPermissionHelper 增加 开启/关闭 忽略数据权限功能
* update 优化 连接池增加 keepaliveTime 探活参数
* update 优化 调整连接池最长生命周期 防止出现警告
* update 优化 代码生成页面模板 校验不必要的表单数据
* add 新增 StringUtils splitTo 与 splitList 方法 优化业务代码
This commit is contained in:
疯狂的狮子Li 2023-02-16 09:06:10 +00:00
parent a8d5644f2e
commit 45ac0f23e1
187 changed files with 6486 additions and 2372 deletions

View File

@ -30,7 +30,7 @@
<hutool.version>5.8.11</hutool.version>
<okhttp.version>4.10.0</okhttp.version>
<spring-boot-admin.version>3.0.0</spring-boot-admin.version>
<redisson.version>3.19.1</redisson.version>
<redisson.version>3.19.2</redisson.version>
<lock4j.version>2.2.4</lock4j.version>
<dynamic-ds.version>3.6.1</dynamic-ds.version>
<alibaba-ttl.version>2.14.2</alibaba-ttl.version>
@ -42,10 +42,10 @@
<snakeyaml.version>1.33</snakeyaml.version>
<!-- OSS 配置 -->
<aws-java-sdk-s3.version>1.12.373</aws-java-sdk-s3.version>
<aws-java-sdk-s3.version>1.12.400</aws-java-sdk-s3.version>
<!-- SMS 配置 -->
<aliyun.sms.version>2.0.23</aliyun.sms.version>
<tencent.sms.version>3.1.660</tencent.sms.version>
<tencent.sms.version>3.1.687</tencent.sms.version>
<!-- 插件版本 -->
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>

View File

@ -5,8 +5,8 @@ import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.SpringUtils;
import com.ruoyi.common.core.utils.StringUtils;
@ -17,8 +17,8 @@ import com.ruoyi.common.sms.core.SmsTemplate;
import com.ruoyi.common.sms.entity.SmsResult;
import com.ruoyi.common.web.config.properties.CaptchaProperties;
import com.ruoyi.common.web.enums.CaptchaType;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.web.domain.vo.CaptchaVo;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.Expression;
@ -28,7 +28,6 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.constraints.NotBlank;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@ -47,7 +46,6 @@ public class CaptchaController {
private final CaptchaProperties captchaProperties;
private final SmsProperties smsProperties;
private final ISysConfigService configService;
/**
* 短信验证码
@ -60,7 +58,7 @@ public class CaptchaController {
if (!smsProperties.getEnabled()) {
return R.fail("当前系统没有开启短信功能!");
}
String key = CacheConstants.CAPTCHA_CODE_KEY + phonenumber;
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 自行处理 (查数据库或写死均可)
@ -82,14 +80,14 @@ public class CaptchaController {
@GetMapping("/captchaImage")
public R<CaptchaVo> getCode() {
CaptchaVo captchaVo = new CaptchaVo();
boolean captchaEnabled = configService.selectCaptchaEnabled();
boolean captchaEnabled = captchaProperties.getEnable();
if (!captchaEnabled) {
captchaVo.setCaptchaEnabled(false);
return R.ok(captchaVo);
}
// 保存验证码信息
String uuid = IdUtil.simpleUUID();
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
// 生成验证码
CaptchaType captchaType = captchaProperties.getType();
boolean isMath = CaptchaType.MATH == captchaType;

View File

@ -1,19 +1,27 @@
package com.ruoyi.web.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.domain.model.LoginBody;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.domain.model.SmsLoginBody;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.satoken.utils.LoginHelper;
import com.ruoyi.system.domain.SysMenu;
import com.ruoyi.system.domain.bo.SysTenantBo;
import com.ruoyi.system.domain.vo.RouterVo;
import com.ruoyi.system.domain.vo.SysTenantVo;
import com.ruoyi.system.domain.vo.SysUserVo;
import com.ruoyi.system.service.ISysMenuService;
import com.ruoyi.system.service.ISysTenantService;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.system.service.SysLoginService;
import com.ruoyi.web.domain.vo.LoginVo;
import com.ruoyi.web.domain.vo.TenantListVo;
import com.ruoyi.web.domain.vo.UserInfoVo;
import com.ruoyi.web.service.SysLoginService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
@ -22,6 +30,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.net.URL;
import java.util.List;
/**
@ -37,20 +46,23 @@ public class SysLoginController {
private final SysLoginService loginService;
private final ISysMenuService menuService;
private final ISysUserService userService;
private final ISysTenantService tenantService;
/**
* 登录方法
*
* @param loginBody 登录信息
* @param body 登录信息
* @return 结果
*/
@SaIgnore
@PostMapping("/login")
public R<LoginVo> login(@Validated @RequestBody LoginBody loginBody) {
public R<LoginVo> login(@Validated @RequestBody LoginBody body) {
LoginVo loginVo = new LoginVo();
// 生成令牌
String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
loginBody.getUuid());
String token = loginService.login(
body.getTenantId(),
body.getUsername(), body.getPassword(),
body.getCode(), body.getUuid());
loginVo.setToken(token);
return R.ok(loginVo);
}
@ -58,15 +70,15 @@ public class SysLoginController {
/**
* 短信登录(示例)
*
* @param smsLoginBody 登录信息
* @param body 登录信息
* @return 结果
*/
@SaIgnore
@PostMapping("/smsLogin")
public R<LoginVo> smsLogin(@Validated @RequestBody SmsLoginBody smsLoginBody) {
public R<LoginVo> smsLogin(@Validated @RequestBody SmsLoginBody body) {
LoginVo loginVo = new LoginVo();
// 生成令牌
String token = loginService.smsLogin(smsLoginBody.getPhonenumber(), smsLoginBody.getSmsCode());
String token = loginService.smsLogin(body.getTenantId(), body.getPhonenumber(), body.getSmsCode());
loginVo.setToken(token);
return R.ok(loginVo);
}
@ -97,6 +109,23 @@ public class SysLoginController {
return R.ok("退出成功");
}
/**
* 登录页面租户下拉框
*
* @return 租户列表
*/
@SaIgnore
@GetMapping("/tenant/list")
public R<List<TenantListVo>> tenantList(HttpServletRequest request) throws Exception {
List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo());
List<TenantListVo> voList = BeanUtil.copyToList(tenantList, TenantListVo.class);
// 获取域名
String host = new URL(request.getRequestURL().toString()).getHost();
// 根据域名进行筛选
List<TenantListVo> list = voList.stream().filter(vo -> StringUtils.equals(vo.getDomain(), host)).toList();
return R.ok(CollUtil.isNotEmpty(list) ? list : voList);
}
/**
* 获取用户信息
*

View File

@ -5,7 +5,7 @@ import com.ruoyi.common.web.core.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.domain.model.RegisterBody;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.SysRegisterService;
import com.ruoyi.web.service.SysRegisterService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;

View File

@ -0,0 +1,19 @@
package com.ruoyi.web.domain.vo;
import lombok.Data;
/**
* 租户列表
*
* @author Lion Li
*/
@Data
public class TenantListVo {
private String tenantId;
private String companyName;
private String domain;
}

View File

@ -1,4 +1,4 @@
package com.ruoyi.system.service;
package com.ruoyi.web.service;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.secure.BCrypt;
@ -6,13 +6,14 @@ import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.domain.dto.RoleDTO;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.domain.model.XcxLoginUser;
import com.ruoyi.common.core.enums.DeviceType;
import com.ruoyi.common.core.enums.LoginType;
import com.ruoyi.common.core.enums.TenantStatus;
import com.ruoyi.common.core.enums.UserStatus;
import com.ruoyi.common.core.exception.user.CaptchaException;
import com.ruoyi.common.core.exception.user.CaptchaExpireException;
@ -21,9 +22,15 @@ import com.ruoyi.common.core.utils.*;
import com.ruoyi.common.log.event.LogininforEvent;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.common.satoken.utils.LoginHelper;
import com.ruoyi.common.tenant.exception.TenantException;
import com.ruoyi.common.tenant.helper.TenantHelper;
import com.ruoyi.common.web.config.properties.CaptchaProperties;
import com.ruoyi.system.domain.SysUser;
import com.ruoyi.system.domain.vo.SysTenantVo;
import com.ruoyi.system.domain.vo.SysUserVo;
import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.system.service.ISysPermissionService;
import com.ruoyi.system.service.ISysTenantService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -31,6 +38,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.function.Supplier;
@ -45,8 +53,9 @@ import java.util.function.Supplier;
public class SysLoginService {
private final SysUserMapper userMapper;
private final ISysConfigService configService;
private final SysPermissionService permissionService;
private final CaptchaProperties captchaProperties;
private final ISysPermissionService permissionService;
private final ISysTenantService tenantService;
@Value("${user.password.maxRetryCount}")
private Integer maxRetryCount;
@ -63,36 +72,41 @@ public class SysLoginService {
* @param uuid 唯一标识
* @return 结果
*/
public String login(String username, String password, String code, String uuid) {
public String login(String tenantId, String username, String password, String code, String uuid) {
HttpServletRequest request = ServletUtils.getRequest();
boolean captchaEnabled = configService.selectCaptchaEnabled();
boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关
if (captchaEnabled) {
validateCaptcha(username, code, uuid, request);
validateCaptcha(tenantId, username, code, uuid, request);
}
SysUserVo user = loadUserByUsername(username);
checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, user.getPassword()));
// 校验租户
checkTenant(tenantId);
SysUserVo user = loadUserByUsername(tenantId, username);
checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
// 此处可根据登录用户的数据不同 自行创建 loginUser
LoginUser loginUser = buildLoginUser(user);
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.PC);
recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLogininfor(loginUser.getTenantId(), username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getUserId());
return StpUtil.getTokenValue();
}
public String smsLogin(String phonenumber, String smsCode) {
public String smsLogin(String tenantId, String phonenumber, String smsCode) {
// 校验租户
checkTenant(tenantId);
// 通过手机号查找用户
SysUserVo user = loadUserByPhonenumber(phonenumber);
SysUserVo user = loadUserByPhonenumber(tenantId, phonenumber);
checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode));
checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser
LoginUser loginUser = buildLoginUser(user);
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.APP);
recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getUserId());
return StpUtil.getTokenValue();
}
@ -104,9 +118,12 @@ public class SysLoginService {
// 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key openid
String openid = "";
SysUserVo user = loadUserByOpenid(openid);
// 校验租户
checkTenant(user.getTenantId());
// 此处可根据登录用户的数据不同 自行创建 loginUser
XcxLoginUser loginUser = new XcxLoginUser();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(user.getUserId());
loginUser.setUsername(user.getUserName());
loginUser.setUserType(user.getUserType());
@ -114,7 +131,7 @@ public class SysLoginService {
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.XCX);
recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getUserId());
return StpUtil.getTokenValue();
}
@ -126,7 +143,7 @@ public class SysLoginService {
try {
LoginUser loginUser = LoginHelper.getLoginUser();
StpUtil.logout();
recordLogininfor(loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
} catch (NotLoginException ignored) {
}
}
@ -134,13 +151,15 @@ public class SysLoginService {
/**
* 记录登录信息
*
* @param tenantId 租户ID
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
private void recordLogininfor(String username, String status, String message) {
private void recordLogininfor(String tenantId, String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(status);
logininforEvent.setMessage(message);
@ -151,10 +170,10 @@ public class SysLoginService {
/**
* 校验短信验证码
*/
private boolean validateSmsCode(String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + phonenumber);
private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
recordLogininfor(phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(smsCode);
@ -167,23 +186,24 @@ public class SysLoginService {
* @param code 验证码
* @param uuid 唯一标识
*/
public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) {
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
public void validateCaptcha(String tenantId, String username, String code, String uuid, HttpServletRequest request) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
private SysUserVo loadUserByUsername(String username) {
private SysUserVo loadUserByUsername(String tenantId, String username) {
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
.select(SysUser::getUserName, SysUser::getStatus)
.eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
.eq(SysUser::getUserName, username));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
@ -195,9 +215,10 @@ public class SysLoginService {
return userMapper.selectUserByUserName(username);
}
private SysUserVo loadUserByPhonenumber(String phonenumber) {
private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) {
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
.select(SysUser::getPhonenumber, SysUser::getStatus)
.eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
.eq(SysUser::getPhonenumber, phonenumber));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", phonenumber);
@ -228,12 +249,13 @@ public class SysLoginService {
*/
private LoginUser buildLoginUser(SysUserVo user) {
LoginUser loginUser = new LoginUser();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(user.getUserId());
loginUser.setDeptId(user.getDeptId());
loginUser.setUsername(user.getUserName());
loginUser.setUserType(user.getUserType());
loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId(), user.isAdmin()));
loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId(), user.isAdmin()));
loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName());
List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class);
loginUser.setRoles(roles);
@ -257,15 +279,15 @@ public class SysLoginService {
/**
* 登录校验
*/
private void checkLogin(LoginType loginType, String username, Supplier<Boolean> supplier) {
String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
private void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
String errorKey = GlobalConstants.PWD_ERR_CNT_KEY + username;
String loginFail = Constants.LOGIN_FAIL;
// 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
Integer errorNumber = RedisUtils.getCacheObject(errorKey);
// 锁定时间内登录 则踢出
if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) {
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
}
@ -275,12 +297,12 @@ public class SysLoginService {
// 达到规定错误次数 则锁定登录
if (errorNumber.equals(maxRetryCount)) {
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
} else {
// 未达到规定错误次数 则递增
RedisUtils.setCacheObject(errorKey, errorNumber);
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
throw new UserException(loginType.getRetryLimitCount(), errorNumber);
}
}
@ -288,4 +310,23 @@ public class SysLoginService {
// 登录成功 清空错误次数
RedisUtils.deleteObject(errorKey);
}
private void checkTenant(String tenantId) {
if (!TenantHelper.isEnable()) {
return;
}
SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
if (ObjectUtil.isNull(tenant)) {
log.info("登录租户:{} 不存在.", tenantId);
throw new TenantException("tenant.not.exists");
} else if (TenantStatus.DISABLE.getCode().equals(tenant.getStatus())) {
log.info("登录租户:{} 已被停用.", tenantId);
throw new TenantException("tenant.blocked");
} else if (ObjectUtil.isNotNull(tenant.getExpireTime())
&& new Date().after(tenant.getExpireTime())) {
log.info("登录租户:{} 已超过有效期.", tenantId);
throw new TenantException("tenant.expired");
}
}
}

View File

@ -1,8 +1,8 @@
package com.ruoyi.system.service;
package com.ruoyi.web.service;
import cn.dev33.satoken.secure.BCrypt;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.domain.model.RegisterBody;
import com.ruoyi.common.core.enums.UserType;
@ -15,7 +15,9 @@ import com.ruoyi.common.core.utils.SpringUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.log.event.LogininforEvent;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.common.web.config.properties.CaptchaProperties;
import com.ruoyi.system.domain.bo.SysUserBo;
import com.ruoyi.system.service.ISysUserService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@ -30,22 +32,23 @@ import org.springframework.stereotype.Service;
public class SysRegisterService {
private final ISysUserService userService;
private final ISysConfigService configService;
private final CaptchaProperties captchaProperties;
/**
* 注册
*/
public void register(RegisterBody registerBody) {
HttpServletRequest request = ServletUtils.getRequest();
String tenantId = registerBody.getTenantId();
String username = registerBody.getUsername();
String password = registerBody.getPassword();
// 校验用户类型是否存在
String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
boolean captchaEnabled = configService.selectCaptchaEnabled();
boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关
if (captchaEnabled) {
validateCaptcha(username, registerBody.getCode(), registerBody.getUuid(), request);
validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid(), request);
}
SysUserBo sysUser = new SysUserBo();
sysUser.setUserName(username);
@ -56,11 +59,11 @@ public class SysRegisterService {
if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(sysUser))) {
throw new UserException("user.register.save.error", username);
}
boolean regFlag = userService.registerUser(sysUser);
boolean regFlag = userService.registerUser(sysUser, tenantId);
if (!regFlag) {
throw new UserException("user.register.error");
}
recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success"));
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
}
/**
@ -71,16 +74,16 @@ public class SysRegisterService {
* @param uuid 唯一标识
* @return 结果
*/
public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) {
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
public void validateCaptcha(String tenantId, String username, String code, String uuid, HttpServletRequest request) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire"));
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error"));
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
@ -88,13 +91,15 @@ public class SysRegisterService {
/**
* 记录登录信息
*
* @param tenantId 租户ID
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
private void recordLogininfor(String username, String status, String message) {
private void recordLogininfor(String tenantId, String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(status);
logininforEvent.setMessage(message);

View File

@ -92,9 +92,11 @@ spring:
# 空闲连接存活最大时间默认10分钟
idleTimeout: 600000
# 此属性控制池中连接的最长生命周期值0表示无限生命周期默认30分钟
maxLifetime: 900000
maxLifetime: 1800000
# 连接测试query配置检测连接是否有效
connectionTestQuery: SELECT 1
# 多久检查一次连接的活性
keepaliveTime: 30000
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring.data:

View File

@ -95,9 +95,11 @@ spring:
# 空闲连接存活最大时间默认10分钟
idleTimeout: 600000
# 此属性控制池中连接的最长生命周期值0表示无限生命周期默认30分钟
maxLifetime: 900000
maxLifetime: 1800000
# 连接测试query配置检测连接是否有效
connectionTestQuery: SELECT 1
# 多久检查一次连接的活性
keepaliveTime: 30000
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring.data:

View File

@ -10,10 +10,9 @@ ruoyi:
demoEnabled: true
# 获取ip地址开关
addressEnabled: true
# 缓存懒加载
cacheLazy: false
captcha:
enable: true
# 页面 <参数设置> 可开启关闭 验证码校验
# 验证码类型 math 数组计算 char 字符验证
type: MATH
@ -136,6 +135,20 @@ security:
- /actuator
- /actuator/**
# 多租户配置
tenant:
# 是否开启
enable: true
# 排除表
excludes:
- sys_menu
- sys_tenant
- sys_tenant_package
- sys_role_dept
- sys_role_menu
- sys_user_post
- sys_user_role
# MyBatisPlus配置
# https://baomidou.com/config/
mybatis-plus:

View File

@ -43,3 +43,8 @@ sms.code.not.blank=短信验证码不能为空
sms.code.retry.limit.count=短信验证码输入错误{0}次
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
xcx.code.not.blank=小程序code不能为空
##租户
tenant.number.not.blank=租户编号不能为空
tenant.not.exists=对不起, 您的租户不存在,请联系管理员
tenant.blocked=对不起,您的租户已禁用,请联系管理员
tenant.expired=对不起,您的租户已过期,请联系管理员

View File

@ -43,3 +43,8 @@ sms.code.not.blank=Sms code cannot be blank
sms.code.retry.limit.count=Sms code input error {0} times
sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {1} minutes
xcx.code.not.blank=Mini program code cannot be blank
##租户
tenant.number.not.blank=Tenant number cannot be blank
tenant.not.exists=Sorry, your tenant does not exist. Please contact the administrator
tenant.blocked=Sorry, your tenant is disabled. Please contact the administrator
tenant.expired=Sorry, your tenant has expired. Please contact the administrator.

View File

@ -43,3 +43,8 @@ sms.code.not.blank=短信验证码不能为空
sms.code.retry.limit.count=短信验证码输入错误{0}次
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
xcx.code.not.blank=小程序code不能为空
##租户
tenant.number.not.blank=租户编号不能为空
tenant.not.exists=对不起, 您的租户不存在,请联系管理员
tenant.blocked=对不起,您的租户已禁用,请联系管理员
tenant.expired=对不起,您的租户已过期,请联系管理员

View File

@ -31,6 +31,7 @@
<module>ruoyi-common-sensitive</module>
<module>ruoyi-common-json</module>
<module>ruoyi-common-encrypt</module>
<module>ruoyi-common-tenant</module>
</modules>
<artifactId>ruoyi-common</artifactId>

View File

@ -152,6 +152,13 @@
<version>${revision}</version>
</dependency>
<!-- 租户模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-tenant</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -36,11 +36,6 @@ public class RuoYiConfig {
*/
private boolean demoEnabled;
/**
* 缓存懒加载
*/
private boolean cacheLazy;
/**
* 获取地址开关
*/

View File

@ -3,25 +3,15 @@ package com.ruoyi.common.core.constant;
/**
* 缓存的key 常量
*
* @author ruoyi
* @author Lion Li
*/
public interface CacheConstants {
/**
* 登录用户 redis key
*/
String LOGIN_TOKEN_KEY = "Authorization:login:token:";
/**
* 在线用户 redis key
*/
String ONLINE_TOKEN_KEY = "online_tokens:";
/**
* 验证码 redis key
*/
String CAPTCHA_CODE_KEY = "captcha_codes:";
/**
* 参数管理 cache key
*/
@ -32,18 +22,4 @@ public interface CacheConstants {
*/
String SYS_DICT_KEY = "sys_dict:";
/**
* 防重提交 redis key
*/
String REPEAT_SUBMIT_KEY = "repeat_submit:";
/**
* 限流 redis key
*/
String RATE_LIMIT_KEY = "rate_limit:";
/**
* 登录账户密码错误次数 redis key
*/
String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
}

View File

@ -30,6 +30,11 @@ public interface CacheNames {
*/
String SYS_DICT = "sys_dict";
/**
* 租户
*/
String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
/**
* 用户账户
*/

View File

@ -72,5 +72,10 @@ public interface Constants {
*/
String TOKEN = "token";
/**
* 顶级部门id
*/
Long TOP_PARENT_ID = 0L;
}

View File

@ -0,0 +1,39 @@
package com.ruoyi.common.core.constant;
/**
* 全局的key常量 (业务无关的key)
*
* @author Lion Li
*/
public interface GlobalConstants {
/**
* 全局 redis key (业务无关的key)
*/
String GLOBAL_REDIS_KEY = "global:";
/**
* 登录用户 redis key
*/
String LOGIN_TOKEN_KEY = GLOBAL_REDIS_KEY + "Authorization:login:token:";
/**
* 验证码 redis key
*/
String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
/**
* 防重提交 redis key
*/
String REPEAT_SUBMIT_KEY = GLOBAL_REDIS_KEY + "repeat_submit:";
/**
* 限流 redis key
*/
String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
/**
* 登录账户密码错误次数 redis key
*/
String PWD_ERR_CNT_KEY = GLOBAL_REDIS_KEY + "pwd_err_cnt:";
}

View File

@ -0,0 +1,51 @@
package com.ruoyi.common.core.constant;
/**
* 租户常量信息
*
* @author Lion Li
*/
public interface TenantConstants {
/**
* 租户正常状态
*/
String NORMAL = "0";
/**
* 租户封禁状态
*/
String DISABLE = "1";
/**
* 校验返回结果码
*/
String PASS = "0";
String NOT_PASS = "1";
/**
* 超级管理员ID
*/
Long SUPER_ADMIN_ID = 1L;
/**
* 超级管理员角色 roleKey
*/
String SUPER_ADMIN_ROLE_KEY = "superadmin";
/**
* 租户管理员角色 roleKey
*/
String TENANT_ADMIN_ROLE_KEY = "admin";
/**
* 租户管理员角色名称
*/
String TENANT_ADMIN_ROLE_NAME = "管理员";
/**
* 默认租户ID
*/
String DEFAULT_TENANT_ID = "000000";
}

View File

@ -15,6 +15,12 @@ import jakarta.validation.constraints.NotBlank;
@Data
public class LoginBody {
/**
* 租户ID
*/
@NotBlank(message = "{tenant.number.not.blank}")
private String tenantId;
/**
* 用户名
*/

View File

@ -22,6 +22,11 @@ public class LoginUser implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/**
* 用户ID
*/

View File

@ -13,6 +13,12 @@ import jakarta.validation.constraints.NotBlank;
@Data
public class SmsLoginBody {
/**
* 租户ID
*/
@NotBlank(message = "{tenant.number.not.blank}")
private String tenantId;
/**
* 用户名
*/

View File

@ -0,0 +1,30 @@
package com.ruoyi.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 用户状态
*
* @author LionLi
*/
@Getter
@AllArgsConstructor
public enum TenantStatus {
/**
* 正常
*/
OK("0", "正常"),
/**
* 停用
*/
DISABLE("1", "停用"),
/**
* 删除
*/
DELETED("2", "删除");
private final String code;
private final String info;
}

View File

@ -92,7 +92,7 @@ public class ServletUtils extends JakartaServletUtil {
public static Map<String, String> getParamMap(ServletRequest request) {
Map<String, String> params = new HashMap<>();
for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) {
params.put(entry.getKey(), StringUtils.join(entry.getValue(), ","));
params.put(entry.getKey(), StringUtils.join(entry.getValue(), StringUtils.SEPARATOR));
}
return params;
}

View File

@ -42,7 +42,7 @@ public class StreamUtils {
* @return 拼接后的list
*/
public static <E> String join(Collection<E> collection, Function<E, String> function) {
return join(collection, function, ",");
return join(collection, function, StringUtils.SEPARATOR);
}
/**

View File

@ -1,16 +1,16 @@
package com.ruoyi.common.core.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.util.AntPathMatcher;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 字符串工具类
@ -20,6 +20,8 @@ import java.util.Set;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class StringUtils extends org.apache.commons.lang3.StringUtils {
public static final String SEPARATOR = ",";
/**
* 获取参数不为空值
*
@ -265,4 +267,55 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
return sb.toString();
}
/**
* 切分字符串(分隔符默认逗号)
*
* @param str 被切分的字符串
* @return 分割后的数据列表
*/
public static List<String> splitList(String str) {
return splitTo(str, Convert::toStr);
}
/**
* 切分字符串
*
* @param str 被切分的字符串
* @param separator 分隔符
* @return 分割后的数据列表
*/
public static List<String> splitList(String str, String separator) {
return splitTo(str, separator, Convert::toStr);
}
/**
* 切分字符串自定义转换(分隔符默认逗号)
*
* @param str 被切分的字符串
* @param mapper 自定义转换
* @return 分割后的数据列表
*/
public static <T> List<T> splitTo(String str, Function<? super Object, T> mapper) {
return splitTo(str, SEPARATOR, mapper);
}
/**
* 切分字符串自定义转换
*
* @param str 被切分的字符串
* @param separator 分隔符
* @param mapper 自定义转换
* @return 分割后的数据列表
*/
public static <T> List<T> splitTo(String str, String separator, Function<? super Object, T> mapper) {
if (isBlank(str)) {
return new ArrayList<>(0);
}
return StrUtil.split(str, separator)
.stream()
.filter(Objects::nonNull)
.map(mapper)
.collect(Collectors.toList());
}
}

View File

@ -1,5 +1,7 @@
package com.ruoyi.common.excel.annotation;
import com.ruoyi.common.core.utils.StringUtils;
import java.lang.annotation.*;
/**
@ -25,6 +27,6 @@ public @interface ExcelDictFormat {
/**
* 分隔符读取字符串组内容
*/
String separator() default ",";
String separator() default StringUtils.SEPARATOR;
}

View File

@ -269,7 +269,7 @@ public class ExcelUtil {
*/
public static String convertByExp(String propertyValue, String converterExp, String separator) {
StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(",");
String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
for (String item : convertSource) {
String[] itemArray = item.split("=");
if (StringUtils.containsAny(propertyValue, separator)) {
@ -298,7 +298,7 @@ public class ExcelUtil {
*/
public static String reverseByExp(String propertyValue, String converterExp, String separator) {
StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(",");
String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
for (String item : convertSource) {
String[] itemArray = item.split("=");
if (StringUtils.containsAny(propertyValue, separator)) {

View File

@ -3,7 +3,7 @@ package com.ruoyi.common.idempotent.aspectj;
import cn.dev33.satoken.SaManager;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SecureUtil;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.MessageUtils;
@ -57,7 +57,7 @@ public class RepeatSubmitAspect {
submitKey = SecureUtil.md5(submitKey + ":" + nowParams);
// 唯一标识指定key + url + 消息头
String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey;
String cacheRepeatKey = GlobalConstants.REPEAT_SUBMIT_KEY + url + submitKey;
String key = RedisUtils.getCacheObject(cacheRepeatKey);
if (key == null) {
RedisUtils.setCacheObject(cacheRepeatKey, "", Duration.ofMillis(interval));

View File

@ -67,6 +67,7 @@ public class LogAspect {
// *========数据库日志=========*//
OperLogEvent operLog = new OperLogEvent();
operLog.setTenantId(LoginHelper.getTenantId());
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = ServletUtils.getClientIP();

View File

@ -19,6 +19,11 @@ public class LogininforEvent implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/**
* 用户账号
*/

View File

@ -23,6 +23,11 @@ public class OperLogEvent implements Serializable {
*/
private Long operId;
/**
* 租户ID
*/
private String tenantId;
/**
* 操作模块
*/

View File

@ -1,41 +0,0 @@
package com.ruoyi.common.mybatis.core.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.util.ArrayList;
import java.util.List;
/**
* Tree基类
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class TreeEntity<T> extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 父菜单名称
*/
@TableField(exist = false)
private String parentName;
/**
* 父菜单ID
*/
private Long parentId;
/**
* 子部门
*/
@TableField(exist = false)
private List<T> children = new ArrayList<>();
}

View File

@ -6,7 +6,7 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.ruoyi.common.core.utils.BeanCopyUtils;
@ -17,6 +17,9 @@ import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 自定义 Mapper 接口, 实现 自定义扩展
@ -193,4 +196,8 @@ public interface BaseMapperPlus<M, T, V> extends BaseMapper<T> {
return (P) voPage;
}
default <C> List<C> selectObjs(Wrapper<T> wrapper, Function<? super Object, C> mapper) {
return this.selectObjs(wrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList());
}
}

View File

@ -89,8 +89,8 @@ public class PageQuery implements Serializable {
// 兼容前端排序类型
isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"});
String[] orderByArr = orderBy.split(",");
String[] isAscArr = isAsc.split(",");
String[] orderByArr = orderBy.split(StringUtils.SEPARATOR);
String[] isAscArr = isAsc.split(StringUtils.SEPARATOR);
if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {
throw new ServiceException("排序参数有误");
}

View File

@ -1,4 +1,4 @@
package com.ruoyi.common.mybatis.helper;
package com.ruoyi.common.mybatis.handler;
import com.ruoyi.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;

View File

@ -35,7 +35,6 @@ import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 数据权限过滤
@ -79,7 +78,7 @@ public class PlusDataPermissionHandler {
DataPermissionHelper.setVariable("user", currentUser);
}
// 如果是超级管理员则不过滤数据
if (LoginHelper.isAdmin()) {
if (LoginHelper.isSuperAdmin()) {
return where;
}
String dataFilterSql = buildDataFilter(dataColumns, isSelect);

View File

@ -3,6 +3,8 @@ package com.ruoyi.common.mybatis.helper;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaStorage;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@ -44,4 +46,19 @@ public class DataPermissionHelper {
}
throw new NullPointerException("data permission context type exception");
}
/**
* 开启忽略数据权限(开启后需手动调用 {@link #disableIgnore()} 关闭)
*/
public static void enableIgnore() {
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build());
}
/**
* 关闭忽略数据权限
*/
public static void disableIgnore() {
InterceptorIgnoreHelper.clearIgnoreStrategy();
}
}

View File

@ -183,6 +183,12 @@ public class OssClient {
return configKey;
}
/**
* 获取私有URL链接
*
* @param objectKey 对象KEY
* @param second 授权时间
*/
public String getPrivateUrl(String objectKey, Integer second) {
GeneratePresignedUrlRequest generatePresignedUrlRequest =
new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey)
@ -192,6 +198,13 @@ public class OssClient {
return url.toString();
}
/**
* 检查配置是否相同
*/
public boolean checkPropertiesSame(OssProperties properties) {
return this.properties.equals(properties);
}
/**
* 获取当前桶权限类型
*

View File

@ -24,21 +24,6 @@ public class OssFactory {
private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>();
/**
* 初始化工厂
*/
public static void init() {
log.info("初始化OSS工厂");
RedisUtils.subscribe(OssConstant.DEFAULT_CONFIG_KEY, String.class, configKey -> {
OssClient client = getClient(configKey);
// 未初始化不处理
if (client != null) {
refresh(configKey);
log.info("订阅刷新OSS配置 => " + configKey);
}
});
}
/**
* 获取默认实例
*/
@ -55,25 +40,24 @@ public class OssFactory {
* 根据类型获取实例
*/
public static OssClient instance(String configKey) {
OssClient client = getClient(configKey);
if (client == null) {
refresh(configKey);
return getClient(configKey);
}
return client;
}
private static void refresh(String configKey) {
String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey);
if (json == null) {
throw new OssException("系统异常, '" + configKey + "'配置信息不存在!");
}
OssProperties properties = JsonUtils.parseObject(json, OssProperties.class);
OssClient client = CLIENT_CACHE.get(configKey);
if (client == null) {
CLIENT_CACHE.put(configKey, new OssClient(configKey, properties));
}
private static OssClient getClient(String configKey) {
log.info("创建OSS实例 key => {}", configKey);
return CLIENT_CACHE.get(configKey);
}
// 配置不相同则重新构建
if (!client.checkPropertiesSame(properties)) {
CLIENT_CACHE.put(configKey, new OssClient(configKey, properties));
log.info("重载OSS实例 key => {}", configKey);
return CLIENT_CACHE.get(configKey);
}
return client;
}
}

View File

@ -1,6 +1,6 @@
package com.ruoyi.common.ratelimiter.annotation;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.ratelimiter.enums.LimitType;
import java.lang.annotation.*;
@ -17,7 +17,7 @@ public @interface RateLimiter {
/**
* 限流key
*/
String key() default CacheConstants.RATE_LIMIT_KEY;
String key() default GlobalConstants.RATE_LIMIT_KEY;
/**
* 限流时间,单位秒

View File

@ -1,8 +1,10 @@
package com.ruoyi.common.satoken.config;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpLogic;
import com.ruoyi.common.satoken.core.dao.PlusSaTokenDao;
import com.ruoyi.common.satoken.core.service.SaPermissionImpl;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
@ -30,4 +32,12 @@ public class SaTokenConfig implements WebMvcConfigurer {
return new SaPermissionImpl();
}
/**
* 自定义dao层存储
*/
@Bean
public SaTokenDao saTokenDao() {
return new PlusSaTokenDao();
}
}

View File

@ -3,7 +3,6 @@ package com.ruoyi.common.satoken.core.dao;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.util.SaFoxUtil;
import com.ruoyi.common.redis.utils.RedisUtils;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.ArrayList;
@ -15,7 +14,6 @@ import java.util.List;
*
* @author Lion Li
*/
@Component
public class PlusSaTokenDao implements SaTokenDao {
/**

View File

@ -1,8 +1,11 @@
package com.ruoyi.common.satoken.utils;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import com.ruoyi.common.core.constant.TenantConstants;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.enums.DeviceType;
@ -12,6 +15,8 @@ import com.ruoyi.common.core.utils.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.Set;
/**
* 登录鉴权助手
* <p>
@ -37,8 +42,7 @@ public class LoginHelper {
*/
public static void login(LoginUser loginUser) {
SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
StpUtil.login(loginUser.getLoginId());
setLoginUser(loginUser);
StpUtil.login(loginUser.getLoginId(), new SaLoginModel().setExtra(LOGIN_USER_KEY, loginUser));
}
/**
@ -49,15 +53,9 @@ public class LoginHelper {
*/
public static void loginByDevice(LoginUser loginUser, DeviceType deviceType) {
SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
StpUtil.login(loginUser.getLoginId(), deviceType.getDevice());
setLoginUser(loginUser);
}
/**
* 设置用户数据(多级缓存)
*/
public static void setLoginUser(LoginUser loginUser) {
StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
StpUtil.login(loginUser.getLoginId(), new SaLoginModel()
.setDevice(deviceType.getDevice())
.setExtra(LOGIN_USER_KEY, loginUser));
}
/**
@ -68,7 +66,7 @@ public class LoginHelper {
if (loginUser != null) {
return loginUser;
}
loginUser = (LoginUser) StpUtil.getTokenSession().get(LOGIN_USER_KEY);
loginUser = ((JSONObject) StpUtil.getExtra(LOGIN_USER_KEY)).toBean(LoginUser.class);
SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
return loginUser;
}
@ -96,6 +94,19 @@ public class LoginHelper {
return loginUser.getUserId();
}
/**
* 获取租户ID
*/
public static String getTenantId() {
LoginUser loginUser;
try {
loginUser = getLoginUser();
} catch (Exception e) {
return null;
}
return loginUser.getTenantId();
}
/**
* 获取部门ID
*/
@ -119,17 +130,31 @@ public class LoginHelper {
}
/**
* 是否为管理员
* 是否为超级管理员
*
* @param userId 用户ID
* @return 结果
*/
public static boolean isAdmin(Long userId) {
public static boolean isSuperAdmin(Long userId) {
return UserConstants.SUPER_ADMIN_ID.equals(userId);
}
public static boolean isAdmin() {
return isAdmin(getUserId());
public static boolean isSuperAdmin() {
return isSuperAdmin(getUserId());
}
/**
* 是否为超级管理员
*
* @param rolePermission 角色权限标识组
* @return 结果
*/
public static boolean isTenantAdmin(Set<String> rolePermission) {
return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY);
}
public static boolean isTenantAdmin() {
return isTenantAdmin(getLoginUser().getRolePermission());
}
}

View File

@ -1,2 +1 @@
com.ruoyi.common.satoken.core.dao.PlusSaTokenDao
com.ruoyi.common.satoken.config.SaTokenConfig

View File

@ -22,12 +22,6 @@
<artifactId>ruoyi-common-satoken</artifactId>
</dependency>
<!-- Sa-Token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>${satoken.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -53,7 +53,7 @@ public class TencentSmsTemplate implements SmsTemplate {
throw new SmsException("模板ID不能为空");
}
SendSmsRequest req = new SendSmsRequest();
Set<String> set = Arrays.stream(phones.split(",")).map(p -> "+86" + p).collect(Collectors.toSet());
Set<String> set = Arrays.stream(phones.split(StringUtils.SEPARATOR)).map(p -> "+86" + p).collect(Collectors.toSet());
req.setPhoneNumberSet(ArrayUtil.toArray(set, String.class));
if (CollUtil.isNotEmpty(param)) {
req.setTemplateParamSet(ArrayUtil.toArray(param.values(), String.class));

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common-tenant</artifactId>
<description>
ruoyi-common-tenant 租户模块
</description>
<dependencies>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-mybatis</artifactId>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,100 @@
package com.ruoyi.common.tenant.config;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import com.ruoyi.common.core.utils.reflect.ReflectUtils;
import com.ruoyi.common.mybatis.config.MybatisPlusConfig;
import com.ruoyi.common.redis.config.RedisConfig;
import com.ruoyi.common.redis.config.properties.RedissonProperties;
import com.ruoyi.common.tenant.core.TenantSaTokenDao;
import com.ruoyi.common.tenant.handle.PlusTenantLineHandler;
import com.ruoyi.common.tenant.handle.TenantKeyPrefixHandler;
import com.ruoyi.common.tenant.manager.TenantSpringCacheManager;
import com.ruoyi.common.tenant.properties.TenantProperties;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.SingleServerConfig;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import java.util.ArrayList;
import java.util.List;
/**
* 租户配置类
*
* @author Lion Li
*/
@EnableConfigurationProperties(TenantProperties.class)
@AutoConfiguration(after = {RedisConfig.class, MybatisPlusConfig.class})
@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
public class TenantConfig {
/**
* 初始化租户配置
*/
@Bean
public boolean tenantInit(MybatisPlusInterceptor mybatisPlusInterceptor,
TenantProperties tenantProperties) {
List<InnerInterceptor> interceptors = new ArrayList<>();
// 多租户插件 必须放到第一位
interceptors.add(tenantLineInnerInterceptor(tenantProperties));
interceptors.addAll(mybatisPlusInterceptor.getInterceptors());
mybatisPlusInterceptor.setInterceptors(interceptors);
return true;
}
/**
* 多租户插件
*/
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties tenantProperties) {
return new TenantLineInnerInterceptor(new PlusTenantLineHandler(tenantProperties));
}
@Bean
public RedissonAutoConfigurationCustomizer tenantRedissonCustomizer(RedissonProperties redissonProperties) {
return config -> {
TenantKeyPrefixHandler nameMapper = new TenantKeyPrefixHandler(redissonProperties.getKeyPrefix());
SingleServerConfig singleServerConfig = ReflectUtils.invokeGetter(config, "singleServerConfig");
if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式
// 设置多租户 redis key前缀
singleServerConfig.setNameMapper(nameMapper);
ReflectUtils.invokeSetter(config, "singleServerConfig", singleServerConfig);
}
ClusterServersConfig clusterServersConfig = ReflectUtils.invokeGetter(config, "clusterServersConfig");
// 集群配置方式 参考下方注释
if (ObjectUtil.isNotNull(clusterServersConfig)) {
// 设置多租户 redis key前缀
clusterServersConfig.setNameMapper(nameMapper);
ReflectUtils.invokeSetter(config, "clusterServersConfig", clusterServersConfig);
}
};
}
/**
* 多租户缓存管理器
*/
@Primary
@Bean
public CacheManager tenantCacheManager() {
return new TenantSpringCacheManager();
}
/**
* 多租户鉴权dao实现
*/
@Primary
@Bean
public SaTokenDao tenantSaTokenDao() {
return new TenantSaTokenDao();
}
}

View File

@ -0,0 +1,21 @@
package com.ruoyi.common.tenant.core;
import com.ruoyi.common.mybatis.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 租户基类
*
* @author Michelle.Chung
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class TenantEntity extends BaseEntity {
/**
* 租户编号
*/
private String tenantId;
}

View File

@ -0,0 +1,114 @@
package com.ruoyi.common.tenant.core;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.satoken.core.dao.PlusSaTokenDao;
import java.util.List;
/**
* SaToken 认证数据持久层 适配多租户
*
* @author Lion Li
*/
public class TenantSaTokenDao extends PlusSaTokenDao {
@Override
public String get(String key) {
return super.get(GlobalConstants.GLOBAL_REDIS_KEY + key);
}
@Override
public void set(String key, String value, long timeout) {
super.set(GlobalConstants.GLOBAL_REDIS_KEY + key, value, timeout);
}
/**
* 修修改指定key-value键值对 (过期时间不变)
*/
@Override
public void update(String key, String value) {
super.update(GlobalConstants.GLOBAL_REDIS_KEY + key, value);
}
/**
* 删除Value
*/
@Override
public void delete(String key) {
super.delete(GlobalConstants.GLOBAL_REDIS_KEY + key);
}
/**
* 获取Value的剩余存活时间 (单位: )
*/
@Override
public long getTimeout(String key) {
return super.getTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
}
/**
* 修改Value的剩余存活时间 (单位: )
*/
@Override
public void updateTimeout(String key, long timeout) {
super.updateTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key, timeout);
}
/**
* 获取Object如无返空
*/
@Override
public Object getObject(String key) {
return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
}
/**
* 写入Object并设定存活时间 (单位: )
*/
@Override
public void setObject(String key, Object object, long timeout) {
super.setObject(GlobalConstants.GLOBAL_REDIS_KEY + key, object, timeout);
}
/**
* 更新Object (过期时间不变)
*/
@Override
public void updateObject(String key, Object object) {
super.updateObject(GlobalConstants.GLOBAL_REDIS_KEY + key, object);
}
/**
* 删除Object
*/
@Override
public void deleteObject(String key) {
super.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
}
/**
* 获取Object的剩余存活时间 (单位: )
*/
@Override
public long getObjectTimeout(String key) {
return super.getObjectTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
}
/**
* 修改Object的剩余存活时间 (单位: )
*/
@Override
public void updateObjectTimeout(String key, long timeout) {
super.updateObjectTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key, timeout);
}
/**
* 搜索数据
*/
@Override
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
return super.searchData(GlobalConstants.GLOBAL_REDIS_KEY + prefix, keyword, start, size, sortType);
}
}

View File

@ -0,0 +1,20 @@
package com.ruoyi.common.tenant.exception;
import com.ruoyi.common.core.exception.base.BaseException;
import java.io.Serial;
/**
* 租户异常类
*
* @author Lion Li
*/
public class TenantException extends BaseException {
@Serial
private static final long serialVersionUID = 1L;
public TenantException(String code, Object... args) {
super("tenant", code, args, null);
}
}

View File

@ -0,0 +1,57 @@
package com.ruoyi.common.tenant.handle;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.satoken.utils.LoginHelper;
import com.ruoyi.common.tenant.helper.TenantHelper;
import com.ruoyi.common.tenant.properties.TenantProperties;
import lombok.AllArgsConstructor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.NullValue;
import java.util.List;
/**
* 自定义租户处理器
*
* @author Lion Li
*/
@AllArgsConstructor
public class PlusTenantLineHandler implements TenantLineHandler {
private final TenantProperties tenantProperties;
@Override
public Expression getTenantId() {
String tenantId = LoginHelper.getTenantId();
if (StringUtils.isBlank(tenantId)) {
return new NullValue();
}
String dynamicTenantId = TenantHelper.getDynamic();
if (StringUtils.isNotBlank(dynamicTenantId)) {
// 返回动态租户
return new LongValue(dynamicTenantId);
}
// 返回固定租户
return new LongValue(tenantId);
}
@Override
public boolean ignoreTable(String tableName) {
String tenantId = LoginHelper.getTenantId();
// 判断是否有租户
if (StringUtils.isNotBlank(tenantId)) {
// 不需要过滤租户的表
List<String> excludes = tenantProperties.getExcludes();
// 非业务表
excludes.addAll(List.of(
"gen_table",
"gen_table_column"
));
return excludes.contains(tableName);
}
return true;
}
}

View File

@ -0,0 +1,58 @@
package com.ruoyi.common.tenant.handle;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.redis.handler.KeyPrefixHandler;
import com.ruoyi.common.tenant.helper.TenantHelper;
/**
* 多租户redis缓存key前缀处理
*
* @author Lion Li
*/
public class TenantKeyPrefixHandler extends KeyPrefixHandler {
public TenantKeyPrefixHandler(String keyPrefix) {
super(keyPrefix);
}
/**
* 增加前缀
*/
@Override
public String map(String name) {
if (StringUtils.isBlank(name)) {
return null;
}
if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
return super.map(name);
}
String tenantId = TenantHelper.getTenantId();
if (StringUtils.startsWith(name, tenantId)) {
// 如果存在则直接返回
return super.map(name);
}
return super.map(tenantId + ":" + name);
}
/**
* 去除前缀
*/
@Override
public String unmap(String name) {
String unmap = super.unmap(name);
if (StringUtils.isBlank(unmap)) {
return null;
}
if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
return super.unmap(name);
}
String tenantId = TenantHelper.getTenantId();
if (StringUtils.startsWith(unmap, tenantId)) {
// 如果存在则删除
return unmap.substring((tenantId + ":").length());
}
return unmap;
}
}

View File

@ -0,0 +1,112 @@
package com.ruoyi.common.tenant.helper;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.spring.SpringMVCUtil;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.utils.SpringUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.common.satoken.utils.LoginHelper;
import com.ruoyi.common.tenant.properties.TenantProperties;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 租户助手
*
* @author Lion Li
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TenantHelper {
private static final TenantProperties PROPERTIES = SpringUtils.getBean(TenantProperties.class);
private static final String DYNAMIC_TENANT_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicTenant";
private static final ThreadLocal<String> TEMP_DYNAMIC_TENANT = new TransmittableThreadLocal<>();
/**
* 租户功能是否启用
*/
public static boolean isEnable() {
return PROPERTIES.getEnable();
}
/**
* 开启忽略租户(开启后需手动调用 {@link #disableIgnore()} 关闭)
*/
public static void enableIgnore() {
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
}
/**
* 关闭忽略租户
*/
public static void disableIgnore() {
InterceptorIgnoreHelper.clearIgnoreStrategy();
}
/**
* 设置动态租户(一直有效 需要手动清理)
* <p>
* 如果为非web环境 那么只在当前线程内生效
*/
public static void setDynamic(String tenantId) {
if (!SpringMVCUtil.isWeb()) {
TEMP_DYNAMIC_TENANT.set(tenantId);
return;
}
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getTenantId();
RedisUtils.setCacheObject(cacheKey, tenantId);
SaHolder.getStorage().set(cacheKey, tenantId);
}
/**
* 获取动态租户(一直有效 需要手动清理)
* <p>
* 如果为非web环境 那么只在当前线程内生效
*/
public static String getDynamic() {
if (!SpringMVCUtil.isWeb()) {
return TEMP_DYNAMIC_TENANT.get();
}
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getTenantId();
String tenantId = (String) SaHolder.getStorage().get(cacheKey);
if (StringUtils.isNotBlank(tenantId)) {
return tenantId;
}
tenantId = RedisUtils.getCacheObject(cacheKey);
SaHolder.getStorage().set(cacheKey, tenantId);
return tenantId;
}
/**
* 清除动态租户
*/
public static void clearDynamic() {
if (!SpringMVCUtil.isWeb()) {
TEMP_DYNAMIC_TENANT.remove();
return;
}
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getTenantId();
RedisUtils.deleteObject(cacheKey);
SaHolder.getStorage().delete(cacheKey);
}
/**
* 获取当前租户id(动态租户优先)
*/
public static String getTenantId() {
String tenantId = TenantHelper.getDynamic();
if (StringUtils.isBlank(tenantId)) {
tenantId = LoginHelper.getTenantId();
}
return tenantId;
}
}

View File

@ -0,0 +1,32 @@
package com.ruoyi.common.tenant.manager;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.redis.manager.PlusSpringCacheManager;
import com.ruoyi.common.tenant.helper.TenantHelper;
import org.springframework.cache.Cache;
/**
* 重写 cacheName 处理方法 支持多租户
*
* @author Lion Li
*/
public class TenantSpringCacheManager extends PlusSpringCacheManager {
public TenantSpringCacheManager() {
}
@Override
public Cache getCache(String name) {
if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
return super.getCache(name);
}
String tenantId = TenantHelper.getTenantId();
if (StringUtils.startsWith(name, tenantId)) {
// 如果存在则直接返回
return super.getCache(name);
}
return super.getCache(tenantId + ":" + name);
}
}

View File

@ -0,0 +1,27 @@
package com.ruoyi.common.tenant.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* 租户 配置属性
*
* @author Lion Li
*/
@Data
@ConfigurationProperties(prefix = "tenant")
public class TenantProperties {
/**
* 是否启用
*/
private Boolean enable;
/**
* 排除表
*/
private List<String> excludes;
}

View File

@ -30,7 +30,7 @@ public class FilterConfig {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new XssFilter());
registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), ","));
registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), StringUtils.SEPARATOR));
registration.setName("xssFilter");
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
Map<String, String> initParameters = new HashMap<>();

View File

@ -14,6 +14,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "captcha")
public class CaptchaProperties {
private Boolean enable;
/**
* 验证码类型
*/

View File

@ -25,7 +25,7 @@ public class XssFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
String tempExcludes = filterConfig.getInitParameter("excludes");
if (StringUtils.isNotEmpty(tempExcludes)) {
String[] url = tempExcludes.split(",");
String[] url = tempExcludes.split(StringUtils.SEPARATOR);
for (int i = 0; url != null && i < url.length; i++) {
excludes.add(url[i]);
}

View File

@ -45,7 +45,7 @@ import java.util.concurrent.TimeUnit;
@RequestMapping("/demo/demo")
public class TestDemoController extends BaseController {
private final ITestDemoService iTestDemoService;
private final ITestDemoService testDemoService;
/**
* 查询测试单表列表
@ -53,7 +53,7 @@ public class TestDemoController extends BaseController {
@SaCheckPermission("demo:demo:list")
@GetMapping("/list")
public TableDataInfo<TestDemoVo> list(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) {
return iTestDemoService.queryPageList(bo, pageQuery);
return testDemoService.queryPageList(bo, pageQuery);
}
/**
@ -62,7 +62,7 @@ public class TestDemoController extends BaseController {
@SaCheckPermission("demo:demo:list")
@GetMapping("/page")
public TableDataInfo<TestDemoVo> page(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) {
return iTestDemoService.customPageList(bo, pageQuery);
return testDemoService.customPageList(bo, pageQuery);
}
/**
@ -77,7 +77,7 @@ public class TestDemoController extends BaseController {
ExcelResult<TestDemoImportVo> excelResult = ExcelUtil.importExcel(file.getInputStream(), TestDemoImportVo.class, true);
List<TestDemoImportVo> volist = excelResult.getList();
List<TestDemo> list = BeanUtil.copyToList(volist, TestDemo.class);
iTestDemoService.saveBatch(list);
testDemoService.saveBatch(list);
return R.ok(excelResult.getAnalysis());
}
@ -88,7 +88,7 @@ public class TestDemoController extends BaseController {
@Log(title = "测试单表", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(@Validated TestDemoBo bo, HttpServletResponse response) {
List<TestDemoVo> list = iTestDemoService.queryList(bo);
List<TestDemoVo> list = testDemoService.queryList(bo);
// 测试雪花id导出
// for (TestDemoVo vo : list) {
// vo.setId(1234567891234567893L);
@ -105,7 +105,7 @@ public class TestDemoController extends BaseController {
@GetMapping("/{id}")
public R<TestDemoVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable("id") Long id) {
return R.ok(iTestDemoService.queryById(id));
return R.ok(testDemoService.queryById(id));
}
/**
@ -119,7 +119,7 @@ public class TestDemoController extends BaseController {
// 使用校验工具对标 @Validated(AddGroup.class) 注解
// 用于在非 Controller 的地方校验对象
ValidatorUtils.validate(bo, AddGroup.class);
return toAjax(iTestDemoService.insertByBo(bo));
return toAjax(testDemoService.insertByBo(bo));
}
/**
@ -130,7 +130,7 @@ public class TestDemoController extends BaseController {
@RepeatSubmit
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody TestDemoBo bo) {
return toAjax(iTestDemoService.updateByBo(bo));
return toAjax(testDemoService.updateByBo(bo));
}
/**
@ -143,6 +143,6 @@ public class TestDemoController extends BaseController {
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(iTestDemoService.deleteWithValidByIds(Arrays.asList(ids), true));
return toAjax(testDemoService.deleteWithValidByIds(Arrays.asList(ids), true));
}
}

View File

@ -35,7 +35,7 @@ import java.util.List;
@RequestMapping("/demo/tree")
public class TestTreeController extends BaseController {
private final ITestTreeService iTestTreeService;
private final ITestTreeService testTreeService;
/**
* 查询测试树表列表
@ -43,7 +43,7 @@ public class TestTreeController extends BaseController {
@SaCheckPermission("demo:tree:list")
@GetMapping("/list")
public R<List<TestTreeVo>> list(@Validated(QueryGroup.class) TestTreeBo bo) {
List<TestTreeVo> list = iTestTreeService.queryList(bo);
List<TestTreeVo> list = testTreeService.queryList(bo);
return R.ok(list);
}
@ -54,7 +54,7 @@ public class TestTreeController extends BaseController {
@Log(title = "测试树表", businessType = BusinessType.EXPORT)
@GetMapping("/export")
public void export(@Validated TestTreeBo bo, HttpServletResponse response) {
List<TestTreeVo> list = iTestTreeService.queryList(bo);
List<TestTreeVo> list = testTreeService.queryList(bo);
ExcelUtil.exportExcel(list, "测试树表", TestTreeVo.class, response);
}
@ -67,7 +67,7 @@ public class TestTreeController extends BaseController {
@GetMapping("/{id}")
public R<TestTreeVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable("id") Long id) {
return R.ok(iTestTreeService.queryById(id));
return R.ok(testTreeService.queryById(id));
}
/**
@ -78,7 +78,7 @@ public class TestTreeController extends BaseController {
@RepeatSubmit
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody TestTreeBo bo) {
return toAjax(iTestTreeService.insertByBo(bo));
return toAjax(testTreeService.insertByBo(bo));
}
/**
@ -89,7 +89,7 @@ public class TestTreeController extends BaseController {
@RepeatSubmit
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody TestTreeBo bo) {
return toAjax(iTestTreeService.updateByBo(bo));
return toAjax(testTreeService.updateByBo(bo));
}
/**
@ -102,6 +102,6 @@ public class TestTreeController extends BaseController {
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(iTestTreeService.deleteWithValidByIds(Arrays.asList(ids), true));
return toAjax(testTreeService.deleteWithValidByIds(Arrays.asList(ids), true));
}
}

View File

@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import com.ruoyi.common.mybatis.core.domain.TreeEntity;
import com.ruoyi.common.mybatis.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -19,7 +19,7 @@ import java.io.Serial;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("test_tree")
public class TestTree extends TreeEntity<TestTree> {
public class TestTree extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
@ -30,6 +30,11 @@ public class TestTree extends TreeEntity<TestTree> {
@TableId(value = "id")
private Long id;
/**
* 父ID
*/
private Long parentId;
/**
* 部门id
*/

View File

@ -2,12 +2,11 @@ package com.ruoyi.demo.domain.bo;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import com.ruoyi.common.mybatis.core.domain.TreeEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.ruoyi.common.mybatis.core.domain.BaseEntity;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 测试树表业务对象 test_tree
@ -18,7 +17,7 @@ import jakarta.validation.constraints.NotNull;
@Data
@EqualsAndHashCode(callSuper = true)
public class TestTreeBo extends TreeEntity<TestTreeBo> {
public class TestTreeBo extends BaseEntity {
/**
* 主键
@ -26,6 +25,11 @@ public class TestTreeBo extends TreeEntity<TestTreeBo> {
@NotNull(message = "主键不能为空", groups = {EditGroup.class})
private Long id;
/**
* 父ID
*/
private Long parentId;
/**
* 部门id
*/

View File

@ -69,35 +69,30 @@ public interface GenConstants {
* BO对象 不需要添加字段
*/
String[] COLUMNNAME_NOT_ADD = {"create_dept", "create_by", "create_time", "del_flag", "update_by",
"update_time", "version"};
"update_time", "version", "tenant_id"};
/**
* BO对象 不需要编辑字段
*/
String[] COLUMNNAME_NOT_EDIT = {"create_dept", "create_by", "create_time", "del_flag", "update_by",
"update_time", "version"};
"update_time", "version", "tenant_id"};
/**
* VO对象 不需要返回字段
*/
String[] COLUMNNAME_NOT_LIST = {"create_dept", "create_by", "create_time", "del_flag", "update_by",
"update_time", "version"};
"update_time", "version", "tenant_id"};
/**
* BO对象 不需要查询字段
*/
String[] COLUMNNAME_NOT_QUERY = {"id", "create_dept", "create_by", "create_time", "del_flag", "update_by",
"update_time", "remark", "version"};
"update_time", "remark", "version", "tenant_id"};
/**
* Entity基类字段
*/
String[] BASE_ENTITY = {"createDept", "createBy", "createTime", "updateBy", "updateTime"};
/**
* Tree基类字段
*/
String[] TREE_ENTITY = {"parentName", "parentId", "children"};
String[] BASE_ENTITY = {"createDept", "createBy", "createTime", "updateBy", "updateTime", "tenantId"};
/**
* 文本框

View File

@ -1,14 +1,16 @@
package com.ruoyi.generator.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.ruoyi.generator.constant.GenConstants;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.mybatis.core.domain.BaseEntity;
import com.ruoyi.generator.constant.GenConstants;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.ArrayUtils;
import java.util.List;
@ -183,10 +185,6 @@ public class GenTable extends BaseEntity {
}
public static boolean isSuperColumn(String tplCategory, String javaField) {
if (isTree(tplCategory)) {
return StringUtils.equalsAnyIgnoreCase(javaField,
ArrayUtils.addAll(GenConstants.TREE_ENTITY, GenConstants.BASE_ENTITY));
}
return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY);
}
}

View File

@ -212,7 +212,7 @@ public class GenTableColumn extends BaseEntity {
if (StringUtils.isNotEmpty(value)) {
Object startStr = value.subSequence(0, 1);
String endStr = value.substring(1);
sb.append("").append(startStr).append("=").append(endStr).append(",");
sb.append(StringUtils.EMPTY).append(startStr).append("=").append(endStr).append(StringUtils.SEPARATOR);
}
}
return sb.deleteCharAt(sb.length() - 1).toString();

View File

@ -59,7 +59,7 @@ public class GenUtils {
column.setHtmlType(GenConstants.HTML_INPUT);
// 如果是浮点型 统一用BigDecimal
String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ",");
String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), StringUtils.SEPARATOR);
if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) {
column.setJavaType(GenConstants.TYPE_BIGDECIMAL);
}
@ -168,7 +168,7 @@ public class GenUtils {
boolean autoRemovePre = GenConfig.getAutoRemovePre();
String tablePrefix = GenConfig.getTablePrefix();
if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) {
String[] searchList = StringUtils.split(tablePrefix, ",");
String[] searchList = StringUtils.split(tablePrefix, StringUtils.SEPARATOR);
tableName = replaceFirst(tableName, searchList);
}
return StringUtils.convertToCamelCase(tableName);

View File

@ -225,7 +225,7 @@ public class VelocityUtils {
*/
public static String getDicts(GenTable genTable) {
List<GenTableColumn> columns = genTable.getColumns();
Set<String> dicts = new HashSet<String>();
Set<String> dicts = new HashSet<>();
addDicts(dicts, columns);
return StringUtils.join(dicts, ", ");
}

View File

@ -1,21 +1,16 @@
package ${packageName}.domain.bo;
import com.ruoyi.common.mybatis.core.domain.BaseEntity;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.util.Date;
#foreach ($import in $importList)
import ${import};
#end
#if($table.crud || $table.sub)
import com.ruoyi.common.mybatis.core.domain.BaseEntity;
#elseif($table.tree)
import com.ruoyi.common.mybatis.core.domain.TreeEntity;
#end
import java.util.Date;
/**
* ${functionName}业务对象 ${tableName}
@ -23,15 +18,9 @@ import com.ruoyi.common.mybatis.core.domain.TreeEntity;
* @author ${author}
* @date ${datetime}
*/
#if($table.crud || $table.sub)
#set($Entity="BaseEntity")
#elseif($table.tree)
#set($Entity="TreeEntity<${ClassName}Bo>")
#end
@Data
@EqualsAndHashCode(callSuper = true)
public class ${ClassName}Bo extends ${Entity} {
public class ${ClassName}Bo extends BaseEntity {
#foreach ($column in $columns)
#if(!$table.isSuperColumn($column.javaField) && ($column.query || $column.isInsert || $column.isEdit))

View File

@ -39,7 +39,7 @@ import com.ruoyi.common.mybatis.core.page.TableDataInfo;
@RequestMapping("/${moduleName}/${businessName}")
public class ${ClassName}Controller extends BaseController {
private final I${ClassName}Service i${ClassName}Service;
private final I${ClassName}Service ${className}Service;
/**
* 查询${functionName}列表
@ -52,7 +52,7 @@ public class ${ClassName}Controller extends BaseController {
}
#elseif($table.tree)
public R<List<${ClassName}Vo>> list(${ClassName}Bo bo) {
List<${ClassName}Vo> list = i${ClassName}Service.queryList(bo);
List<${ClassName}Vo> list = ${className}Service.queryList(bo);
return R.ok(list);
}
#end
@ -64,7 +64,7 @@ public class ${ClassName}Controller extends BaseController {
@Log(title = "${functionName}", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(${ClassName}Bo bo, HttpServletResponse response) {
List<${ClassName}Vo> list = i${ClassName}Service.queryList(bo);
List<${ClassName}Vo> list = ${className}Service.queryList(bo);
ExcelUtil.exportExcel(list, "${functionName}", ${ClassName}Vo.class, response);
}
@ -77,7 +77,7 @@ public class ${ClassName}Controller extends BaseController {
@GetMapping("/{${pkColumn.javaField}}")
public R<${ClassName}Vo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable ${pkColumn.javaType} ${pkColumn.javaField}) {
return R.ok(i${ClassName}Service.queryById(${pkColumn.javaField}));
return R.ok(${className}Service.queryById(${pkColumn.javaField}));
}
/**
@ -88,7 +88,7 @@ public class ${ClassName}Controller extends BaseController {
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody ${ClassName}Bo bo) {
return toAjax(i${ClassName}Service.insertByBo(bo));
return toAjax(${className}Service.insertByBo(bo));
}
/**
@ -99,7 +99,7 @@ public class ${ClassName}Controller extends BaseController {
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody ${ClassName}Bo bo) {
return toAjax(i${ClassName}Service.updateByBo(bo));
return toAjax(${className}Service.updateByBo(bo));
}
/**
@ -112,6 +112,6 @@ public class ${ClassName}Controller extends BaseController {
@DeleteMapping("/{${pkColumn.javaField}s}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) {
return toAjax(i${ClassName}Service.deleteWithValidByIds(List.of(${pkColumn.javaField}s), true));
return toAjax(${className}Service.deleteWithValidByIds(List.of(${pkColumn.javaField}s), true));
}
}

View File

@ -1,21 +1,25 @@
package ${packageName}.domain;
#foreach ($column in $columns)
#if($column.javaField=='tenantId')
#set($IsTenant=1)
#end
#end
#if($IsTenant==1)
import com.ruoyi.common.tenant.core.TenantEntity;
#else
import com.ruoyi.common.mybatis.core.domain.BaseEntity;
#end
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.math.BigDecimal;
#foreach ($import in $importList)
import ${import};
#end
#if($table.crud || $table.sub)
import com.ruoyi.common.mybatis.core.domain.BaseEntity;
#elseif($table.tree)
import com.ruoyi.common.mybatis.core.domain.TreeEntity;
#end
import java.io.Serial;
import java.util.Date;
import java.math.BigDecimal;
/**
* ${functionName}对象 ${tableName}
@ -23,10 +27,10 @@ import com.ruoyi.common.mybatis.core.domain.TreeEntity;
* @author ${author}
* @date ${datetime}
*/
#if($table.crud || $table.sub)
#if($IsTenant==1)
#set($Entity="TenantEntity")
#else
#set($Entity="BaseEntity")
#elseif($table.tree)
#set($Entity="TreeEntity<${ClassName}>")
#end
@Data
@EqualsAndHashCode(callSuper = true)
@ -51,6 +55,7 @@ public class ${ClassName} extends ${Entity} {
@TableId(value = "$column.columnName")
#end
private $column.javaType $column.javaField;
#end
#end

View File

@ -333,6 +333,7 @@ export default {
// 表单校验
rules: {
#foreach ($column in $columns)
#if($column.isInsert || $column.isEdit)
#if($column.required)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
@ -344,6 +345,7 @@ export default {
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
]#if($foreach.count != $columns.size()),#end
#end
#end
#end
}
};
@ -404,11 +406,13 @@ export default {
reset() {
this.form = {
#foreach ($column in $columns)
#if($column.isInsert || $column.isEdit)
#if($column.htmlType == "checkbox")
$column.javaField: []#if($foreach.count != $columns.size()),#end
#else
$column.javaField: null#if($foreach.count != $columns.size()),#end
#end
#end
#end
};
this.resetForm("form");

View File

@ -343,6 +343,7 @@ export default {
// 表单校验
rules: {
#foreach ($column in $columns)
#if($column.isInsert || $column.isEdit)
#if($column.required)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
@ -354,6 +355,7 @@ export default {
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
]#if($foreach.count != $columns.size()),#end
#end
#end
#end
}
};
@ -395,11 +397,13 @@ export default {
reset() {
this.form = {
#foreach ($column in $columns)
#if($column.isInsert || $column.isEdit)
#if($column.htmlType == "checkbox")
$column.javaField: []#if($foreach.count != $columns.size()),#end
#else
$column.javaField: undefined#if($foreach.count != $columns.size()),#end
#end
#end
#end
};
this.resetForm("form");

View File

@ -304,6 +304,7 @@ const data = reactive({
},
rules: {
#foreach ($column in $columns)
#if($column.isInsert || $column.isEdit)
#if($column.required)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
@ -315,6 +316,7 @@ const data = reactive({
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
]#if($foreach.count != $columns.size()),#end
#end
#end
#end
}
});
@ -365,11 +367,13 @@ function cancel() {
function reset() {
form.value = {
#foreach ($column in $columns)
#if($column.isInsert || $column.isEdit)
#if($column.htmlType == "checkbox")
$column.javaField: []#if($foreach.count != $columns.size()),#end
#else
$column.javaField: null#if($foreach.count != $columns.size()),#end
#end
#end
#end
};
proxy.resetForm("${businessName}Ref");

View File

@ -315,6 +315,7 @@ const data = reactive({
},
rules: {
#foreach ($column in $columns)
#if($column.isInsert || $column.isEdit)
#if($column.required)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
@ -326,6 +327,7 @@ const data = reactive({
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
]#if($foreach.count != $columns.size()),#end
#end
#end
#end
}
});
@ -367,11 +369,13 @@ function cancel() {
function reset() {
form.value = {
#foreach ($column in $columns)
#if($column.isInsert || $column.isEdit)
#if($column.htmlType == "checkbox")
$column.javaField: []#if($foreach.count != $columns.size()),#end
#else
$column.javaField: null#if($foreach.count != $columns.size()),#end
#end
#end
#end
};
proxy.resetForm("${businessName}Ref");

View File

@ -63,7 +63,7 @@
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-satoken</artifactId>
<artifactId>ruoyi-common-tenant</artifactId>
</dependency>
<dependency>

View File

@ -1,168 +0,0 @@
package com.ruoyi.system.controller.monitor;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.collection.CollUtil;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.CacheNames;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.StreamUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.json.utils.JsonUtils;
import com.ruoyi.common.redis.utils.CacheUtils;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.system.domain.SysCache;
import com.ruoyi.system.domain.vo.CacheListInfoVo;
import lombok.RequiredArgsConstructor;
import org.redisson.spring.data.connection.RedissonConnectionFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* 缓存监控
*
* @author Lion Li
*/
@RequiredArgsConstructor
@RestController
@RequestMapping("/monitor/cache")
public class CacheController {
private final RedissonConnectionFactory connectionFactory;
private final static List<SysCache> CACHES = new ArrayList<>();
static {
CACHES.add(new SysCache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息"));
CACHES.add(new SysCache(CacheConstants.ONLINE_TOKEN_KEY, "在线用户"));
CACHES.add(new SysCache(CacheNames.SYS_CONFIG, "配置信息"));
CACHES.add(new SysCache(CacheNames.SYS_DICT, "数据字典"));
CACHES.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码"));
CACHES.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交"));
CACHES.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理"));
CACHES.add(new SysCache(CacheNames.SYS_OSS_CONFIG, "OSS配置"));
CACHES.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数"));
}
/**
* 获取缓存监控列表
*/
@SaCheckPermission("monitor:cache:list")
@GetMapping()
public R<CacheListInfoVo> getInfo() throws Exception {
RedisConnection connection = connectionFactory.getConnection();
Properties commandStats = connection.commands().info("commandstats");
List<Map<String, String>> pieList = new ArrayList<>();
if (commandStats != null) {
commandStats.stringPropertyNames().forEach(key -> {
Map<String, String> data = new HashMap<>(2);
String property = commandStats.getProperty(key);
data.put("name", StringUtils.removeStart(key, "cmdstat_"));
data.put("value", StringUtils.substringBetween(property, "calls=", ",usec"));
pieList.add(data);
});
}
CacheListInfoVo infoVo = new CacheListInfoVo();
infoVo.setInfo(connection.commands().info());
infoVo.setDbSize(connection.commands().dbSize());
infoVo.setCommandStats(pieList);
return R.ok(infoVo);
}
/**
* 获取缓存监控缓存名列表
*/
@SaCheckPermission("monitor:cache:list")
@GetMapping("/getNames")
public R<List<SysCache>> cache() {
return R.ok(CACHES);
}
/**
* 获取缓存监控Key列表
*
* @param cacheName 缓存名
*/
@SaCheckPermission("monitor:cache:list")
@GetMapping("/getKeys/{cacheName}")
public R<Collection<String>> getCacheKeys(@PathVariable String cacheName) {
Collection<String> cacheKeys = new HashSet<>(0);
if (isCacheNames(cacheName)) {
Set<Object> keys = CacheUtils.keys(cacheName);
if (CollUtil.isNotEmpty(keys)) {
cacheKeys = StreamUtils.toList(keys, Object::toString);
}
} else {
cacheKeys = RedisUtils.keys(cacheName + "*");
}
return R.ok(cacheKeys);
}
/**
* 获取缓存监控缓存值详情
*
* @param cacheName 缓存名
* @param cacheKey 缓存key
*/
@SaCheckPermission("monitor:cache:list")
@GetMapping("/getValue/{cacheName}/{cacheKey}")
public R<SysCache> getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey) {
Object cacheValue;
if (isCacheNames(cacheName)) {
cacheValue = CacheUtils.get(cacheName, cacheKey);
} else {
cacheValue = RedisUtils.getCacheObject(cacheKey);
}
SysCache sysCache = new SysCache(cacheName, cacheKey, JsonUtils.toJsonString(cacheValue));
return R.ok(sysCache);
}
/**
* 清理缓存监控缓存名
*
* @param cacheName 缓存名
*/
@SaCheckPermission("monitor:cache:list")
@DeleteMapping("/clearCacheName/{cacheName}")
public R<Void> clearCacheName(@PathVariable String cacheName) {
if (isCacheNames(cacheName)) {
CacheUtils.clear(cacheName);
} else {
RedisUtils.deleteKeys(cacheName + "*");
}
return R.ok();
}
/**
* 清理缓存监控Key
*
* @param cacheKey key名
*/
@SaCheckPermission("monitor:cache:list")
@DeleteMapping("/clearCacheKey/{cacheName}/{cacheKey}")
public R<Void> clearCacheKey(@PathVariable String cacheName, @PathVariable String cacheKey) {
if (isCacheNames(cacheName)) {
CacheUtils.evict(cacheName, cacheKey);
} else {
RedisUtils.deleteObject(cacheKey);
}
return R.ok();
}
/**
* 清理全部缓存监控
*/
@SaCheckPermission("monitor:cache:list")
@DeleteMapping("/clearCacheAll")
public R<Void> clearCacheAll() {
RedisUtils.deleteKeys("*");
return R.ok();
}
private boolean isCacheNames(String cacheName) {
return !StringUtils.contains(cacheName, ":");
}
}

View File

@ -1,22 +1,23 @@
package com.ruoyi.system.controller.monitor;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.mybatis.core.page.PageQuery;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.mybatis.core.page.TableDataInfo;
import com.ruoyi.common.web.core.BaseController;
import com.ruoyi.common.excel.utils.ExcelUtil;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.mybatis.core.page.PageQuery;
import com.ruoyi.common.mybatis.core.page.TableDataInfo;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.system.domain.SysLogininfor;
import com.ruoyi.common.web.core.BaseController;
import com.ruoyi.system.domain.bo.SysLogininforBo;
import com.ruoyi.system.domain.vo.SysLogininforVo;
import com.ruoyi.system.service.ISysLogininforService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
@ -37,7 +38,7 @@ public class SysLogininforController extends BaseController {
*/
@SaCheckPermission("monitor:logininfor:list")
@GetMapping("/list")
public TableDataInfo<SysLogininfor> list(SysLogininfor logininfor, PageQuery pageQuery) {
public TableDataInfo<SysLogininforVo> list(SysLogininforBo logininfor, PageQuery pageQuery) {
return logininforService.selectPageLogininforList(logininfor, pageQuery);
}
@ -47,9 +48,9 @@ public class SysLogininforController extends BaseController {
@Log(title = "登录日志", businessType = BusinessType.EXPORT)
@SaCheckPermission("monitor:logininfor:export")
@PostMapping("/export")
public void export(SysLogininfor logininfor, HttpServletResponse response) {
List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
ExcelUtil.exportExcel(list, "登录日志", SysLogininfor.class, response);
public void export(SysLogininforBo logininfor, HttpServletResponse response) {
List<SysLogininforVo> list = logininforService.selectLogininforList(logininfor);
ExcelUtil.exportExcel(list, "登录日志", SysLogininforVo.class, response);
}
/**
@ -78,7 +79,7 @@ public class SysLogininforController extends BaseController {
@Log(title = "账户解锁", businessType = BusinessType.OTHER)
@GetMapping("/unlock/{userName}")
public R<Void> unlock(@PathVariable("userName") String userName) {
String loginName = CacheConstants.PWD_ERR_CNT_KEY + userName;
String loginName = GlobalConstants.PWD_ERR_CNT_KEY + userName;
if (RedisUtils.hasKey(loginName)) {
RedisUtils.deleteObject(loginName);
}

View File

@ -8,7 +8,8 @@ import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.mybatis.core.page.TableDataInfo;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.excel.utils.ExcelUtil;
import com.ruoyi.system.domain.SysOperLog;
import com.ruoyi.system.domain.bo.SysOperLogBo;
import com.ruoyi.system.domain.vo.SysOperLogVo;
import com.ruoyi.system.service.ISysOperLogService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
@ -35,7 +36,7 @@ public class SysOperlogController extends BaseController {
*/
@SaCheckPermission("monitor:operlog:list")
@GetMapping("/list")
public TableDataInfo<SysOperLog> list(SysOperLog operLog, PageQuery pageQuery) {
public TableDataInfo<SysOperLogVo> list(SysOperLogBo operLog, PageQuery pageQuery) {
return operLogService.selectPageOperLogList(operLog, pageQuery);
}
@ -45,9 +46,9 @@ public class SysOperlogController extends BaseController {
@Log(title = "操作日志", businessType = BusinessType.EXPORT)
@SaCheckPermission("monitor:operlog:export")
@PostMapping("/export")
public void export(SysOperLog operLog, HttpServletResponse response) {
List<SysOperLog> list = operLogService.selectOperLogList(operLog);
ExcelUtil.exportExcel(list, "操作日志", SysOperLog.class, response);
public void export(SysOperLogBo operLog, HttpServletResponse response) {
List<SysOperLogVo> list = operLogService.selectOperLogList(operLog);
ExcelUtil.exportExcel(list, "操作日志", SysOperLogVo.class, response);
}
/**

View File

@ -4,16 +4,17 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.web.core.BaseController;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.domain.dto.UserOnlineDTO;
import com.ruoyi.common.mybatis.core.page.TableDataInfo;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.core.utils.StreamUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.mybatis.core.page.TableDataInfo;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.common.web.core.BaseController;
import com.ruoyi.system.domain.SysUserOnline;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@ -45,7 +46,7 @@ public class SysUserOnlineController extends BaseController {
List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
List<UserOnlineDTO> userOnlineDTOList = new ArrayList<>();
for (String key : keys) {
String token = key.replace(CacheConstants.LOGIN_TOKEN_KEY, "");
String token = key.replace(GlobalConstants.LOGIN_TOKEN_KEY, "");
// 如果已经过期则跳过
if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) {
continue;

View File

@ -1,13 +1,13 @@
package com.ruoyi.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ArrayUtil;
import com.ruoyi.common.log.annotation.Log;
import cn.hutool.core.convert.Convert;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.web.core.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.web.core.BaseController;
import com.ruoyi.system.domain.bo.SysDeptBo;
import com.ruoyi.system.domain.vo.SysDeptVo;
import com.ruoyi.system.service.ISysDeptService;
@ -50,7 +50,7 @@ public class SysDeptController extends BaseController {
public R<List<SysDeptVo>> excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) {
List<SysDeptVo> depts = deptService.selectDeptList(new SysDeptBo());
depts.removeIf(d -> d.getDeptId().equals(deptId)
|| ArrayUtil.contains(StringUtils.split(d.getAncestors(), ","), deptId + ""));
|| StringUtils.splitList(d.getAncestors()).contains(Convert.toStr(deptId)));
return R.ok(depts);
}

View File

@ -1,7 +1,10 @@
package com.ruoyi.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.lang.tree.Tree;
import com.ruoyi.common.core.constant.TenantConstants;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.StringUtils;
@ -35,6 +38,10 @@ public class SysMenuController extends BaseController {
/**
* 获取菜单列表
*/
@SaCheckRole(value = {
TenantConstants.SUPER_ADMIN_ROLE_KEY,
TenantConstants.TENANT_ADMIN_ROLE_KEY
}, mode = SaMode.OR)
@SaCheckPermission("system:menu:list")
@GetMapping("/list")
public R<List<SysMenuVo>> list(SysMenuBo menu) {
@ -47,6 +54,10 @@ public class SysMenuController extends BaseController {
*
* @param menuId 菜单ID
*/
@SaCheckRole(value = {
TenantConstants.SUPER_ADMIN_ROLE_KEY,
TenantConstants.TENANT_ADMIN_ROLE_KEY
}, mode = SaMode.OR)
@SaCheckPermission("system:menu:query")
@GetMapping(value = "/{menuId}")
public R<SysMenuVo> getInfo(@PathVariable Long menuId) {
@ -56,6 +67,11 @@ public class SysMenuController extends BaseController {
/**
* 获取菜单下拉树列表
*/
@SaCheckRole(value = {
TenantConstants.SUPER_ADMIN_ROLE_KEY,
TenantConstants.TENANT_ADMIN_ROLE_KEY
}, mode = SaMode.OR)
@SaCheckPermission("system:menu:query")
@GetMapping("/treeselect")
public R<List<Tree<Long>>> treeselect(SysMenuBo menu) {
List<SysMenuVo> menus = menuService.selectMenuList(menu, LoginHelper.getUserId());
@ -67,6 +83,11 @@ public class SysMenuController extends BaseController {
*
* @param roleId 角色ID
*/
@SaCheckRole(value = {
TenantConstants.SUPER_ADMIN_ROLE_KEY,
TenantConstants.TENANT_ADMIN_ROLE_KEY
}, mode = SaMode.OR)
@SaCheckPermission("system:menu:query")
@GetMapping(value = "/roleMenuTreeselect/{roleId}")
public R<MenuTreeSelectVo> roleMenuTreeselect(@PathVariable("roleId") Long roleId) {
List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
@ -76,9 +97,26 @@ public class SysMenuController extends BaseController {
return R.ok(selectVo);
}
/**
* 加载对应租户套餐菜单列表树
*
* @param packageId 租户套餐ID
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:menu:query")
@GetMapping(value = "/tenantPackageMenuTreeselect/{packageId}")
public R<MenuTreeSelectVo> tenantPackageMenuTreeselect(@PathVariable("packageId") Long packageId) {
List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
MenuTreeSelectVo selectVo = new MenuTreeSelectVo();
selectVo.setCheckedKeys(menuService.selectMenuListByPackageId(packageId));
selectVo.setMenus(menuService.buildMenuTreeSelect(menus));
return R.ok(selectVo);
}
/**
* 新增菜单
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:menu:add")
@Log(title = "菜单管理", businessType = BusinessType.INSERT)
@PostMapping
@ -94,6 +132,7 @@ public class SysMenuController extends BaseController {
/**
* 修改菜单
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:menu:edit")
@Log(title = "菜单管理", businessType = BusinessType.UPDATE)
@PutMapping
@ -113,6 +152,7 @@ public class SysMenuController extends BaseController {
*
* @param menuId 菜单ID
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:menu:remove")
@Log(title = "菜单管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{menuId}")
@ -125,4 +165,5 @@ public class SysMenuController extends BaseController {
}
return toAjax(menuService.deleteMenuById(menuId));
}
}

View File

@ -35,7 +35,7 @@ import java.util.List;
@RequestMapping("/system/oss/config")
public class SysOssConfigController extends BaseController {
private final ISysOssConfigService iSysOssConfigService;
private final ISysOssConfigService sysOssConfigService;
/**
* 查询对象存储配置列表
@ -43,7 +43,7 @@ public class SysOssConfigController extends BaseController {
@SaCheckPermission("system:oss:list")
@GetMapping("/list")
public TableDataInfo<SysOssConfigVo> list(@Validated(QueryGroup.class) SysOssConfigBo bo, PageQuery pageQuery) {
return iSysOssConfigService.queryPageList(bo, pageQuery);
return sysOssConfigService.queryPageList(bo, pageQuery);
}
/**
@ -55,7 +55,7 @@ public class SysOssConfigController extends BaseController {
@GetMapping("/{ossConfigId}")
public R<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long ossConfigId) {
return R.ok(iSysOssConfigService.queryById(ossConfigId));
return R.ok(sysOssConfigService.queryById(ossConfigId));
}
/**
@ -66,7 +66,7 @@ public class SysOssConfigController extends BaseController {
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysOssConfigBo bo) {
return toAjax(iSysOssConfigService.insertByBo(bo));
return toAjax(sysOssConfigService.insertByBo(bo));
}
/**
@ -77,7 +77,7 @@ public class SysOssConfigController extends BaseController {
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssConfigBo bo) {
return toAjax(iSysOssConfigService.updateByBo(bo));
return toAjax(sysOssConfigService.updateByBo(bo));
}
/**
@ -90,7 +90,7 @@ public class SysOssConfigController extends BaseController {
@DeleteMapping("/{ossConfigIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossConfigIds) {
return toAjax(iSysOssConfigService.deleteWithValidByIds(List.of(ossConfigIds), true));
return toAjax(sysOssConfigService.deleteWithValidByIds(List.of(ossConfigIds), true));
}
/**
@ -100,6 +100,6 @@ public class SysOssConfigController extends BaseController {
@Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE)
@PutMapping("/changeStatus")
public R<Void> changeStatus(@RequestBody SysOssConfigBo bo) {
return toAjax(iSysOssConfigService.updateOssConfigStatus(bo));
return toAjax(sysOssConfigService.updateOssConfigStatus(bo));
}
}

View File

@ -38,7 +38,7 @@ import java.util.List;
@RequestMapping("/system/oss")
public class SysOssController extends BaseController {
private final ISysOssService iSysOssService;
private final ISysOssService sysOssService;
/**
* 查询OSS对象存储列表
@ -46,7 +46,7 @@ public class SysOssController extends BaseController {
@SaCheckPermission("system:oss:list")
@GetMapping("/list")
public TableDataInfo<SysOssVo> list(@Validated(QueryGroup.class) SysOssBo bo, PageQuery pageQuery) {
return iSysOssService.queryPageList(bo, pageQuery);
return sysOssService.queryPageList(bo, pageQuery);
}
/**
@ -58,7 +58,7 @@ public class SysOssController extends BaseController {
@GetMapping("/listByIds/{ossIds}")
public R<List<SysOssVo>> listByIds(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossIds) {
List<SysOssVo> list = iSysOssService.listByIds(Arrays.asList(ossIds));
List<SysOssVo> list = sysOssService.listByIds(Arrays.asList(ossIds));
return R.ok(list);
}
@ -74,7 +74,7 @@ public class SysOssController extends BaseController {
if (ObjectUtil.isNull(file)) {
throw new ServiceException("上传文件不能为空");
}
SysOssVo oss = iSysOssService.upload(file);
SysOssVo oss = sysOssService.upload(file);
SysOssUploadVo uploadVo = new SysOssUploadVo();
uploadVo.setUrl(oss.getUrl());
uploadVo.setFileName(oss.getOriginalName());
@ -90,7 +90,7 @@ public class SysOssController extends BaseController {
@SaCheckPermission("system:oss:download")
@GetMapping("/download/{ossId}")
public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
iSysOssService.download(ossId, response);
sysOssService.download(ossId, response);
}
/**
@ -103,7 +103,7 @@ public class SysOssController extends BaseController {
@DeleteMapping("/{ossIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossIds) {
return toAjax(iSysOssService.deleteWithValidByIds(List.of(ossIds), true));
return toAjax(sysOssService.deleteWithValidByIds(List.of(ossIds), true));
}
}

View File

@ -39,7 +39,7 @@ import java.util.Arrays;
public class SysProfileController extends BaseController {
private final ISysUserService userService;
private final ISysOssService iSysOssService;
private final ISysOssService sysOssService;
/**
* 个人信息
@ -114,7 +114,7 @@ public class SysProfileController extends BaseController {
if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {
return R.fail("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式");
}
SysOssVo oss = iSysOssService.upload(avatarfile);
SysOssVo oss = sysOssService.upload(avatarfile);
String avatar = oss.getUrl();
if (userService.updateUserAvatar(LoginHelper.getUsername(), oss.getOssId())) {
AvatarVo avatarVo = new AvatarVo();

View File

@ -1,16 +1,13 @@
package com.ruoyi.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.excel.utils.ExcelUtil;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.mybatis.core.page.PageQuery;
import com.ruoyi.common.mybatis.core.page.TableDataInfo;
import com.ruoyi.common.satoken.utils.LoginHelper;
import com.ruoyi.common.web.core.BaseController;
import com.ruoyi.system.domain.SysDept;
import com.ruoyi.system.domain.SysUserRole;
@ -20,9 +17,9 @@ import com.ruoyi.system.domain.vo.DeptTreeSelectVo;
import com.ruoyi.system.domain.vo.SysRoleVo;
import com.ruoyi.system.domain.vo.SysUserVo;
import com.ruoyi.system.service.ISysDeptService;
import com.ruoyi.system.service.ISysPermissionService;
import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.system.service.SysPermissionService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
@ -44,7 +41,7 @@ public class SysRoleController extends BaseController {
private final ISysRoleService roleService;
private final ISysUserService userService;
private final ISysDeptService deptService;
private final SysPermissionService permissionService;
private final ISysPermissionService permissionService;
/**
* 获取角色信息列表
@ -110,13 +107,14 @@ public class SysRoleController extends BaseController {
}
if (roleService.updateRole(role) > 0) {
// 更新缓存用户权限
LoginUser loginUser = LoginHelper.getLoginUser();
SysUserVo sysUser = userService.selectUserById(loginUser.getUserId());
if (ObjectUtil.isNotNull(sysUser) && !LoginHelper.isAdmin()) {
loginUser.setMenuPermission(permissionService.getMenuPermission(sysUser.getUserId(), sysUser.isAdmin()));
LoginHelper.setLoginUser(loginUser);
}
// // 更新缓存用户权限
// LoginUser loginUser = LoginHelper.getLoginUser();
// SysUserVo sysUser = userService.selectUserById(loginUser.getUserId());
// if (ObjectUtil.isNotNull(sysUser)) {
// loginUser.setMenuPermission(permissionService.getMenuPermission(sysUser.getUserId()));
// LoginHelper.setLoginUser(loginUser);
// }
// todo LoginUser 改为存储到token内部 无法热更新 等待后续想办法
return R.ok();
}
return R.fail("修改角色'" + role.getRoleName() + "'失败,请联系管理员");

View File

@ -0,0 +1,168 @@
package com.ruoyi.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import com.baomidou.lock.annotation.Lock4j;
import com.ruoyi.common.core.constant.TenantConstants;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import com.ruoyi.common.excel.utils.ExcelUtil;
import com.ruoyi.common.idempotent.annotation.RepeatSubmit;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.mybatis.core.page.PageQuery;
import com.ruoyi.common.mybatis.core.page.TableDataInfo;
import com.ruoyi.common.tenant.helper.TenantHelper;
import com.ruoyi.common.web.core.BaseController;
import com.ruoyi.system.domain.bo.SysTenantBo;
import com.ruoyi.system.domain.bo.SysUserBo;
import com.ruoyi.system.domain.vo.SysTenantVo;
import com.ruoyi.system.service.ISysTenantService;
import com.ruoyi.system.service.ISysUserService;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 租户管理
*
* @author Michelle.Chung
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/tenant")
public class SysTenantController extends BaseController {
private final ISysTenantService sysTenantService;
private final ISysUserService sysUserService;
/**
* 查询租户列表
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:list")
@GetMapping("/list")
public TableDataInfo<SysTenantVo> list(SysTenantBo bo, PageQuery pageQuery) {
return sysTenantService.queryPageList(bo, pageQuery);
}
/**
* 导出租户列表
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:export")
@Log(title = "租户", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(SysTenantBo bo, HttpServletResponse response) {
List<SysTenantVo> list = sysTenantService.queryList(bo);
ExcelUtil.exportExcel(list, "租户", SysTenantVo.class, response);
}
/**
* 获取租户详细信息
*
* @param id 主键
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:query")
@GetMapping("/{id}")
public R<SysTenantVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(sysTenantService.queryById(id));
}
/**
* 新增租户
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:add")
@Log(title = "租户", businessType = BusinessType.INSERT)
@Lock4j
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysTenantBo bo) {
if (TenantConstants.NOT_PASS.equals(sysTenantService.checkCompanyNameUnique(bo))) {
throw new ServiceException("新增租户'" + bo.getCompanyName() + "'失败,企业名称已存在");
}
SysUserBo userBo = new SysUserBo();
userBo.setUserName(bo.getUsername());
// 判断用户名是否重复
if (UserConstants.NOT_UNIQUE.equals(sysUserService.checkUserNameUnique(userBo))) {
throw new ServiceException("新增用户'" + bo.getUsername() + "'失败,登录账号已存在");
}
return toAjax(sysTenantService.insertByBo(bo));
}
/**
* 修改租户
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:edit")
@Log(title = "租户", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantBo bo) {
if (UserConstants.NOT_UNIQUE.equals(sysTenantService.checkCompanyNameUnique(bo))) {
throw new ServiceException("修改租户'" + bo.getCompanyName() + "'失败,公司名称已存在");
}
return toAjax(sysTenantService.updateByBo(bo));
}
/**
* 状态修改
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:edit")
@Log(title = "租户套餐", businessType = BusinessType.UPDATE)
@PutMapping("/changeStatus")
public R<Void> changeStatus(@RequestBody SysTenantBo bo) {
return toAjax(sysTenantService.updateTenantStatus(bo));
}
/**
* 删除租户
*
* @param ids 主键串
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:remove")
@Log(title = "租户", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(sysTenantService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 动态切换租户
*
* @param tenantId 租户ID
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@GetMapping("/dynamic/{tenantId}")
public R<Void> dynamicTenant(@NotBlank(message = "租户ID不能为空") @PathVariable String tenantId) {
TenantHelper.setDynamic(tenantId);
return R.ok();
}
/**
* 清除动态租户
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@GetMapping("/dynamic/clear")
public R<Void> dynamicClear() {
TenantHelper.clearDynamic();
return R.ok();
}
}

View File

@ -0,0 +1,124 @@
package com.ruoyi.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import com.ruoyi.common.core.constant.TenantConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import com.ruoyi.common.excel.utils.ExcelUtil;
import com.ruoyi.common.idempotent.annotation.RepeatSubmit;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.mybatis.core.page.PageQuery;
import com.ruoyi.common.mybatis.core.page.TableDataInfo;
import com.ruoyi.common.web.core.BaseController;
import com.ruoyi.system.domain.bo.SysTenantPackageBo;
import com.ruoyi.system.domain.vo.SysTenantPackageVo;
import com.ruoyi.system.service.ISysTenantPackageService;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 租户套餐管理
*
* @author Michelle.Chung
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/tenant/package")
public class SysTenantPackageController extends BaseController {
private final ISysTenantPackageService sysTenantPackageService;
/**
* 查询租户套餐列表
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:list")
@GetMapping("/list")
public TableDataInfo<SysTenantPackageVo> list(SysTenantPackageBo bo, PageQuery pageQuery) {
return sysTenantPackageService.queryPageList(bo, pageQuery);
}
/**
* 导出租户套餐列表
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:export")
@Log(title = "租户套餐", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(SysTenantPackageBo bo, HttpServletResponse response) {
List<SysTenantPackageVo> list = sysTenantPackageService.queryList(bo);
ExcelUtil.exportExcel(list, "租户套餐", SysTenantPackageVo.class, response);
}
/**
* 获取租户套餐详细信息
*
* @param packageId 主键
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:query")
@GetMapping("/{packageId}")
public R<SysTenantPackageVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long packageId) {
return R.ok(sysTenantPackageService.queryById(packageId));
}
/**
* 新增租户套餐
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:add")
@Log(title = "租户套餐", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysTenantPackageBo bo) {
return toAjax(sysTenantPackageService.insertByBo(bo));
}
/**
* 修改租户套餐
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:edit")
@Log(title = "租户套餐", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantPackageBo bo) {
return toAjax(sysTenantPackageService.updateByBo(bo));
}
/**
* 状态修改
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:edit")
@Log(title = "租户套餐", businessType = BusinessType.UPDATE)
@PutMapping("/changeStatus")
public R<Void> changeStatus(@RequestBody SysTenantPackageBo bo) {
return toAjax(sysTenantPackageService.updatePackageStatus(bo));
}
/**
* 删除租户套餐
*
* @param packageIds 主键串
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:remove")
@Log(title = "租户套餐", businessType = BusinessType.DELETE)
@DeleteMapping("/{packageIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] packageIds) {
return toAjax(sysTenantPackageService.deleteWithValidByIds(List.of(packageIds), true));
}
}

View File

@ -6,6 +6,7 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.core.constant.TenantConstants;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.StreamUtils;
@ -16,16 +17,14 @@ import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.mybatis.core.page.PageQuery;
import com.ruoyi.common.mybatis.core.page.TableDataInfo;
import com.ruoyi.common.tenant.helper.TenantHelper;
import com.ruoyi.common.satoken.utils.LoginHelper;
import com.ruoyi.common.web.core.BaseController;
import com.ruoyi.system.domain.SysDept;
import com.ruoyi.system.domain.bo.SysUserBo;
import com.ruoyi.system.domain.vo.*;
import com.ruoyi.system.listener.SysUserImportListener;
import com.ruoyi.system.service.ISysDeptService;
import com.ruoyi.system.service.ISysPostService;
import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.system.service.*;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
@ -51,6 +50,7 @@ public class SysUserController extends BaseController {
private final ISysRoleService roleService;
private final ISysPostService postService;
private final ISysDeptService deptService;
private final ISysTenantService tenantService;
/**
* 获取用户列表
@ -114,7 +114,7 @@ public class SysUserController extends BaseController {
userService.checkUserDataScope(userId);
SysUserInfoVo userInfoVo = new SysUserInfoVo();
List<SysRoleVo> roles = roleService.selectRoleAll();
userInfoVo.setRoles(LoginHelper.isAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isAdmin()));
userInfoVo.setRoles(LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isSuperAdmin()));
userInfoVo.setPosts(postService.selectPostAll());
if (ObjectUtil.isNotNull(userId)) {
SysUserVo sysUser = userService.selectUserById(userId);
@ -141,6 +141,12 @@ public class SysUserController extends BaseController {
&& UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) {
return R.fail("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
if (TenantHelper.isEnable()) {
String status = tenantService.checkAccountBalance(LoginHelper.getTenantId());
if (TenantConstants.NOT_PASS.equals(status)) {
return R.fail("当前租户下用户名额不足,请联系管理员");
}
}
user.setPassword(BCrypt.hashpw(user.getPassword()));
return toAjax(userService.insertUser(user));
}
@ -218,7 +224,7 @@ public class SysUserController extends BaseController {
List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
SysUserInfoVo userInfoVo = new SysUserInfoVo();
userInfoVo.setUser(user);
userInfoVo.setRoles(LoginHelper.isAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isAdmin()));
userInfoVo.setRoles(LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isSuperAdmin()));
return R.ok(userInfoVo);
}

View File

@ -2,7 +2,7 @@ package com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.mybatis.core.domain.BaseEntity;
import com.ruoyi.common.tenant.core.TenantEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_config")
public class SysConfig extends BaseEntity {
public class SysConfig extends TenantEntity {
/**
* 参数主键

View File

@ -3,11 +3,7 @@ package com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.mybatis.core.domain.TreeEntity;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import com.ruoyi.common.tenant.core.TenantEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -22,7 +18,7 @@ import java.io.Serial;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_dept")
public class SysDept extends TreeEntity<SysDept> {
public class SysDept extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
@ -33,6 +29,11 @@ public class SysDept extends TreeEntity<SysDept> {
@TableId(value = "dept_id")
private Long deptId;
/**
* 父部门ID
*/
private Long parentId;
/**
* 部门名称
*/

View File

@ -3,7 +3,7 @@ package com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.mybatis.core.domain.BaseEntity;
import com.ruoyi.common.tenant.core.TenantEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -16,7 +16,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_dict_data")
public class SysDictData extends BaseEntity {
public class SysDictData extends TenantEntity {
/**
* 字典编码

View File

@ -2,7 +2,7 @@ package com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.mybatis.core.domain.BaseEntity;
import com.ruoyi.common.tenant.core.TenantEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_dict_type")
public class SysDictType extends BaseEntity {
public class SysDictType extends TenantEntity {
/**
* 字典主键

View File

@ -1,19 +1,12 @@
package com.ruoyi.system.domain;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.excel.annotation.ExcelDictFormat;
import com.ruoyi.common.excel.convert.ExcelDictConvert;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 系统访问记录表 sys_logininfor
@ -23,7 +16,6 @@ import java.util.Map;
@Data
@TableName("sys_logininfor")
@ExcelIgnoreUnannotated
public class SysLogininfor implements Serializable {
@Serial
@ -32,63 +24,52 @@ public class SysLogininfor implements Serializable {
/**
* ID
*/
@ExcelProperty(value = "序号")
@TableId(value = "info_id")
private Long infoId;
/**
* 租户编号
*/
private String tenantId;
/**
* 用户账号
*/
@ExcelProperty(value = "用户账号")
private String userName;
/**
* 登录状态 0成功 1失败
*/
@ExcelProperty(value = "登录状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_common_status")
private String status;
/**
* 登录IP地址
*/
@ExcelProperty(value = "登录地址")
private String ipaddr;
/**
* 登录地点
*/
@ExcelProperty(value = "登录地点")
private String loginLocation;
/**
* 浏览器类型
*/
@ExcelProperty(value = "浏览器")
private String browser;
/**
* 操作系统
*/
@ExcelProperty(value = "操作系统")
private String os;
/**
* 提示消息
*/
@ExcelProperty(value = "提示消息")
private String msg;
/**
* 访问时间
*/
@ExcelProperty(value = "访问时间")
private Date loginTime;
/**
* 请求参数
*/
@TableField(exist = false)
private Map<String, Object> params = new HashMap<>();
}

View File

@ -1,14 +1,18 @@
package com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.mybatis.core.domain.TreeEntity;
import com.ruoyi.common.mybatis.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.ArrayList;
import java.util.List;
/**
* 菜单权限表 sys_menu
*
@ -18,7 +22,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_menu")
public class SysMenu extends TreeEntity<SysMenu> {
public class SysMenu extends BaseEntity {
/**
* 菜单ID
@ -26,6 +30,11 @@ public class SysMenu extends TreeEntity<SysMenu> {
@TableId(value = "menu_id")
private Long menuId;
/**
* 父菜单ID
*/
private Long parentId;
/**
* 菜单名称
*/
@ -91,6 +100,18 @@ public class SysMenu extends TreeEntity<SysMenu> {
*/
private String remark;
/**
* 父菜单名称
*/
@TableField(exist = false)
private String parentName;
/**
* 子菜单
*/
@TableField(exist = false)
private List<SysMenu> children = new ArrayList<>();
/**
* 获取路由名称
*/

View File

@ -2,7 +2,7 @@ package com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.mybatis.core.domain.BaseEntity;
import com.ruoyi.common.tenant.core.TenantEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_notice")
public class SysNotice extends BaseEntity {
public class SysNotice extends TenantEntity {
/**
* 公告ID

Some files were not shown because too many files have changed in this diff Show More