Merge remote-tracking branch 'origin/dev' into 4.X

# Conflicts:
#	README.md
This commit is contained in:
疯狂的狮子li 2023-05-08 09:32:40 +08:00
commit c631a084f3
92 changed files with 883 additions and 305 deletions

View File

@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> <configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile"> <deployment type="dockerfile">
<settings> <settings>
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:4.6.0" /> <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:4.7.0" />
<option name="buildOnly" value="true" /> <option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" /> <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
</settings> </settings>

View File

@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile"> <deployment type="dockerfile">
<settings> <settings>
<option name="imageTag" value="ruoyi/ruoyi-server:4.6.0" /> <option name="imageTag" value="ruoyi/ruoyi-server:4.7.0" />
<option name="buildOnly" value="true" /> <option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" /> <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
</settings> </settings>

View File

@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-xxl-job-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> <configuration default="false" name="ruoyi-xxl-job-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile"> <deployment type="dockerfile">
<settings> <settings>
<option name="imageTag" value="ruoyi/ruoyi-xxl-job-admin:4.6.0" /> <option name="imageTag" value="ruoyi/ruoyi-xxl-job-admin:4.7.0" />
<option name="buildOnly" value="true" /> <option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-xxl-job-admin/Dockerfile" /> <option name="sourceFilePath" value="ruoyi-extend/ruoyi-xxl-job-admin/Dockerfile" />
</settings> </settings>

View File

