From 5b08bba16c5c0f48b0ed8ddfdb7bc7731db315cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=96=AF=E7=8B=82=E7=9A=84=E7=8B=AE=E5=AD=90li?= <15040126243@163.com> Date: Wed, 8 Feb 2023 15:59:00 +0800 Subject: [PATCH] =?UTF-8?q?add=20=E6=A1=86=E6=9E=B6=E6=95=B4=E4=BD=93=20?= =?UTF-8?q?=E6=95=B4=E5=90=88=E9=80=82=E9=85=8D=E5=A4=9A=E7=A7=9F=E6=88=B7?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/controller/SysLoginController.java | 16 ++-- .../ruoyi/web/service/SysLoginService.java | 94 +++++++++++++------ .../ruoyi/web/service/SysRegisterService.java | 16 ++-- .../src/main/resources/application.yml | 15 +++ .../main/resources/i18n/messages.properties | 5 + .../resources/i18n/messages_en_US.properties | 5 + .../resources/i18n/messages_zh_CN.properties | 5 + .../common/core/domain/model/LoginBody.java | 6 ++ .../common/core/domain/model/LoginUser.java | 5 + .../core/domain/model/SmsLoginBody.java | 6 ++ .../ruoyi/common/core/enums/TenantStatus.java | 30 ++++++ .../core/exception/TenantException.java | 20 ++++ .../ruoyi/common/log/aspect/LogAspect.java | 1 + .../common/log/event/LogininforEvent.java | 5 + .../ruoyi/common/log/event/OperLogEvent.java | 5 + .../mybatis/config/MybatisPlusConfig.java | 18 +++- .../mybatis/core/domain/TenantEntity.java | 2 +- .../handler/PlusTenantLineHandler.java | 54 +++++++++++ .../mybatis/properties/TenantProperties.java | 27 ++++++ .../common/satoken/utils/LoginHelper.java | 13 +++ .../system/domain/bo/SysLogininforBo.java | 2 +- .../ruoyi/system/domain/bo/SysOperLogBo.java | 2 +- .../com/ruoyi/system/domain/vo/SysUserVo.java | 5 + .../runner/SystemApplicationRunner.java | 5 + .../system/service/ISysTenantService.java | 5 + .../impl/SysLogininforServiceImpl.java | 1 + .../service/impl/SysTenantServiceImpl.java | 5 + .../resources/mapper/system/SysUserMapper.xml | 1 + ruoyi-ui/src/api/login.js | 3 +- ruoyi-ui/src/store/modules/user.js | 3 +- ruoyi-ui/src/views/login.vue | 18 ++++ ruoyi-ui/src/views/register.vue | 14 +++ 32 files changed, 366 insertions(+), 46 deletions(-) create mode 100644 ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/enums/TenantStatus.java create mode 100644 ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/TenantException.java create mode 100644 ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/handler/PlusTenantLineHandler.java create mode 100644 ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/properties/TenantProperties.java diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/SysLoginController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/SysLoginController.java index 7ae669a12..447f86284 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/SysLoginController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/SysLoginController.java @@ -41,16 +41,18 @@ public class SysLoginController { /** * 登录方法 * - * @param loginBody 登录信息 + * @param body 登录信息 * @return 结果 */ @SaIgnore @PostMapping("/login") - public R login(@Validated @RequestBody LoginBody loginBody) { + public R login(@Validated @RequestBody LoginBody body) { LoginVo loginVo = new LoginVo(); // 生成令牌 - String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), - loginBody.getUuid()); + String token = loginService.login( + body.getTenantId(), + body.getUsername(), body.getPassword(), + body.getCode(), body.getUuid()); loginVo.setToken(token); return R.ok(loginVo); } @@ -58,15 +60,15 @@ public class SysLoginController { /** * 短信登录(示例) * - * @param smsLoginBody 登录信息 + * @param body 登录信息 * @return 结果 */ @SaIgnore @PostMapping("/smsLogin") - public R smsLogin(@Validated @RequestBody SmsLoginBody smsLoginBody) { + public R smsLogin(@Validated @RequestBody SmsLoginBody body) { LoginVo loginVo = new LoginVo(); // 生成令牌 - String token = loginService.smsLogin(smsLoginBody.getPhonenumber(), smsLoginBody.getSmsCode()); + String token = loginService.smsLogin(body.getTenantId(), body.getPhonenumber(), body.getSmsCode()); loginVo.setToken(token); return R.ok(loginVo); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/service/SysLoginService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/service/SysLoginService.java index bf934b1df..d885e7523 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/service/SysLoginService.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/service/SysLoginService.java @@ -13,7 +13,9 @@ import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.XcxLoginUser; import com.ruoyi.common.core.enums.DeviceType; import com.ruoyi.common.core.enums.LoginType; +import com.ruoyi.common.core.enums.TenantStatus; import com.ruoyi.common.core.enums.UserStatus; +import com.ruoyi.common.core.exception.TenantException; import com.ruoyi.common.core.exception.user.CaptchaException; import com.ruoyi.common.core.exception.user.CaptchaExpireException; import com.ruoyi.common.core.exception.user.UserException; @@ -22,10 +24,12 @@ import com.ruoyi.common.log.event.LogininforEvent; import com.ruoyi.common.redis.utils.RedisUtils; import com.ruoyi.common.satoken.utils.LoginHelper; import com.ruoyi.system.domain.SysUser; +import com.ruoyi.system.domain.vo.SysTenantVo; import com.ruoyi.system.domain.vo.SysUserVo; import com.ruoyi.system.mapper.SysUserMapper; import com.ruoyi.system.service.ISysConfigService; import com.ruoyi.system.service.ISysPermissionService; +import com.ruoyi.system.service.ISysTenantService; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -33,6 +37,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.time.Duration; +import java.util.Date; import java.util.List; import java.util.function.Supplier; @@ -49,6 +54,7 @@ public class SysLoginService { private final SysUserMapper userMapper; private final ISysConfigService configService; private final ISysPermissionService permissionService; + private final ISysTenantService tenantService; @Value("${user.password.maxRetryCount}") private Integer maxRetryCount; @@ -56,6 +62,9 @@ public class SysLoginService { @Value("${user.password.lockTime}") private Integer lockTime; + @Value("${tenant.enable}") + private Boolean tenantEnable; + /** * 登录验证 * @@ -65,36 +74,41 @@ public class SysLoginService { * @param uuid 唯一标识 * @return 结果 */ - public String login(String username, String password, String code, String uuid) { + public String login(String tenantId, String username, String password, String code, String uuid) { HttpServletRequest request = ServletUtils.getRequest(); boolean captchaEnabled = configService.selectCaptchaEnabled(); // 验证码开关 if (captchaEnabled) { - validateCaptcha(username, code, uuid, request); + validateCaptcha(tenantId, username, code, uuid, request); } - SysUserVo user = loadUserByUsername(username); - checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, user.getPassword())); + // 校验租户 + checkTenant(tenantId); + + SysUserVo user = loadUserByUsername(tenantId, username); + checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword())); // 此处可根据登录用户的数据不同 自行创建 loginUser LoginUser loginUser = buildLoginUser(user); // 生成token LoginHelper.loginByDevice(loginUser, DeviceType.PC); - recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")); + recordLogininfor(loginUser.getTenantId(), username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")); recordLoginInfo(user.getUserId()); return StpUtil.getTokenValue(); } - public String smsLogin(String phonenumber, String smsCode) { + public String smsLogin(String tenantId, String phonenumber, String smsCode) { + // 校验租户 + checkTenant(tenantId); // 通过手机号查找用户 - SysUserVo user = loadUserByPhonenumber(phonenumber); + SysUserVo user = loadUserByPhonenumber(tenantId, phonenumber); - checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode)); + checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode)); // 此处可根据登录用户的数据不同 自行创建 loginUser LoginUser loginUser = buildLoginUser(user); // 生成token LoginHelper.loginByDevice(loginUser, DeviceType.APP); - recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")); + recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")); recordLoginInfo(user.getUserId()); return StpUtil.getTokenValue(); } @@ -106,9 +120,12 @@ public class SysLoginService { // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid String openid = ""; SysUserVo user = loadUserByOpenid(openid); + // 校验租户 + checkTenant(user.getTenantId()); // 此处可根据登录用户的数据不同 自行创建 loginUser XcxLoginUser loginUser = new XcxLoginUser(); + loginUser.setTenantId(user.getTenantId()); loginUser.setUserId(user.getUserId()); loginUser.setUsername(user.getUserName()); loginUser.setUserType(user.getUserType()); @@ -116,7 +133,7 @@ public class SysLoginService { // 生成token LoginHelper.loginByDevice(loginUser, DeviceType.XCX); - recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")); + recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")); recordLoginInfo(user.getUserId()); return StpUtil.getTokenValue(); } @@ -128,7 +145,7 @@ public class SysLoginService { try { LoginUser loginUser = LoginHelper.getLoginUser(); StpUtil.logout(); - recordLogininfor(loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success")); + recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success")); } catch (NotLoginException ignored) { } } @@ -136,13 +153,15 @@ public class SysLoginService { /** * 记录登录信息 * + * @param tenantId 租户ID * @param username 用户名 * @param status 状态 * @param message 消息内容 * @return */ - private void recordLogininfor(String username, String status, String message) { + private void recordLogininfor(String tenantId, String username, String status, String message) { LogininforEvent logininforEvent = new LogininforEvent(); + logininforEvent.setTenantId(tenantId); logininforEvent.setUsername(username); logininforEvent.setStatus(status); logininforEvent.setMessage(message); @@ -153,10 +172,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); if (StringUtils.isBlank(code)) { - recordLogininfor(phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); + recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); throw new CaptchaExpireException(); } return code.equals(smsCode); @@ -169,24 +188,25 @@ public class SysLoginService { * @param code 验证码 * @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 captcha = RedisUtils.getCacheObject(verifyKey); RedisUtils.deleteObject(verifyKey); if (captcha == null) { - recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); + recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); throw new CaptchaExpireException(); } if (!code.equalsIgnoreCase(captcha)) { - recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")); + recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")); throw new CaptchaException(); } } - private SysUserVo loadUserByUsername(String username) { + private SysUserVo loadUserByUsername(String tenantId, String username) { SysUser user = userMapper.selectOne(new LambdaQueryWrapper() - .select(SysUser::getUserName, SysUser::getStatus) - .eq(SysUser::getUserName, username)); + .select(SysUser::getUserName, SysUser::getStatus) + .eq(tenantEnable, SysUser::getTenantId, tenantId) + .eq(SysUser::getUserName, username)); if (ObjectUtil.isNull(user)) { log.info("登录用户:{} 不存在.", username); throw new UserException("user.not.exists", username); @@ -197,10 +217,11 @@ public class SysLoginService { return userMapper.selectUserByUserName(username); } - private SysUserVo loadUserByPhonenumber(String phonenumber) { + private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) { SysUser user = userMapper.selectOne(new LambdaQueryWrapper() - .select(SysUser::getPhonenumber, SysUser::getStatus) - .eq(SysUser::getPhonenumber, phonenumber)); + .select(SysUser::getPhonenumber, SysUser::getStatus) + .eq(tenantEnable, SysUser::getTenantId, tenantId) + .eq(SysUser::getPhonenumber, phonenumber)); if (ObjectUtil.isNull(user)) { log.info("登录用户:{} 不存在.", phonenumber); throw new UserException("user.not.exists", phonenumber); @@ -230,6 +251,7 @@ public class SysLoginService { */ private LoginUser buildLoginUser(SysUserVo user) { LoginUser loginUser = new LoginUser(); + loginUser.setTenantId(user.getTenantId()); loginUser.setUserId(user.getUserId()); loginUser.setDeptId(user.getDeptId()); loginUser.setUsername(user.getUserName()); @@ -259,7 +281,7 @@ public class SysLoginService { /** * 登录校验 */ - private void checkLogin(LoginType loginType, String username, Supplier supplier) { + private void checkLogin(LoginType loginType, String tenantId, String username, Supplier supplier) { String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username; String loginFail = Constants.LOGIN_FAIL; @@ -267,7 +289,7 @@ public class SysLoginService { Integer errorNumber = RedisUtils.getCacheObject(errorKey); // 锁定时间内登录 则踢出 if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) { - recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime)); + recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime)); throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime); } @@ -277,12 +299,12 @@ public class SysLoginService { // 达到规定错误次数 则锁定登录 if (errorNumber.equals(maxRetryCount)) { RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime)); - recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime)); + recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime)); throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime); } else { // 未达到规定错误次数 则递增 RedisUtils.setCacheObject(errorKey, errorNumber); - recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber)); + recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber)); throw new UserException(loginType.getRetryLimitCount(), errorNumber); } } @@ -290,4 +312,22 @@ public class SysLoginService { // 登录成功 清空错误次数 RedisUtils.deleteObject(errorKey); } + + private void checkTenant(String tenantId) { + if (tenantEnable) { + 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 (tenant.getExpireTime() != null && new Date().after(tenant.getExpireTime())) { + log.info("登录租户:{} 已超过有效期.", tenantId); + throw new TenantException("tenant.expired"); + } + } + } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/service/SysRegisterService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/service/SysRegisterService.java index f7734700e..12b255ef8 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/service/SysRegisterService.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/service/SysRegisterService.java @@ -39,6 +39,7 @@ public class SysRegisterService { */ public void register(RegisterBody registerBody) { HttpServletRequest request = ServletUtils.getRequest(); + String tenantId = registerBody.getTenantId(); String username = registerBody.getUsername(); String password = registerBody.getPassword(); // 校验用户类型是否存在 @@ -47,9 +48,10 @@ public class SysRegisterService { boolean captchaEnabled = configService.selectCaptchaEnabled(); // 验证码开关 if (captchaEnabled) { - validateCaptcha(username, registerBody.getCode(), registerBody.getUuid(), request); + validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid(), request); } SysUserBo sysUser = new SysUserBo(); + sysUser.setTenantId(tenantId); sysUser.setUserName(username); sysUser.setNickName(username); sysUser.setPassword(BCrypt.hashpw(password)); @@ -62,7 +64,7 @@ public class SysRegisterService { if (!regFlag) { throw new UserException("user.register.error"); } - recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success")); + recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success")); } /** @@ -73,16 +75,16 @@ public class SysRegisterService { * @param uuid 唯一标识 * @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 captcha = RedisUtils.getCacheObject(verifyKey); RedisUtils.deleteObject(verifyKey); if (captcha == null) { - recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire")); + recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire")); throw new CaptchaExpireException(); } if (!code.equalsIgnoreCase(captcha)) { - recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error")); + recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error")); throw new CaptchaException(); } } @@ -90,13 +92,15 @@ public class SysRegisterService { /** * 记录登录信息 * + * @param tenantId 租户ID * @param username 用户名 * @param status 状态 * @param message 消息内容 * @return */ - private void recordLogininfor(String username, String status, String message) { + private void recordLogininfor(String tenantId, String username, String status, String message) { LogininforEvent logininforEvent = new LogininforEvent(); + logininforEvent.setTenantId(tenantId); logininforEvent.setUsername(username); logininforEvent.setStatus(status); logininforEvent.setMessage(message); diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 2f496b023..b6ed108fb 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -136,6 +136,21 @@ security: - /actuator - /actuator/** +# 多租户配置 +tenant: + # 是否开启 + enable: true + # 排除表 + excludes: + - sys_menu + - sys_config + - sys_dict_type + - sys_dict_data + - sys_role_dept + - sys_role_menu + - sys_user_post + - sys_user_role + # MyBatisPlus配置 # https://baomidou.com/config/ mybatis-plus: diff --git a/ruoyi-admin/src/main/resources/i18n/messages.properties b/ruoyi-admin/src/main/resources/i18n/messages.properties index 4ff55f41a..3a5eeac88 100644 --- a/ruoyi-admin/src/main/resources/i18n/messages.properties +++ b/ruoyi-admin/src/main/resources/i18n/messages.properties @@ -43,3 +43,8 @@ sms.code.not.blank=短信验证码不能为空 sms.code.retry.limit.count=短信验证码输入错误{0}次 sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟 xcx.code.not.blank=小程序code不能为空 +##租户 +tenant.number.not.blank=租户编号不能为空 +tenant.not.exists=对不起, 您的租户不存在,请联系管理员 +tenant.blocked=对不起,您的租户已禁用,请联系管理员 +tenant.expired=对不起,您的租户已过期,请联系管理员 diff --git a/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties b/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties index c0faca93a..7568f6805 100644 --- a/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties +++ b/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties @@ -43,3 +43,8 @@ sms.code.not.blank=Sms code cannot be blank sms.code.retry.limit.count=Sms code input error {0} times sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {1} minutes xcx.code.not.blank=Mini program code cannot be blank +##租户 +tenant.number.not.blank=Tenant number cannot be blank +tenant.not.exists=Sorry, your tenant does not exist. Please contact the administrator +tenant.blocked=Sorry, your tenant is disabled. Please contact the administrator +tenant.expired=Sorry, your tenant has expired. Please contact the administrator. diff --git a/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties b/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties index 4ff55f41a..3a5eeac88 100644 --- a/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties +++ b/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties @@ -43,3 +43,8 @@ sms.code.not.blank=短信验证码不能为空 sms.code.retry.limit.count=短信验证码输入错误{0}次 sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟 xcx.code.not.blank=小程序code不能为空 +##租户 +tenant.number.not.blank=租户编号不能为空 +tenant.not.exists=对不起, 您的租户不存在,请联系管理员 +tenant.blocked=对不起,您的租户已禁用,请联系管理员 +tenant.expired=对不起,您的租户已过期,请联系管理员 diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java index eab65812f..153c02fd6 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java @@ -15,6 +15,12 @@ import jakarta.validation.constraints.NotBlank; @Data public class LoginBody { + /** + * 租户ID + */ + @NotBlank(message = "{tenant.number.not.blank}") + private String tenantId; + /** * 用户名 */ diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java index 7db0464de..47126f2b5 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java @@ -22,6 +22,11 @@ public class LoginUser implements Serializable { @Serial private static final long serialVersionUID = 1L; + /** + * 租户ID + */ + private String tenantId; + /** * 用户ID */ diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java index 5bf95c471..3b25fce00 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java @@ -13,6 +13,12 @@ import jakarta.validation.constraints.NotBlank; @Data public class SmsLoginBody { + /** + * 租户ID + */ + @NotBlank(message = "{tenant.number.not.blank}") + private String tenantId; + /** * 用户名 */ diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/enums/TenantStatus.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/enums/TenantStatus.java new file mode 100644 index 000000000..166804be0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/enums/TenantStatus.java @@ -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; + +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/TenantException.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/TenantException.java new file mode 100644 index 000000000..187410181 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/TenantException.java @@ -0,0 +1,20 @@ +package com.ruoyi.common.core.exception; + +import com.ruoyi.common.core.exception.base.BaseException; + +import java.io.Serial; + +/** + * 用户信息异常类 + * + * @author ruoyi + */ +public class TenantException extends BaseException { + + @Serial + private static final long serialVersionUID = 1L; + + public TenantException(String code, Object... args) { + super("tenant", code, args, null); + } +} diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/aspect/LogAspect.java b/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/aspect/LogAspect.java index ca8a568d7..d85d15219 100644 --- a/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/aspect/LogAspect.java +++ b/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/aspect/LogAspect.java @@ -67,6 +67,7 @@ public class LogAspect { // *========数据库日志=========*// OperLogEvent operLog = new OperLogEvent(); + operLog.setTenantId(LoginHelper.getTenantId()); operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); // 请求的地址 String ip = ServletUtils.getClientIP(); diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/LogininforEvent.java b/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/LogininforEvent.java index e52e10292..785f5899d 100644 --- a/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/LogininforEvent.java +++ b/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/LogininforEvent.java @@ -19,6 +19,11 @@ public class LogininforEvent implements Serializable { @Serial private static final long serialVersionUID = 1L; + /** + * 租户ID + */ + private String tenantId; + /** * 用户账号 */ diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/OperLogEvent.java b/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/OperLogEvent.java index be07dd0d4..ecb4b7c18 100644 --- a/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/OperLogEvent.java +++ b/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/event/OperLogEvent.java @@ -23,6 +23,11 @@ public class OperLogEvent implements Serializable { */ private Long operId; + /** + * 租户ID + */ + private String tenantId; + /** * 操作模块 */ diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/config/MybatisPlusConfig.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/config/MybatisPlusConfig.java index 7b824f1a6..8b90b23d9 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/config/MybatisPlusConfig.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/config/MybatisPlusConfig.java @@ -7,10 +7,14 @@ import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; import com.ruoyi.common.mybatis.handler.InjectionMetaObjectHandler; +import com.ruoyi.common.mybatis.handler.PlusTenantLineHandler; import com.ruoyi.common.mybatis.interceptor.PlusDataPermissionInterceptor; +import com.ruoyi.common.mybatis.properties.TenantProperties; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.transaction.annotation.EnableTransactionManagement; @@ -19,14 +23,19 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; * * @author Lion Li */ +@EnableConfigurationProperties(TenantProperties.class) @EnableTransactionManagement(proxyTargetClass = true) @AutoConfiguration @MapperScan("${mybatis-plus.mapperPackage}") public class MybatisPlusConfig { @Bean - public MybatisPlusInterceptor mybatisPlusInterceptor() { + public MybatisPlusInterceptor mybatisPlusInterceptor(TenantProperties tenantProperties) { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + if (tenantProperties.getEnable()) { + // 多租户插件 + interceptor.addInnerInterceptor(tenantLineInnerInterceptor(tenantProperties)); + } // 数据权限处理 interceptor.addInnerInterceptor(dataPermissionInterceptor()); // 分页插件 @@ -36,6 +45,13 @@ public class MybatisPlusConfig { return interceptor; } + /** + * 多租户插件 + */ + public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties tenantProperties) { + return new TenantLineInnerInterceptor(new PlusTenantLineHandler(tenantProperties)); + } + /** * 数据权限拦截器 */ diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/core/domain/TenantEntity.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/core/domain/TenantEntity.java index 50681efef..28ea1411b 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/core/domain/TenantEntity.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/core/domain/TenantEntity.java @@ -15,6 +15,6 @@ public class TenantEntity extends BaseEntity { /** * 租户编号 */ - private String tenantId = "000000"; + private String tenantId; } diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/handler/PlusTenantLineHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/handler/PlusTenantLineHandler.java new file mode 100644 index 000000000..9215c71fd --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/handler/PlusTenantLineHandler.java @@ -0,0 +1,54 @@ +package com.ruoyi.common.mybatis.handler; + +import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; +import com.ruoyi.common.core.utils.StringUtils; +import com.ruoyi.common.mybatis.properties.TenantProperties; +import com.ruoyi.common.satoken.utils.LoginHelper; +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.isNotBlank(tenantId)) { + //多租户模式,租户id从当前用户获取 + return new LongValue(tenantId); + } + return new NullValue(); + } + + @Override + public boolean ignoreTable(String tableName) { + String tenantId = LoginHelper.getTenantId(); + // 判断是否有租户 + if (StringUtils.isNotBlank(tenantId)) { + // 判断是否平台超级管理员,如果是平台超级管理员则拥有所有数据权限 + if (!LoginHelper.isAdmin()) { + // 不需要过滤租户的表 + List excludes = tenantProperties.getExcludes(); + // 非业务表 + excludes.addAll(List.of( + "gen_table", + "gen_table_column" + )); + return excludes.contains(tableName); + } + } + return true; + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/properties/TenantProperties.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/properties/TenantProperties.java new file mode 100644 index 000000000..4a592f50a --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/ruoyi/common/mybatis/properties/TenantProperties.java @@ -0,0 +1,27 @@ +package com.ruoyi.common.mybatis.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 excludes; + +} diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/com/ruoyi/common/satoken/utils/LoginHelper.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/com/ruoyi/common/satoken/utils/LoginHelper.java index f7fefa3c5..5b37788ba 100644 --- a/ruoyi-common/ruoyi-common-satoken/src/main/java/com/ruoyi/common/satoken/utils/LoginHelper.java +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/com/ruoyi/common/satoken/utils/LoginHelper.java @@ -96,6 +96,19 @@ public class LoginHelper { return loginUser.getUserId(); } + /** + * 获取租户ID + */ + public static String getTenantId() { + LoginUser loginUser; + try { + loginUser = getLoginUser(); + } catch (Exception e) { + return null; + } + return loginUser.getTenantId(); + } + /** * 获取部门ID */ diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysLogininforBo.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysLogininforBo.java index df5a8f37b..4bbd35532 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysLogininforBo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysLogininforBo.java @@ -23,7 +23,7 @@ public class SysLogininforBo { /** * 租户编号 */ - private String tenantId = "000000"; + private String tenantId; /** * 用户账号 diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysOperLogBo.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysOperLogBo.java index e074e18dc..443ff2b8e 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysOperLogBo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysOperLogBo.java @@ -25,7 +25,7 @@ public class SysOperLogBo { /** * 租户编号 */ - private String tenantId = "000000"; + private String tenantId; /** * 模块标题 diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysUserVo.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysUserVo.java index 90849b1d9..6025f1d5d 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysUserVo.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysUserVo.java @@ -27,6 +27,11 @@ public class SysUserVo implements Serializable { */ private Long userId; + /** + * 租户ID + */ + private String tenantId; + /** * 部门ID */ diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/runner/SystemApplicationRunner.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/runner/SystemApplicationRunner.java index 36ed651a0..c084975af 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/runner/SystemApplicationRunner.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/runner/SystemApplicationRunner.java @@ -1,5 +1,7 @@ package com.ruoyi.system.runner; +import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy; +import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; import com.ruoyi.common.core.config.RuoYiConfig; import com.ruoyi.system.service.ISysConfigService; import com.ruoyi.system.service.ISysDictTypeService; @@ -27,15 +29,18 @@ public class SystemApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { + InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build()); ossConfigService.init(); log.info("初始化OSS配置成功"); if (ruoyiConfig.isCacheLazy()) { + InterceptorIgnoreHelper.clearIgnoreStrategy(); return; } configService.loadingConfigCache(); log.info("加载参数缓存数据成功"); dictTypeService.loadingDictCache(); log.info("加载字典缓存数据成功"); + InterceptorIgnoreHelper.clearIgnoreStrategy(); } } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTenantService.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTenantService.java index ba0f8cdeb..a8b8a18ce 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTenantService.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTenantService.java @@ -20,6 +20,11 @@ public interface ISysTenantService { */ SysTenantVo queryById(Long id); + /** + * 基于租户ID查询租户 + */ + SysTenantVo queryByTenantId(String tenantId); + /** * 查询租户列表 */ diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java index f07ca7669..c8306624c 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java @@ -68,6 +68,7 @@ public class SysLogininforServiceImpl implements ISysLogininforService { String browser = userAgent.getBrowser().getName(); // 封装对象 SysLogininforBo logininfor = new SysLogininforBo(); + logininfor.setTenantId(logininforEvent.getTenantId()); logininfor.setUserName(logininforEvent.getUsername()); logininfor.setIpaddr(ip); logininfor.setLoginLocation(address); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTenantServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTenantServiceImpl.java index c3a48a41b..0cc1f2464 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTenantServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTenantServiceImpl.java @@ -53,6 +53,11 @@ public class SysTenantServiceImpl implements ISysTenantService { return baseMapper.selectVoById(id); } + @Override + public SysTenantVo queryByTenantId(String tenantId) { + return baseMapper.selectVoOne(new LambdaQueryWrapper().eq(SysTenant::getTenantId, tenantId)); + } + /** * 查询租户列表 */ diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml index b6ba60108..74e41b99a 100644 --- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml +++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -22,6 +22,7 @@ select u.user_id, + u.tenant_id, u.dept_id, u.user_name, u.nick_name, diff --git a/ruoyi-ui/src/api/login.js b/ruoyi-ui/src/api/login.js index 3c5bcfe21..0ac58102e 100644 --- a/ruoyi-ui/src/api/login.js +++ b/ruoyi-ui/src/api/login.js @@ -1,8 +1,9 @@ import request from '@/utils/request' // 登录方法 -export function login(username, password, code, uuid) { +export function login(tenantId, username, password, code, uuid) { const data = { + tenantId, username, password, code, diff --git a/ruoyi-ui/src/store/modules/user.js b/ruoyi-ui/src/store/modules/user.js index d67c9a4a6..934d66d4f 100644 --- a/ruoyi-ui/src/store/modules/user.js +++ b/ruoyi-ui/src/store/modules/user.js @@ -31,12 +31,13 @@ const user = { actions: { // 登录 Login({ commit }, userInfo) { + const tenantId = userInfo.tenantId.trim() const username = userInfo.username.trim() const password = userInfo.password const code = userInfo.code const uuid = userInfo.uuid return new Promise((resolve, reject) => { - login(username, password, code, uuid).then(res => { + login(tenantId, username, password, code, uuid).then(res => { setToken(res.data.token) commit('SET_TOKEN', res.data.token) resolve() diff --git a/ruoyi-ui/src/views/login.vue b/ruoyi-ui/src/views/login.vue index ed8d971a2..56fbd2f16 100644 --- a/ruoyi-ui/src/views/login.vue +++ b/ruoyi-ui/src/views/login.vue @@ -2,6 +2,16 @@