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

This commit is contained in:
疯狂的狮子li 2022-03-01 10:12:32 +08:00
commit ca085b9aaa
54 changed files with 381 additions and 144 deletions

View File

@ -4,7 +4,7 @@
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/JavaLionLi/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)
<br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-4.0.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-4.0.1-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.5-blue.svg)]()
[![JDK-8+](https://img.shields.io/badge/JDK-8-green.svg)]()
[![JDK-11](https://img.shields.io/badge/JDK-11-green.svg)]()
@ -84,7 +84,7 @@
## 捐献作者
作者为兼职做开源,平时还需要工作,如果帮到了您可以请作者吃个盒饭
<img src="https://images.gitee.com/uploads/images/2021/0525/101654_451e4523_1766278.jpeg" width="300px" height="450px" />
<img src="https://images.gitee.com/uploads/images/2022/0218/213734_b1b8197f_1766278.jpeg" width="300px" height="450px" />
<img src="https://images.gitee.com/uploads/images/2021/0525/101713_3d18b119_1766278.jpeg" width="300px" height="450px" />
## 业务功能

12
pom.xml
View File

@ -6,15 +6,15 @@
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-vue-plus</artifactId>
<version>4.0.0</version>
<version>4.0.1</version>
<name>RuoYi-Vue-Plus</name>
<url>https://gitee.com/JavaLionLi/RuoYi-Vue-Plus</url>
<description>RuoYi-Vue-Plus后台管理系统</description>
<properties>
<ruoyi-vue-plus.version>4.0.0</ruoyi-vue-plus.version>
<spring-boot.version>2.6.3</spring-boot.version>
<ruoyi-vue-plus.version>4.0.1</ruoyi-vue-plus.version>
<spring-boot.version>2.6.4</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
@ -29,7 +29,7 @@
<satoken.version>1.29.0</satoken.version>
<mybatis-plus.version>3.5.1</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.7.20</hutool.version>
<hutool.version>5.7.21</hutool.version>
<okhttp.version>4.9.2</okhttp.version>
<spring-boot-admin.version>2.6.2</spring-boot-admin.version>
<redisson.version>3.16.8</redisson.version>
@ -42,10 +42,10 @@
<jaxb.version>3.0.1</jaxb.version>
<!-- OSS 配置 -->
<qiniu.version>7.9.2</qiniu.version>
<qiniu.version>7.9.3</qiniu.version>
<aliyun.oss.version>3.14.0</aliyun.oss.version>
<qcloud.cos.version>5.6.68</qcloud.cos.version>
<minio.version>8.3.5</minio.version>
<minio.version>8.3.7</minio.version>
<!-- docker 配置 -->
<docker.registry.url>localhost</docker.registry.url>

View File

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

View File

@ -65,6 +65,7 @@ public class SysLoginController {
public R<Void> logout() {
try {
StpUtil.logout();
loginService.logout(LoginHelper.getUsername());
} catch (NotLoginException e) {
}
return R.ok("退出成功");

View File

@ -7,7 +7,6 @@ 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.RepeatSubmit;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
@ -70,7 +69,6 @@ public class SysOssController extends BaseController {
})
@SaCheckPermission("system:oss:upload")
@Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
@RepeatSubmit
@PostMapping("/upload")
public R<Map<String, String>> upload(@RequestPart("file") MultipartFile file) {
if (ObjectUtil.isNull(file)) {

View File

@ -16,6 +16,7 @@ import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.excel.ExcelResult;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.domain.vo.SysUserExportVo;
@ -109,7 +110,7 @@ public class SysUserController extends BaseController {
userService.checkUserDataScope(userId);
Map<String, Object> ajax = new HashMap<>();
List<SysRole> roles = roleService.selectRoleAll();
ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
ajax.put("roles", LoginHelper.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
ajax.put("posts", postService.selectPostAll());
if (ObjectUtil.isNotNull(userId)) {
SysUser sysUser = userService.selectUserById(userId);
@ -213,7 +214,7 @@ public class SysUserController extends BaseController {
List<SysRole> roles = roleService.selectRolesByUserId(userId);
Map<String, Object> ajax = new HashMap<>();
ajax.put("user", user);
ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
ajax.put("roles", LoginHelper.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
return R.ok(ajax);
}

View File

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

View File

@ -41,14 +41,14 @@ public class UnsignedMathGenerator implements CodeGenerator {
@Override
public String generate() {
final int limit = getLimit();
int min = RandomUtil.randomInt(limit);
int max = RandomUtil.randomInt(min, limit);
String number1 = Integer.toString(max);
String number2 = Integer.toString(min);
number1 = StringUtils.rightPad(number1, this.numberLength, CharUtil.SPACE);
number2 = StringUtils.rightPad(number2, this.numberLength, CharUtil.SPACE);
int a = RandomUtil.randomInt(limit);
int b = RandomUtil.randomInt(limit);
String max = Integer.toString(Math.max(a,b));
String min = Integer.toString(Math.min(a,b));
max = StringUtils.rightPad(max, this.numberLength, CharUtil.SPACE);
min = StringUtils.rightPad(min, this.numberLength, CharUtil.SPACE);
return number1 + RandomUtil.randomChar(OPERATORS) + number2 + '=';
return max + RandomUtil.randomChar(OPERATORS) + min + '=';
}
@Override

View File

@ -109,4 +109,10 @@ public interface UserConstants {
*/
int PASSWORD_MIN_LENGTH = 5;
int PASSWORD_MAX_LENGTH = 20;
/**
* 管理员ID
*/
Long ADMIN_ID = 1L;
}

View File

@ -38,11 +38,11 @@ public class R<T> implements Serializable {
private T data;
public static <T> R<T> ok() {
return restResult(null, SUCCESS, null);
return restResult(null, SUCCESS, "操作成功");
}
public static <T> R<T> ok(T data) {
return restResult(data, SUCCESS, null);
return restResult(data, SUCCESS, "操作成功");
}
public static <T> R<T> ok(String msg) {
@ -54,7 +54,7 @@ public class R<T> implements Serializable {
}
public static <T> R<T> fail() {
return restResult(null, FAIL, null);
return restResult(null, FAIL, "操作失败");
}
public static <T> R<T> fail(String msg) {
@ -62,7 +62,7 @@ public class R<T> implements Serializable {
}
public static <T> R<T> fail(T data) {
return restResult(data, FAIL, null);
return restResult(data, FAIL, "操作失败");
}
public static <T> R<T> fail(String msg, T data) {

View File

@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.annotation.ExcelDictFormat;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.convert.ExcelDictConvert;
import com.ruoyi.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
@ -132,11 +133,7 @@ public class SysRole extends BaseEntity {
@ApiModelProperty(value = "是否管理员")
public boolean isAdmin() {
return isAdmin(this.roleId);
}
public static boolean isAdmin(Long roleId) {
return roleId != null && 1L == roleId;
return UserConstants.ADMIN_ID.equals(this.roleId);
}
}

View File

@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ruoyi.common.annotation.Sensitive;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.enums.SensitiveStrategy;
import com.ruoyi.common.xss.Xss;
@ -185,11 +186,7 @@ public class SysUser extends BaseEntity {
@ApiModelProperty(value = "是否管理员")
public boolean isAdmin() {
return isAdmin(this.userId);
}
public static boolean isAdmin(Long userId) {
return userId != null && 1L == userId;
return UserConstants.ADMIN_ID.equals(this.userId);
}
}

View File

@ -2,6 +2,7 @@ package com.ruoyi.common.helper;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.DeviceType;
import com.ruoyi.common.enums.UserType;
@ -130,12 +131,11 @@ public class LoginHelper {
* @return 结果
*/
public static boolean isAdmin(Long userId) {
return userId != null && 1L == userId;
return UserConstants.ADMIN_ID.equals(userId);
}
public static boolean isAdmin() {
Long userId = getUserId();
return userId != null && 1L == userId;
return isAdmin(getUserId());
}
}

View File

@ -7,6 +7,11 @@ import org.apache.commons.lang3.time.DateFormatUtils;
import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
/**
@ -116,6 +121,14 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
return new Date(time);
}
/**
* 计算相差天数
*/
public static int differentDaysByMillisecond(Date date1, Date date2)
{
return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
}
/**
* 计算两个时间差
*/
@ -136,4 +149,21 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
// long sec = diff % nd % nh % nm / ns;
return day + "" + hour + "小时" + min + "分钟";
}
/**
* 增加 LocalDateTime ==> Date
*/
public static Date toDate(LocalDateTime temporalAccessor) {
ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}
/**
* 增加 LocalDate ==> Date
*/
public static Date toDate(LocalDate temporalAccessor) {
LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}
}

View File

@ -86,7 +86,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
* 如果想输出 {} 使用 \\转义 { 即可如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
* <br>
* 通常使用format("this is {} for {}", "a", "b") -> this is a for b<br>
* 转义{} format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
* 转义{} format("this is \\{} for {}", "a", "b") -> this is {} for a<br>
* 转义\ format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
*
* @param template 文本模板被替换的部分用 {} 表示
@ -231,4 +231,43 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
return matcher.match(pattern, url);
}
/**
* 数字左边补齐0使之达到指定长度注意如果数字转换为字符串后长度大于size则只保留 最后size个字符
*
* @param num 数字对象
* @param size 字符串指定长度
* @return 返回数字的字符串格式该字符串为指定长度
*/
public static final String padl(final Number num, final int size) {
return padl(num.toString(), size, '0');
}
/**
* 字符串左补齐如果原始字符串s长度大于size则只保留最后size个字符
*
* @param s 原始字符串
* @param size 字符串指定长度
* @param c 用于补齐的字符
* @return 返回指定长度的字符串由原字符串左补齐或截取得到
*/
public static final String padl(final String s, final int size, final char c) {
final StringBuilder sb = new StringBuilder(size);
if (s != null) {
final int len = s.length();
if (s.length() <= size) {
for (int i = size - len; i > 0; i--) {
sb.append(c);
}
sb.append(s);
} else {
return s.substring(len - size, len);
}
} else {
for (int i = size; i > 0; i--) {
sb.append(c);
}
}
return sb.toString();
}
}

View File

@ -22,7 +22,6 @@ public class FileUtils extends FileUtil {
*
* @param response 响应对象
* @param realFileName 真实文件名
* @return
*/
public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException {
String percentEncodedFileName = percentEncode(realFileName);
@ -35,7 +34,6 @@ public class FileUtils extends FileUtil {
.append("utf-8''")
.append(percentEncodedFileName);
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
response.setHeader("Content-disposition", contentDispositionValue.toString());
response.setHeader("download-filename", percentEncodedFileName);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,54 @@
package com.ruoyi.framework.config;
import cn.hutool.core.util.ArrayUtil;
import com.ruoyi.common.exception.ServiceException;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.Arrays;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
/**
* 异步配置
*
* @author Lion Li
*/
@EnableAsync
@Configuration
public class AsyncConfig extends AsyncConfigurerSupport {
@Autowired
@Qualifier("scheduledExecutorService")
private ScheduledExecutorService scheduledExecutorService;
/**
* 自定义 @Async 注解使用系统线程池
*/
@Override
public Executor getAsyncExecutor() {
return scheduledExecutorService;
}
/**
* 异步执行异常处理
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> {
throwable.printStackTrace();
StringBuilder sb = new StringBuilder();
sb.append("Exception message - ").append(throwable.getMessage())
.append(", Method name - ").append(method.getName());
if (ArrayUtil.isNotEmpty(objects)) {
sb.append(", Parameter value - ").append(Arrays.toString(objects));
}
throw new ServiceException(sb.toString());
};
}
}

View File

@ -79,7 +79,7 @@ public class PlusDataPermissionHandler {
DataPermissionHelper.setVariable("user", currentUser);
}
// 如果是超级管理员则不过滤数据
if (ObjectUtil.isNull(currentUser) || LoginHelper.isAdmin(currentUser.getUserId())) {
if (LoginHelper.isAdmin()) {
return where;
}
String dataFilterSql = buildDataFilter(dataColumns, isSelect);

View File

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

View File

@ -1,8 +1,6 @@
package com.ruoyi.generator.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.*;
import com.ruoyi.common.constant.GenConstants;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.utils.StringUtils;
@ -102,6 +100,7 @@ public class GenTable extends BaseEntity {
/**
* 生成路径不填默认项目路径
*/
@TableField(updateStrategy = FieldStrategy.NOT_EMPTY)
private String genPath;
/**

View File

@ -244,14 +244,28 @@ public class VelocityUtils {
public static String getDicts(GenTable genTable) {
List<GenTableColumn> columns = genTable.getColumns();
Set<String> dicts = new HashSet<String>();
addDicts(dicts, columns);
if (ObjectUtil.isNotNull(genTable.getSubTable())) {
List<GenTableColumn> subColumns = genTable.getSubTable().getColumns();
addDicts(dicts, subColumns);
}
return StringUtils.join(dicts, ", ");
}
/**
* 添加字典列表
*
* @param dicts 字典列表
* @param columns 列集合
*/
public static void addDicts(Set<String> dicts, List<GenTableColumn> columns) {
for (GenTableColumn column : columns) {
if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny(
column.getHtmlType(),
new String[]{GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX})) {
new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX })) {
dicts.add("'" + column.getDictType() + "'");
}
}
return StringUtils.join(dicts, ", ");
}
/**

View File

@ -48,7 +48,7 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service {
/**
* 查询${functionName}列表
*
* @param ${className} ${functionName}
* @param bo ${functionName}
* @return ${functionName}
*/
@Override
@ -62,7 +62,7 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service {
/**
* 查询${functionName}列表
*
* @param ${className} ${functionName}
* @param bo ${functionName}
* @return ${functionName}
*/
@Override
@ -101,7 +101,7 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service {
/**
* 新增${functionName}
*
* @param ${className} ${functionName}
* @param bo ${functionName}
* @return 结果
*/
@Override
@ -119,7 +119,7 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service {
/**
* 修改${functionName}
*
* @param ${className} ${functionName}
* @param bo ${functionName}
* @return 结果
*/
@Override

View File

@ -306,7 +306,7 @@ export default {
queryParams: {
#foreach ($column in $columns)
#if($column.query)
$column.javaField: null#if($foreach.count != $columns.size()),#end
$column.javaField: undefined#if($foreach.count != $columns.size()),#end
#end
#end
},

View File

@ -44,7 +44,7 @@
v-model="queryParams.${column.javaField}"
type="date"
value-format="yyyy-MM-dd"
placeholder="选择${comment}">
placeholder="选择${comment}">
</el-date-picker>
</el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
@ -268,7 +268,7 @@
v-model="form.${field}"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择${comment}">
placeholder="选择${comment}">
</el-date-picker>
</el-form-item>
#elseif($column.htmlType == "textarea")
@ -300,12 +300,39 @@
#set($comment=$column.columnComment)
#end
#if($column.pk || $javaField == ${subTableFkclassName})
#elseif($column.list && "" != $javaField)
<el-table-column label="$comment" prop="${javaField}">
#elseif($column.list && $column.htmlType == "input")
<el-table-column label="$comment" prop="${javaField}" width="150">
<template slot-scope="scope">
<el-input v-model="scope.row.$javaField" placeholder="请输入$comment" />
</template>
</el-table-column>
#elseif($column.list && $column.htmlType == "datetime")
<el-table-column label="$comment" prop="${javaField}" width="240">
<template slot-scope="scope">
<el-date-picker clearable v-model="scope.row.$javaField" type="date" value-format="yyyy-MM-dd" placeholder="请选择$comment" />
</template>
</el-table-column>
#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" != $column.dictType)
<el-table-column label="$comment" prop="${javaField}" width="150">
<template slot-scope="scope">
<el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
<el-option
v-for="dict in dict.type.$column.dictType"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</template>
</el-table-column>
#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" == $column.dictType)
<el-table-column label="$comment" prop="${javaField}" width="150">
<template slot-scope="scope">
<el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
<el-option label="请选择字典生成" value="" />
</el-select>
</template>
</el-table-column>
#end
#end
</el-table>

View File

@ -43,7 +43,7 @@
v-model="queryParams.${column.javaField}"
type="date"
value-format="YYYY-MM-DD"
placeholder="选择${comment}">
placeholder="选择${comment}">
</el-date-picker>
</el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
@ -259,7 +259,7 @@
v-model="form.${field}"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="选择${comment}">
placeholder="选择${comment}">
</el-date-picker>
</el-form-item>
#elseif($column.htmlType == "textarea")
@ -291,12 +291,44 @@
#set($comment=$column.columnComment)
#end
#if($column.pk || $javaField == ${subTableFkclassName})
#elseif($column.list && "" != $javaField)
<el-table-column label="$comment" prop="${javaField}">
#elseif($column.list && $column.htmlType == "input")
<el-table-column label="$comment" prop="${javaField}" width="150">
<template #default="scope">
<el-input v-model="scope.row.$javaField" placeholder="请输入$comment" />
</template>
</el-table-column>
#elseif($column.list && $column.htmlType == "datetime")
<el-table-column label="$comment" prop="${javaField}" width="240">
<template #default="scope">
<el-date-picker clearable
v-model="scope.row.$javaField"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择$comment">
</el-date-picker>
</template>
</el-table-column>
#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" != $column.dictType)
<el-table-column label="$comment" prop="${javaField}" width="150">
<template #default="scope">
<el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
<el-option
v-for="dict in $column.dictType"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</template>
</el-table-column>
#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" == $column.dictType)
<el-table-column label="$comment" prop="${javaField}" width="150">
<template #default="scope">
<el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
<el-option label="请选择字典生成" value="" />
</el-select>
</template>
</el-table-column>
#end
#end
</el-table>

View File

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

View File

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

View File

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

View File

@ -87,16 +87,19 @@ public class SysLoginService {
// 登录成功 清空错误次数
RedisUtils.deleteObject(Constants.LOGIN_ERROR + username);
asyncService.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
recordLoginInfo(user.getUserId(), username);
LoginUser loginUser = buildLoginUser(user);
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.PC);
asyncService.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
recordLoginInfo(user.getUserId(), username);
return StpUtil.getTokenValue();
}
public void logout(String loginName) {
asyncService.recordLogininfor(loginName, Constants.LOGOUT, MessageUtils.message("user.logout.success"), ServletUtils.getRequest());
}
/**
* 校验验证码
*

View File

@ -152,7 +152,7 @@ public class SysDeptServiceImpl implements ISysDeptService {
*/
@Override
public void checkDeptDataScope(Long deptId) {
if (!SysUser.isAdmin(LoginHelper.getUserId())) {
if (!LoginHelper.isAdmin()) {
SysDept dept = new SysDept();
dept.setDeptId(deptId);
List<SysDept> depts = SpringUtils.getAopProxy(this).selectDeptList(dept);

View File

@ -8,7 +8,6 @@ import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysMenu;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.TreeBuildUtils;
@ -58,7 +57,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
public List<SysMenu> selectMenuList(SysMenu menu, Long userId) {
List<SysMenu> menuList = null;
// 管理员显示所有菜单信息
if (SysUser.isAdmin(userId)) {
if (LoginHelper.isAdmin(userId)) {
menuList = baseMapper.selectList(new LambdaQueryWrapper<SysMenu>()
.like(StringUtils.isNotBlank(menu.getMenuName()), SysMenu::getMenuName, menu.getMenuName())
.eq(StringUtils.isNotBlank(menu.getVisible()), SysMenu::getVisible, menu.getVisible())

View File

@ -7,7 +7,6 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.helper.LoginHelper;
@ -181,7 +180,7 @@ public class SysRoleServiceImpl implements ISysRoleService {
*/
@Override
public void checkRoleDataScope(Long roleId) {
if (!SysUser.isAdmin(LoginHelper.getUserId())) {
if (!LoginHelper.isAdmin()) {
SysRole role = new SysRole();
role.setRoleId(roleId);
List<SysRole> roles = SpringUtils.getAopProxy(this).selectRoleList(role);

View File

@ -206,7 +206,7 @@ public class SysUserServiceImpl implements ISysUserService {
*/
@Override
public void checkUserDataScope(Long userId) {
if (!SysUser.isAdmin(LoginHelper.getUserId())) {
if (!LoginHelper.isAdmin()) {
SysUser user = new SysUser();
user.setUserId(userId);
List<SysUser> users = SpringUtils.getAopProxy(this).selectUserList(user);

View File

@ -1,6 +1,6 @@
{
"name": "ruoyi-vue-plus",
"version": "4.0.0",
"version": "4.0.1",
"description": "RuoYi-Vue-Plus后台管理系统",
"author": "LionLi",
"license": "MIT",

View File

@ -1,6 +1,7 @@
<template>
<div class="upload-file">
<el-upload
multiple
:action="uploadFileUrl"
:before-upload="handleBeforeUpload"
:file-list="fileList"
@ -69,6 +70,8 @@ export default {
},
data() {
return {
number: 0,
uploadList: [],
baseUrl: process.env.VUE_APP_BASE_API,
uploadFileUrl: process.env.VUE_APP_BASE_API + "/system/oss/upload", //
headers: {
@ -122,7 +125,7 @@ export default {
return false;
});
if (!isTypeOk) {
this.$message.error(`文件格式不正确, 请上传${this.fileType.join("/")}格式文件!`);
this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join("/")}格式文件!`);
return false;
}
}
@ -130,29 +133,37 @@ export default {
if (this.fileSize) {
const isLt = file.size / 1024 / 1024 < this.fileSize;
if (!isLt) {
this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`);
this.$modal.msgError(`上传文件大小不能超过 ${this.fileSize} MB!`);
return false;
}
}
this.$modal.loading("正在上传文件,请稍候...");
this.number++;
return true;
},
//
handleExceed() {
this.$message.error(`上传文件数量不能超过 ${this.limit} 个!`);
this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`);
},
//
handleUploadError(err) {
this.$message.error("上传失败, 请重试");
this.$modal.msgError("上传图片失败,请重试");
this.$modal.closeLoading()
},
//
handleUploadSuccess(res, file) {
if (res.code === 200) {
this.$message.success("上传成功");
this.fileList.push({ name: res.data.fileName, url: res.data.url });
this.$emit("input", this.listToString(this.fileList));
this.uploadList.push({ name: res.data.fileName, url: res.data.url });
if (this.uploadList.length === this.number) {
this.fileList = this.fileList.concat(this.uploadList);
this.uploadList = [];
this.number = 0;
this.$emit("input", this.listToString(this.fileList));
this.$modal.closeLoading();
}
} else {
this.$message.error(res.msg);
this.loading.close();
this.$modal.msgError(res.msg);
this.$modal.closeLoading();
}
},
//
@ -163,7 +174,7 @@ export default {
//
getFileName(name) {
if (name.lastIndexOf("/") > -1) {
return name.slice(name.lastIndexOf("/") + 1).toLowerCase();
return name.slice(name.lastIndexOf("/") + 1);
} else {
return "";
}

View File

@ -1,6 +1,7 @@
<template>
<div class="component-upload-image">
<el-upload
multiple
:action="uploadImgUrl"
list-type="picture-card"
:on-success="handleUploadSuccess"
@ -18,7 +19,7 @@
>
<i class="el-icon-plus"></i>
</el-upload>
<!-- 上传提示 -->
<div class="el-upload__tip" slot="tip" v-if="showTip">
请上传
@ -70,6 +71,8 @@ export default {
},
data() {
return {
number: 0,
uploadList: [],
dialogImageUrl: "",
dialogVisible: false,
hideUpload: false,
@ -121,12 +124,17 @@ export default {
//
handleUploadSuccess(res) {
if (res.code == 200) {
this.fileList.push({ name: res.data.fileName, url: res.data.url });
this.$emit("input", this.listToString(this.fileList));
this.loading.close();
this.uploadList.push({ name: res.data.fileName, url: res.data.url });
if (this.uploadList.length === this.number) {
this.fileList = this.fileList.concat(this.uploadList);
this.uploadList = [];
this.number = 0;
this.$emit("input", this.listToString(this.fileList));
this.$modal.closeLoading();
}
} else {
this.$message.error(res.msg);
this.loading.close();
this.$modal.msgError(res.msg);
this.$modal.closeLoading();
}
},
// loading
@ -147,35 +155,27 @@ export default {
}
if (!isImg) {
this.$message.error(
`文件格式不正确, 请上传${this.fileType.join("/")}图片格式文件!`
);
this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join("/")}图片格式文件!`);
return false;
}
if (this.fileSize) {
const isLt = file.size / 1024 / 1024 < this.fileSize;
if (!isLt) {
this.$message.error(`上传头像图片大小不能超过 ${this.fileSize} MB!`);
this.$modal.msgError(`上传头像图片大小不能超过 ${this.fileSize} MB!`);
return false;
}
}
this.loading = this.$loading({
lock: true,
text: "上传中",
background: "rgba(0, 0, 0, 0.7)",
});
this.$modal.loading("正在上传图片,请稍候...");
this.number++;
},
//
handleExceed() {
this.$message.error(`上传文件数量不能超过 ${this.limit} 个!`);
this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`);
},
//
handleUploadError(res) {
this.$message({
type: "error",
message: "上传失败",
});
this.loading.close();
this.$modal.msgError("上传图片失败,请重试");
this.$modal.closeLoading();
},
//
handlePictureCardPreview(file) {

View File

@ -1,7 +1,6 @@
<template>
<div :class="{'hidden':hidden}" class="pagination-container">
<el-pagination
v-if="pageShow"
:background="background"
:current-page.sync="currentPage"
:page-size.sync="pageSize"
@ -64,7 +63,6 @@ export default {
},
data() {
return {
pageShow: true
};
},
computed: {
@ -88,10 +86,7 @@ export default {
methods: {
handleSizeChange(val) {
if (this.currentPage * val > this.total) {
this.pageShow = false;
this.$nextTick(() => {
this.pageShow = true
})
this.currentPage = 1
}
this.$emit('pagination', { page: this.currentPage, limit: val })
if (this.autoScroll) {

View File

@ -4,6 +4,7 @@ import { Message } from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isRelogin } from '@/utils/request'
NProgress.configure({ showSpinner: false })
@ -19,8 +20,10 @@ router.beforeEach((to, from, next) => {
NProgress.done()
} else {
if (store.getters.roles.length === 0) {
isRelogin.show = true
// 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(() => {
isRelogin.show = false
store.dispatch('GenerateRoutes').then(accessRoutes => {
// 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表

View File

@ -9,7 +9,7 @@ import { saveAs } from 'file-saver'
let downloadLoadingInstance;
// 是否显示重新登录
let isReloginShow;
export let isRelogin = { show: false };
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 对应国际化资源文件后缀
@ -78,23 +78,20 @@ service.interceptors.response.use(res => {
return res.data
}
if (code === 401) {
if (!isReloginShow) {
isReloginShow = true;
if (!isRelogin.show) {
isRelogin.show = true;
MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
isReloginShow = false;
isRelogin.show = false;
store.dispatch('LogOut').then(() => {
// 如果是登录页面不需要重新加载
if (window.location.hash.indexOf("#/login") != 0) {
location.href = process.env.VUE_APP_CONTEXT_PATH + "index";
}
location.href = process.env.VUE_APP_CONTEXT_PATH + "index";
})
}).catch(() => {
isReloginShow = false;
isRelogin.show = false;
});
}
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')

View File

@ -70,6 +70,9 @@ export function addDateRange(params, dateRange, propName) {
// 回显数据字典
export function selectDictLabel(datas, value) {
if (value === undefined) {
return "";
}
var actions = [];
Object.keys(datas).some((key) => {
if (datas[key].value == ('' + value)) {
@ -77,23 +80,31 @@ export function selectDictLabel(datas, value) {
return true;
}
})
if (actions.length === 0) {
actions.push(value);
}
return actions.join('');
}
// 回显数据字典(字符串数组)
export function selectDictLabels(datas, value, separator) {
if(value === undefined) {
if (value === undefined) {
return "";
}
var actions = [];
var currentSeparator = undefined === separator ? "," : separator;
var temp = value.split(currentSeparator);
Object.keys(value.split(currentSeparator)).some((val) => {
var match = false;
Object.keys(datas).some((key) => {
if (datas[key].value == ('' + temp[val])) {
actions.push(datas[key].label + currentSeparator);
match = true;
}
})
if (!match) {
actions.push(temp[val] + currentSeparator);
}
})
return actions.join('').substring(0, actions.join('').length - 1);
}

View File

@ -101,6 +101,29 @@
<span>更新日志</span>
</div>
<el-collapse accordion>
<el-collapse-item title="v4.0.1 - 2022-03-01">
<ol>
<li>update 图片上传 文件上传 支持并发上传</li>
<li>update 组件ImageUpload支持多图同时选择上传</li>
<li>udpate 组件fileUpload支持多文件同时选择上传</li>
<li>update springboot 2.6.3 => 2.6.4</li>
<li>update hutool 5.7.20 => 5.7.21</li>
<li>update qiniu 7.9.2 => 7.9.3</li>
<li>update minio 8.3.5 => 8.3.7</li>
<li>update 优化 R 默认返回 msg</li>
<li>update 增加 用户注册 用户类型默认值</li>
<li>update 增加用户登出日志</li>
<li>update 更新 多用户多设备的注释说明</li>
<li>update 优化 是否为管理员的判断</li>
<li>update 优化 页面若未匹配到字典标签则返回原字典值</li>
<li>update 调整用户登录 将日志调整到最后 防止获取不到用户警告</li>
<li>update 优化随机数生成方式 避免容易生成两个相同随机数的问题</li>
<li>fix 修复代码生成 基于路径生成 路径为空问题</li>
<li>fix 恢复误删 @Async 注解线程池配置类</li>
<li>fix 修复 minio 适配 https 导致的问题</li>
<li>fix 修复分页组件请求两次问题</li>
</ol>
</el-collapse-item>
<el-collapse-item title="v4.0.0 - 2022-02-18">
<ol>
<li>[重大更新] 重写项目整体结构 数据处理下沉至Mapper符合MVC规范 减少循环依赖</li>
@ -708,7 +731,7 @@ export default {
data() {
return {
//
version: "4.0.0",
version: "4.0.1",
};
},
methods: {

View File

@ -86,7 +86,8 @@ export default {
password: "",
confirmPassword: "",
code: "",
uuid: ""
uuid: "",
user_type: "sys_user"
},
registerRules: {
username: [

View File

@ -206,7 +206,7 @@ export default {
email: [
{
type: "email",
message: "'请输入正确的邮箱地址",
message: "请输入正确的邮箱地址",
trigger: ["blur", "change"]
}
],

View File

@ -78,7 +78,7 @@
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@ -199,7 +199,7 @@
<el-form-item>
<el-input v-model="form.perms" placeholder="请输入权限标识" maxlength="100" />
<span slot="label">
<el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasPermi('system:user:list')`)" placement="top">
<el-tooltip content="控制器中定义的权限字符,如:@SaCheckPermission('system:user:list')" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
权限字符

View File

@ -135,7 +135,7 @@
</el-col>
<el-col :span="12">
<el-form-item label="公告类型" prop="noticeType">
<el-select v-model="form.noticeType" placeholder="请选择">
<el-select v-model="form.noticeType" placeholder="请选择公告类型">
<el-option
v-for="dict in dict.type.sys_notice_type"
:key="dict.value"

View File

@ -248,7 +248,7 @@
<el-row>
<el-col :span="12">
<el-form-item label="用户性别">
<el-select v-model="form.sex" placeholder="请选择">
<el-select v-model="form.sex" placeholder="请选择性别">
<el-option
v-for="dict in dict.type.sys_user_sex"
:key="dict.value"
@ -273,7 +273,7 @@
<el-row>
<el-col :span="12">
<el-form-item label="岗位">
<el-select v-model="form.postIds" multiple placeholder="请选择">
<el-select v-model="form.postIds" multiple placeholder="请选择岗位">
<el-option
v-for="item in postOptions"
:key="item.postId"
@ -286,7 +286,7 @@
</el-col>
<el-col :span="12">
<el-form-item label="角色">
<el-select v-model="form.roleIds" multiple placeholder="请选择">
<el-select v-model="form.roleIds" multiple placeholder="请选择角色">
<el-option
v-for="item in roleOptions"
:key="item.roleId"
@ -443,7 +443,7 @@ export default {
email: [
{
type: "email",
message: "'请输入正确的邮箱地址",
message: "请输入正确的邮箱地址",
trigger: ["blur", "change"]
}
],

View File

@ -42,7 +42,7 @@ export default {
{ required: true, message: "邮箱地址不能为空", trigger: "blur" },
{
type: "email",
message: "'请输入正确的邮箱地址",
message: "请输入正确的邮箱地址",
trigger: ["blur", "change"]
}
],

View File

@ -89,12 +89,14 @@ services:
MINIO_ACCESS_KEY: ruoyi
# 管理后台密码最小8个字符
MINIO_SECRET_KEY: ruoyi123
# https需要指定域名
MINIO_SERVER_URL: ""
volumes:
# 映射当前目录下的data目录至容器内/data目录
- /docker/minio/data:/data
# 映射配置目录
- /docker/minio/config:/root/.minio/
command: server --console-address ':9001' /data # 指定容器中的目录 /data
command: server --address ':9000' --console-address ':9001' /data # 指定容器中的目录 /data
privileged: true
restart: always
networks:
@ -102,7 +104,7 @@ services:
ipv4_address: 172.30.0.54
ruoyi-server1:
image: ruoyi/ruoyi-server:4.0.0
image: ruoyi/ruoyi-server:4.0.1
container_name: ruoyi-server1
environment:
# 时区上海
@ -117,7 +119,7 @@ services:
ipv4_address: 172.30.0.60
ruoyi-server2:
image: "ruoyi/ruoyi-server:4.0.0"
image: "ruoyi/ruoyi-server:4.0.1"
container_name: ruoyi-server2
environment:
# 时区上海
@ -132,7 +134,7 @@ services:
ipv4_address: 172.30.0.61
ruoyi-monitor-admin:
image: ruoyi/ruoyi-monitor-admin:4.0.0
image: ruoyi/ruoyi-monitor-admin:4.0.1
container_name: ruoyi-monitor-admin
environment:
# 时区上海
@ -147,7 +149,7 @@ services:
ipv4_address: 172.30.0.90
ruoyi-xxl-job-admin:
image: ruoyi/ruoyi-xxl-job-admin:4.0.0
image: ruoyi/ruoyi-xxl-job-admin:4.0.1
container_name: ruoyi-xxl-job-admin
environment:
# 时区上海

View File

@ -66,7 +66,7 @@ create table sys_user (
-- 初始化-用户信息表数据
-- ----------------------------
insert into sys_user values(1, 103, 'admin', '疯狂的狮子Li', 'sys_user', 'crazyLionLi@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '管理员');
insert into sys_user values(2, 105, 'lionli', '疯狂的狮子Li', 'sys_user', 'crazyLionLi@163.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '测试员');
insert into sys_user values(2, 105, 'lionli', '疯狂的狮子Li', 'sys_user', 'crazyLionLi@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '测试员');
-- ----------------------------