@ -2,6 +2,7 @@
<div style="height: 10px; clear: both;"></div> <div style="height: 10px; clear: both;"></div>
- - - - - -
## 平台简介 ## 平台简介
[![码云Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Vue-Plus) [![码云Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Vue-Plus)
@ -9,7 +10,7 @@
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus) [![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
<br> <br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-4.6.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus) [![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-4.7.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.7-blue.svg)]() [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.7-blue.svg)]()
[![JDK-8+](https://img.shields.io/badge/JDK-8-green.svg)]() [![JDK-8+](https://img.shields.io/badge/JDK-8-green.svg)]()
[![JDK-11](https://img.shields.io/badge/JDK-11-green.svg)]() [![JDK-11](https://img.shields.io/badge/JDK-11-green.svg)]()
@ -161,4 +162,3 @@
| ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png "屏幕截图") | | ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png "屏幕截图") |

21
pom.xml
View File

@ -6,15 +6,15 @@
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<version>4.6.0</version> <version>4.7.0</version>
<name>RuoYi-Vue-Plus</name> <name>RuoYi-Vue-Plus</name>
<url>https://gitee.com/dromara/RuoYi-Vue-Plus</url> <url>https://gitee.com/dromara/RuoYi-Vue-Plus</url>
<description>RuoYi-Vue-Plus后台管理系统</description> <description>RuoYi-Vue-Plus后台管理系统</description>
<properties> <properties>
<ruoyi-vue-plus.version>4.6.0</ruoyi-vue-plus.version> <ruoyi-vue-plus.version>4.7.0</ruoyi-vue-plus.version>
<spring-boot.version>2.7.9</spring-boot.version> <spring-boot.version>2.7.11</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version> <java.version>1.8</java.version>
@ -27,16 +27,18 @@
<satoken.version>1.34.0</satoken.version> <satoken.version>1.34.0</satoken.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version> <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version> <p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.15</hutool.version> <hutool.version>5.8.18</hutool.version>
<okhttp.version>4.10.0</okhttp.version> <okhttp.version>4.10.0</okhttp.version>
<spring-boot-admin.version>2.7.10</spring-boot-admin.version> <spring-boot-admin.version>2.7.10</spring-boot-admin.version>
<redisson.version>3.20.0</redisson.version> <redisson.version>3.20.1</redisson.version>
<lock4j.version>2.2.3</lock4j.version> <lock4j.version>2.2.3</lock4j.version>
<dynamic-ds.version>3.5.2</dynamic-ds.version> <dynamic-ds.version>3.5.2</dynamic-ds.version>
<alibaba-ttl.version>2.14.2</alibaba-ttl.version> <alibaba-ttl.version>2.14.2</alibaba-ttl.version>
<xxl-job.version>2.3.1</xxl-job.version> <xxl-job.version>2.4.0</xxl-job.version>
<lombok.version>1.18.26</lombok.version> <lombok.version>1.18.26</lombok.version>
<bouncycastle.version>1.72</bouncycastle.version> <bouncycastle.version>1.72</bouncycastle.version>
<!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version>
<!-- 临时修复 snakeyaml 漏洞 --> <!-- 临时修复 snakeyaml 漏洞 -->
<snakeyaml.version>1.33</snakeyaml.version> <snakeyaml.version>1.33</snakeyaml.version>
@ -258,6 +260,13 @@
<version>${alibaba-ttl.version}</version> <version>${alibaba-ttl.version}</version>
</dependency> </dependency>
<!-- 离线IP地址定位库 ip2region -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>${ip2region.version}</version>
</dependency>
<!-- 临时修复 snakeyaml 漏洞 --> <!-- 临时修复 snakeyaml 漏洞 -->
<dependency> <dependency>
<groupId>org.yaml</groupId> <groupId>org.yaml</groupId>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>4.6.0</version> <version>4.7.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging> <packaging>jar</packaging>

View File

@ -10,10 +10,12 @@ import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.enums.CaptchaType; import com.ruoyi.common.enums.CaptchaType;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.email.MailUtils;
import com.ruoyi.common.utils.redis.RedisUtils; import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.common.utils.reflect.ReflectUtils; import com.ruoyi.common.utils.reflect.ReflectUtils;
import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.config.properties.CaptchaProperties; import com.ruoyi.framework.config.properties.CaptchaProperties;
import com.ruoyi.framework.config.properties.MailProperties;
import com.ruoyi.sms.config.properties.SmsProperties; import com.ruoyi.sms.config.properties.SmsProperties;
import com.ruoyi.sms.core.SmsTemplate; import com.ruoyi.sms.core.SmsTemplate;
import com.ruoyi.sms.entity.SmsResult; import com.ruoyi.sms.entity.SmsResult;
@ -47,6 +49,7 @@ public class CaptchaController {
private final CaptchaProperties captchaProperties; private final CaptchaProperties captchaProperties;
private final SmsProperties smsProperties; private final SmsProperties smsProperties;
private final ISysConfigService configService; private final ISysConfigService configService;
private final MailProperties mailProperties;
/** /**
* 短信验证码 * 短信验证码
@ -54,8 +57,7 @@ public class CaptchaController {
* @param phonenumber 用户手机号 * @param phonenumber 用户手机号
*/ */
@GetMapping("/captchaSms") @GetMapping("/captchaSms")
public R<Void> smsCaptcha(@NotBlank(message = "{user.phonenumber.not.blank}") public R<Void> smsCaptcha(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
String phonenumber) {
if (!smsProperties.getEnabled()) { if (!smsProperties.getEnabled()) {
return R.fail("当前系统没有开启短信功能!"); return R.fail("当前系统没有开启短信功能!");
} }
@ -75,6 +77,28 @@ public class CaptchaController {
return R.ok(); return R.ok();
} }
/**
* 邮箱验证码
*
* @param email 邮箱
*/
@GetMapping("/captchaEmail")
public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
if (!mailProperties.getEnabled()) {
return R.fail("当前系统没有开启邮箱功能!");
}
String key = CacheConstants.CAPTCHA_CODE_KEY + email;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
try {
MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
} catch (Exception e) {
log.error("验证码短信发送异常 => {}", e.getMessage());
return R.fail(e.getMessage());
}
return R.ok();
}
/** /**
* 生成验证码 * 生成验证码
*/ */

View File

@ -33,7 +33,6 @@ public class CacheController {
private final static List<SysCache> CACHES = new ArrayList<>(); private final static List<SysCache> CACHES = new ArrayList<>();
static { static {
CACHES.add(new SysCache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息"));
CACHES.add(new SysCache(CacheConstants.ONLINE_TOKEN_KEY, "在线用户")); CACHES.add(new SysCache(CacheConstants.ONLINE_TOKEN_KEY, "在线用户"));
CACHES.add(new SysCache(CacheNames.SYS_CONFIG, "配置信息")); CACHES.add(new SysCache(CacheNames.SYS_CONFIG, "配置信息"));
CACHES.add(new SysCache(CacheNames.SYS_DICT, "数据字典")); CACHES.add(new SysCache(CacheNames.SYS_DICT, "数据字典"));

View File

@ -45,7 +45,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 = StringUtils.substringAfterLast(key, ":");
// 如果已经过期则跳过 // 如果已经过期则跳过
if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) { if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) {
continue; continue;

View File

@ -5,6 +5,7 @@ import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.domain.entity.SysMenu; import com.ruoyi.common.core.domain.entity.SysMenu;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.EmailLoginBody;
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;
@ -57,7 +58,7 @@ public class SysLoginController {
} }
/** /**
* 短信登录(示例) * 短信登录
* *
* @param smsLoginBody 登录信息 * @param smsLoginBody 登录信息
* @return 结果 * @return 结果
@ -72,6 +73,21 @@ public class SysLoginController {
return R.ok(ajax); return R.ok(ajax);
} }
/**
* 邮件登录
*
* @param body 登录信息
* @return 结果
*/
@PostMapping("/emailLogin")
public R<Map<String, Object>> emailLogin(@Validated @RequestBody EmailLoginBody body) {
Map<String, Object> ajax = new HashMap<>();
// 生成令牌
String token = loginService.emailLogin(body.getEmail(), body.getEmailCode());
ajax.put(Constants.TOKEN, token);
return R.ok(ajax);
}
/** /**
* 小程序登录(示例) * 小程序登录(示例)
* *

View File

@ -2,10 +2,7 @@ package com.ruoyi.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpUtil;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.PageQuery; import com.ruoyi.common.core.domain.PageQuery;
@ -13,11 +10,6 @@ import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.validate.QueryGroup; import com.ruoyi.common.core.validate.QueryGroup;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.oss.core.OssClient;
import com.ruoyi.oss.factory.OssFactory;
import com.ruoyi.system.domain.SysOss;
import com.ruoyi.system.domain.bo.SysOssBo; import com.ruoyi.system.domain.bo.SysOssBo;
import com.ruoyi.system.domain.vo.SysOssVo; import com.ruoyi.system.domain.vo.SysOssVo;
import com.ruoyi.system.service.ISysOssService; import com.ruoyi.system.service.ISysOssService;
@ -80,7 +72,7 @@ public class SysOssController extends BaseController {
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<Map<String, String>> upload(@RequestPart("file") MultipartFile file) { public R<Map<String, String>> upload(@RequestPart("file") MultipartFile file) {
if (ObjectUtil.isNull(file)) { if (ObjectUtil.isNull(file)) {
throw new ServiceException("上传文件不能为空"); return R.fail("上传文件不能为空");
} }
SysOssVo oss = iSysOssService.upload(file); SysOssVo oss = iSysOssService.upload(file);
Map<String, String> map = new HashMap<>(2); Map<String, String> map = new HashMap<>(2);

View File

@ -1,22 +1,15 @@
package com.ruoyi.web.controller.system; package com.ruoyi.web.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.PageQuery; import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.domain.SysUserRole; import com.ruoyi.system.domain.SysUserRole;
import com.ruoyi.system.service.ISysDeptService; import com.ruoyi.system.service.ISysDeptService;
@ -112,25 +105,7 @@ public class SysRoleController extends BaseController {
} }
if (roleService.updateRole(role) > 0) { if (roleService.updateRole(role) > 0) {
List<String> keys = StpUtil.searchTokenValue("", 0, -1, false); roleService.cleanOnlineUserByRole(role.getRoleId());
if (CollUtil.isEmpty(keys)) {
return R.ok();
}
// 角色关联的在线用户量过大会导致redis阻塞卡顿 谨慎操作
keys.parallelStream().forEach(key -> {
String token = key.replace(CacheConstants.LOGIN_TOKEN_KEY, "");
// 如果已经过期则跳过
if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) {
return;
}
LoginUser loginUser = LoginHelper.getLoginUser(token);
if (loginUser.getRoles().stream().anyMatch(r -> r.getRoleId().equals(role.getRoleId()))) {
try {
StpUtil.logoutByTokenValue(token);
} catch (NotLoginException ignored) {
}
}
});
return R.ok(); return R.ok();
} }
return R.fail("修改角色'" + role.getRoleName() + "'失败,请联系管理员"); return R.fail("修改角色'" + role.getRoleName() + "'失败,请联系管理员");

View File

@ -51,7 +51,7 @@ logging:
level: level:
com.ruoyi: @logging.level@ com.ruoyi: @logging.level@
org.springframework: warn org.springframework: warn
config: classpath:logback.xml config: classpath:logback-plus.xml
# 用户配置 # 用户配置
user: user:

View File

@ -18,6 +18,7 @@ user.password.not.blank=用户密码不能为空
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间 user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
user.password.not.valid=* 5-50个字符 user.password.not.valid=* 5-50个字符
user.email.not.valid=邮箱格式错误 user.email.not.valid=邮箱格式错误
user.email.not.blank=邮箱不能为空
user.phonenumber.not.blank=用户手机号不能为空 user.phonenumber.not.blank=用户手机号不能为空
user.mobile.phone.number.not.valid=手机号格式错误 user.mobile.phone.number.not.valid=手机号格式错误
user.login.success=登录成功 user.login.success=登录成功
@ -42,4 +43,7 @@ rate.limiter.message=访问过于频繁,请稍候再试
sms.code.not.blank=短信验证码不能为空 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}分钟
email.code.not.blank=邮箱验证码不能为空
email.code.retry.limit.count=邮箱验证码输入错误{0}次
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
xcx.code.not.blank=小程序code不能为空 xcx.code.not.blank=小程序code不能为空

View File

@ -18,6 +18,7 @@ user.password.not.blank=Password cannot be empty
user.password.length.valid=Password length must be between {min} and {max} characters user.password.length.valid=Password length must be between {min} and {max} characters
user.password.not.valid=* 5-50 characters user.password.not.valid=* 5-50 characters
user.email.not.valid=Mailbox format error user.email.not.valid=Mailbox format error
user.email.not.blank=Mailbox cannot be blank
user.phonenumber.not.blank=Phone number cannot be blank user.phonenumber.not.blank=Phone number cannot be blank
user.mobile.phone.number.not.valid=Phone number format error user.mobile.phone.number.not.valid=Phone number format error
user.login.success=Login successful user.login.success=Login successful
@ -42,4 +43,7 @@ rate.limiter.message=Visit too frequently, please try again later
sms.code.not.blank=Sms code cannot be blank 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
email.code.not.blank=Email code cannot be blank
email.code.retry.limit.count=Email code input error {0} times
email.code.retry.limit.exceed=Email 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

View File

@ -18,6 +18,7 @@ user.password.not.blank=用户密码不能为空
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间 user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
user.password.not.valid=* 5-50个字符 user.password.not.valid=* 5-50个字符
user.email.not.valid=邮箱格式错误 user.email.not.valid=邮箱格式错误
user.email.not.blank=邮箱不能为空
user.phonenumber.not.blank=用户手机号不能为空 user.phonenumber.not.blank=用户手机号不能为空
user.mobile.phone.number.not.valid=手机号格式错误 user.mobile.phone.number.not.valid=手机号格式错误
user.login.success=登录成功 user.login.success=登录成功
@ -42,4 +43,7 @@ rate.limiter.message=访问过于频繁,请稍候再试
sms.code.not.blank=短信验证码不能为空 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}分钟
email.code.not.blank=邮箱验证码不能为空
email.code.retry.limit.count=邮箱验证码输入错误{0}次
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
xcx.code.not.blank=小程序code不能为空 xcx.code.not.blank=小程序code不能为空

Binary file not shown.

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>4.6.0</version> <version>4.7.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@ -159,6 +159,12 @@
<artifactId>bcprov-jdk15to18</artifactId> <artifactId>bcprov-jdk15to18</artifactId>
</dependency> </dependency>
<!-- 离线IP地址定位库 -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -7,11 +7,6 @@ package com.ruoyi.common.constant;
*/ */
public interface CacheConstants { public interface CacheConstants {
/**
* 登录用户 redis key
*/
String LOGIN_TOKEN_KEY = "Authorization:login:token:";
/** /**
* 在线用户 redis key * 在线用户 redis key
*/ */

View File

@ -0,0 +1,30 @@
package com.ruoyi.common.core.domain.model;
import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
/**
* 短信登录对象
*
* @author Lion Li
*/
@Data
public class EmailLoginBody {
/**
* 邮箱
*/
@NotBlank(message = "{user.email.not.blank}")
@Email(message = "{user.email.not.valid}")
private String email;
/**
* 邮箱code
*/
@NotBlank(message = "{email.code.not.blank}")
private String emailCode;
}

View File

@ -14,13 +14,13 @@ import javax.validation.constraints.NotBlank;
public class SmsLoginBody { public class SmsLoginBody {
/** /**
* 用户名 * 手机号
*/ */
@NotBlank(message = "{user.phonenumber.not.blank}") @NotBlank(message = "{user.phonenumber.not.blank}")
private String phonenumber; private String phonenumber;
/** /**
* 用户密码 * 短信code
*/ */
@NotBlank(message = "{sms.code.not.blank}") @NotBlank(message = "{sms.code.not.blank}")
private String smsCode; private String smsCode;

View File

@ -22,6 +22,11 @@ public enum LoginType {
*/ */
SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"), SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"),
/**
* 邮箱登录
*/
EMAIL("email.code.retry.limit.exceed", "email.code.retry.limit.count"),
/** /**
* 小程序登录 * 小程序登录
*/ */

View File

@ -42,7 +42,7 @@ public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements
private ExcelResult<T> excelResult; private ExcelResult<T> excelResult;
public DefaultExcelListener(boolean isValidate) { public DefaultExcelListener(boolean isValidate) {
this.excelResult = new DefautExcelResult<>(); this.excelResult = new DefaultExcelResult<>();
this.isValidate = isValidate; this.isValidate = isValidate;
} }

View File

@ -12,7 +12,7 @@ import java.util.List;
* @author Yjoioooo * @author Yjoioooo
* @author Lion Li * @author Lion Li
*/ */
public class DefautExcelResult<T> implements ExcelResult<T> { public class DefaultExcelResult<T> implements ExcelResult<T> {
/** /**
* 数据对象list * 数据对象list
@ -26,17 +26,17 @@ public class DefautExcelResult<T> implements ExcelResult<T> {
@Setter @Setter
private List<String> errorList; private List<String> errorList;
public DefautExcelResult() { public DefaultExcelResult() {
this.list = new ArrayList<>(); this.list = new ArrayList<>();
this.errorList = new ArrayList<>(); this.errorList = new ArrayList<>();
} }
public DefautExcelResult(List<T> list, List<String> errorList) { public DefaultExcelResult(List<T> list, List<String> errorList) {
this.list = list; this.list = list;
this.errorList = errorList; this.errorList = errorList;
} }
public DefautExcelResult(ExcelResult<T> excelResult) { public DefaultExcelResult(ExcelResult<T> excelResult) {
this.list = excelResult.getList(); this.list = excelResult.getList();
this.errorList = excelResult.getErrorList(); this.errorList = excelResult.getErrorList();
} }

View File

@ -66,7 +66,7 @@ public class DataBaseHelper {
// instr(',0,100,101,' , ',100,') <> 0 // instr(',0,100,101,' , ',100,') <> 0
return "instr(','||" + var2 + "||',' , '," + var + ",') <> 0"; return "instr(','||" + var2 + "||',' , '," + var + ",') <> 0";
} }
// find_in_set(100 , '0,100,101') // find_in_set('100' , '0,100,101')
return "find_in_set(" + var + " , " + var2 + ") <> 0"; return "find_in_set('" + var + "' , " + var2 + ") <> 0";
} }
} }

View File

@ -10,6 +10,7 @@ import lombok.NoArgsConstructor;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier;
/** /**
* 数据权限助手 * 数据权限助手
@ -61,4 +62,32 @@ public class DataPermissionHelper {
InterceptorIgnoreHelper.clearIgnoreStrategy(); InterceptorIgnoreHelper.clearIgnoreStrategy();
} }
/**
* 在忽略数据权限中执行
*
* @param handle 处理执行方法
*/
public static void ignore(Runnable handle) {
enableIgnore();
try {
handle.run();
} finally {
disableIgnore();
}
}
/**
* 在忽略数据权限中执行
*
* @param handle 处理执行方法
*/
public static <T> T ignore(Supplier<T> handle) {
enableIgnore();
try {
return handle.get();
} finally {
disableIgnore();
}
}
} }

View File

@ -19,6 +19,7 @@ public class DeptNameTranslationImpl implements TranslationInterface<String> {
private final DeptService deptService; private final DeptService deptService;
@Override
public String translation(Object key, String other) { public String translation(Object key, String other) {
return deptService.selectDeptNameByIds(key.toString()); return deptService.selectDeptNameByIds(key.toString());
} }

View File

@ -20,6 +20,7 @@ public class DictTypeTranslationImpl implements TranslationInterface<String> {
private final DictService dictService; private final DictService dictService;
@Override
public String translation(Object key, String other) { public String translation(Object key, String other) {
if (key instanceof String && StringUtils.isNotBlank(other)) { if (key instanceof String && StringUtils.isNotBlank(other)) {
return dictService.getDictLabel(other, key.toString()); return dictService.getDictLabel(other, key.toString());

View File

@ -19,6 +19,7 @@ public class OssUrlTranslationImpl implements TranslationInterface<String> {
private final OssService ossService; private final OssService ossService;
@Override
public String translation(Object key, String other) { public String translation(Object key, String other) {
return ossService.selectUrlByIds(key.toString()); return ossService.selectUrlByIds(key.toString());
} }

View File

@ -19,6 +19,7 @@ public class UserNameTranslationImpl implements TranslationInterface<String> {
private final UserService userService; private final UserService userService;
@Override
public String translation(Object key, String other) { public String translation(Object key, String other) {
if (key instanceof Long) { if (key instanceof Long) {
return userService.selectUserNameById((Long) key); return userService.selectUserNameById((Long) key);

View File

@ -0,0 +1,243 @@
package com.ruoyi.common.utils;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.asymmetric.SM2;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* 安全相关工具类
*
* @author 老马
*/
public class EncryptUtils {
/**
* 公钥
*/
public static final String PUBLIC_KEY = "publicKey";
/**
* 私钥
*/
public static final String PRIVATE_KEY = "privateKey";
/**
* Base64加密
*
* @param data 待加密数据
* @return 加密后字符串
*/
public static String encryptByBase64(String data) {
return Base64.encode(data, StandardCharsets.UTF_8);
}
/**
* Base64解密
*
* @param data 待解密数据
* @return 解密后字符串
*/
public static String decryptByBase64(String data) {
return Base64.decodeStr(data, StandardCharsets.UTF_8);
}
/**
* AES加密
*
* @param data 待解密数据
* @param password 秘钥字符串
* @return 加密后字符串, 采用Base64编码
*/
public static String encryptByAes(String data, String password) {
if (StrUtil.isBlank(password)) {
throw new IllegalArgumentException("AES需要传入秘钥信息");
}
// aes算法的秘钥要求是16位24位32位
int[] array = {16, 24, 32};
if (!ArrayUtil.contains(array, password.length())) {
throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
}
return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
}
/**
* AES解密
*
* @param data 待解密数据
* @param password 秘钥字符串
* @return 解密后字符串
*/
public static String decryptByAes(String data, String password) {
if (StrUtil.isBlank(password)) {
throw new IllegalArgumentException("AES需要传入秘钥信息");
}
// aes算法的秘钥要求是16位24位32位
int[] array = {16, 24, 32};
if (!ArrayUtil.contains(array, password.length())) {
throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
}
return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
}
/**
* sm4加密
*
* @param data 待加密数据
* @param password 秘钥字符串
* @return 加密后字符串, 采用Base64编码
*/
public static String encryptBySm4(String data, String password) {
if (StrUtil.isBlank(password)) {
throw new IllegalArgumentException("SM4需要传入秘钥信息");
}
// sm4算法的秘钥要求是16位长度
int sm4PasswordLength = 16;
if (sm4PasswordLength != password.length()) {
throw new IllegalArgumentException("SM4秘钥长度要求为16位");
}
return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
}
/**
* sm4解密
*
* @param data 待解密数据
* @param password 秘钥字符串
* @return 解密后字符串
*/
public static String decryptBySm4(String data, String password) {
if (StrUtil.isBlank(password)) {
throw new IllegalArgumentException("SM4需要传入秘钥信息");
}
// sm4算法的秘钥要求是16位长度
int sm4PasswordLength = 16;
if (sm4PasswordLength != password.length()) {
throw new IllegalArgumentException("SM4秘钥长度要求为16位");
}
return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
}
/**
* 产生sm2加解密需要的公钥和私钥
*
* @return 公私钥Map
*/
public static Map<String, String> generateSm2Key() {
Map<String, String> keyMap = new HashMap<>(2);
SM2 sm2 = SmUtil.sm2();
keyMap.put(PRIVATE_KEY, sm2.getPrivateKeyBase64());
keyMap.put(PUBLIC_KEY, sm2.getPublicKeyBase64());
return keyMap;
}
/**
* sm2公钥加密
*
* @param data 待加密数据
* @param publicKey 公钥
* @return 加密后字符串, 采用Base64编码
*/
public static String encryptBySm2(String data, String publicKey) {
if (StrUtil.isBlank(publicKey)) {
throw new IllegalArgumentException("SM2需要传入公钥进行加密");
}
SM2 sm2 = SmUtil.sm2(null, publicKey);
return sm2.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
}
/**
* sm2私钥解密
*
* @param data 待加密数据
* @param privateKey 私钥
* @return 解密后字符串
*/
public static String decryptBySm2(String data, String privateKey) {
if (StrUtil.isBlank(privateKey)) {
throw new IllegalArgumentException("SM2需要传入私钥进行解密");
}
SM2 sm2 = SmUtil.sm2(privateKey, null);
return sm2.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
}
/**
* 产生RSA加解密需要的公钥和私钥
*
* @return 公私钥Map
*/
public static Map<String, String> generateRsaKey() {
Map<String, String> keyMap = new HashMap<>(2);
RSA rsa = SecureUtil.rsa();
keyMap.put(PRIVATE_KEY, rsa.getPrivateKeyBase64());
keyMap.put(PUBLIC_KEY, rsa.getPublicKeyBase64());
return keyMap;
}
/**
* rsa公钥加密
*
* @param data 待加密数据
* @param publicKey 公钥
* @return 加密后字符串, 采用Base64编码
*/
public static String encryptByRsa(String data, String publicKey) {
if (StrUtil.isBlank(publicKey)) {
throw new IllegalArgumentException("RSA需要传入公钥进行加密");
}
RSA rsa = SecureUtil.rsa(null, publicKey);
return rsa.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
}
/**
* rsa私钥解密
*
* @param data 待加密数据
* @param privateKey 私钥
* @return 解密后字符串
*/
public static String decryptByRsa(String data, String privateKey) {
if (StrUtil.isBlank(privateKey)) {
throw new IllegalArgumentException("RSA需要传入私钥进行解密");
}
RSA rsa = SecureUtil.rsa(privateKey, null);
return rsa.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
}
/**
* md5加密
*
* @param data 待加密数据
* @return 加密后字符串, 采用Hex编码
*/
public static String encryptByMd5(String data) {
return SecureUtil.md5(data);
}
/**
* sha256加密
*
* @param data 待加密数据
* @return 加密后字符串, 采用Hex编码
*/
public static String encryptBySha256(String data) {
return SecureUtil.sha256(data);
}
/**
* sm3加密
*
* @param data 待加密数据
* @return 加密后字符串, 采用Hex编码
*/
public static String encryptBySm3(String data) {
return SmUtil.sm3(data);
}
}

View File

@ -1,12 +1,7 @@
package com.ruoyi.common.utils.ip; package com.ruoyi.common.utils.ip;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.net.NetUtil; import cn.hutool.core.net.NetUtil;
import cn.hutool.http.HtmlUtil; import cn.hutool.http.HtmlUtil;
import cn.hutool.http.HttpUtil;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@ -21,40 +16,18 @@ import lombok.extern.slf4j.Slf4j;
@NoArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PRIVATE)
public class AddressUtils { public class AddressUtils {
// IP地址查询
public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";
// 未知地址 // 未知地址
public static final String UNKNOWN = "XX XX"; public static final String UNKNOWN = "XX XX";
public static String getRealAddressByIP(String ip) { public static String getRealAddressByIP(String ip) {
String address = UNKNOWN;
if (StringUtils.isBlank(ip)) { if (StringUtils.isBlank(ip)) {
return address; return UNKNOWN;
} }
// 内网不查询 // 内网不查询
ip = "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip); ip = "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
if (NetUtil.isInnerIP(ip)) { if (NetUtil.isInnerIP(ip)) {
return "内网IP"; return "内网IP";
} }
if (RuoYiConfig.isAddressEnabled()) { return RegionUtils.getCityInfo(ip);
try {
String rspStr = HttpUtil.createGet(IP_URL)
.body("ip=" + ip + "&json=true", Constants.GBK)
.execute()
.body();
if (StringUtils.isEmpty(rspStr)) {
log.error("获取地理位置异常 {}", ip);
return UNKNOWN;
}
Dict obj = JsonUtils.parseMap(rspStr);
String region = obj.getStr("pro");
String city = obj.getStr("city");
return String.format("%s %s", region, city);
} catch (Exception e) {
log.error("获取地理位置异常 {}", ip);
}
}
return UNKNOWN;
} }
} }

View File

@ -0,0 +1,66 @@
package com.ruoyi.common.utils.ip;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.file.FileUtils;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import java.io.File;
/**
* 根据ip地址定位工具类离线方式
* 参考地址<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
*
* @author lishuyan
*/
@Slf4j
public class RegionUtils {
private static final Searcher SEARCHER;
static {
String fileName = "/ip2region.xdb";
File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
if (!FileUtils.exist(existFile)) {
ClassPathResource fileStream = new ClassPathResource(fileName);
if (ObjectUtil.isEmpty(fileStream.getStream())) {
throw new ServiceException("RegionUtils初始化失败原因IP地址库数据不存在");
}
FileUtils.writeFromStream(fileStream.getStream(), existFile);
}
String dbPath = existFile.getPath();
// 1 dbPath 加载整个 xdb 到内存
byte[] cBuff;
try {
cBuff = Searcher.loadContentFromFile(dbPath);
} catch (Exception e) {
throw new ServiceException("RegionUtils初始化失败原因从ip2region.xdb文件加载内容失败" + e.getMessage());
}
// 2使用上述的 cBuff 创建一个完全基于内存的查询对象
try {
SEARCHER = Searcher.newWithBuffer(cBuff);
} catch (Exception e) {
throw new ServiceException("RegionUtils初始化失败原因" + e.getMessage());
}
}
/**
* 根据IP地址离线获取城市
*/
public static String getCityInfo(String ip) {
try {
ip = ip.trim();
// 3执行查询
String region = SEARCHER.search(ip);
return region.replace("0|", "").replace("|0", "");
} catch (Exception e) {
log.error("IP地址离线获取城市异常 {}", ip);
return "未知";
}
}
}

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>4.6.0</version> <version>4.7.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -49,4 +49,16 @@ public class RedisRateLimiterController {
return R.ok("操作成功", value); return R.ok("操作成功", value);
} }
/**
* 测试请求IP限流(key基于参数获取)
* 同一IP请求受影响
*
* 简单变量获取 #变量 复杂表达式 #{#变量 != 1 ? 1 : 0}
*/
@RateLimiter(count = 2, time = 10, limitType = LimitType.IP, key = "#value")
@GetMapping("/testObj")
public R<String> testObj(String value) {
return R.ok("操作成功", value);
}
} }

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>4.6.0</version> <version>4.7.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-extend</artifactId> <artifactId>ruoyi-extend</artifactId>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-extend</artifactId> <artifactId>ruoyi-extend</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>4.6.0</version> <version>4.7.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging> <packaging>jar</packaging>

View File

@ -6,6 +6,9 @@ spring:
profiles: profiles:
active: @profiles.active@ active: @profiles.active@
logging:
config: classpath:logback-plus.xml
--- # 监控中心服务端配置 --- # 监控中心服务端配置
spring: spring:
security: security:

View File

@ -4,7 +4,7 @@
<parent> <parent>
<artifactId>ruoyi-extend</artifactId> <artifactId>ruoyi-extend</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>4.6.0</version> <version>4.7.0</version>
</parent> </parent>
<artifactId>ruoyi-xxl-job-admin</artifactId> <artifactId>ruoyi-xxl-job-admin</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>

View File

@ -1,5 +1,6 @@
package com.xxl.job.admin.controller; package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.admin.core.model.XxlJobGroup; import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobRegistry; import com.xxl.job.admin.core.model.XxlJobRegistry;
import com.xxl.job.admin.core.util.I18nUtil; import com.xxl.job.admin.core.util.I18nUtil;
@ -35,12 +36,14 @@ public class JobGroupController {
private XxlJobRegistryDao xxlJobRegistryDao; private XxlJobRegistryDao xxlJobRegistryDao;
@RequestMapping @RequestMapping
@PermissionLimit(adminuser = true)
public String index(Model model) { public String index(Model model) {
return "jobgroup/jobgroup.index"; return "jobgroup/jobgroup.index";
} }
@RequestMapping("/pageList") @RequestMapping("/pageList")
@ResponseBody @ResponseBody
@PermissionLimit(adminuser = true)
public Map<String, Object> pageList(HttpServletRequest request, public Map<String, Object> pageList(HttpServletRequest request,
@RequestParam(required = false, defaultValue = "0") int start, @RequestParam(required = false, defaultValue = "0") int start,
@RequestParam(required = false, defaultValue = "10") int length, @RequestParam(required = false, defaultValue = "10") int length,
@ -60,6 +63,7 @@ public class JobGroupController {
@RequestMapping("/save") @RequestMapping("/save")
@ResponseBody @ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> save(XxlJobGroup xxlJobGroup) { public ReturnT<String> save(XxlJobGroup xxlJobGroup) {
// valid // valid
@ -103,6 +107,7 @@ public class JobGroupController {
@RequestMapping("/update") @RequestMapping("/update")
@ResponseBody @ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> update(XxlJobGroup xxlJobGroup) { public ReturnT<String> update(XxlJobGroup xxlJobGroup) {
// valid // valid
if (xxlJobGroup.getAppname() == null || xxlJobGroup.getAppname().trim().length() == 0) { if (xxlJobGroup.getAppname() == null || xxlJobGroup.getAppname().trim().length() == 0) {
@ -171,6 +176,7 @@ public class JobGroupController {
@RequestMapping("/remove") @RequestMapping("/remove")
@ResponseBody @ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> remove(int id) { public ReturnT<String> remove(int id) {
// valid // valid
@ -190,6 +196,7 @@ public class JobGroupController {
@RequestMapping("/loadById") @RequestMapping("/loadById")
@ResponseBody @ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<XxlJobGroup> loadById(int id) { public ReturnT<XxlJobGroup> loadById(int id) {
XxlJobGroup jobGroup = xxlJobGroupDao.load(id); XxlJobGroup jobGroup = xxlJobGroupDao.load(id);
return jobGroup != null ? new ReturnT<XxlJobGroup>(jobGroup) : new ReturnT<XxlJobGroup>(ReturnT.FAIL_CODE, null); return jobGroup != null ? new ReturnT<XxlJobGroup>(jobGroup) : new ReturnT<XxlJobGroup>(ReturnT.FAIL_CODE, null);

View File

@ -130,22 +130,26 @@ public class JobLogController {
model.addAttribute("triggerCode", jobLog.getTriggerCode()); model.addAttribute("triggerCode", jobLog.getTriggerCode());
model.addAttribute("handleCode", jobLog.getHandleCode()); model.addAttribute("handleCode", jobLog.getHandleCode());
model.addAttribute("executorAddress", jobLog.getExecutorAddress());
model.addAttribute("triggerTime", jobLog.getTriggerTime().getTime());
model.addAttribute("logId", jobLog.getId()); model.addAttribute("logId", jobLog.getId());
return "joblog/joblog.detail"; return "joblog/joblog.detail";
} }
@RequestMapping("/logDetailCat") @RequestMapping("/logDetailCat")
@ResponseBody @ResponseBody
public ReturnT<LogResult> logDetailCat(String executorAddress, long triggerTime, long logId, int fromLineNum) { public ReturnT<LogResult> logDetailCat(long logId, int fromLineNum) {
try { try {
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress); // valid
ReturnT<LogResult> logResult = executorBiz.log(new LogParam(triggerTime, logId, fromLineNum)); XxlJobLog jobLog = xxlJobLogDao.load(logId); // todo, need to improve performance
if (jobLog == null) {
return new ReturnT<LogResult>(ReturnT.FAIL_CODE, I18nUtil.getString("joblog_logid_unvalid"));
}
// log cat
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(jobLog.getExecutorAddress());
ReturnT<LogResult> logResult = executorBiz.log(new LogParam(jobLog.getTriggerTime().getTime(), logId, fromLineNum));
// is end // is end
if (logResult.getContent() != null && logResult.getContent().getFromLineNum() > logResult.getContent().getToLineNum()) { if (logResult.getContent() != null && logResult.getContent().getFromLineNum() > logResult.getContent().getToLineNum()) {
XxlJobLog jobLog = xxlJobLogDao.load(logId);
if (jobLog.getHandleCode() > 0) { if (jobLog.getHandleCode() > 0) {
logResult.getContent().setEnd(true); logResult.getContent().setEnd(true);
} }

View File

@ -16,6 +16,9 @@ spring:
resources: resources:
static-locations: classpath:/static/ static-locations: classpath:/static/
logging:
config: classpath:logback-plus.xml
--- # mybatis 配置 --- # mybatis 配置
mybatis: mybatis:
mapper-locations: classpath:/mybatis-mapper/*Mapper.xml mapper-locations: classpath:/mybatis-mapper/*Mapper.xml

View File

@ -1,6 +1,6 @@
admin_name=Scheduling Center admin_name=Scheduling Center
admin_name_full=Distributed Task Scheduling Platform XXL-JOB admin_name_full=Distributed Task Scheduling Platform XXL-JOB
admin_version=2.3.1 admin_version=2.4.0
admin_i18n=en admin_i18n=en
## system ## system

View File

@ -1,6 +1,6 @@
admin_name=任务调度中心 admin_name=任务调度中心
admin_name_full=分布式任务调度平台XXL-JOB admin_name_full=分布式任务调度平台XXL-JOB
admin_version=2.3.1 admin_version=2.4.0
admin_i18n= admin_i18n=
## system ## system

View File

@ -1,6 +1,6 @@
admin_name=任務調度中心 admin_name=任務調度中心
admin_name_full=分布式任務調度平臺XXL-JOB admin_name_full=分布式任務調度平臺XXL-JOB
admin_version=2.3.1 admin_version=2.4.0
admin_i18n= admin_i18n=
## system ## system

View File

@ -25,8 +25,6 @@ $(function() {
async: false, // sync, make log ordered async: false, // sync, make log ordered
url : base_url + '/joblog/logDetailCat', url : base_url + '/joblog/logDetailCat',
data : { data : {
"executorAddress":executorAddress,
"triggerTime":triggerTime,
"logId":logId, "logId":logId,
"fromLineNum":fromLineNum "fromLineNum":fromLineNum
}, },

View File

@ -62,8 +62,6 @@
// 参数 // 参数
var triggerCode = '${triggerCode}'; var triggerCode = '${triggerCode}';
var handleCode = '${handleCode}'; var handleCode = '${handleCode}';
var executorAddress = '${executorAddress!}';
var triggerTime = '${triggerTime?c}';
var logId = '${logId}'; var logId = '${logId}';
</script> </script>
<script src="${request.contextPath}/static/js/joblog.detail.1.js"></script> <script src="${request.contextPath}/static/js/joblog.detail.1.js"></script>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>4.6.0</version> <version>4.7.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -18,6 +18,7 @@ import org.redisson.api.RateType;
import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser; import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext; import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.common.TemplateParserContext;
@ -102,7 +103,14 @@ public class RateLimiterAspect {
} }
// 解析返回给key // 解析返回给key
try { try {
key = parser.parseExpression(key, parserContext).getValue(context, String.class) + ":"; Expression expression;
if (StringUtils.startsWith(key, parserContext.getExpressionPrefix())
&& StringUtils.endsWith(key, parserContext.getExpressionSuffix())) {
expression = parser.parseExpression(key, parserContext);
} else {
expression = parser.parseExpression(key);
}
key = expression.getValue(context, String.class) + ":";
} catch (Exception e) { } catch (Exception e) {
throw new ServiceException("限流key解析异常!请联系管理员!"); throw new ServiceException("限流key解析异常!请联系管理员!");
} }

View File

@ -1,6 +1,6 @@
package com.ruoyi.framework.encrypt; package com.ruoyi.framework.encrypt;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.annotation.EncryptField; import com.ruoyi.common.annotation.EncryptField;
import com.ruoyi.common.encrypt.EncryptContext; import com.ruoyi.common.encrypt.EncryptContext;
@ -62,12 +62,12 @@ public class MybatisDecryptInterceptor implements Interceptor {
} }
if (sourceObject instanceof List<?>) { if (sourceObject instanceof List<?>) {
List<?> sourceList = (List<?>) sourceObject; List<?> sourceList = (List<?>) sourceObject;
if(CollectionUtil.isEmpty(sourceList)) { if(CollUtil.isEmpty(sourceList)) {
return; return;
} }
// 判断第一个元素是否含有注解如果没有直接返回提高效率 // 判断第一个元素是否含有注解如果没有直接返回提高效率
Object firstItem = sourceList.get(0); Object firstItem = sourceList.get(0);
if (CollectionUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) { if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
return; return;
} }
((List<?>) sourceObject).forEach(this::decryptHandler); ((List<?>) sourceObject).forEach(this::decryptHandler);
@ -91,6 +91,9 @@ public class MybatisDecryptInterceptor implements Interceptor {
* @return 加密后结果 * @return 加密后结果
*/ */
private String decryptField(String value, Field field) { private String decryptField(String value, Field field) {
if (ObjectUtil.isNull(value)) {
return null;
}
EncryptField encryptField = field.getAnnotation(EncryptField.class); EncryptField encryptField = field.getAnnotation(EncryptField.class);
EncryptContext encryptContext = new EncryptContext(); EncryptContext encryptContext = new EncryptContext();
encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm()); encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());

View File

@ -1,6 +1,6 @@
package com.ruoyi.framework.encrypt; package com.ruoyi.framework.encrypt;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.annotation.EncryptField; import com.ruoyi.common.annotation.EncryptField;
import com.ruoyi.common.encrypt.EncryptContext; import com.ruoyi.common.encrypt.EncryptContext;
@ -72,12 +72,12 @@ public class MybatisEncryptInterceptor implements Interceptor {
} }
if (sourceObject instanceof List<?>) { if (sourceObject instanceof List<?>) {
List<?> sourceList = (List<?>) sourceObject; List<?> sourceList = (List<?>) sourceObject;
if(CollectionUtil.isEmpty(sourceList)) { if(CollUtil.isEmpty(sourceList)) {
return; return;
} }
// 判断第一个元素是否含有注解如果没有直接返回提高效率 // 判断第一个元素是否含有注解如果没有直接返回提高效率
Object firstItem = sourceList.get(0); Object firstItem = sourceList.get(0);
if (CollectionUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) { if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
return; return;
} }
((List<?>) sourceObject).forEach(this::encryptHandler); ((List<?>) sourceObject).forEach(this::encryptHandler);
@ -101,6 +101,9 @@ public class MybatisEncryptInterceptor implements Interceptor {
* @return 加密后结果 * @return 加密后结果
*/ */
private String encryptField(String value, Field field) { private String encryptField(String value, Field field) {
if (ObjectUtil.isNull(value)) {
return null;
}
EncryptField encryptField = field.getAnnotation(EncryptField.class); EncryptField encryptField = field.getAnnotation(EncryptField.class);
EncryptContext encryptContext = new EncryptContext(); EncryptContext encryptContext = new EncryptContext();
encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm()); encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());

View File

@ -73,7 +73,7 @@ public class CreateAndUpdateMetaObjectHandler implements MetaObjectHandler {
log.warn("自动注入警告 => 用户未登录"); log.warn("自动注入警告 => 用户未登录");
return null; return null;
} }
return loginUser.getUsername(); return ObjectUtil.isNotNull(loginUser) ? loginUser.getUsername() : null;
} }
} }

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>4.6.0</version> <version>4.7.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -317,13 +317,11 @@ public class GenTableServiceImpl implements IGenTableService {
column.setIsRequired(prevColumn.getIsRequired()); column.setIsRequired(prevColumn.getIsRequired());
column.setHtmlType(prevColumn.getHtmlType()); column.setHtmlType(prevColumn.getHtmlType());
} }
genTableColumnMapper.updateById(column);
} else {
genTableColumnMapper.insert(column);
} }
saveColumns.add(column);
}); });
if (CollUtil.isNotEmpty(saveColumns)) { if (CollUtil.isNotEmpty(saveColumns)) {
genTableColumnMapper.insertBatch(saveColumns); genTableColumnMapper.insertOrUpdateBatch(saveColumns);
} }
List<GenTableColumn> delColumns = StreamUtils.filter(tableColumns, column -> !dbTableColumnNames.contains(column.getColumnName())); List<GenTableColumn> delColumns = StreamUtils.filter(tableColumns, column -> !dbTableColumnNames.contains(column.getColumnName()));
if (CollUtil.isNotEmpty(delColumns)) { if (CollUtil.isNotEmpty(delColumns)) {

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>4.6.0</version> <version>4.7.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging> <packaging>jar</packaging>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>4.6.0</version> <version>4.7.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>4.6.0</version> <version>4.7.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ruoyi-vue-plus</artifactId> <artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<version>4.6.0</version> <version>4.7.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -76,6 +76,14 @@ public interface SysUserMapper extends BaseMapperPlus<SysUserMapper, SysUser, Sy
*/ */
SysUser selectUserByPhonenumber(String phonenumber); SysUser selectUserByPhonenumber(String phonenumber);
/**
* 通过邮箱查询用户
*
* @param email 邮箱
* @return 用户对象信息
*/
SysUser selectUserByEmail(String email);
/** /**
* 通过用户ID查询用户 * 通过用户ID查询用户
* *

View File

@ -176,4 +176,6 @@ public interface ISysRoleService {
* @return 结果 * @return 结果
*/ */
int insertAuthUsers(Long roleId, Long[] userIds); int insertAuthUsers(Long roleId, Long[] userIds);
void cleanOnlineUserByRole(Long roleId);
} }

View File

@ -32,7 +32,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.time.Duration; import java.time.Duration;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -67,11 +66,10 @@ public class SysLoginService {
* @return 结果 * @return 结果
*/ */
public String login(String username, String password, String code, String uuid) { public String login(String username, String password, String code, String uuid) {
HttpServletRequest request = ServletUtils.getRequest();
boolean captchaEnabled = configService.selectCaptchaEnabled(); boolean captchaEnabled = configService.selectCaptchaEnabled();
// 验证码开关 // 验证码开关
if (captchaEnabled) { if (captchaEnabled) {
validateCaptcha(username, code, uuid, request); validateCaptcha(username, code, uuid);
} }
SysUser user = loadUserByUsername(username); SysUser user = loadUserByUsername(username);
checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, user.getPassword())); checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, user.getPassword()));
@ -100,6 +98,20 @@ public class SysLoginService {
return StpUtil.getTokenValue(); return StpUtil.getTokenValue();
} }
public String emailLogin(String email, String emailCode) {
// 通过手机号查找用户
SysUser user = loadUserByEmail(email);
checkLogin(LoginType.EMAIL, user.getUserName(), () -> !validateEmailCode(email, emailCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser
LoginUser loginUser = buildLoginUser(user);
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.APP);
recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getUserId(), user.getUserName());
return StpUtil.getTokenValue();
}
public String xcxLogin(String xcxCode) { public String xcxLogin(String xcxCode) {
// xcxCode 小程序调用 wx.login 授权后获取 // xcxCode 小程序调用 wx.login 授权后获取
@ -140,7 +152,6 @@ public class SysLoginService {
* @param username 用户名 * @param username 用户名
* @param status 状态 * @param status 状态
* @param message 消息内容 * @param message 消息内容
* @return
*/ */
private void recordLogininfor(String username, String status, String message) { private void recordLogininfor(String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent(); LogininforEvent logininforEvent = new LogininforEvent();
@ -163,6 +174,18 @@ public class SysLoginService {
return code.equals(smsCode); return code.equals(smsCode);
} }
/**
* 校验邮箱验证码
*/
private boolean validateEmailCode(String email, String emailCode) {
String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + email);
if (StringUtils.isBlank(code)) {
recordLogininfor(email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(emailCode);
}
/** /**
* 校验验证码 * 校验验证码
* *
@ -170,7 +193,7 @@ 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 username, String code, String uuid) {
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, ""); String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey); String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey); RedisUtils.deleteObject(verifyKey);
@ -212,6 +235,20 @@ public class SysLoginService {
return userMapper.selectUserByPhonenumber(phonenumber); return userMapper.selectUserByPhonenumber(phonenumber);
} }
private SysUser loadUserByEmail(String email) {
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
.select(SysUser::getPhonenumber, SysUser::getStatus)
.eq(SysUser::getEmail, email));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", email);
throw new UserException("user.not.exists", email);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", email);
throw new UserException("user.blocked", email);
}
return userMapper.selectUserByEmail(email);
}
private SysUser loadUserByOpenid(String openid) { private SysUser loadUserByOpenid(String openid) {
// 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户 // 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
// todo 自行实现 userService.selectUserByOpenid(openid); // todo 自行实现 userService.selectUserByOpenid(openid);

View File

@ -3,7 +3,6 @@ package com.ruoyi.system.service;
import cn.dev33.satoken.secure.BCrypt; import cn.dev33.satoken.secure.BCrypt;
import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.event.LogininforEvent; import com.ruoyi.common.core.domain.event.LogininforEvent;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.RegisterBody; import com.ruoyi.common.core.domain.model.RegisterBody;
@ -19,8 +18,6 @@ import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
/** /**
* 注册校验方法 * 注册校验方法
* *
@ -37,7 +34,6 @@ public class SysRegisterService {
* 注册 * 注册
*/ */
public void register(RegisterBody registerBody) { public void register(RegisterBody registerBody) {
HttpServletRequest request = ServletUtils.getRequest();
String username = registerBody.getUsername(); String username = registerBody.getUsername();
String password = registerBody.getPassword(); String password = registerBody.getPassword();
// 校验用户类型是否存在 // 校验用户类型是否存在
@ -46,7 +42,7 @@ public class SysRegisterService {
boolean captchaEnabled = configService.selectCaptchaEnabled(); boolean captchaEnabled = configService.selectCaptchaEnabled();
// 验证码开关 // 验证码开关
if (captchaEnabled) { if (captchaEnabled) {
validateCaptcha(username, registerBody.getCode(), registerBody.getUuid(), request); validateCaptcha(username, registerBody.getCode(), registerBody.getUuid());
} }
SysUser sysUser = new SysUser(); SysUser sysUser = new SysUser();
sysUser.setUserName(username); sysUser.setUserName(username);
@ -70,9 +66,8 @@ public class SysRegisterService {
* @param username 用户名 * @param username 用户名
* @param code 验证码 * @param code 验证码
* @param uuid 唯一标识 * @param uuid 唯一标识
* @return 结果
*/ */
public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) { public void validateCaptcha(String username, String code, String uuid) {
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, ""); String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey); String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey); RedisUtils.deleteObject(verifyKey);

View File

@ -1,5 +1,7 @@
package com.ruoyi.system.service.impl; package com.ruoyi.system.service.impl;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.Wrapper;
@ -10,6 +12,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.PageQuery; import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.helper.LoginHelper; import com.ruoyi.common.helper.LoginHelper;
@ -70,7 +73,7 @@ public class SysRoleServiceImpl implements ISysRoleService {
.like(StringUtils.isNotBlank(role.getRoleKey()), "r.role_key", role.getRoleKey()) .like(StringUtils.isNotBlank(role.getRoleKey()), "r.role_key", role.getRoleKey())
.between(params.get("beginTime") != null && params.get("endTime") != null, .between(params.get("beginTime") != null && params.get("endTime") != null,
"r.create_time", params.get("beginTime"), params.get("endTime")) "r.create_time", params.get("beginTime"), params.get("endTime"))
.orderByAsc("r.role_sort"); .orderByAsc("r.role_sort").orderByAsc("r.create_time");
return wrapper; return wrapper;
} }
@ -362,9 +365,13 @@ public class SysRoleServiceImpl implements ISysRoleService {
*/ */
@Override @Override
public int deleteAuthUser(SysUserRole userRole) { public int deleteAuthUser(SysUserRole userRole) {
return userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>() int rows = userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
.eq(SysUserRole::getRoleId, userRole.getRoleId()) .eq(SysUserRole::getRoleId, userRole.getRoleId())
.eq(SysUserRole::getUserId, userRole.getUserId())); .eq(SysUserRole::getUserId, userRole.getUserId()));
if (rows > 0) {
cleanOnlineUserByRole(userRole.getRoleId());
}
return rows;
} }
/** /**
@ -376,9 +383,13 @@ public class SysRoleServiceImpl implements ISysRoleService {
*/ */
@Override @Override
public int deleteAuthUsers(Long roleId, Long[] userIds) { public int deleteAuthUsers(Long roleId, Long[] userIds) {
return userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>() int rows = userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
.eq(SysUserRole::getRoleId, roleId) .eq(SysUserRole::getRoleId, roleId)
.in(SysUserRole::getUserId, Arrays.asList(userIds))); .in(SysUserRole::getUserId, Arrays.asList(userIds)));
if (rows > 0) {
cleanOnlineUserByRole(roleId);
}
return rows;
} }
/** /**
@ -401,6 +412,32 @@ public class SysRoleServiceImpl implements ISysRoleService {
if (CollUtil.isNotEmpty(list)) { if (CollUtil.isNotEmpty(list)) {
rows = userRoleMapper.insertBatch(list) ? list.size() : 0; rows = userRoleMapper.insertBatch(list) ? list.size() : 0;
} }
if (rows > 0) {
cleanOnlineUserByRole(roleId);
}
return rows; return rows;
} }
@Override
public void cleanOnlineUserByRole(Long roleId) {
List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
if (CollUtil.isEmpty(keys)) {
return;
}
// 角色关联的在线用户量过大会导致redis阻塞卡顿 谨慎操作
keys.parallelStream().forEach(key -> {
String token = StringUtils.substringAfterLast(key, ":");
// 如果已经过期则跳过
if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) {
return;
}
LoginUser loginUser = LoginHelper.getLoginUser(token);
if (loginUser.getRoles().stream().anyMatch(r -> r.getRoleId().equals(roleId))) {
try {
StpUtil.logoutByTokenValue(token);
} catch (NotLoginException ignored) {
}
}
});
}
} }

View File

@ -128,6 +128,11 @@
where u.del_flag = '0' and u.phonenumber = #{phonenumber} where u.del_flag = '0' and u.phonenumber = #{phonenumber}
</select> </select>
<select id="selectUserByEmail" parameterType="String" resultMap="SysUserResult">
<include refid="selectUserVo"/>
where u.del_flag = '0' and u.email = #{email}
</select>
<select id="selectUserById" parameterType="Long" resultMap="SysUserResult"> <select id="selectUserById" parameterType="Long" resultMap="SysUserResult">
<include refid="selectUserVo"/> <include refid="selectUserVo"/>
where u.del_flag = '0' and u.user_id = #{userId} where u.del_flag = '0' and u.user_id = #{userId}

View File

@ -4,14 +4,16 @@ VUE_APP_TITLE = RuoYi-Vue-Plus后台管理系统
# 生产环境配置 # 生产环境配置
ENV = 'production' ENV = 'production'
# 若依管理系统/生产环境
VUE_APP_BASE_API = '/prod-api'
# 应用访问路径 例如使用前缀 /admin/ # 应用访问路径 例如使用前缀 /admin/
VUE_APP_CONTEXT_PATH = '/' VUE_APP_CONTEXT_PATH = '/'
# 监控地址 # 监控地址
VUE_APP_MONITRO_ADMIN = '/admin/login' VUE_APP_MONITRO_ADMIN = '/admin/login'
# 监控地址 # xxl-job 控制台地址
VUE_APP_XXL_JOB_ADMIN = '/xxl-job-admin' VUE_APP_XXL_JOB_ADMIN = '/xxl-job-admin'
# 若依管理系统/生产环境
VUE_APP_BASE_API = '/prod-api'

View File

@ -1,6 +1,6 @@
{ {
"name": "ruoyi-vue-plus", "name": "ruoyi-vue-plus",
"version": "4.6.0", "version": "4.7.0",
"description": "RuoYi-Vue-Plus后台管理系统", "description": "RuoYi-Vue-Plus后台管理系统",
"author": "LionLi", "author": "LionLi",
"license": "MIT", "license": "MIT",
@ -32,7 +32,7 @@
], ],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitee.com/y_project/RuoYi-Vue.git" "url": "https://gitee.com/dromara/RuoYi-Vue-Plus.git"
}, },
"dependencies": { "dependencies": {
"@riophae/vue-treeselect": "0.4.0", "@riophae/vue-treeselect": "0.4.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 269 KiB

View File

@ -180,12 +180,3 @@ aside {
margin-bottom: 10px; margin-bottom: 10px;
} }
} }
//refine vue-multiselect plugin
.multiselect {
line-height: 16px;
}
.multiselect--active {
z-index: 1000 !important;
}

View File

@ -1,7 +1,7 @@
#app { #app {
.main-container { .main-container {
min-height: 100%; height: 100%;
transition: margin-left .28s; transition: margin-left .28s;
margin-left: $base-sidebar-width; margin-left: $base-sidebar-width;
position: relative; position: relative;

View File

@ -18,10 +18,6 @@
transition: all .5s; transition: all .5s;
} }
.fade-transform-leave-active {
position: absolute;
}
.fade-transform-enter { .fade-transform-enter {
opacity: 0; opacity: 0;
transform: translateX(-30px); transform: translateX(-30px);

View File

@ -7,7 +7,7 @@
:key="item.value" :key="item.value"
:index="index" :index="index"
:class="item.raw.cssClass" :class="item.raw.cssClass"
>{{ item.label }}</span >{{ item.label + ' ' }}</span
> >
<el-tag <el-tag
v-else v-else
@ -17,10 +17,13 @@
:type="item.raw.listClass == 'primary' ? '' : item.raw.listClass" :type="item.raw.listClass == 'primary' ? '' : item.raw.listClass"
:class="item.raw.cssClass" :class="item.raw.cssClass"
> >
{{ item.label }} {{ item.label + ' ' }}
</el-tag> </el-tag>
</template> </template>
</template> </template>
<template v-if="unmatch && showValue">
{{ unmatchArray | handleArray }}
</template>
</div> </div>
</template> </template>
@ -33,6 +36,16 @@ export default {
default: null, default: null,
}, },
value: [Number, String, Array], value: [Number, String, Array],
// value
showValue: {
type: Boolean,
default: true,
}
},
data() {
return {
unmatchArray: [], //
}
}, },
computed: { computed: {
values() { values() {
@ -42,7 +55,34 @@ export default {
return []; return [];
} }
}, },
unmatch(){
this.unmatchArray = [];
if (this.value !== null && typeof this.value !== 'undefined') {
//
if(!Array.isArray(this.value)){
if(this.options.some(v=> v.value == this.value )) return false;
this.unmatchArray.push(this.value);
return true;
}
// Array
this.value.forEach(item => {
if (!this.options.some(v=> v.value == item )) this.unmatchArray.push(item)
});
return true;
}
// value
return false;
}, },
},
filters: {
handleArray(array) {
if(array.length===0) return '';
return array.reduce((pre, cur) => {
return pre + ' ' + cur;
})
},
}
}; };
</script> </script>
<style scoped> <style scoped>

View File

@ -1,22 +1,31 @@
<!-- @author zhengjie --> <!-- @author zhengjie -->
<template> <template>
<div class="icon-body"> <div class="icon-body">
<el-input v-model="name" style="position: relative;" clearable placeholder="请输入图标名称" @clear="filterIcons" @input.native="filterIcons"> <el-input v-model="name" class="icon-search" clearable placeholder="请输入图标名称" @clear="filterIcons" @input="filterIcons">
<i slot="suffix" class="el-icon-search el-input__icon" /> <i slot="suffix" class="el-icon-search el-input__icon" />
</el-input> </el-input>
<div class="icon-list"> <div class="icon-list">
<div v-for="(item, index) in iconList" :key="index" @click="selectedIcon(item)"> <div class="list-container">
<svg-icon :icon-class="item" style="height: 30px;width: 16px;" /> <div v-for="(item, index) in iconList" class="icon-item-wrapper" :key="index" @click="selectedIcon(item)">
<div :class="['icon-item', { active: activeIcon === item }]">
<svg-icon :icon-class="item" class-name="icon" style="height: 25px;width: 16px;"/>
<span>{{ item }}</span> <span>{{ item }}</span>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</template> </template>
<script> <script>
import icons from './requireIcons' import icons from './requireIcons'
export default { export default {
name: 'IconSelect', name: 'IconSelect',
props: {
activeIcon: {
type: String
}
},
data() { data() {
return { return {
name: '', name: '',
@ -46,22 +55,49 @@ export default {
.icon-body { .icon-body {
width: 100%; width: 100%;
padding: 10px; padding: 10px;
.icon-search {
position: relative;
margin-bottom: 5px;
}
.icon-list { .icon-list {
height: 200px; height: 200px;
overflow-y: scroll; overflow: auto;
div { .list-container {
height: 30px; display: flex;
line-height: 30px; flex-wrap: wrap;
margin-bottom: -5px; .icon-item-wrapper {
width: calc(100% / 3);
height: 25px;
line-height: 25px;
cursor: pointer; cursor: pointer;
width: 33%; display: flex;
float: left; .icon-item {
display: flex;
max-width: 100%;
height: 100%;
padding: 0 5px;
&:hover {
background: #ececec;
border-radius: 5px;
}
.icon {
flex-shrink: 0;
} }
span { span {
display: inline-block; display: inline-block;
vertical-align: -0.15em; vertical-align: -0.15em;
fill: currentColor; fill: currentColor;
padding-left: 2px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.icon-item.active {
background: #ececec;
border-radius: 5px;
}
}
} }
} }
} }

View File

@ -127,7 +127,13 @@ export default {
window.open(key, "_blank"); window.open(key, "_blank");
} else if (!route || !route.children) { } else if (!route || !route.children) {
// //
const routeMenu = this.childrenMenus.find(item => item.path === key);
if (routeMenu && routeMenu.query) {
let query = JSON.parse(routeMenu.query);
this.$router.push({ path: key, query: query });
} else {
this.$router.push({ path: key }); this.$router.push({ path: key });
}
this.$store.dispatch('app/toggleSideBarHide', true); this.$store.dispatch('app/toggleSideBarHide', true);
} else { } else {
// //

View File

@ -55,7 +55,21 @@ export default {
// fix css style bug in open el-dialog // fix css style bug in open el-dialog
.el-popup-parent--hidden { .el-popup-parent--hidden {
.fixed-header { .fixed-header {
padding-right: 17px; padding-right: 6px;
} }
} }
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background-color: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background-color: #c0c0c0;
border-radius: 3px;
}
</style> </style>

View File

@ -87,7 +87,7 @@ export default {
bottom: 0px; bottom: 0px;
} }
.el-scrollbar__wrap { .el-scrollbar__wrap {
height: 49px; height: 39px;
} }
} }
} }

View File

@ -182,7 +182,7 @@ export default {
}) })
}, },
closeOthersTags() { closeOthersTags() {
this.$router.push(this.selectedTag).catch(()=>{}); this.$router.push(this.selectedTag.fullPath).catch(()=>{});
this.$tab.closeOtherPage(this.selectedTag).then(() => { this.$tab.closeOtherPage(this.selectedTag).then(() => {
this.moveToCurrentTag() this.moveToCurrentTag()
}) })

View File

@ -32,8 +32,12 @@ export default {
// 关闭指定tab页签 // 关闭指定tab页签
closePage(obj) { closePage(obj) {
if (obj === undefined) { if (obj === undefined) {
return store.dispatch('tagsView/delView', router.currentRoute).then(({ lastPath }) => { return store.dispatch('tagsView/delView', router.currentRoute).then(({ visitedViews }) => {
return router.push(lastPath || '/'); const latestView = visitedViews.slice(-1)[0]
if (latestView) {
return router.push(latestView.fullPath)
}
return router.push('/');
}); });
} }
return store.dispatch('tagsView/delView', obj); return store.dispatch('tagsView/delView', obj);

View File

@ -166,9 +166,15 @@ export const dynamicRoutes = [
// 防止连续点击多次路由报错 // 防止连续点击多次路由报错
let routerPush = Router.prototype.push; let routerPush = Router.prototype.push;
let routerReplace = Router.prototype.replace;
// push
Router.prototype.push = function push(location) { Router.prototype.push = function push(location) {
return routerPush.call(this, location).catch(err => err) return routerPush.call(this, location).catch(err => err)
} }
// replace
Router.prototype.replace = function push(location) {
return routerReplace.call(this, location).catch(err => err)
}
export default new Router({ export default new Router({
base: process.env.VUE_APP_CONTEXT_PATH, base: process.env.VUE_APP_CONTEXT_PATH,

View File

@ -114,7 +114,7 @@ export default {
data() { data() {
return { return {
// //
version: "4.6.0", version: "4.7.0",
}; };
}, },
methods: { methods: {

View File

@ -187,7 +187,7 @@ export default {
/** 清理指定名称缓存 */ /** 清理指定名称缓存 */
handleClearCacheName(row) { handleClearCacheName(row) {
clearCacheName(row.cacheName).then(response => { clearCacheName(row.cacheName).then(response => {
this.$modal.msgSuccess("清理缓存名称[" + this.nowCacheName + "]成功"); this.$modal.msgSuccess("清理缓存名称[" + row.cacheName + "]成功");
this.getCacheKeys(); this.getCacheKeys();
}); });
}, },

View File

@ -134,14 +134,13 @@
trigger="click" trigger="click"
@show="$refs['iconSelect'].reset()" @show="$refs['iconSelect'].reset()"
> >
<IconSelect ref="iconSelect" @selected="selected" /> <IconSelect ref="iconSelect" @selected="selected" :active-icon="form.icon" />
<el-input slot="reference" v-model="form.icon" placeholder="点击选择图标" readonly> <el-input slot="reference" v-model="form.icon" placeholder="点击选择图标" readonly>
<svg-icon <svg-icon
v-if="form.icon" v-if="form.icon"
slot="prefix" slot="prefix"
:icon-class="form.icon" :icon-class="form.icon"
class="el-input__icon" style="width: 25px;"
style="height: 32px;width: 16px;"
/> />
<i v-else slot="prefix" class="el-icon-search el-input__icon" /> <i v-else slot="prefix" class="el-icon-search el-input__icon" />
</el-input> </el-input>

View File

@ -406,9 +406,7 @@ export default {
}).then(() => { }).then(() => {
this.getList() this.getList()
this.$modal.msgSuccess(text + "成功"); this.$modal.msgSuccess(text + "成功");
}).catch(() => { }).catch(() => {})
this.previewListResource = previewListResource !== true;
})
} }
} }
}; };

View File

@ -371,19 +371,6 @@ export default {
</script> </script>
<style lang='scss'> <style lang='scss'>
body, html{
margin: 0;
padding: 0;
background: #fff;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
}
input, textarea{
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
}
.editor-tabs{ .editor-tabs{
background: #121315; background: #121315;

View File

@ -67,7 +67,7 @@ services:
network_mode: "host" network_mode: "host"
minio: minio:
image: minio/minio:RELEASE.2022-05-26T05-48-41Z image: minio/minio:RELEASE.2023-03-24T21-41-23Z
container_name: minio container_name: minio
ports: ports:
# api 端口 # api 端口
@ -100,7 +100,7 @@ services:
network_mode: "host" network_mode: "host"
ruoyi-server1: ruoyi-server1:
image: ruoyi/ruoyi-server:4.6.0 image: ruoyi/ruoyi-server:4.7.0
container_name: ruoyi-server1 container_name: ruoyi-server1
environment: environment:
# 时区上海 # 时区上海
@ -115,7 +115,7 @@ services:
network_mode: "host" network_mode: "host"
ruoyi-server2: ruoyi-server2:
image: "ruoyi/ruoyi-server:4.6.0" image: "ruoyi/ruoyi-server:4.7.0"
container_name: ruoyi-server2 container_name: ruoyi-server2
environment: environment:
# 时区上海 # 时区上海
@ -130,7 +130,7 @@ services:
network_mode: "host" network_mode: "host"
ruoyi-monitor-admin: ruoyi-monitor-admin:
image: ruoyi/ruoyi-monitor-admin:4.6.0 image: ruoyi/ruoyi-monitor-admin:4.7.0
container_name: ruoyi-monitor-admin container_name: ruoyi-monitor-admin
environment: environment:
# 时区上海 # 时区上海
@ -142,7 +142,7 @@ services:
network_mode: "host" network_mode: "host"
ruoyi-xxl-job-admin: ruoyi-xxl-job-admin:
image: ruoyi/ruoyi-xxl-job-admin:4.6.0 image: ruoyi/ruoyi-xxl-job-admin:4.7.0
container_name: ruoyi-xxl-job-admin container_name: ruoyi-xxl-job-admin
environment: environment:
# 时区上海 # 时区上海

View File

@ -531,9 +531,9 @@ create table sys_oper_log (
); );
alter table sys_oper_log add constraint pk_sys_oper_log primary key (oper_id); alter table sys_oper_log add constraint pk_sys_oper_log primary key (oper_id);
create unique index idx_sys_oper_log_bt on sys_oper_log (business_type); create index idx_sys_oper_log_bt on sys_oper_log (business_type);
create unique index idx_sys_oper_log_s on sys_oper_log (status); create index idx_sys_oper_log_s on sys_oper_log (status);
create unique index idx_sys_oper_log_ot on sys_oper_log (oper_time); create index idx_sys_oper_log_ot on sys_oper_log (oper_time);
comment on table sys_oper_log is '操作日志记录'; comment on table sys_oper_log is '操作日志记录';
comment on column sys_oper_log.oper_id is '日志主键'; comment on column sys_oper_log.oper_id is '日志主键';
@ -711,8 +711,8 @@ create table sys_logininfor (
); );
alter table sys_logininfor add constraint pk_sys_logininfor primary key (info_id); alter table sys_logininfor add constraint pk_sys_logininfor primary key (info_id);
create unique index idx_sys_logininfor_s on sys_logininfor (status); create index idx_sys_logininfor_s on sys_logininfor (status);
create unique index idx_sys_logininfor_lt on sys_logininfor (login_time); create index idx_sys_logininfor_lt on sys_logininfor (login_time);
comment on table sys_logininfor is '系统访问记录'; comment on table sys_logininfor is '系统访问记录';
comment on column sys_logininfor.info_id is '访问ID'; comment on column sys_logininfor.info_id is '访问ID';
@ -938,7 +938,7 @@ comment on column sys_oss_config.domain is '自定义域名';
comment on column sys_oss_config.is_https is '是否httpsY=是,N=否)'; comment on column sys_oss_config.is_https is '是否httpsY=是,N=否)';
comment on column sys_oss_config.region is ''; comment on column sys_oss_config.region is '';
comment on column sys_oss_config.access_policy is '桶权限类型(0=private 1=public 2=custom)'; comment on column sys_oss_config.access_policy is '桶权限类型(0=private 1=public 2=custom)';
comment on column sys_oss_config.status is '状态0=正常,1=停用'; comment on column sys_oss_config.status is '是否默认0=是,1=否';
comment on column sys_oss_config.ext1 is '扩展字段'; comment on column sys_oss_config.ext1 is '扩展字段';
comment on column sys_oss_config.remark is '备注'; comment on column sys_oss_config.remark is '备注';
comment on column sys_oss_config.create_by is '创建者'; comment on column sys_oss_config.create_by is '创建者';

View File

@ -540,9 +540,9 @@ create table if not exists sys_oper_log
constraint sys_oper_log_pk primary key (oper_id) constraint sys_oper_log_pk primary key (oper_id)
); );
create unique index idx_sys_oper_log_bt ON sys_oper_log (business_type); create index idx_sys_oper_log_bt ON sys_oper_log (business_type);
create unique index idx_sys_oper_log_s ON sys_oper_log (status); create index idx_sys_oper_log_s ON sys_oper_log (status);
create unique index idx_sys_oper_log_ot ON sys_oper_log (oper_time); create index idx_sys_oper_log_ot ON sys_oper_log (oper_time);
comment on table sys_oper_log is '操作日志记录'; comment on table sys_oper_log is '操作日志记录';
comment on column sys_oper_log.oper_id is '日志主键'; comment on column sys_oper_log.oper_id is '日志主键';
@ -726,8 +726,8 @@ create table if not exists sys_logininfor
constraint sys_logininfor_pk primary key (info_id) constraint sys_logininfor_pk primary key (info_id)
); );
create unique index idx_sys_logininfor_s ON sys_logininfor (status); create index idx_sys_logininfor_s ON sys_logininfor (status);
create unique index idx_sys_logininfor_lt ON sys_logininfor (login_time); create index idx_sys_logininfor_lt ON sys_logininfor (login_time);
comment on table sys_logininfor is '系统访问记录'; comment on table sys_logininfor is '系统访问记录';
comment on column sys_logininfor.info_id is '访问ID'; comment on column sys_logininfor.info_id is '访问ID';
@ -954,7 +954,7 @@ comment on column sys_oss_config.domain is '自定义域名';
comment on column sys_oss_config.is_https is '是否httpsY=是,N=否)'; comment on column sys_oss_config.is_https is '是否httpsY=是,N=否)';
comment on column sys_oss_config.region is ''; comment on column sys_oss_config.region is '';
comment on column sys_oss_config.access_policy is '桶权限类型(0=private 1=public 2=custom)'; comment on column sys_oss_config.access_policy is '桶权限类型(0=private 1=public 2=custom)';
comment on column sys_oss_config.status is '状态0=正常,1=停用'; comment on column sys_oss_config.status is '是否默认0=是,1=否';
comment on column sys_oss_config.ext1 is '扩展字段'; comment on column sys_oss_config.ext1 is '扩展字段';
comment on column sys_oss_config.create_by is '创建者'; comment on column sys_oss_config.create_by is '创建者';
comment on column sys_oss_config.create_time is '创建时间'; comment on column sys_oss_config.create_time is '创建时间';

View File

@ -679,7 +679,7 @@ create table sys_oss_config (
is_https char(1) default 'N' comment '是否httpsY=是,N=否)', is_https char(1) default 'N' comment '是否httpsY=是,N=否)',
region varchar(255) default '' comment '', region varchar(255) default '' comment '',
access_policy char(1) not null default '1' comment '桶权限类型(0=private 1=public 2=custom)', access_policy char(1) not null default '1' comment '桶权限类型(0=private 1=public 2=custom)',
status char(1) default '1' comment '状态0=正常,1=停用', status char(1) default '1' comment '是否默认0=是,1=否',
ext1 varchar(255) default '' comment '扩展字段', ext1 varchar(255) default '' comment '扩展字段',
create_by varchar(64) default '' comment '创建者', create_by varchar(64) default '' comment '创建者',
create_time datetime default null comment '创建时间', create_time datetime default null comment '创建时间',

View File

@ -2285,7 +2285,7 @@ EXEC sp_addextendedproperty
'COLUMN', N'access_policy' 'COLUMN', N'access_policy'
GO GO
EXEC sp_addextendedproperty EXEC sp_addextendedproperty
'MS_Description', N'状态0=正常,1=停用', 'MS_Description', N'是否默认0=是,1=否',
'SCHEMA', N'dbo', 'SCHEMA', N'dbo',
'TABLE', N'sys_oss_config', 'TABLE', N'sys_oss_config',
'COLUMN', N'status' 'COLUMN', N'status'