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

View File

@ -1,19 +1,27 @@
package com.ruoyi.web.controller; package com.ruoyi.web.controller;
import cn.dev33.satoken.annotation.SaIgnore; 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.R;
import com.ruoyi.common.core.domain.model.LoginBody; import com.ruoyi.common.core.domain.model.LoginBody;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.domain.model.SmsLoginBody; 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.common.satoken.utils.LoginHelper;
import com.ruoyi.system.domain.SysMenu; 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.RouterVo;
import com.ruoyi.system.domain.vo.SysTenantVo;
import com.ruoyi.system.domain.vo.SysUserVo; import com.ruoyi.system.domain.vo.SysUserVo;
import com.ruoyi.system.service.ISysMenuService; import com.ruoyi.system.service.ISysMenuService;
import com.ruoyi.system.service.ISysTenantService;
import com.ruoyi.system.service.ISysUserService; 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.LoginVo;
import com.ruoyi.web.domain.vo.TenantListVo;
import com.ruoyi.web.domain.vo.UserInfoVo; import com.ruoyi.web.domain.vo.UserInfoVo;
import com.ruoyi.web.service.SysLoginService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated; 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.RequestBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.net.URL;
import java.util.List; import java.util.List;
/** /**
@ -37,20 +46,23 @@ public class SysLoginController {
private final SysLoginService loginService; private final SysLoginService loginService;
private final ISysMenuService menuService; private final ISysMenuService menuService;
private final ISysUserService userService; private final ISysUserService userService;
private final ISysTenantService tenantService;
/** /**
* 登录方法 * 登录方法
* *
* @param loginBody 登录信息 * @param body 登录信息
* @return 结果 * @return 结果
*/ */
@SaIgnore @SaIgnore
@PostMapping("/login") @PostMapping("/login")
public R<LoginVo> login(@Validated @RequestBody LoginBody loginBody) { public R<LoginVo> login(@Validated @RequestBody LoginBody body) {
LoginVo loginVo = new LoginVo(); LoginVo loginVo = new LoginVo();
// 生成令牌 // 生成令牌
String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), String token = loginService.login(
loginBody.getUuid()); body.getTenantId(),
body.getUsername(), body.getPassword(),
body.getCode(), body.getUuid());
loginVo.setToken(token); loginVo.setToken(token);
return R.ok(loginVo); return R.ok(loginVo);
} }
@ -58,15 +70,15 @@ public class SysLoginController {
/** /**
* 短信登录(示例) * 短信登录(示例)
* *
* @param smsLoginBody 登录信息 * @param body 登录信息
* @return 结果 * @return 结果
*/ */
@SaIgnore @SaIgnore
@PostMapping("/smsLogin") @PostMapping("/smsLogin")
public R<LoginVo> smsLogin(@Validated @RequestBody SmsLoginBody smsLoginBody) { public R<LoginVo> smsLogin(@Validated @RequestBody SmsLoginBody body) {
LoginVo loginVo = new LoginVo(); 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); loginVo.setToken(token);
return R.ok(loginVo); return R.ok(loginVo);
} }
@ -97,6 +109,23 @@ public class SysLoginController {
return R.ok("退出成功"); 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.R;
import com.ruoyi.common.core.domain.model.RegisterBody; import com.ruoyi.common.core.domain.model.RegisterBody;
import com.ruoyi.system.service.ISysConfigService; import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.SysRegisterService; import com.ruoyi.web.service.SysRegisterService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping; 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.exception.NotLoginException;
import cn.dev33.satoken.secure.BCrypt; 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.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 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.Constants;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.domain.dto.RoleDTO; import com.ruoyi.common.core.domain.dto.RoleDTO;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.domain.model.XcxLoginUser; import com.ruoyi.common.core.domain.model.XcxLoginUser;
import com.ruoyi.common.core.enums.DeviceType; import com.ruoyi.common.core.enums.DeviceType;
import com.ruoyi.common.core.enums.LoginType; 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.enums.UserStatus;
import com.ruoyi.common.core.exception.user.CaptchaException; import com.ruoyi.common.core.exception.user.CaptchaException;
import com.ruoyi.common.core.exception.user.CaptchaExpireException; 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.log.event.LogininforEvent;
import com.ruoyi.common.redis.utils.RedisUtils; import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.common.satoken.utils.LoginHelper; 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.SysUser;
import com.ruoyi.system.domain.vo.SysTenantVo;
import com.ruoyi.system.domain.vo.SysUserVo; import com.ruoyi.system.domain.vo.SysUserVo;
import com.ruoyi.system.mapper.SysUserMapper; import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.system.service.ISysPermissionService;
import com.ruoyi.system.service.ISysTenantService;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -31,6 +38,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Duration; import java.time.Duration;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -45,8 +53,9 @@ import java.util.function.Supplier;
public class SysLoginService { public class SysLoginService {
private final SysUserMapper userMapper; private final SysUserMapper userMapper;
private final ISysConfigService configService; private final CaptchaProperties captchaProperties;
private final SysPermissionService permissionService; private final ISysPermissionService permissionService;
private final ISysTenantService tenantService;
@Value("${user.password.maxRetryCount}") @Value("${user.password.maxRetryCount}")
private Integer maxRetryCount; private Integer maxRetryCount;
@ -63,36 +72,41 @@ public class SysLoginService {
* @param uuid 唯一标识 * @param uuid 唯一标识
* @return 结果 * @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(); HttpServletRequest request = ServletUtils.getRequest();
boolean captchaEnabled = configService.selectCaptchaEnabled(); boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关 // 验证码开关
if (captchaEnabled) { 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 loginUser = buildLoginUser(user); LoginUser loginUser = buildLoginUser(user);
// 生成token // 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.PC); 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()); recordLoginInfo(user.getUserId());
return StpUtil.getTokenValue(); 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 loginUser = buildLoginUser(user); LoginUser loginUser = buildLoginUser(user);
// 生成token // 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.APP); 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()); recordLoginInfo(user.getUserId());
return StpUtil.getTokenValue(); return StpUtil.getTokenValue();
} }
@ -104,9 +118,12 @@ public class SysLoginService {
// 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key openid // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key openid
String openid = ""; String openid = "";
SysUserVo user = loadUserByOpenid(openid); SysUserVo user = loadUserByOpenid(openid);
// 校验租户
checkTenant(user.getTenantId());
// 此处可根据登录用户的数据不同 自行创建 loginUser // 此处可根据登录用户的数据不同 自行创建 loginUser
XcxLoginUser loginUser = new XcxLoginUser(); XcxLoginUser loginUser = new XcxLoginUser();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(user.getUserId()); loginUser.setUserId(user.getUserId());
loginUser.setUsername(user.getUserName()); loginUser.setUsername(user.getUserName());
loginUser.setUserType(user.getUserType()); loginUser.setUserType(user.getUserType());
@ -114,7 +131,7 @@ public class SysLoginService {
// 生成token // 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.XCX); 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()); recordLoginInfo(user.getUserId());
return StpUtil.getTokenValue(); return StpUtil.getTokenValue();
} }
@ -126,7 +143,7 @@ public class SysLoginService {
try { try {
LoginUser loginUser = LoginHelper.getLoginUser(); LoginUser loginUser = LoginHelper.getLoginUser();
StpUtil.logout(); 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) { } catch (NotLoginException ignored) {
} }
} }
@ -134,13 +151,15 @@ public class SysLoginService {
/** /**
* 记录登录信息 * 记录登录信息
* *
* @param tenantId 租户ID
* @param username 用户名 * @param username 用户名
* @param status 状态 * @param status 状态
* @param message 消息内容 * @param message 消息内容
* @return * @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 logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username); logininforEvent.setUsername(username);
logininforEvent.setStatus(status); logininforEvent.setStatus(status);
logininforEvent.setMessage(message); logininforEvent.setMessage(message);
@ -151,10 +170,10 @@ public class SysLoginService {
/** /**
* 校验短信验证码 * 校验短信验证码
*/ */
private boolean validateSmsCode(String phonenumber, String smsCode) { private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + phonenumber); String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) { 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(); throw new CaptchaExpireException();
} }
return code.equals(smsCode); return code.equals(smsCode);
@ -167,24 +186,25 @@ public class SysLoginService {
* @param code 验证码 * @param code 验证码
* @param uuid 唯一标识 * @param uuid 唯一标识
*/ */
public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) { public void validateCaptcha(String tenantId, String username, String code, String uuid, HttpServletRequest request) {
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, ""); String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey); String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey); RedisUtils.deleteObject(verifyKey);
if (captcha == null) { 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(); throw new CaptchaExpireException();
} }
if (!code.equalsIgnoreCase(captcha)) { 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(); throw new CaptchaException();
} }
} }
private SysUserVo loadUserByUsername(String username) { private SysUserVo loadUserByUsername(String tenantId, String username) {
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>() SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
.select(SysUser::getUserName, SysUser::getStatus) .select(SysUser::getUserName, SysUser::getStatus)
.eq(SysUser::getUserName, username)); .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
.eq(SysUser::getUserName, username));
if (ObjectUtil.isNull(user)) { if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username); log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username); throw new UserException("user.not.exists", username);
@ -195,10 +215,11 @@ public class SysLoginService {
return userMapper.selectUserByUserName(username); return userMapper.selectUserByUserName(username);
} }
private SysUserVo loadUserByPhonenumber(String phonenumber) { private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) {
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>() SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
.select(SysUser::getPhonenumber, SysUser::getStatus) .select(SysUser::getPhonenumber, SysUser::getStatus)
.eq(SysUser::getPhonenumber, phonenumber)); .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
.eq(SysUser::getPhonenumber, phonenumber));
if (ObjectUtil.isNull(user)) { if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", phonenumber); log.info("登录用户:{} 不存在.", phonenumber);
throw new UserException("user.not.exists", phonenumber); throw new UserException("user.not.exists", phonenumber);
@ -228,12 +249,13 @@ public class SysLoginService {
*/ */
private LoginUser buildLoginUser(SysUserVo user) { private LoginUser buildLoginUser(SysUserVo user) {
LoginUser loginUser = new LoginUser(); LoginUser loginUser = new LoginUser();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(user.getUserId()); loginUser.setUserId(user.getUserId());
loginUser.setDeptId(user.getDeptId()); loginUser.setDeptId(user.getDeptId());
loginUser.setUsername(user.getUserName()); loginUser.setUsername(user.getUserName());
loginUser.setUserType(user.getUserType()); loginUser.setUserType(user.getUserType());
loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId(), user.isAdmin())); loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId(), user.isAdmin())); loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName()); loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName());
List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class); List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class);
loginUser.setRoles(roles); loginUser.setRoles(roles);
@ -257,15 +279,15 @@ public class SysLoginService {
/** /**
* 登录校验 * 登录校验
*/ */
private void checkLogin(LoginType loginType, String username, Supplier<Boolean> supplier) { private void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username; String errorKey = GlobalConstants.PWD_ERR_CNT_KEY + username;
String loginFail = Constants.LOGIN_FAIL; String loginFail = Constants.LOGIN_FAIL;
// 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip) // 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
Integer errorNumber = RedisUtils.getCacheObject(errorKey); Integer errorNumber = RedisUtils.getCacheObject(errorKey);
// 锁定时间内登录 则踢出 // 锁定时间内登录 则踢出
if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) { 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); throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
} }
@ -275,12 +297,12 @@ public class SysLoginService {
// 达到规定错误次数 则锁定登录 // 达到规定错误次数 则锁定登录
if (errorNumber.equals(maxRetryCount)) { if (errorNumber.equals(maxRetryCount)) {
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime)); 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); throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
} else { } else {
// 未达到规定错误次数 则递增 // 未达到规定错误次数 则递增
RedisUtils.setCacheObject(errorKey, errorNumber); 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); throw new UserException(loginType.getRetryLimitCount(), errorNumber);
} }
} }
@ -288,4 +310,23 @@ public class SysLoginService {
// 登录成功 清空错误次数 // 登录成功 清空错误次数
RedisUtils.deleteObject(errorKey); 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 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.Constants;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.constant.UserConstants; import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.domain.model.RegisterBody; import com.ruoyi.common.core.domain.model.RegisterBody;
import com.ruoyi.common.core.enums.UserType; 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.core.utils.StringUtils;
import com.ruoyi.common.log.event.LogininforEvent; import com.ruoyi.common.log.event.LogininforEvent;
import com.ruoyi.common.redis.utils.RedisUtils; 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.domain.bo.SysUserBo;
import com.ruoyi.system.service.ISysUserService;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -30,22 +32,23 @@ import org.springframework.stereotype.Service;
public class SysRegisterService { public class SysRegisterService {
private final ISysUserService userService; private final ISysUserService userService;
private final ISysConfigService configService; private final CaptchaProperties captchaProperties;
/** /**
* 注册 * 注册
*/ */
public void register(RegisterBody registerBody) { public void register(RegisterBody registerBody) {
HttpServletRequest request = ServletUtils.getRequest(); HttpServletRequest request = ServletUtils.getRequest();
String tenantId = registerBody.getTenantId();
String username = registerBody.getUsername(); String username = registerBody.getUsername();
String password = registerBody.getPassword(); String password = registerBody.getPassword();
// 校验用户类型是否存在 // 校验用户类型是否存在
String userType = UserType.getUserType(registerBody.getUserType()).getUserType(); String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
boolean captchaEnabled = configService.selectCaptchaEnabled(); boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关 // 验证码开关
if (captchaEnabled) { if (captchaEnabled) {
validateCaptcha(username, registerBody.getCode(), registerBody.getUuid(), request); validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid(), request);
} }
SysUserBo sysUser = new SysUserBo(); SysUserBo sysUser = new SysUserBo();
sysUser.setUserName(username); sysUser.setUserName(username);
@ -56,11 +59,11 @@ public class SysRegisterService {
if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(sysUser))) { if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(sysUser))) {
throw new UserException("user.register.save.error", username); throw new UserException("user.register.save.error", username);
} }
boolean regFlag = userService.registerUser(sysUser); boolean regFlag = userService.registerUser(sysUser, tenantId);
if (!regFlag) { if (!regFlag) {
throw new UserException("user.register.error"); 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 唯一标识 * @param uuid 唯一标识
* @return 结果 * @return 结果
*/ */
public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) { public void validateCaptcha(String tenantId, String username, String code, String uuid, HttpServletRequest request) {
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, ""); String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey); String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey); RedisUtils.deleteObject(verifyKey);
if (captcha == null) { 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(); throw new CaptchaExpireException();
} }
if (!code.equalsIgnoreCase(captcha)) { 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(); throw new CaptchaException();
} }
} }
@ -88,13 +91,15 @@ public class SysRegisterService {
/** /**
* 记录登录信息 * 记录登录信息
* *
* @param tenantId 租户ID
* @param username 用户名 * @param username 用户名
* @param status 状态 * @param status 状态
* @param message 消息内容 * @param message 消息内容
* @return * @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 logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username); logininforEvent.setUsername(username);
logininforEvent.setStatus(status); logininforEvent.setStatus(status);
logininforEvent.setMessage(message); logininforEvent.setMessage(message);

View File

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

View File

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

View File

@ -10,10 +10,9 @@ ruoyi:
demoEnabled: true demoEnabled: true
# 获取ip地址开关 # 获取ip地址开关
addressEnabled: true addressEnabled: true
# 缓存懒加载
cacheLazy: false
captcha: captcha:
enable: true
# 页面 <参数设置> 可开启关闭 验证码校验 # 页面 <参数设置> 可开启关闭 验证码校验
# 验证码类型 math 数组计算 char 字符验证 # 验证码类型 math 数组计算 char 字符验证
type: MATH type: MATH
@ -136,6 +135,20 @@ security:
- /actuator - /actuator
- /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配置 # MyBatisPlus配置
# https://baomidou.com/config/ # https://baomidou.com/config/
mybatis-plus: mybatis-plus:

View File

@ -43,3 +43,8 @@ sms.code.not.blank=短信验证码不能为空
sms.code.retry.limit.count=短信验证码输入错误{0}次 sms.code.retry.limit.count=短信验证码输入错误{0}次
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟 sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
xcx.code.not.blank=小程序code不能为空 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.count=Sms code input error {0} times
sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {1} minutes 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 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.count=短信验证码输入错误{0}次
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟 sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
xcx.code.not.blank=小程序code不能为空 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-sensitive</module>
<module>ruoyi-common-json</module> <module>ruoyi-common-json</module>
<module>ruoyi-common-encrypt</module> <module>ruoyi-common-encrypt</module>
<module>ruoyi-common-tenant</module>
</modules> </modules>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>

View File

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

View File

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

View File

@ -3,25 +3,15 @@ package com.ruoyi.common.core.constant;
/** /**
* 缓存的key 常量 * 缓存的key 常量
* *
* @author ruoyi * @author Lion Li
*/ */
public interface CacheConstants { public interface CacheConstants {
/**
* 登录用户 redis key
*/
String LOGIN_TOKEN_KEY = "Authorization:login:token:";
/** /**
* 在线用户 redis key * 在线用户 redis key
*/ */
String ONLINE_TOKEN_KEY = "online_tokens:"; String ONLINE_TOKEN_KEY = "online_tokens:";
/**
* 验证码 redis key
*/
String CAPTCHA_CODE_KEY = "captcha_codes:";
/** /**
* 参数管理 cache key * 参数管理 cache key
*/ */
@ -32,18 +22,4 @@ public interface CacheConstants {
*/ */
String SYS_DICT_KEY = "sys_dict:"; 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_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"; 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 @Data
public class LoginBody { 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 @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/** /**
* 用户ID * 用户ID
*/ */

View File

@ -13,6 +13,12 @@ import jakarta.validation.constraints.NotBlank;
@Data @Data
public class SmsLoginBody { 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) { public static Map<String, String> getParamMap(ServletRequest request) {
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) { 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; return params;
} }

View File

@ -42,7 +42,7 @@ public class StreamUtils {
* @return 拼接后的list * @return 拼接后的list
*/ */
public static <E> String join(Collection<E> collection, Function<E, String> function) { 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; package com.ruoyi.common.core.utils;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Validator; import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import java.util.ArrayList; import java.util.*;
import java.util.HashSet; import java.util.function.Function;
import java.util.List; import java.util.stream.Collectors;
import java.util.Set;
/** /**
* 字符串工具类 * 字符串工具类
@ -20,6 +20,8 @@ import java.util.Set;
@NoArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PRIVATE)
public class StringUtils extends org.apache.commons.lang3.StringUtils { public class StringUtils extends org.apache.commons.lang3.StringUtils {
public static final String SEPARATOR = ",";
/** /**
* 获取参数不为空值 * 获取参数不为空值
* *
@ -233,7 +235,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
/** /**
* 数字左边补齐0使之达到指定长度注意如果数字转换为字符串后长度大于size则只保留 最后size个字符 * 数字左边补齐0使之达到指定长度注意如果数字转换为字符串后长度大于size则只保留 最后size个字符
* *
* @param num 数字对象 * @param num 数字对象
* @param size 字符串指定长度 * @param size 字符串指定长度
* @return 返回数字的字符串格式该字符串为指定长度 * @return 返回数字的字符串格式该字符串为指定长度
*/ */
@ -244,9 +246,9 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
/** /**
* 字符串左补齐如果原始字符串s长度大于size则只保留最后size个字符 * 字符串左补齐如果原始字符串s长度大于size则只保留最后size个字符
* *
* @param s 原始字符串 * @param s 原始字符串
* @param size 字符串指定长度 * @param size 字符串指定长度
* @param c 用于补齐的字符 * @param c 用于补齐的字符
* @return 返回指定长度的字符串由原字符串左补齐或截取得到 * @return 返回指定长度的字符串由原字符串左补齐或截取得到
*/ */
public static String padl(final String s, final int size, final char c) { public static String padl(final String s, final int size, final char c) {
@ -265,4 +267,55 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
return sb.toString(); 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; package com.ruoyi.common.excel.annotation;
import com.ruoyi.common.core.utils.StringUtils;
import java.lang.annotation.*; 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) { public static String convertByExp(String propertyValue, String converterExp, String separator) {
StringBuilder propertyString = new StringBuilder(); StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(","); String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
for (String item : convertSource) { for (String item : convertSource) {
String[] itemArray = item.split("="); String[] itemArray = item.split("=");
if (StringUtils.containsAny(propertyValue, separator)) { if (StringUtils.containsAny(propertyValue, separator)) {
@ -298,7 +298,7 @@ public class ExcelUtil {
*/ */
public static String reverseByExp(String propertyValue, String converterExp, String separator) { public static String reverseByExp(String propertyValue, String converterExp, String separator) {
StringBuilder propertyString = new StringBuilder(); StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(","); String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
for (String item : convertSource) { for (String item : convertSource) {
String[] itemArray = item.split("="); String[] itemArray = item.split("=");
if (StringUtils.containsAny(propertyValue, separator)) { if (StringUtils.containsAny(propertyValue, separator)) {

View File

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

View File

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

View File

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

View File

@ -23,6 +23,11 @@ public class OperLogEvent implements Serializable {
*/ */
private Long operId; 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.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage; 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.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.Db; import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.ruoyi.common.core.utils.BeanCopyUtils; import com.ruoyi.common.core.utils.BeanCopyUtils;
@ -17,6 +17,9 @@ import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
/** /**
* 自定义 Mapper 接口, 实现 自定义扩展 * 自定义 Mapper 接口, 实现 自定义扩展
@ -193,4 +196,8 @@ public interface BaseMapperPlus<M, T, V> extends BaseMapper<T> {
return (P) voPage; 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"}); isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"});
String[] orderByArr = orderBy.split(","); String[] orderByArr = orderBy.split(StringUtils.SEPARATOR);
String[] isAscArr = isAsc.split(","); String[] isAscArr = isAsc.split(StringUtils.SEPARATOR);
if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) { if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {
throw new ServiceException("排序参数有误"); 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 com.ruoyi.common.core.domain.R;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;

View File

@ -35,7 +35,6 @@ import java.lang.reflect.Method;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors;
/** /**
* 数据权限过滤 * 数据权限过滤
@ -79,7 +78,7 @@ public class PlusDataPermissionHandler {
DataPermissionHelper.setVariable("user", currentUser); DataPermissionHelper.setVariable("user", currentUser);
} }
// 如果是超级管理员则不过滤数据 // 如果是超级管理员则不过滤数据
if (LoginHelper.isAdmin()) { if (LoginHelper.isSuperAdmin()) {
return where; return where;
} }
String dataFilterSql = buildDataFilter(dataColumns, isSelect); 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.SaHolder;
import cn.dev33.satoken.context.model.SaStorage; import cn.dev33.satoken.context.model.SaStorage;
import cn.hutool.core.util.ObjectUtil; 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.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@ -44,4 +46,19 @@ public class DataPermissionHelper {
} }
throw new NullPointerException("data permission context type exception"); 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; return configKey;
} }
/**
* 获取私有URL链接
*
* @param objectKey 对象KEY
* @param second 授权时间
*/
public String getPrivateUrl(String objectKey, Integer second) { public String getPrivateUrl(String objectKey, Integer second) {
GeneratePresignedUrlRequest generatePresignedUrlRequest = GeneratePresignedUrlRequest generatePresignedUrlRequest =
new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey) new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey)
@ -192,6 +198,13 @@ public class OssClient {
return url.toString(); 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<>(); 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) { 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); String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey);
if (json == null) { if (json == null) {
throw new OssException("系统异常, '" + configKey + "'配置信息不存在!"); throw new OssException("系统异常, '" + configKey + "'配置信息不存在!");
} }
OssProperties properties = JsonUtils.parseObject(json, OssProperties.class); OssProperties properties = JsonUtils.parseObject(json, OssProperties.class);
CLIENT_CACHE.put(configKey, new OssClient(configKey, properties)); 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); 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; 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 com.ruoyi.common.ratelimiter.enums.LimitType;
import java.lang.annotation.*; import java.lang.annotation.*;
@ -17,7 +17,7 @@ public @interface RateLimiter {
/** /**
* 限流key * 限流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; package com.ruoyi.common.satoken.config;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple; import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.stp.StpInterface; import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.stp.StpLogic;
import com.ruoyi.common.satoken.core.dao.PlusSaTokenDao;
import com.ruoyi.common.satoken.core.service.SaPermissionImpl; import com.ruoyi.common.satoken.core.service.SaPermissionImpl;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -30,4 +32,12 @@ public class SaTokenConfig implements WebMvcConfigurer {
return new SaPermissionImpl(); 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.dao.SaTokenDao;
import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaFoxUtil;
import com.ruoyi.common.redis.utils.RedisUtils; import com.ruoyi.common.redis.utils.RedisUtils;
import org.springframework.stereotype.Component;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
@ -15,7 +14,6 @@ import java.util.List;
* *
* @author Lion Li * @author Lion Li
*/ */
@Component
public class PlusSaTokenDao implements SaTokenDao { public class PlusSaTokenDao implements SaTokenDao {
/** /**

View File

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

View File

@ -22,12 +22,6 @@
<artifactId>ruoyi-common-satoken</artifactId> <artifactId>ruoyi-common-satoken</artifactId>
</dependency> </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> </dependencies>
</project> </project>

View File

@ -53,7 +53,7 @@ public class TencentSmsTemplate implements SmsTemplate {
throw new SmsException("模板ID不能为空"); throw new SmsException("模板ID不能为空");
} }
SendSmsRequest req = new SendSmsRequest(); 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)); req.setPhoneNumberSet(ArrayUtil.toArray(set, String.class));
if (CollUtil.isNotEmpty(param)) { if (CollUtil.isNotEmpty(param)) {
req.setTemplateParamSet(ArrayUtil.toArray(param.values(), String.class)); 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(); FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST); registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new XssFilter()); registration.setFilter(new XssFilter());
registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), ",")); registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), StringUtils.SEPARATOR));
registration.setName("xssFilter"); registration.setName("xssFilter");
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
Map<String, String> initParameters = new HashMap<>(); Map<String, String> initParameters = new HashMap<>();

View File

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

View File

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

View File

@ -45,7 +45,7 @@ import java.util.concurrent.TimeUnit;
@RequestMapping("/demo/demo") @RequestMapping("/demo/demo")
public class TestDemoController extends BaseController { 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") @SaCheckPermission("demo:demo:list")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo<TestDemoVo> list(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) { 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") @SaCheckPermission("demo:demo:list")
@GetMapping("/page") @GetMapping("/page")
public TableDataInfo<TestDemoVo> page(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) { 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); ExcelResult<TestDemoImportVo> excelResult = ExcelUtil.importExcel(file.getInputStream(), TestDemoImportVo.class, true);
List<TestDemoImportVo> volist = excelResult.getList(); List<TestDemoImportVo> volist = excelResult.getList();
List<TestDemo> list = BeanUtil.copyToList(volist, TestDemo.class); List<TestDemo> list = BeanUtil.copyToList(volist, TestDemo.class);
iTestDemoService.saveBatch(list); testDemoService.saveBatch(list);
return R.ok(excelResult.getAnalysis()); return R.ok(excelResult.getAnalysis());
} }
@ -88,7 +88,7 @@ public class TestDemoController extends BaseController {
@Log(title = "测试单表", businessType = BusinessType.EXPORT) @Log(title = "测试单表", businessType = BusinessType.EXPORT)
@PostMapping("/export") @PostMapping("/export")
public void export(@Validated TestDemoBo bo, HttpServletResponse response) { public void export(@Validated TestDemoBo bo, HttpServletResponse response) {
List<TestDemoVo> list = iTestDemoService.queryList(bo); List<TestDemoVo> list = testDemoService.queryList(bo);
// 测试雪花id导出 // 测试雪花id导出
// for (TestDemoVo vo : list) { // for (TestDemoVo vo : list) {
// vo.setId(1234567891234567893L); // vo.setId(1234567891234567893L);
@ -105,7 +105,7 @@ public class TestDemoController extends BaseController {
@GetMapping("/{id}") @GetMapping("/{id}")
public R<TestDemoVo> getInfo(@NotNull(message = "主键不能为空") public R<TestDemoVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable("id") Long id) { @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) 注解 // 使用校验工具对标 @Validated(AddGroup.class) 注解
// 用于在非 Controller 的地方校验对象 // 用于在非 Controller 的地方校验对象
ValidatorUtils.validate(bo, AddGroup.class); 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 @RepeatSubmit
@PutMapping() @PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody TestDemoBo bo) { 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}") @DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) { @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") @RequestMapping("/demo/tree")
public class TestTreeController extends BaseController { 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") @SaCheckPermission("demo:tree:list")
@GetMapping("/list") @GetMapping("/list")
public R<List<TestTreeVo>> list(@Validated(QueryGroup.class) TestTreeBo bo) { 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); return R.ok(list);
} }
@ -54,7 +54,7 @@ public class TestTreeController extends BaseController {
@Log(title = "测试树表", businessType = BusinessType.EXPORT) @Log(title = "测试树表", businessType = BusinessType.EXPORT)
@GetMapping("/export") @GetMapping("/export")
public void export(@Validated TestTreeBo bo, HttpServletResponse response) { 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); ExcelUtil.exportExcel(list, "测试树表", TestTreeVo.class, response);
} }
@ -67,7 +67,7 @@ public class TestTreeController extends BaseController {
@GetMapping("/{id}") @GetMapping("/{id}")
public R<TestTreeVo> getInfo(@NotNull(message = "主键不能为空") public R<TestTreeVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable("id") Long id) { @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 @RepeatSubmit
@PostMapping() @PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody TestTreeBo bo) { 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 @RepeatSubmit
@PutMapping() @PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody TestTreeBo bo) { 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}") @DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) { @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.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version; 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.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -19,7 +19,7 @@ import java.io.Serial;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@TableName("test_tree") @TableName("test_tree")
public class TestTree extends TreeEntity<TestTree> { public class TestTree extends BaseEntity {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -30,6 +30,11 @@ public class TestTree extends TreeEntity<TestTree> {
@TableId(value = "id") @TableId(value = "id")
private Long id; private Long id;
/**
* 父ID
*/
private Long parentId;
/** /**
* 部门id * 部门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.AddGroup;
import com.ruoyi.common.core.validate.EditGroup; import com.ruoyi.common.core.validate.EditGroup;
import com.ruoyi.common.mybatis.core.domain.TreeEntity; import com.ruoyi.common.mybatis.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
/** /**
* 测试树表业务对象 test_tree * 测试树表业务对象 test_tree
@ -18,7 +17,7 @@ import jakarta.validation.constraints.NotNull;
@Data @Data
@EqualsAndHashCode(callSuper = true) @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}) @NotNull(message = "主键不能为空", groups = {EditGroup.class})
private Long id; private Long id;
/**
* 父ID
*/
private Long parentId;
/** /**
* 部门id * 部门id
*/ */

View File

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

View File

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

View File

@ -212,7 +212,7 @@ public class GenTableColumn extends BaseEntity {
if (StringUtils.isNotEmpty(value)) { if (StringUtils.isNotEmpty(value)) {
Object startStr = value.subSequence(0, 1); Object startStr = value.subSequence(0, 1);
String endStr = value.substring(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(); return sb.deleteCharAt(sb.length() - 1).toString();

View File

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

View File

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

View File

@ -1,21 +1,16 @@
package ${packageName}.domain.bo; 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.AddGroup;
import com.ruoyi.common.core.validate.EditGroup; import com.ruoyi.common.core.validate.EditGroup;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.util.Date;
#foreach ($import in $importList) #foreach ($import in $importList)
import ${import}; import ${import};
#end #end
#if($table.crud || $table.sub)
import com.ruoyi.common.mybatis.core.domain.BaseEntity; import java.util.Date;
#elseif($table.tree)
import com.ruoyi.common.mybatis.core.domain.TreeEntity;
#end
/** /**
* ${functionName}业务对象 ${tableName} * ${functionName}业务对象 ${tableName}
@ -23,15 +18,9 @@ import com.ruoyi.common.mybatis.core.domain.TreeEntity;
* @author ${author} * @author ${author}
* @date ${datetime} * @date ${datetime}
*/ */
#if($table.crud || $table.sub)
#set($Entity="BaseEntity")
#elseif($table.tree)
#set($Entity="TreeEntity<${ClassName}Bo>")
#end
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ${ClassName}Bo extends ${Entity} { public class ${ClassName}Bo extends BaseEntity {
#foreach ($column in $columns) #foreach ($column in $columns)
#if(!$table.isSuperColumn($column.javaField) && ($column.query || $column.isInsert || $column.isEdit)) #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}") @RequestMapping("/${moduleName}/${businessName}")
public class ${ClassName}Controller extends BaseController { public class ${ClassName}Controller extends BaseController {
private final I${ClassName}Service i${ClassName}Service; private final I${ClassName}Service ${className}Service;
/** /**
* 查询${functionName}列表 * 查询${functionName}列表
@ -52,7 +52,7 @@ public class ${ClassName}Controller extends BaseController {
} }
#elseif($table.tree) #elseif($table.tree)
public R<List<${ClassName}Vo>> list(${ClassName}Bo bo) { 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); return R.ok(list);
} }
#end #end
@ -64,7 +64,7 @@ public class ${ClassName}Controller extends BaseController {
@Log(title = "${functionName}", businessType = BusinessType.EXPORT) @Log(title = "${functionName}", businessType = BusinessType.EXPORT)
@PostMapping("/export") @PostMapping("/export")
public void export(${ClassName}Bo bo, HttpServletResponse response) { 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); ExcelUtil.exportExcel(list, "${functionName}", ${ClassName}Vo.class, response);
} }
@ -77,7 +77,7 @@ public class ${ClassName}Controller extends BaseController {
@GetMapping("/{${pkColumn.javaField}}") @GetMapping("/{${pkColumn.javaField}}")
public R<${ClassName}Vo> getInfo(@NotNull(message = "主键不能为空") public R<${ClassName}Vo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable ${pkColumn.javaType} ${pkColumn.javaField}) { @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() @RepeatSubmit()
@PostMapping() @PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody ${ClassName}Bo bo) { 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() @RepeatSubmit()
@PutMapping() @PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody ${ClassName}Bo bo) { 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}") @DeleteMapping("/{${pkColumn.javaField}s}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) { @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; 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 com.baomidou.mybatisplus.annotation.*;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.math.BigDecimal;
#foreach ($import in $importList) #foreach ($import in $importList)
import ${import}; import ${import};
#end #end
#if($table.crud || $table.sub)
import com.ruoyi.common.mybatis.core.domain.BaseEntity; import java.io.Serial;
#elseif($table.tree) import java.util.Date;
import com.ruoyi.common.mybatis.core.domain.TreeEntity; import java.math.BigDecimal;
#end
/** /**
* ${functionName}对象 ${tableName} * ${functionName}对象 ${tableName}
@ -23,10 +27,10 @@ import com.ruoyi.common.mybatis.core.domain.TreeEntity;
* @author ${author} * @author ${author}
* @date ${datetime} * @date ${datetime}
*/ */
#if($table.crud || $table.sub) #if($IsTenant==1)
#set($Entity="BaseEntity") #set($Entity="TenantEntity")
#elseif($table.tree) #else
#set($Entity="TreeEntity<${ClassName}>") #set($Entity="BaseEntity")
#end #end
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ -51,6 +55,7 @@ public class ${ClassName} extends ${Entity} {
@TableId(value = "$column.columnName") @TableId(value = "$column.columnName")
#end #end
private $column.javaType $column.javaField; private $column.javaType $column.javaField;
#end #end
#end #end

View File

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

View File

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

View File

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

View File

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

View File

@ -63,7 +63,7 @@
<dependency> <dependency>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-satoken</artifactId> <artifactId>ruoyi-common-tenant</artifactId>
</dependency> </dependency>
<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; package com.ruoyi.system.controller.monitor;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import com.ruoyi.common.core.constant.CacheConstants; import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.mybatis.core.page.PageQuery;
import com.ruoyi.common.core.domain.R; 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.excel.utils.ExcelUtil;
import com.ruoyi.common.log.annotation.Log; import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType; 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.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 com.ruoyi.system.service.ISysLogininforService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List; import java.util.List;
/** /**
@ -37,7 +38,7 @@ public class SysLogininforController extends BaseController {
*/ */
@SaCheckPermission("monitor:logininfor:list") @SaCheckPermission("monitor:logininfor:list")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo<SysLogininfor> list(SysLogininfor logininfor, PageQuery pageQuery) { public TableDataInfo<SysLogininforVo> list(SysLogininforBo logininfor, PageQuery pageQuery) {
return logininforService.selectPageLogininforList(logininfor, pageQuery); return logininforService.selectPageLogininforList(logininfor, pageQuery);
} }
@ -47,9 +48,9 @@ public class SysLogininforController extends BaseController {
@Log(title = "登录日志", businessType = BusinessType.EXPORT) @Log(title = "登录日志", businessType = BusinessType.EXPORT)
@SaCheckPermission("monitor:logininfor:export") @SaCheckPermission("monitor:logininfor:export")
@PostMapping("/export") @PostMapping("/export")
public void export(SysLogininfor logininfor, HttpServletResponse response) { public void export(SysLogininforBo logininfor, HttpServletResponse response) {
List<SysLogininfor> list = logininforService.selectLogininforList(logininfor); List<SysLogininforVo> list = logininforService.selectLogininforList(logininfor);
ExcelUtil.exportExcel(list, "登录日志", SysLogininfor.class, response); ExcelUtil.exportExcel(list, "登录日志", SysLogininforVo.class, response);
} }
/** /**
@ -78,7 +79,7 @@ public class SysLogininforController extends BaseController {
@Log(title = "账户解锁", businessType = BusinessType.OTHER) @Log(title = "账户解锁", businessType = BusinessType.OTHER)
@GetMapping("/unlock/{userName}") @GetMapping("/unlock/{userName}")
public R<Void> unlock(@PathVariable("userName") String 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)) { if (RedisUtils.hasKey(loginName)) {
RedisUtils.deleteObject(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.mybatis.core.page.TableDataInfo;
import com.ruoyi.common.log.enums.BusinessType; import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.excel.utils.ExcelUtil; 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 com.ruoyi.system.service.ISysOperLogService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@ -35,7 +36,7 @@ public class SysOperlogController extends BaseController {
*/ */
@SaCheckPermission("monitor:operlog:list") @SaCheckPermission("monitor:operlog:list")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo<SysOperLog> list(SysOperLog operLog, PageQuery pageQuery) { public TableDataInfo<SysOperLogVo> list(SysOperLogBo operLog, PageQuery pageQuery) {
return operLogService.selectPageOperLogList(operLog, pageQuery); return operLogService.selectPageOperLogList(operLog, pageQuery);
} }
@ -45,9 +46,9 @@ public class SysOperlogController extends BaseController {
@Log(title = "操作日志", businessType = BusinessType.EXPORT) @Log(title = "操作日志", businessType = BusinessType.EXPORT)
@SaCheckPermission("monitor:operlog:export") @SaCheckPermission("monitor:operlog:export")
@PostMapping("/export") @PostMapping("/export")
public void export(SysOperLog operLog, HttpServletResponse response) { public void export(SysOperLogBo operLog, HttpServletResponse response) {
List<SysOperLog> list = operLogService.selectOperLogList(operLog); List<SysOperLogVo> list = operLogService.selectOperLogList(operLog);
ExcelUtil.exportExcel(list, "操作日志", SysOperLog.class, response); 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.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.core.constant.CacheConstants; 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.R;
import com.ruoyi.common.core.domain.dto.UserOnlineDTO; 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.StreamUtils;
import com.ruoyi.common.core.utils.StringUtils; 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.redis.utils.RedisUtils;
import com.ruoyi.common.web.core.BaseController;
import com.ruoyi.system.domain.SysUserOnline; import com.ruoyi.system.domain.SysUserOnline;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -45,7 +46,7 @@ public class SysUserOnlineController extends BaseController {
List<String> keys = StpUtil.searchTokenValue("", 0, -1, false); List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
List<UserOnlineDTO> userOnlineDTOList = new ArrayList<>(); List<UserOnlineDTO> userOnlineDTOList = new ArrayList<>();
for (String key : keys) { 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) { if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) {
continue; continue;

View File

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

View File

@ -1,7 +1,10 @@
package com.ruoyi.system.controller.system; package com.ruoyi.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission; 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 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.constant.UserConstants;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.StringUtils; 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") @SaCheckPermission("system:menu:list")
@GetMapping("/list") @GetMapping("/list")
public R<List<SysMenuVo>> list(SysMenuBo menu) { public R<List<SysMenuVo>> list(SysMenuBo menu) {
@ -47,6 +54,10 @@ public class SysMenuController extends BaseController {
* *
* @param menuId 菜单ID * @param menuId 菜单ID
*/ */
@SaCheckRole(value = {
TenantConstants.SUPER_ADMIN_ROLE_KEY,
TenantConstants.TENANT_ADMIN_ROLE_KEY
}, mode = SaMode.OR)
@SaCheckPermission("system:menu:query") @SaCheckPermission("system:menu:query")
@GetMapping(value = "/{menuId}") @GetMapping(value = "/{menuId}")
public R<SysMenuVo> getInfo(@PathVariable Long 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") @GetMapping("/treeselect")
public R<List<Tree<Long>>> treeselect(SysMenuBo menu) { public R<List<Tree<Long>>> treeselect(SysMenuBo menu) {
List<SysMenuVo> menus = menuService.selectMenuList(menu, LoginHelper.getUserId()); List<SysMenuVo> menus = menuService.selectMenuList(menu, LoginHelper.getUserId());
@ -67,6 +83,11 @@ public class SysMenuController extends BaseController {
* *
* @param roleId 角色ID * @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}") @GetMapping(value = "/roleMenuTreeselect/{roleId}")
public R<MenuTreeSelectVo> roleMenuTreeselect(@PathVariable("roleId") Long roleId) { public R<MenuTreeSelectVo> roleMenuTreeselect(@PathVariable("roleId") Long roleId) {
List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId()); List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
@ -76,9 +97,26 @@ public class SysMenuController extends BaseController {
return R.ok(selectVo); 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") @SaCheckPermission("system:menu:add")
@Log(title = "菜单管理", businessType = BusinessType.INSERT) @Log(title = "菜单管理", businessType = BusinessType.INSERT)
@PostMapping @PostMapping
@ -94,6 +132,7 @@ public class SysMenuController extends BaseController {
/** /**
* 修改菜单 * 修改菜单
*/ */
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:menu:edit") @SaCheckPermission("system:menu:edit")
@Log(title = "菜单管理", businessType = BusinessType.UPDATE) @Log(title = "菜单管理", businessType = BusinessType.UPDATE)
@PutMapping @PutMapping
@ -113,6 +152,7 @@ public class SysMenuController extends BaseController {
* *
* @param menuId 菜单ID * @param menuId 菜单ID
*/ */
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:menu:remove") @SaCheckPermission("system:menu:remove")
@Log(title = "菜单管理", businessType = BusinessType.DELETE) @Log(title = "菜单管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{menuId}") @DeleteMapping("/{menuId}")
@ -125,4 +165,5 @@ public class SysMenuController extends BaseController {
} }
return toAjax(menuService.deleteMenuById(menuId)); return toAjax(menuService.deleteMenuById(menuId));
} }
} }

View File

@ -35,7 +35,7 @@ import java.util.List;
@RequestMapping("/system/oss/config") @RequestMapping("/system/oss/config")
public class SysOssConfigController extends BaseController { 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") @SaCheckPermission("system:oss:list")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo<SysOssConfigVo> list(@Validated(QueryGroup.class) SysOssConfigBo bo, PageQuery pageQuery) { 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}") @GetMapping("/{ossConfigId}")
public R<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空") public R<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long ossConfigId) { @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() @RepeatSubmit()
@PostMapping() @PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysOssConfigBo bo) { 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() @RepeatSubmit()
@PutMapping() @PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssConfigBo bo) { 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}") @DeleteMapping("/{ossConfigIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossConfigIds) { @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) @Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE)
@PutMapping("/changeStatus") @PutMapping("/changeStatus")
public R<Void> changeStatus(@RequestBody SysOssConfigBo bo) { 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") @RequestMapping("/system/oss")
public class SysOssController extends BaseController { public class SysOssController extends BaseController {
private final ISysOssService iSysOssService; private final ISysOssService sysOssService;
/** /**
* 查询OSS对象存储列表 * 查询OSS对象存储列表
@ -46,7 +46,7 @@ public class SysOssController extends BaseController {
@SaCheckPermission("system:oss:list") @SaCheckPermission("system:oss:list")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo<SysOssVo> list(@Validated(QueryGroup.class) SysOssBo bo, PageQuery pageQuery) { 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}") @GetMapping("/listByIds/{ossIds}")
public R<List<SysOssVo>> listByIds(@NotEmpty(message = "主键不能为空") public R<List<SysOssVo>> listByIds(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossIds) { @PathVariable Long[] ossIds) {
List<SysOssVo> list = iSysOssService.listByIds(Arrays.asList(ossIds)); List<SysOssVo> list = sysOssService.listByIds(Arrays.asList(ossIds));
return R.ok(list); return R.ok(list);
} }
@ -74,7 +74,7 @@ public class SysOssController extends BaseController {
if (ObjectUtil.isNull(file)) { if (ObjectUtil.isNull(file)) {
throw new ServiceException("上传文件不能为空"); throw new ServiceException("上传文件不能为空");
} }
SysOssVo oss = iSysOssService.upload(file); SysOssVo oss = sysOssService.upload(file);
SysOssUploadVo uploadVo = new SysOssUploadVo(); SysOssUploadVo uploadVo = new SysOssUploadVo();
uploadVo.setUrl(oss.getUrl()); uploadVo.setUrl(oss.getUrl());
uploadVo.setFileName(oss.getOriginalName()); uploadVo.setFileName(oss.getOriginalName());
@ -90,7 +90,7 @@ public class SysOssController extends BaseController {
@SaCheckPermission("system:oss:download") @SaCheckPermission("system:oss:download")
@GetMapping("/download/{ossId}") @GetMapping("/download/{ossId}")
public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException { 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}") @DeleteMapping("/{ossIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossIds) { @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 { public class SysProfileController extends BaseController {
private final ISysUserService userService; 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)) { if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {
return R.fail("文件格式不正确,请上传" + Arrays.toString(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(); String avatar = oss.getUrl();
if (userService.updateUserAvatar(LoginHelper.getUsername(), oss.getOssId())) { if (userService.updateUserAvatar(LoginHelper.getUsername(), oss.getOssId())) {
AvatarVo avatarVo = new AvatarVo(); AvatarVo avatarVo = new AvatarVo();

View File

@ -1,16 +1,13 @@
package com.ruoyi.system.controller.system; package com.ruoyi.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.core.constant.UserConstants; import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.domain.R; 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.excel.utils.ExcelUtil;
import com.ruoyi.common.log.annotation.Log; import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType; import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.mybatis.core.page.PageQuery; import com.ruoyi.common.mybatis.core.page.PageQuery;
import com.ruoyi.common.mybatis.core.page.TableDataInfo; 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.common.web.core.BaseController;
import com.ruoyi.system.domain.SysDept; import com.ruoyi.system.domain.SysDept;
import com.ruoyi.system.domain.SysUserRole; 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.SysRoleVo;
import com.ruoyi.system.domain.vo.SysUserVo; import com.ruoyi.system.domain.vo.SysUserVo;
import com.ruoyi.system.service.ISysDeptService; import com.ruoyi.system.service.ISysDeptService;
import com.ruoyi.system.service.ISysPermissionService;
import com.ruoyi.system.service.ISysRoleService; import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService; import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.system.service.SysPermissionService;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@ -44,7 +41,7 @@ public class SysRoleController extends BaseController {
private final ISysRoleService roleService; private final ISysRoleService roleService;
private final ISysUserService userService; private final ISysUserService userService;
private final ISysDeptService deptService; 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) { if (roleService.updateRole(role) > 0) {
// 更新缓存用户权限 // // 更新缓存用户权限
LoginUser loginUser = LoginHelper.getLoginUser(); // LoginUser loginUser = LoginHelper.getLoginUser();
SysUserVo sysUser = userService.selectUserById(loginUser.getUserId()); // SysUserVo sysUser = userService.selectUserById(loginUser.getUserId());
if (ObjectUtil.isNotNull(sysUser) && !LoginHelper.isAdmin()) { // if (ObjectUtil.isNotNull(sysUser)) {
loginUser.setMenuPermission(permissionService.getMenuPermission(sysUser.getUserId(), sysUser.isAdmin())); // loginUser.setMenuPermission(permissionService.getMenuPermission(sysUser.getUserId()));
LoginHelper.setLoginUser(loginUser); // LoginHelper.setLoginUser(loginUser);
} // }
// todo LoginUser 改为存储到token内部 无法热更新 等待后续想办法
return R.ok(); return R.ok();
} }
return R.fail("修改角色'" + role.getRoleName() + "'失败,请联系管理员"); 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.lang.tree.Tree;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil; 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.constant.UserConstants;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.StreamUtils; 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.log.enums.BusinessType;
import com.ruoyi.common.mybatis.core.page.PageQuery; import com.ruoyi.common.mybatis.core.page.PageQuery;
import com.ruoyi.common.mybatis.core.page.TableDataInfo; 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.satoken.utils.LoginHelper;
import com.ruoyi.common.web.core.BaseController; import com.ruoyi.common.web.core.BaseController;
import com.ruoyi.system.domain.SysDept; import com.ruoyi.system.domain.SysDept;
import com.ruoyi.system.domain.bo.SysUserBo; import com.ruoyi.system.domain.bo.SysUserBo;
import com.ruoyi.system.domain.vo.*; import com.ruoyi.system.domain.vo.*;
import com.ruoyi.system.listener.SysUserImportListener; import com.ruoyi.system.listener.SysUserImportListener;
import com.ruoyi.system.service.ISysDeptService; import com.ruoyi.system.service.*;
import com.ruoyi.system.service.ISysPostService;
import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -51,6 +50,7 @@ public class SysUserController extends BaseController {
private final ISysRoleService roleService; private final ISysRoleService roleService;
private final ISysPostService postService; private final ISysPostService postService;
private final ISysDeptService deptService; private final ISysDeptService deptService;
private final ISysTenantService tenantService;
/** /**
* 获取用户列表 * 获取用户列表
@ -114,7 +114,7 @@ public class SysUserController extends BaseController {
userService.checkUserDataScope(userId); userService.checkUserDataScope(userId);
SysUserInfoVo userInfoVo = new SysUserInfoVo(); SysUserInfoVo userInfoVo = new SysUserInfoVo();
List<SysRoleVo> roles = roleService.selectRoleAll(); 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()); userInfoVo.setPosts(postService.selectPostAll());
if (ObjectUtil.isNotNull(userId)) { if (ObjectUtil.isNotNull(userId)) {
SysUserVo sysUser = userService.selectUserById(userId); SysUserVo sysUser = userService.selectUserById(userId);
@ -141,6 +141,12 @@ public class SysUserController extends BaseController {
&& UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) { && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) {
return R.fail("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在"); 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())); user.setPassword(BCrypt.hashpw(user.getPassword()));
return toAjax(userService.insertUser(user)); return toAjax(userService.insertUser(user));
} }
@ -218,7 +224,7 @@ public class SysUserController extends BaseController {
List<SysRoleVo> roles = roleService.selectRolesByUserId(userId); List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
SysUserInfoVo userInfoVo = new SysUserInfoVo(); SysUserInfoVo userInfoVo = new SysUserInfoVo();
userInfoVo.setUser(user); 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); 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.TableId;
import com.baomidou.mybatisplus.annotation.TableName; 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.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@TableName("sys_config") @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.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.mybatis.core.domain.TreeEntity; import com.ruoyi.common.tenant.core.TenantEntity;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -22,7 +18,7 @@ import java.io.Serial;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@TableName("sys_dept") @TableName("sys_dept")
public class SysDept extends TreeEntity<SysDept> { public class SysDept extends TenantEntity {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -33,6 +29,11 @@ public class SysDept extends TreeEntity<SysDept> {
@TableId(value = "dept_id") @TableId(value = "dept_id")
private Long deptId; 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.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.constant.UserConstants; 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.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -16,7 +16,7 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@TableName("sys_dict_data") @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.TableId;
import com.baomidou.mybatisplus.annotation.TableName; 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.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@TableName("sys_dict_type") @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; 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.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.excel.annotation.ExcelDictFormat;
import com.ruoyi.common.excel.convert.ExcelDictConvert;
import lombok.Data; import lombok.Data;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/** /**
* 系统访问记录表 sys_logininfor * 系统访问记录表 sys_logininfor
@ -23,7 +16,6 @@ import java.util.Map;
@Data @Data
@TableName("sys_logininfor") @TableName("sys_logininfor")
@ExcelIgnoreUnannotated
public class SysLogininfor implements Serializable { public class SysLogininfor implements Serializable {
@Serial @Serial
@ -32,63 +24,52 @@ public class SysLogininfor implements Serializable {
/** /**
* ID * ID
*/ */
@ExcelProperty(value = "序号")
@TableId(value = "info_id") @TableId(value = "info_id")
private Long infoId; private Long infoId;
/**
* 租户编号
*/
private String tenantId;
/** /**
* 用户账号 * 用户账号
*/ */
@ExcelProperty(value = "用户账号")
private String userName; private String userName;
/** /**
* 登录状态 0成功 1失败 * 登录状态 0成功 1失败
*/ */
@ExcelProperty(value = "登录状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_common_status")
private String status; private String status;
/** /**
* 登录IP地址 * 登录IP地址
*/ */
@ExcelProperty(value = "登录地址")
private String ipaddr; private String ipaddr;
/** /**
* 登录地点 * 登录地点
*/ */
@ExcelProperty(value = "登录地点")
private String loginLocation; private String loginLocation;
/** /**
* 浏览器类型 * 浏览器类型
*/ */
@ExcelProperty(value = "浏览器")
private String browser; private String browser;
/** /**
* 操作系统 * 操作系统
*/ */
@ExcelProperty(value = "操作系统")
private String os; private String os;
/** /**
* 提示消息 * 提示消息
*/ */
@ExcelProperty(value = "提示消息")
private String msg; private String msg;
/** /**
* 访问时间 * 访问时间
*/ */
@ExcelProperty(value = "访问时间")
private Date loginTime; private Date loginTime;
/**
* 请求参数
*/
@TableField(exist = false)
private Map<String, Object> params = new HashMap<>();
} }

View File

@ -1,14 +1,18 @@
package com.ruoyi.system.domain; package com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.constant.Constants; import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.UserConstants; import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.utils.StringUtils; 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.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.util.ArrayList;
import java.util.List;
/** /**
* 菜单权限表 sys_menu * 菜单权限表 sys_menu
* *
@ -18,7 +22,7 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@TableName("sys_menu") @TableName("sys_menu")
public class SysMenu extends TreeEntity<SysMenu> { public class SysMenu extends BaseEntity {
/** /**
* 菜单ID * 菜单ID
@ -26,6 +30,11 @@ public class SysMenu extends TreeEntity<SysMenu> {
@TableId(value = "menu_id") @TableId(value = "menu_id")
private Long menuId; private Long menuId;
/**
* 父菜单ID
*/
private Long parentId;
/** /**
* 菜单名称 * 菜单名称
*/ */
@ -91,6 +100,18 @@ public class SysMenu extends TreeEntity<SysMenu> {
*/ */
private String remark; 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.TableId;
import com.baomidou.mybatisplus.annotation.TableName; 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.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@TableName("sys_notice") @TableName("sys_notice")
public class SysNotice extends BaseEntity { public class SysNotice extends TenantEntity {
/** /**
* 公告ID * 公告ID

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