sync: 同步 RuoYi-Vue-Plus(v4.5.0) 更新

[重大更新] 使用 spring 事件发布机制 重构登录日志与操作日志 支持多事件监听无入侵扩展
This commit is contained in:
konbai
2023-02-02 23:16:20 +08:00
parent 653e1159fb
commit 558ec6eb69
54 changed files with 866 additions and 812 deletions

View File

@@ -1,13 +1,49 @@
### 使用版本(未按照模板填写直接删除)
- jdk版本(带上尾号): 例如 1.8.0_202
- 框架版本(项目启动时输出的版本号): 例如 4.4.0
- 其他依赖版本(你觉得有必要的):
### 问题前提
> 功能不好用 不会用 是否已经看过项目文档
> 项目运行报错 是否已经拿着报错信息去百度 常见报错百度百度足以
> 是否搜索过其他issue 一些已经解决的问题 会在issue内留下解决方法
> 无法线上解决或者与框架无关的问题的欢迎加VIP群跟作者一对一谈
### 异常模块
> 此报错都涉及到那些系统模块
例如 ruoyi-system ruoyi-auth 等等
### 问题描述
> 越详细越容易直击问题所在
已知: XXX功能不好用 或 XXX数据不正常 等等
### 希望结果
> 想知道你觉得怎么样是正常或者合理的
希望功能可以有XXX结果 或者 XXX现象
### 重现步骤
> 作者并不知道这个问题是如何出现的
### 报错信息(截图为主 请勿发混乱格式)
- 1
- 2
- 3
### 相关代码与报错信息(请勿发混乱格式)
> 代码可按照如下形式提供或者截图均可 越详细越好
> 大多数问题都是 代码编写错误问题 逻辑问题 或者用法错误等问题
```java
public class XXX {
}
```

View File

@@ -1,7 +1,7 @@
### 更改目的 解决了什么问题(请提交到dev分支)
### 描述 做了哪些改动
### 改动逻辑 这么写的思路(让作者更好的理解你的意图)
### 测试 都做了哪些测试(未经过测试不采纳)

119
pom.xml
View File

@@ -14,23 +14,23 @@
<properties>
<ruoyi-flowable-plus.version>0.8.0</ruoyi-flowable-plus.version>
<spring-boot.version>2.7.6</spring-boot.version>
<spring-boot.version>2.7.7</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>
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
<spring-boot.mybatis>2.2.2</spring-boot.mybatis>
<springdoc.version>1.6.13</springdoc.version>
<springdoc.version>1.6.14</springdoc.version>
<poi.version>5.2.3</poi.version>
<easyexcel.version>3.1.3</easyexcel.version>
<easyexcel.version>3.1.5</easyexcel.version>
<velocity.version>2.3</velocity.version>
<satoken.version>1.33.0</satoken.version>
<mybatis-plus.version>3.5.2</mybatis-plus.version>
<satoken.version>1.34.0</satoken.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.10</hutool.version>
<hutool.version>5.8.11</hutool.version>
<okhttp.version>4.10.0</okhttp.version>
<spring-boot-admin.version>2.7.7</spring-boot-admin.version>
<redisson.version>3.18.0</redisson.version>
<spring-boot-admin.version>2.7.10</spring-boot-admin.version>
<redisson.version>3.19.1</redisson.version>
<lock4j.version>2.2.3</lock4j.version>
<dynamic-ds.version>3.5.2</dynamic-ds.version>
<alibaba-ttl.version>2.14.2</alibaba-ttl.version>
@@ -38,18 +38,46 @@
<lombok.version>1.18.24</lombok.version>
<flowable.version>6.7.2</flowable.version>
<!-- 统一 guava 版本 解决隐式漏洞问题 -->
<guava.version>31.1-jre</guava.version>
<!-- 临时修复 snakeyaml 漏洞 -->
<snakeyaml.version>1.32</snakeyaml.version>
<snakeyaml.version>1.33</snakeyaml.version>
<!-- OSS 配置 -->
<aws-java-sdk-s3.version>1.12.349</aws-java-sdk-s3.version>
<aws-java-sdk-s3.version>1.12.373</aws-java-sdk-s3.version>
<!-- SMS 配置 -->
<aliyun.sms.version>2.0.22</aliyun.sms.version>
<tencent.sms.version>3.1.635</tencent.sms.version>
<aliyun.sms.version>2.0.23</aliyun.sms.version>
<tencent.sms.version>3.1.660</tencent.sms.version>
</properties>
<profiles>
<profile>
<id>local</id>
<properties>
<!-- 环境标识,需要与配置文件的名称相对应 -->
<profiles.active>local</profiles.active>
<logging.level>debug</logging.level>
</properties>
</profile>
<profile>
<id>dev</id>
<properties>
<!-- 环境标识,需要与配置文件的名称相对应 -->
<profiles.active>dev</profiles.active>
<logging.level>debug</logging.level>
</properties>
<activation>
<!-- 默认环境 -->
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>prod</id>
<properties>
<profiles.active>prod</profiles.active>
<logging.level>warn</logging.level>
</properties>
</profile>
</profiles>
<!-- 依赖声明 -->
<dependencyManagement>
<dependencies>
@@ -150,11 +178,7 @@
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- sql性能分析插件 -->
<dependency>
<groupId>p6spy</groupId>
@@ -202,7 +226,19 @@
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
<exclusions>
<exclusion>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-30</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-27</artifactId>
<version>${redisson.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
@@ -222,13 +258,6 @@
<version>${alibaba-ttl.version}</version>
</dependency>
<!-- 统一 guava 版本 解决隐式漏洞问题 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!-- 临时修复 snakeyaml 漏洞 -->
<dependency>
<groupId>org.yaml</groupId>
@@ -390,8 +419,8 @@
<repositories>
<repository>
<id>public</id>
<name>huawei nexus</name>
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public/</url>
<releases>
<enabled>true</enabled>
</releases>
@@ -401,8 +430,8 @@
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>huawei nexus</name>
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public/</url>
<releases>
<enabled>true</enabled>
</releases>
@@ -412,34 +441,4 @@
</pluginRepository>
</pluginRepositories>
<profiles>
<profile>
<id>local</id>
<properties>
<!-- 环境标识,需要与配置文件的名称相对应 -->
<profiles.active>local</profiles.active>
<logging.level>debug</logging.level>
</properties>
</profile>
<profile>
<id>dev</id>
<properties>
<!-- 环境标识,需要与配置文件的名称相对应 -->
<profiles.active>dev</profiles.active>
<logging.level>debug</logging.level>
</properties>
<activation>
<!-- 默认环境 -->
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>prod</id>
<properties>
<profiles.active>prod</profiles.active>
<logging.level>warn</logging.level>
</properties>
</profile>
</profiles>
</project>

View File

@@ -3,7 +3,6 @@ package com.ruoyi.web.controller.common;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import com.ruoyi.common.constant.CacheConstants;
@@ -21,6 +20,9 @@ import com.ruoyi.sms.entity.SmsResult;
import com.ruoyi.system.service.ISysConfigService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -95,28 +97,16 @@ public class CaptchaController {
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
captcha.setGenerator(codeGenerator);
captcha.createCode();
String code = isMath ? getCodeResult(captcha.getCode()) : captcha.getCode();
String code = captcha.getCode();
if (isMath) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
code = exp.getValue(String.class);
}
RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
ajax.put("uuid", uuid);
ajax.put("img", captcha.getImageBase64());
return R.ok(ajax);
}
private String getCodeResult(String capStr) {
int numberLength = captchaProperties.getNumberLength();
int a = Convert.toInt(StringUtils.substring(capStr, 0, numberLength).trim());
char operator = capStr.charAt(numberLength);
int b = Convert.toInt(StringUtils.substring(capStr, numberLength + 1, numberLength + 1 + numberLength).trim());
switch (operator) {
case '*':
return Convert.toStr(a * b);
case '+':
return Convert.toStr(a + b);
case '-':
return Convert.toStr(a - b);
default:
return StringUtils.EMPTY;
}
}
}

View File

@@ -13,7 +13,6 @@ import com.ruoyi.system.domain.vo.RouterVo;
import com.ruoyi.system.service.ISysMenuService;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.system.service.SysLoginService;
import com.ruoyi.system.service.SysPermissionService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
@@ -39,7 +38,6 @@ public class SysLoginController {
private final SysLoginService loginService;
private final ISysMenuService menuService;
private final ISysUserService userService;
private final SysPermissionService permissionService;
/**
* 登录方法

View File

@@ -83,6 +83,9 @@ spring:
restart:
# 热部署开关
enabled: true
mvc:
format:
date-time: yyyy-MM-dd HH:mm:ss
jackson:
# 日期格式化
date-format: yyyy-MM-dd HH:mm:ss
@@ -202,6 +205,9 @@ swagger:
name: ${sa-token.token-name}
springdoc:
swagger-ui:
# 持久化认证数据
persistAuthorization: true
#这里定义了两个分组,可定义多个,也可以不定义
group-configs:
- group: 1.演示模块

View File

@@ -41,5 +41,5 @@ repeat.submit.message=不允许重复提交,请稍候再试
rate.limiter.message=访问过于频繁,请稍候再试
sms.code.not.blank=短信验证码不能为空
sms.code.retry.limit.count=短信验证码输入错误{0}次
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{0}分钟
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
xcx.code.not.blank=小程序code不能为空

View File

@@ -41,5 +41,5 @@ repeat.submit.message=Repeat submit is not allowed, please try again later
rate.limiter.message=Visit too frequently, please try again later
sms.code.not.blank=Sms code cannot be blank
sms.code.retry.limit.count=Sms code input error {0} times
sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {0} minutes
sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {1} minutes
xcx.code.not.blank=Mini program code cannot be blank

View File

@@ -41,5 +41,5 @@ repeat.submit.message=不允许重复提交,请稍候再试
rate.limiter.message=访问过于频繁,请稍候再试
sms.code.not.blank=短信验证码不能为空
sms.code.retry.limit.count=短信验证码输入错误{0}次
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{0}分钟
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
xcx.code.not.blank=小程序code不能为空

View File

@@ -79,10 +79,6 @@
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
<!-- dynamic-datasource 多数据源-->
<dependency>
@@ -147,6 +143,12 @@
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-27</artifactId>
<version>${redisson.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>

View File

@@ -1,6 +1,5 @@
package com.ruoyi.common.core.domain.dto;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

View File

@@ -0,0 +1,44 @@
package com.ruoyi.common.core.domain.event;
import lombok.Data;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
/**
* 登录事件
*
* @author Lion Li
*/
@Data
public class LogininforEvent implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户账号
*/
private String username;
/**
* 登录状态 0成功 1失败
*/
private String status;
/**
* 提示消息
*/
private String message;
/**
* 请求体
*/
private HttpServletRequest request;
/**
* 其他参数
*/
private Object[] args;
}

View File

@@ -1,4 +1,4 @@
package com.ruoyi.common.core.domain.dto;
package com.ruoyi.common.core.domain.event;
import lombok.Data;
@@ -6,13 +6,13 @@ import java.io.Serializable;
import java.util.Date;
/**
* 通用操作日志实体
* 操作日志事件
*
* @author Lion Li
*/
@Data
public class OperLogDTO implements Serializable {
public class OperLogEvent implements Serializable {
private static final long serialVersionUID = 1L;

View File

@@ -4,16 +4,12 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.ruoyi.common.utils.BeanCopyUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
@@ -21,7 +17,6 @@ import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 自定义 Mapper 接口, 实现 自定义扩展
@@ -37,8 +32,6 @@ public interface BaseMapperPlus<M, T, V> extends BaseMapper<T> {
Log log = LogFactory.getLog(BaseMapperPlus.class);
int DEFAULT_BATCH_SIZE = 1000;
default Class<V> currentVoClass() {
return (Class<V>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 2);
}
@@ -59,79 +52,49 @@ public interface BaseMapperPlus<M, T, V> extends BaseMapper<T> {
* 批量插入
*/
default boolean insertBatch(Collection<T> entityList) {
return insertBatch(entityList, DEFAULT_BATCH_SIZE);
return Db.saveBatch(entityList);
}
/**
* 批量更新
*/
default boolean updateBatchById(Collection<T> entityList) {
return updateBatchById(entityList, DEFAULT_BATCH_SIZE);
return Db.updateBatchById(entityList);
}
/**
* 批量插入或更新
*/
default boolean insertOrUpdateBatch(Collection<T> entityList) {
return insertOrUpdateBatch(entityList, DEFAULT_BATCH_SIZE);
return Db.saveOrUpdateBatch(entityList);
}
/**
* 批量插入(包含限制条数)
*/
default boolean insertBatch(Collection<T> entityList, int batchSize) {
String sqlStatement = SqlHelper.getSqlStatement(this.currentMapperClass(), SqlMethod.INSERT_ONE);
return SqlHelper.executeBatch(this.currentModelClass(), log, entityList, batchSize,
(sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
return Db.saveBatch(entityList, batchSize);
}
/**
* 批量更新(包含限制条数)
*/
default boolean updateBatchById(Collection<T> entityList, int batchSize) {
String sqlStatement = SqlHelper.getSqlStatement(this.currentMapperClass(), SqlMethod.UPDATE_BY_ID);
return SqlHelper.executeBatch(this.currentModelClass(), log, entityList, batchSize,
(sqlSession, entity) -> {
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
param.put(Constants.ENTITY, entity);
sqlSession.update(sqlStatement, param);
});
return Db.updateBatchById(entityList, batchSize);
}
/**
* 批量插入或更新(包含限制条数)
*/
default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(this.currentModelClass());
Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
String keyProperty = tableInfo.getKeyProperty();
Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
return SqlHelper.saveOrUpdateBatch(this.currentModelClass(), this.currentMapperClass(), log, entityList, batchSize, (sqlSession, entity) -> {
Object idVal = tableInfo.getPropertyValue(entity, keyProperty);
String sqlStatement = SqlHelper.getSqlStatement(this.currentMapperClass(), SqlMethod.SELECT_BY_ID);
return StringUtils.checkValNull(idVal)
|| CollectionUtils.isEmpty(sqlSession.selectList(sqlStatement, entity));
}, (sqlSession, entity) -> {
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
param.put(Constants.ENTITY, entity);
String sqlStatement = SqlHelper.getSqlStatement(this.currentMapperClass(), SqlMethod.UPDATE_BY_ID);
sqlSession.update(sqlStatement, param);
});
return Db.saveOrUpdateBatch(entityList, batchSize);
}
/**
* 插入或更新(包含限制条数)
*/
default boolean insertOrUpdate(T entity) {
if (null != entity) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(this.currentModelClass());
Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
String keyProperty = tableInfo.getKeyProperty();
Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
Object idVal = tableInfo.getPropertyValue(entity, tableInfo.getKeyProperty());
return StringUtils.checkValNull(idVal) || Objects.isNull(selectById((Serializable) idVal)) ? insert(entity) > 0 : updateById(entity) > 0;
}
return false;
return Db.saveOrUpdate(entity);
}
default V selectVoById(Serializable id) {

View File

@@ -1,14 +0,0 @@
package com.ruoyi.common.core.service;
import javax.servlet.http.HttpServletRequest;
/**
* 通用 系统访问日志
*
* @author Lion Li
*/
public interface LogininforService {
void recordLogininfor(String username, String status, String message,
HttpServletRequest request, Object... args);
}

View File

@@ -1,15 +0,0 @@
package com.ruoyi.common.core.service;
import com.ruoyi.common.core.domain.dto.OperLogDTO;
import org.springframework.scheduling.annotation.Async;
/**
* 通用 操作日志
*
* @author Lion Li
*/
public interface OperLogService {
@Async
void recordOper(OperLogDTO operLogDTO);
}

View File

@@ -86,7 +86,7 @@ public class CellMergeStrategy extends AbstractMergeStrategy {
// 空值跳过不合并
continue;
}
if (cellValue != val) {
if (!cellValue.equals(val)) {
if (i - repeatCell.getCurrent() > 1) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
}

View File

@@ -33,12 +33,12 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
String[] values = super.getParameterValues(name);
if (values != null) {
int length = values.length;
String[] escapseValues = new String[length];
String[] escapesValues = new String[length];
for (int i = 0; i < length; i++) {
// 防xss攻击和过滤前后空格
escapseValues[i] = HtmlUtil.cleanHtmlTag(values[i]).trim();
escapesValues[i] = HtmlUtil.cleanHtmlTag(values[i]).trim();
}
return escapseValues;
return escapesValues;
}
return super.getParameterValues(name);
}

View File

@@ -24,6 +24,7 @@ import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.List;
@@ -84,7 +85,13 @@ public class ExcelUtil {
* @param response 响应体
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
exportExcel(list, sheetName, clazz, false, response);
try {
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
exportExcel(list, sheetName, clazz, false, os);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
/**
@@ -100,23 +107,48 @@ public class ExcelUtil {
try {
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
.autoCloseStream(false)
// 自动适配
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.sheet(sheetName);
if (merge) {
// 合并处理器
builder.registerWriteHandler(new CellMergeStrategy(list, true));
}
builder.doWrite(list);
exportExcel(list, sheetName, clazz, merge, os);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param os 输出流
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os) {
exportExcel(list, sheetName, clazz, false, os);
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param merge 是否合并单元格
* @param os 输出流
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge, OutputStream os) {
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
.autoCloseStream(false)
// 自动适配
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.sheet(sheetName);
if (merge) {
// 合并处理器
builder.registerWriteHandler(new CellMergeStrategy(list, true));
}
builder.doWrite(list);
}
/**
* 单表多数据模板导出 模板格式为 {.属性}
*
@@ -125,31 +157,46 @@ public class ExcelUtil {
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
* @param response 响应体
*/
public static void exportTemplate(List<Object> data, String filename, String templatePath, HttpServletResponse response) {
try {
resetResponse(filename, response);
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
// 单表多数据导出 模板格式为 {.属性}
for (Object d : data) {
excelWriter.fill(d, writeSheet);
}
excelWriter.finish();
ServletOutputStream os = response.getOutputStream();
exportTemplate(data, templatePath, os);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
/**
* 单表多数据模板导出 模板格式为 {.属性}
*
* @param templatePath 模板路径 resource 目录下的路径包括模板文件名
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
* @param os 输出流
*/
public static void exportTemplate(List<Object> data, String templatePath, OutputStream os) {
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(os)
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
// 单表多数据导出 模板格式为 {.属性}
for (Object d : data) {
excelWriter.fill(d, writeSheet);
}
excelWriter.finish();
}
/**
* 多表多数据模板导出 模板格式为 {key.属性}
*
@@ -158,37 +205,52 @@ public class ExcelUtil {
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
* @param response 响应体
*/
public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
try {
resetResponse(filename, response);
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
for (Map.Entry<String, Object> map : data.entrySet()) {
// 设置列表后续还有数据
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
if (map.getValue() instanceof Collection) {
// 多表导出必须使用 FillWrapper
excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
} else {
excelWriter.fill(map.getValue(), writeSheet);
}
}
excelWriter.finish();
ServletOutputStream os = response.getOutputStream();
exportTemplateMultiList(data, templatePath, os);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
/**
* 多表多数据模板导出 模板格式为 {key.属性}
*
* @param templatePath 模板路径 resource 目录下的路径包括模板文件名
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
* @param os 输出流
*/
public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(os)
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
for (Map.Entry<String, Object> map : data.entrySet()) {
// 设置列表后续还有数据
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
if (map.getValue() instanceof Collection) {
// 多表导出必须使用 FillWrapper
excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
} else {
excelWriter.fill(map.getValue(), writeSheet);
}
}
excelWriter.finish();
}
/**
* 重置响应体
*/

View File

@@ -4,7 +4,6 @@ import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.redisson.api.*;
import org.redisson.config.Config;
import java.time.Duration;
import java.util.Collection;
@@ -27,14 +26,6 @@ public class RedisUtils {
private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
public static NameMapper getNameMapper() {
Config config = CLIENT.getConfig();
if (config.isClusterConfig()) {
return config.useClusterServers().getNameMapper();
}
return config.useSingleServer().getNameMapper();
}
/**
* 限流
*
@@ -218,6 +209,15 @@ public class RedisUtils {
batch.execute();
}
/**
* 检查缓存对象是否存在
*
* @param key 缓存的键值
*/
public static boolean isExistsObject(final String key) {
return CLIENT.getBucket(key).isExists();
}
/**
* 缓存List数据
*
@@ -437,8 +437,8 @@ public class RedisUtils {
* @return 对象列表
*/
public static Collection<String> keys(final String pattern) {
Stream<String> stream = CLIENT.getKeys().getKeysStreamByPattern(getNameMapper().map(pattern));
return stream.map(key -> getNameMapper().unmap(key)).collect(Collectors.toList());
Stream<String> stream = CLIENT.getKeys().getKeysStreamByPattern(pattern);
return stream.collect(Collectors.toList());
}
/**
@@ -447,7 +447,7 @@ public class RedisUtils {
* @param pattern 字符串前缀
*/
public static void deleteKeys(final String pattern) {
CLIENT.getKeys().deleteByPattern(getNameMapper().map(pattern));
CLIENT.getKeys().deleteByPattern(pattern);
}
/**
@@ -457,6 +457,6 @@ public class RedisUtils {
*/
public static Boolean hasKey(String key) {
RKeys rKeys = CLIENT.getKeys();
return rKeys.countExists(getNameMapper().map(key)) > 0;
return rKeys.countExists(key) > 0;
}
}

View File

@@ -3,6 +3,7 @@ package com.ruoyi.common.utils.spring;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
@@ -62,4 +63,12 @@ public final class SpringUtils extends SpringUtil {
return (T) AopContext.currentProxy();
}
/**
* 获取spring上下文
*/
public static ApplicationContext context() {
return getApplicationContext();
}
}

View File

@@ -2,7 +2,6 @@ package com.ruoyi.demo.controller.queue;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.NotNull;
/**
* 实体类 注意不允许使用内部类 否则会找不到类
@@ -17,7 +16,7 @@ public class PriorityDemo implements Comparable<PriorityDemo> {
private Integer orderNum;
@Override
public int compareTo(@NotNull PriorityDemo other) {
public int compareTo(PriorityDemo other) {
return Integer.compare(getOrderNum(), other.getOrderNum());
}
}

View File

@@ -4,8 +4,7 @@ import cn.hutool.core.lang.Dict;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.domain.dto.OperLogDTO;
import com.ruoyi.common.core.service.OperLogService;
import com.ruoyi.common.core.domain.event.OperLogEvent;
import com.ruoyi.common.enums.BusinessStatus;
import com.ruoyi.common.enums.HttpMethod;
import com.ruoyi.common.helper.LoginHelper;
@@ -67,7 +66,7 @@ public class LogAspect {
try {
// *========数据库日志=========*//
OperLogDTO operLog = new OperLogDTO();
OperLogEvent operLog = new OperLogEvent();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = ServletUtils.getClientIP();
@@ -87,8 +86,8 @@ public class LogAspect {
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// 保存数据库
SpringUtils.getBean(OperLogService.class).recordOper(operLog);
// 发布事件保存数据库
SpringUtils.context().publishEvent(operLog);
} catch (Exception exp) {
// 记录本地异常日志
log.error("异常信息:{}", exp.getMessage());
@@ -103,7 +102,7 @@ public class LogAspect {
* @param operLog 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, OperLogDTO operLog, Object jsonResult) throws Exception {
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, OperLogEvent operLog, Object jsonResult) throws Exception {
// 设置action动作
operLog.setBusinessType(log.businessType().ordinal());
// 设置标题
@@ -127,7 +126,7 @@ public class LogAspect {
* @param operLog 操作日志
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, OperLogDTO operLog) throws Exception {
private void setRequestValue(JoinPoint joinPoint, OperLogEvent operLog) throws Exception {
String requestMethod = operLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
String params = argsArrayToString(joinPoint.getArgs());

View File

@@ -18,7 +18,7 @@ import java.util.concurrent.ScheduledExecutorService;
*
* @author Lion Li
*/
@EnableAsync
@EnableAsync(proxyTargetClass = true)
@Configuration
public class AsyncConfig extends AsyncConfigurerSupport {

View File

@@ -11,7 +11,6 @@ import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -25,7 +24,7 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
@EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfig extends CachingConfigurerSupport {
public class RedisConfig {
@Autowired
private RedissonProperties redissonProperties;

View File

@@ -23,6 +23,7 @@ import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* Swagger 文档配置
@@ -51,8 +52,11 @@ public class SwaggerConfig {
openApi.tags(swaggerProperties.getTags());
openApi.paths(swaggerProperties.getPaths());
openApi.components(swaggerProperties.getComponents());
Set<String> keySet = swaggerProperties.getComponents().getSecuritySchemes().keySet();
List<SecurityRequirement> list = new ArrayList<>();
list.add(new SecurityRequirement().addList("apikey"));
SecurityRequirement securityRequirement = new SecurityRequirement();
keySet.forEach(securityRequirement::addList);
list.add(securityRequirement);
openApi.security(list);
return openApi;

View File

@@ -54,7 +54,7 @@ public class PlusDataPermissionHandler {
/**
* 无效注解方法缓存用于快速返回
*/
private final Set<String> inavlidCacheSet = new ConcurrentHashSet<>();
private final Set<String> invalidCacheSet = new ConcurrentHashSet<>();
/**
* spel 解析器
@@ -70,7 +70,7 @@ public class PlusDataPermissionHandler {
public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
DataColumn[] dataColumns = findAnnotation(mappedStatementId);
if (ArrayUtil.isEmpty(dataColumns)) {
inavlidCacheSet.add(mappedStatementId);
invalidCacheSet.add(mappedStatementId);
return where;
}
LoginUser currentUser = DataPermissionHelper.getVariable("user");
@@ -194,6 +194,6 @@ public class PlusDataPermissionHandler {
* 是否为无效方法 无数据权限
*/
public boolean isInvalid(String mappedStatementId) {
return inavlidCacheSet.contains(mappedStatementId);
return invalidCacheSet.contains(mappedStatementId);
}
}

View File

@@ -12,7 +12,7 @@ import org.springframework.stereotype.Component;
*/
@Component
@ConfigurationProperties(prefix = "gen")
@PropertySource(value = {"classpath:generator.yml"})
@PropertySource(value = {"classpath:generator.yml"}, encoding = "UTF-8")
public class GenConfig {
/**

View File

@@ -3,12 +3,11 @@ package com.ruoyi.generator.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.constant.Constants;
@@ -58,6 +57,7 @@ public class GenTableServiceImpl implements IGenTableService {
private final GenTableMapper baseMapper;
private final GenTableColumnMapper genTableColumnMapper;
private final IdentifierGenerator identifierGenerator;
/**
* 查询业务字段列表
@@ -205,10 +205,9 @@ public class GenTableServiceImpl implements IGenTableService {
Map<String, String> dataMap = new LinkedHashMap<>();
// 查询表信息
GenTable table = baseMapper.selectGenTableById(tableId);
Snowflake snowflake = IdUtil.getSnowflake();
List<Long> menuIds = new ArrayList<>();
for (int i = 0; i < 6; i++) {
menuIds.add(snowflake.nextId());
menuIds.add(identifierGenerator.nextId(null).longValue());
}
table.setMenuIds(menuIds);
// 设置主子表信息
@@ -356,10 +355,9 @@ public class GenTableServiceImpl implements IGenTableService {
private void generatorCode(String tableName, ZipOutputStream zip) {
// 查询表信息
GenTable table = baseMapper.selectGenTableByName(tableName);
Snowflake snowflake = IdUtil.getSnowflake();
List<Long> menuIds = new ArrayList<>();
for (int i = 0; i < 6; i++) {
menuIds.add(snowflake.nextId());
menuIds.add(identifierGenerator.nextId(null).longValue());
}
table.setMenuIds(menuIds);
// 设置主子表信息

View File

@@ -186,11 +186,11 @@
<el-input v-model="form.${field}" placeholder="请输入${comment}" />
</el-form-item>
#elseif($column.htmlType == "imageUpload")
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<image-upload v-model="form.${field}"/>
</el-form-item>
#elseif($column.htmlType == "fileUpload")
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<file-upload v-model="form.${field}"/>
</el-form-item>
#elseif($column.htmlType == "editor")
@@ -204,8 +204,11 @@
v-for="dict in dict.type.${dictType}"
:key="dict.value"
:label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end
#if($column.javaType == "Integer" || $column.javaType == "Long")
:value="parseInt(dict.value)"
#else
:value="dict.value"
#end
></el-option>
</el-select>
</el-form-item>
@@ -216,7 +219,7 @@
</el-select>
</el-form-item>
#elseif($column.htmlType == "checkbox" && "" != $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox
v-for="dict in dict.type.${dictType}"
@@ -227,24 +230,27 @@
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "checkbox" && $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox>请选择字典生成</el-checkbox>
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "radio" && "" != $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio
v-for="dict in dict.type.${dictType}"
:key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end
#if($column.javaType == "Integer" || $column.javaType == "Long")
:label="parseInt(dict.value)"
#else
:label="dict.value"
#end
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "radio" && $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio label="1">请选择字典生成</el-radio>
</el-radio-group>
@@ -335,7 +341,7 @@ export default {
#set($comment=$column.columnComment)
#end
$column.javaField: [
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
]#if($foreach.count != $columns.size()),#end
#end
#end
@@ -398,10 +404,7 @@ export default {
reset() {
this.form = {
#foreach ($column in $columns)
#if($column.htmlType == "radio")
$column.javaField: #if($column.javaType == "Integer" || $column.javaType == "Long")0#else"0"#end#if($foreach.count != $columns.size()),#end
#elseif($column.htmlType == "checkbox")
#if($column.htmlType == "checkbox")
$column.javaField: []#if($foreach.count != $columns.size()),#end
#else
$column.javaField: null#if($foreach.count != $columns.size()),#end

View File

@@ -133,9 +133,9 @@
</el-table-column>
#elseif($column.list && $column.htmlType == "imageUpload")
<el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
<template slot-scope="scope">
<image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
</template>
<template slot-scope="scope">
<image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
</template>
</el-table-column>
#elseif($column.list && $column.dictType && "" != $column.dictType)
<el-table-column label="${comment}" align="center" prop="${javaField}">
@@ -197,11 +197,11 @@
<el-input v-model="form.${field}" placeholder="请输入${comment}" />
</el-form-item>
#elseif($column.htmlType == "imageUpload")
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<image-upload v-model="form.${field}"/>
</el-form-item>
#elseif($column.htmlType == "fileUpload")
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<file-upload v-model="form.${field}"/>
</el-form-item>
#elseif($column.htmlType == "editor")
@@ -215,8 +215,11 @@
v-for="dict in dict.type.${dictType}"
:key="dict.value"
:label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end
#if($column.javaType == "Integer" || $column.javaType == "Long")
:value="parseInt(dict.value)"
#else
:value="dict.value"
#end
></el-option>
</el-select>
</el-form-item>
@@ -227,7 +230,7 @@
</el-select>
</el-form-item>
#elseif($column.htmlType == "checkbox" && "" != $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox
v-for="dict in dict.type.${dictType}"
@@ -238,24 +241,27 @@
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "checkbox" && $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox>请选择字典生成</el-checkbox>
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "radio" && "" != $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio
v-for="dict in dict.type.${dictType}"
:key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end
#if($column.javaType == "Integer" || $column.javaType == "Long")
:label="parseInt(dict.value)"
#else
:label="dict.value"
#end
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "radio" && $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio label="1">请选择字典生成</el-radio>
</el-radio-group>
@@ -412,7 +418,7 @@ export default {
#set($comment=$column.columnComment)
#end
$column.javaField: [
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
]#if($foreach.count != $columns.size()),#end
#end
#end
@@ -456,9 +462,7 @@ export default {
reset() {
this.form = {
#foreach ($column in $columns)
#if($column.htmlType == "radio")
$column.javaField: #if($column.javaType == "Integer" || $column.javaType == "Long")0#else"0"#end#if($foreach.count != $columns.size()),#end
#elseif($column.htmlType == "checkbox")
#if($column.htmlType == "checkbox")
$column.javaField: []#if($foreach.count != $columns.size()),#end
#else
$column.javaField: undefined#if($foreach.count != $columns.size()),#end

View File

@@ -135,11 +135,11 @@
#end
#end
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']">修改</el-button>
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button>
</template>
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']">修改</el-button>
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -172,11 +172,11 @@
<el-input v-model="form.${field}" placeholder="请输入${comment}" />
</el-form-item>
#elseif($column.htmlType == "imageUpload")
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<image-upload v-model="form.${field}"/>
</el-form-item>
#elseif($column.htmlType == "fileUpload")
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<file-upload v-model="form.${field}"/>
</el-form-item>
#elseif($column.htmlType == "editor")
@@ -190,8 +190,11 @@
v-for="dict in ${dictType}"
:key="dict.value"
:label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end
#if($column.javaType == "Integer" || $column.javaType == "Long")
:value="parseInt(dict.value)"
#else
:value="dict.value"
#end
></el-option>
</el-select>
</el-form-item>
@@ -202,7 +205,7 @@
</el-select>
</el-form-item>
#elseif($column.htmlType == "checkbox" && "" != $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox
v-for="dict in ${dictType}"
@@ -213,24 +216,27 @@
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "checkbox" && $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox>请选择字典生成</el-checkbox>
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "radio" && "" != $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio
v-for="dict in ${dictType}"
:key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end
#if($column.javaType == "Integer" || $column.javaType == "Long")
:label="parseInt(dict.value)"
#else
:label="dict.value"
#end
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "radio" && $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio label="1">请选择字典生成</el-radio>
</el-radio-group>
@@ -306,7 +312,7 @@ const data = reactive({
#set($comment=$column.columnComment)
#end
$column.javaField: [
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
]#if($foreach.count != $columns.size()),#end
#end
#end
@@ -359,10 +365,7 @@ function cancel() {
function reset() {
form.value = {
#foreach ($column in $columns)
#if($column.htmlType == "radio")
$column.javaField: #if($column.javaType == "Integer" || $column.javaType == "Long")0#else"0"#end#if($foreach.count != $columns.size()),#end
#elseif($column.htmlType == "checkbox")
#if($column.htmlType == "checkbox")
$column.javaField: []#if($foreach.count != $columns.size()),#end
#else
$column.javaField: null#if($foreach.count != $columns.size()),#end

View File

@@ -147,10 +147,10 @@
#end
#end
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button>
</template>
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -180,11 +180,11 @@
<el-input v-model="form.${field}" placeholder="请输入${comment}" />
</el-form-item>
#elseif($column.htmlType == "imageUpload")
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<image-upload v-model="form.${field}"/>
</el-form-item>
#elseif($column.htmlType == "fileUpload")
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<file-upload v-model="form.${field}"/>
</el-form-item>
#elseif($column.htmlType == "editor")
@@ -198,8 +198,11 @@
v-for="dict in ${dictType}"
:key="dict.value"
:label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end
#if($column.javaType == "Integer" || $column.javaType == "Long")
:value="parseInt(dict.value)"
#else
:value="dict.value"
#end
></el-option>
</el-select>
</el-form-item>
@@ -210,7 +213,7 @@
</el-select>
</el-form-item>
#elseif($column.htmlType == "checkbox" && "" != $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox
v-for="dict in ${dictType}"
@@ -221,24 +224,27 @@
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "checkbox" && $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox>请选择字典生成</el-checkbox>
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "radio" && "" != $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio
v-for="dict in ${dictType}"
:key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end
#if($column.javaType == "Integer" || $column.javaType == "Long")
:label="parseInt(dict.value)"
#else
:label="dict.value"
#end
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "radio" && $dictType)
<el-form-item label="${comment}">
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio label="1">请选择字典生成</el-radio>
</el-radio-group>
@@ -387,7 +393,7 @@ const data = reactive({
#set($comment=$column.columnComment)
#end
$column.javaField: [
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
]#if($foreach.count != $columns.size()),#end
#end
#end
@@ -431,9 +437,7 @@ function cancel() {
function reset() {
form.value = {
#foreach ($column in $columns)
#if($column.htmlType == "radio")
$column.javaField: #if($column.javaType == "Integer" || $column.javaType == "Long")0#else"0"#end#if($foreach.count != $columns.size()),#end
#elseif($column.htmlType == "checkbox")
#if($column.htmlType == "checkbox")
$column.javaField: []#if($foreach.count != $columns.size()),#end
#else
$column.javaField: null#if($foreach.count != $columns.size()),#end

View File

@@ -32,3 +32,4 @@
</dependencies>
</project>

View File

@@ -5,23 +5,28 @@ import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.event.LogininforEvent;
import com.ruoyi.common.core.domain.dto.RoleDTO;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.domain.model.XcxLoginUser;
import com.ruoyi.common.core.service.LogininforService;
import com.ruoyi.common.enums.DeviceType;
import com.ruoyi.common.enums.LoginType;
import com.ruoyi.common.enums.UserStatus;
import com.ruoyi.common.exception.user.*;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
import com.ruoyi.common.exception.user.UserException;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.mapper.SysUserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@@ -42,9 +47,8 @@ import java.util.function.Supplier;
@Service
public class SysLoginService {
private final ISysUserService userService;
private final SysUserMapper userMapper;
private final ISysConfigService configService;
private final LogininforService asyncService;
private final SysPermissionService permissionService;
@Value("${user.password.maxRetryCount}")
@@ -76,7 +80,7 @@ public class SysLoginService {
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.PC);
asyncService.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getUserId(), username);
return StpUtil.getTokenValue();
}
@@ -85,21 +89,19 @@ public class SysLoginService {
// 通过手机号查找用户
SysUser user = loadUserByPhonenumber(phonenumber);
HttpServletRequest request = ServletUtils.getRequest();
checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode, request));
checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser
LoginUser loginUser = buildLoginUser(user);
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.APP);
asyncService.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getUserId(), user.getUserName());
return StpUtil.getTokenValue();
}
public String xcxLogin(String xcxCode) {
HttpServletRequest request = ServletUtils.getRequest();
// xcxCode 为 小程序调用 wx.login 授权后获取
// todo 以下自行实现
// 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
@@ -115,7 +117,7 @@ public class SysLoginService {
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.XCX);
asyncService.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getUserId(), user.getUserName());
return StpUtil.getTokenValue();
}
@@ -127,18 +129,35 @@ public class SysLoginService {
try {
LoginUser loginUser = LoginHelper.getLoginUser();
StpUtil.logout();
asyncService.recordLogininfor(loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"), ServletUtils.getRequest());
recordLogininfor(loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
} catch (NotLoginException ignored) {
}
}
/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
private void recordLogininfor(String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setUsername(username);
logininforEvent.setStatus(status);
logininforEvent.setMessage(message);
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
}
/**
* 校验短信验证码
*/
private boolean validateSmsCode(String phonenumber, String smsCode, HttpServletRequest request) {
private boolean validateSmsCode(String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
asyncService.recordLogininfor(phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"), request);
recordLogininfor(phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(smsCode);
@@ -156,43 +175,41 @@ public class SysLoginService {
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"), request);
recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"), request);
recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
private SysUser loadUserByUsername(String username) {
SysUser user = userService.selectUserByUserName(username);
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
.select(SysUser::getUserName, SysUser::getStatus)
.eq(SysUser::getUserName, username));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username);
} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
log.info("登录用户:{} 已被删除.", username);
throw new UserException("user.password.delete", username);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new UserException("user.blocked", username);
}
return user;
return userMapper.selectUserByUserName(username);
}
private SysUser loadUserByPhonenumber(String phonenumber) {
SysUser user = userService.selectUserByPhonenumber(phonenumber);
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
.select(SysUser::getPhonenumber, SysUser::getStatus)
.eq(SysUser::getPhonenumber, phonenumber));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", phonenumber);
throw new UserException("user.not.exists", phonenumber);
} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
log.info("登录用户:{} 已被删除.", phonenumber);
throw new UserException("user.password.delete", phonenumber);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", phonenumber);
throw new UserException("user.blocked", phonenumber);
}
return user;
return userMapper.selectUserByPhonenumber(phonenumber);
}
private SysUser loadUserByOpenid(String openid) {
@@ -202,9 +219,6 @@ public class SysLoginService {
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", openid);
// todo 用户不存在 业务逻辑自行实现
} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
log.info("登录用户:{} 已被删除.", openid);
// todo 用户已被删除 业务逻辑自行实现
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", openid);
// todo 用户已被停用 业务逻辑自行实现
@@ -241,14 +255,13 @@ public class SysLoginService {
sysUser.setLoginIp(ServletUtils.getClientIP());
sysUser.setLoginDate(DateUtils.getNowDate());
sysUser.setUpdateBy(username);
userService.updateUserProfile(sysUser);
userMapper.updateById(sysUser);
}
/**
* 登录校验
*/
private void checkLogin(LoginType loginType, String username, Supplier<Boolean> supplier) {
HttpServletRequest request = ServletUtils.getRequest();
String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
String loginFail = Constants.LOGIN_FAIL;
@@ -256,7 +269,7 @@ public class SysLoginService {
Integer errorNumber = RedisUtils.getCacheObject(errorKey);
// 锁定时间内登录 则踢出
if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) {
asyncService.recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime), request);
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
}
@@ -266,12 +279,12 @@ public class SysLoginService {
// 达到规定错误次数 则锁定登录
if (errorNumber.equals(maxRetryCount)) {
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
asyncService.recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime), request);
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
} else {
// 未达到规定错误次数 则递增
RedisUtils.setCacheObject(errorKey, errorNumber);
asyncService.recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber), request);
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
throw new UserException(loginType.getRetryLimitCount(), errorNumber);
}
}

View File

@@ -4,9 +4,9 @@ import cn.dev33.satoken.secure.BCrypt;
import com.ruoyi.common.constant.CacheConstants;
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.entity.SysUser;
import com.ruoyi.common.core.domain.model.RegisterBody;
import com.ruoyi.common.core.service.LogininforService;
import com.ruoyi.common.enums.UserType;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
@@ -15,6 +15,7 @@ import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -31,7 +32,6 @@ public class SysRegisterService {
private final ISysUserService userService;
private final ISysConfigService configService;
private final LogininforService asyncService;
/**
* 注册
@@ -61,7 +61,7 @@ public class SysRegisterService {
if (!regFlag) {
throw new UserException("user.register.error");
}
asyncService.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success"), request);
recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success"));
}
/**
@@ -77,12 +77,30 @@ public class SysRegisterService {
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
asyncService.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire"), request);
recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
asyncService.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error"), request);
recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
private void recordLogininfor(String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setUsername(username);
logininforEvent.setStatus(status);
logininforEvent.setMessage(message);
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
}
}

View File

@@ -138,6 +138,10 @@ public class SysConfigServiceImpl implements ISysConfigService, ConfigService {
public String updateConfig(SysConfig config) {
int row = 0;
if (config.getConfigId() != null) {
SysConfig temp = baseMapper.selectById(config.getConfigId());
if (!StringUtils.equals(temp.getConfigKey(), config.getConfigKey())) {
CacheUtils.evict(CacheNames.SYS_CONFIG, temp.getConfigKey());
}
row = baseMapper.updateById(config);
} else {
row = baseMapper.update(config, new LambdaQueryWrapper<SysConfig>()

View File

@@ -6,8 +6,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.event.LogininforEvent;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.service.LogininforService;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.AddressUtils;
@@ -16,6 +16,7 @@ import com.ruoyi.system.mapper.SysLogininforMapper;
import com.ruoyi.system.service.ISysLogininforService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@@ -33,22 +34,19 @@ import java.util.Map;
@RequiredArgsConstructor
@Slf4j
@Service
public class SysLogininforServiceImpl implements ISysLogininforService, LogininforService {
public class SysLogininforServiceImpl implements ISysLogininforService {
private final SysLogininforMapper baseMapper;
/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param message 消息
* @param args 列表
* @param logininforEvent 登录事件
*/
@Async
@Override
public void recordLogininfor(final String username, final String status, final String message,
HttpServletRequest request, final Object... args) {
@EventListener
public void recordLogininfor(LogininforEvent logininforEvent) {
HttpServletRequest request = logininforEvent.getRequest();
final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
final String ip = ServletUtils.getClientIP(request);
@@ -56,27 +54,27 @@ public class SysLogininforServiceImpl implements ISysLogininforService, Logininf
StringBuilder s = new StringBuilder();
s.append(getBlock(ip));
s.append(address);
s.append(getBlock(username));
s.append(getBlock(status));
s.append(getBlock(message));
s.append(getBlock(logininforEvent.getUsername()));
s.append(getBlock(logininforEvent.getStatus()));
s.append(getBlock(logininforEvent.getMessage()));
// 打印信息到日志
log.info(s.toString(), args);
log.info(s.toString(), logininforEvent.getArgs());
// 获取客户端操作系统
String os = userAgent.getOs().getName();
// 获取客户端浏览器
String browser = userAgent.getBrowser().getName();
// 封装对象
SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username);
logininfor.setUserName(logininforEvent.getUsername());
logininfor.setIpaddr(ip);
logininfor.setLoginLocation(address);
logininfor.setBrowser(browser);
logininfor.setOs(os);
logininfor.setMsg(message);
logininfor.setMsg(logininforEvent.getMessage());
// 日志状态
if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) {
if (StringUtils.equalsAny(logininforEvent.getStatus(), Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) {
logininfor.setStatus(Constants.SUCCESS);
} else if (Constants.LOGIN_FAIL.equals(status)) {
} else if (Constants.LOGIN_FAIL.equals(logininforEvent.getStatus())) {
logininfor.setStatus(Constants.FAIL);
}
// 插入数据

View File

@@ -5,15 +5,15 @@ import cn.hutool.core.util.ArrayUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.dto.OperLogDTO;
import com.ruoyi.common.core.domain.event.OperLogEvent;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.service.OperLogService;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.system.domain.SysOperLog;
import com.ruoyi.system.mapper.SysOperLogMapper;
import com.ruoyi.system.service.ISysOperLogService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@@ -29,19 +29,19 @@ import java.util.Map;
*/
@RequiredArgsConstructor
@Service
public class SysOperLogServiceImpl implements ISysOperLogService, OperLogService {
public class SysOperLogServiceImpl implements ISysOperLogService {
private final SysOperLogMapper baseMapper;
/**
* 操作日志记录
*
* @param operLogDTO 操作日志信息
* @param operLogEvent 操作日志事件
*/
@Async
@Override
public void recordOper(final OperLogDTO operLogDTO) {
SysOperLog operLog = BeanUtil.toBean(operLogDTO, SysOperLog.class);
@EventListener
public void recordOper(OperLogEvent operLogEvent) {
SysOperLog operLog = BeanUtil.toBean(operLogEvent, SysOperLog.class);
// 远程查询操作地点
operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
insertOperlog(operLog);

View File

@@ -16,6 +16,7 @@ import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.redis.CacheUtils;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.oss.factory.OssFactory;
import com.ruoyi.system.domain.SysOssConfig;
@@ -25,6 +26,7 @@ import com.ruoyi.system.mapper.SysOssConfigMapper;
import com.ruoyi.system.service.ISysOssConfigService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -57,7 +59,7 @@ public class SysOssConfigServiceImpl implements ISysOssConfigService {
if ("0".equals(config.getStatus())) {
RedisUtils.setCacheObject(OssConstant.DEFAULT_CONFIG_KEY, configKey);
}
setConfigCache(true, config);
SpringUtils.context().publishEvent(config);
}
// 初始化OSS工厂
OssFactory.init();
@@ -88,7 +90,11 @@ public class SysOssConfigServiceImpl implements ISysOssConfigService {
public Boolean insertByBo(SysOssConfigBo bo) {
SysOssConfig config = BeanUtil.toBean(bo, SysOssConfig.class);
validEntityBeforeSave(config);
return setConfigCache(baseMapper.insert(config) > 0, config);
boolean flag = baseMapper.insert(config) > 0;
if (flag) {
SpringUtils.context().publishEvent(config);
}
return flag;
}
@Override
@@ -101,7 +107,11 @@ public class SysOssConfigServiceImpl implements ISysOssConfigService {
luw.set(ObjectUtil.isNull(config.getExt1()), SysOssConfig::getExt1, "");
luw.set(ObjectUtil.isNull(config.getRemark()), SysOssConfig::getRemark, "");
luw.eq(SysOssConfig::getOssConfigId, config.getOssConfigId());
return setConfigCache(baseMapper.update(config, luw) > 0, config);
boolean flag = baseMapper.update(config, luw) > 0;
if (flag) {
SpringUtils.context().publishEvent(config);
}
return flag;
}
/**
@@ -165,19 +175,15 @@ public class SysOssConfigServiceImpl implements ISysOssConfigService {
}
/**
* 如果操作成功 则更新缓存
* 更新配置缓存
*
* @param flag 操作状态
* @param config 配置
* @return 返回操作状态
*/
private boolean setConfigCache(boolean flag, SysOssConfig config) {
if (flag) {
CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
RedisUtils.publish(OssConstant.DEFAULT_CONFIG_KEY, config.getConfigKey(), msg -> {
log.info("发布刷新OSS配置 => " + msg);
});
}
return flag;
@EventListener
public void updateConfigCache(SysOssConfig config) {
CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
RedisUtils.publish(OssConstant.DEFAULT_CONFIG_KEY, config.getConfigKey(), msg -> {
log.info("发布刷新OSS配置 => " + msg);
});
}
}

View File

@@ -40,7 +40,7 @@
"axios": "0.24.0",
"clipboard": "2.0.8",
"core-js": "3.25.3",
"echarts": "4.9.0",
"echarts": "5.4.0",
"element-ui": "2.15.10",
"file-saver": "2.0.5",
"fuse.js": "6.4.3",

View File

@@ -73,6 +73,10 @@
color: inherit;
}
.el-message-box__status + .el-message-box__message{
word-break: break-word;
}
.el-dialog:not(.is-fullscreen) {
margin-top: 6vh !important;
}

View File

@@ -90,7 +90,6 @@ export default {
threshold: 0.4,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [{
name: 'title',

View File

@@ -39,7 +39,6 @@ export default {
}
},
mounted() {
this.insertToBody()
this.addEventClick()
},
beforeDestroy() {
@@ -56,11 +55,6 @@ export default {
this.show = false
window.removeEventListener('click', this.closeSidebar)
}
},
insertToBody() {
const elx = this.$refs.rightPanel
const body = document.querySelector('body')
body.insertBefore(elx, body.firstChild)
}
}
}

View File

@@ -21,7 +21,7 @@ export default {
const isLogin = await blobValidate(res.data);
if (isLogin) {
const blob = new Blob([res.data], { type: 'application/octet-stream' })
this.saveAs(blob, decodeURI(res.headers['download-filename']))
this.saveAs(blob, decodeURIComponent(res.headers['download-filename']))
} else {
this.printErrMsg(res.data);
}

View File

@@ -1,5 +1,5 @@
/* eslint-disable max-len */
import ruleTrigger from './ruleTrigger'
import { trigger } from './config'
let confGlobal
let someSpanIsNot24
@@ -34,27 +34,27 @@ export function cssStyle(cssStr) {
</style>`
}
function buildFormTemplate(scheme, child, type) {
function buildFormTemplate(conf, child, type) {
let labelPosition = ''
if (scheme.labelPosition !== 'right') {
labelPosition = `label-position="${scheme.labelPosition}"`
if (conf.labelPosition !== 'right') {
labelPosition = `label-position="${conf.labelPosition}"`
}
const disabled = scheme.disabled ? `:disabled="${scheme.disabled}"` : ''
let str = `<el-form ref="${scheme.formRef}" :model="${scheme.formModel}" :rules="${scheme.formRules}" size="${scheme.size}" ${disabled} label-width="${scheme.labelWidth}px" ${labelPosition}>
const disabled = conf.disabled ? `:disabled="${conf.disabled}"` : ''
let str = `<el-form ref="${conf.formRef}" :model="${conf.formModel}" :rules="${conf.formRules}" size="${conf.size}" ${disabled} label-width="${conf.labelWidth}px" ${labelPosition}>
${child}
${buildFromBtns(scheme, type)}
${buildFromBtns(conf, type)}
</el-form>`
if (someSpanIsNot24) {
str = `<el-row :gutter="${scheme.gutter}">
str = `<el-row :gutter="${conf.gutter}">
${str}
</el-row>`
}
return str
}
function buildFromBtns(scheme, type) {
function buildFromBtns(conf, type) {
let str = ''
if (scheme.formBtns && type === 'file') {
if (conf.formBtns && type === 'file') {
str = `<el-form-item size="large">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
@@ -69,9 +69,9 @@ function buildFromBtns(scheme, type) {
}
// span不为24的用el-col包裹
function colWrapper(scheme, str) {
if (someSpanIsNot24 || scheme.__config__.span !== 24) {
return `<el-col :span="${scheme.__config__.span}">
function colWrapper(element, str) {
if (someSpanIsNot24 || element.span !== 24) {
return `<el-col :span="${element.span}">
${str}
</el-col>`
}
@@ -79,36 +79,29 @@ function colWrapper(scheme, str) {
}
const layouts = {
colFormItem(scheme) {
const config = scheme.__config__
colFormItem(element) {
let labelWidth = ''
let label = `label="${config.label}"`
if (config.labelWidth && config.labelWidth !== confGlobal.labelWidth) {
labelWidth = `label-width="${config.labelWidth}px"`
if (element.labelWidth && element.labelWidth !== confGlobal.labelWidth) {
labelWidth = `label-width="${element.labelWidth}px"`
}
if (config.showLabel === false) {
labelWidth = 'label-width="0"'
label = ''
}
const required = !ruleTrigger[config.tag] && config.required ? 'required' : ''
const tagDom = tags[config.tag] ? tags[config.tag](scheme) : null
let str = `<el-form-item ${labelWidth} ${label} prop="${scheme.__vModel__}" ${required}>
const required = !trigger[element.tag] && element.required ? 'required' : ''
const tagDom = tags[element.tag] ? tags[element.tag](element) : null
let str = `<el-form-item ${labelWidth} label="${element.label}" prop="${element.vModel}" ${required}>
${tagDom}
</el-form-item>`
str = colWrapper(scheme, str)
str = colWrapper(element, str)
return str
},
rowFormItem(scheme) {
const config = scheme.__config__
const type = scheme.type === 'default' ? '' : `type="${scheme.type}"`
const justify = scheme.type === 'default' ? '' : `justify="${scheme.justify}"`
const align = scheme.type === 'default' ? '' : `align="${scheme.align}"`
const gutter = scheme.gutter ? `:gutter="${scheme.gutter}"` : ''
const children = config.children.map(el => layouts[el.__config__.layout](el))
rowFormItem(element) {
const type = element.type === 'default' ? '' : `type="${element.type}"`
const justify = element.type === 'default' ? '' : `justify="${element.justify}"`
const align = element.type === 'default' ? '' : `align="${element.align}"`
const gutter = element.gutter ? `gutter="${element.gutter}"` : ''
const children = element.children.map(el => layouts[el.layout](el))
let str = `<el-row ${type} ${justify} ${align} ${gutter}>
${children.join('\n')}
</el-row>`
str = colWrapper(scheme, str)
str = colWrapper(element, str)
return str
}
}
@@ -120,18 +113,15 @@ const tags = {
} = attrBuilder(el)
const type = el.type ? `type="${el.type}"` : ''
const icon = el.icon ? `icon="${el.icon}"` : ''
const round = el.round ? 'round' : ''
const size = el.size ? `size="${el.size}"` : ''
const plain = el.plain ? 'plain' : ''
const circle = el.circle ? 'circle' : ''
let child = buildElButtonChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${tag} ${type} ${icon} ${round} ${size} ${plain} ${disabled} ${circle}>${child}</${tag}>`
return `<${el.tag} ${type} ${icon} ${size} ${disabled}>${child}</${el.tag}>`
},
'el-input': el => {
const {
tag, disabled, vModel, clearable, placeholder, width
disabled, vModel, clearable, placeholder, width
} = attrBuilder(el)
const maxlength = el.maxlength ? `:maxlength="${el.maxlength}"` : ''
const showWordLimit = el['show-word-limit'] ? 'show-word-limit' : ''
@@ -146,12 +136,10 @@ const tags = {
let child = buildElInputChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${tag} ${vModel} ${type} ${placeholder} ${maxlength} ${showWordLimit} ${readonly} ${disabled} ${clearable} ${prefixIcon} ${suffixIcon} ${showPassword} ${autosize} ${width}>${child}</${tag}>`
return `<${el.tag} ${vModel} ${type} ${placeholder} ${maxlength} ${showWordLimit} ${readonly} ${disabled} ${clearable} ${prefixIcon} ${suffixIcon} ${showPassword} ${autosize} ${width}>${child}</${el.tag}>`
},
'el-input-number': el => {
const {
tag, disabled, vModel, placeholder
} = attrBuilder(el)
const { disabled, vModel, placeholder } = attrBuilder(el)
const controlsPosition = el['controls-position'] ? `controls-position=${el['controls-position']}` : ''
const min = el.min ? `:min='${el.min}'` : ''
const max = el.max ? `:max='${el.max}'` : ''
@@ -159,39 +147,39 @@ const tags = {
const stepStrictly = el['step-strictly'] ? 'step-strictly' : ''
const precision = el.precision ? `:precision='${el.precision}'` : ''
return `<${tag} ${vModel} ${placeholder} ${step} ${stepStrictly} ${precision} ${controlsPosition} ${min} ${max} ${disabled}></${tag}>`
return `<${el.tag} ${vModel} ${placeholder} ${step} ${stepStrictly} ${precision} ${controlsPosition} ${min} ${max} ${disabled}></${el.tag}>`
},
'el-select': el => {
const {
tag, disabled, vModel, clearable, placeholder, width
disabled, vModel, clearable, placeholder, width
} = attrBuilder(el)
const filterable = el.filterable ? 'filterable' : ''
const multiple = el.multiple ? 'multiple' : ''
let child = buildElSelectChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${tag} ${vModel} ${placeholder} ${disabled} ${multiple} ${filterable} ${clearable} ${width}>${child}</${tag}>`
return `<${el.tag} ${vModel} ${placeholder} ${disabled} ${multiple} ${filterable} ${clearable} ${width}>${child}</${el.tag}>`
},
'el-radio-group': el => {
const { tag, disabled, vModel } = attrBuilder(el)
const { disabled, vModel } = attrBuilder(el)
const size = `size="${el.size}"`
let child = buildElRadioGroupChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${tag} ${vModel} ${size} ${disabled}>${child}</${tag}>`
return `<${el.tag} ${vModel} ${size} ${disabled}>${child}</${el.tag}>`
},
'el-checkbox-group': el => {
const { tag, disabled, vModel } = attrBuilder(el)
const { disabled, vModel } = attrBuilder(el)
const size = `size="${el.size}"`
const min = el.min ? `:min="${el.min}"` : ''
const max = el.max ? `:max="${el.max}"` : ''
let child = buildElCheckboxGroupChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${tag} ${vModel} ${min} ${max} ${size} ${disabled}>${child}</${tag}>`
return `<${el.tag} ${vModel} ${min} ${max} ${size} ${disabled}>${child}</${el.tag}>`
},
'el-switch': el => {
const { tag, disabled, vModel } = attrBuilder(el)
const { disabled, vModel } = attrBuilder(el)
const activeText = el['active-text'] ? `active-text="${el['active-text']}"` : ''
const inactiveText = el['inactive-text'] ? `inactive-text="${el['inactive-text']}"` : ''
const activeColor = el['active-color'] ? `active-color="${el['active-color']}"` : ''
@@ -199,33 +187,33 @@ const tags = {
const activeValue = el['active-value'] !== true ? `:active-value='${JSON.stringify(el['active-value'])}'` : ''
const inactiveValue = el['inactive-value'] !== false ? `:inactive-value='${JSON.stringify(el['inactive-value'])}'` : ''
return `<${tag} ${vModel} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor} ${activeValue} ${inactiveValue} ${disabled}></${tag}>`
return `<${el.tag} ${vModel} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor} ${activeValue} ${inactiveValue} ${disabled}></${el.tag}>`
},
'el-cascader': el => {
const {
tag, disabled, vModel, clearable, placeholder, width
disabled, vModel, clearable, placeholder, width
} = attrBuilder(el)
const options = el.options ? `:options="${el.__vModel__}Options"` : ''
const props = el.props ? `:props="${el.__vModel__}Props"` : ''
const options = el.options ? `:options="${el.vModel}Options"` : ''
const props = el.props ? `:props="${el.vModel}Props"` : ''
const showAllLevels = el['show-all-levels'] ? '' : ':show-all-levels="false"'
const filterable = el.filterable ? 'filterable' : ''
const separator = el.separator === '/' ? '' : `separator="${el.separator}"`
return `<${tag} ${vModel} ${options} ${props} ${width} ${showAllLevels} ${placeholder} ${separator} ${filterable} ${clearable} ${disabled}></${tag}>`
return `<${el.tag} ${vModel} ${options} ${props} ${width} ${showAllLevels} ${placeholder} ${separator} ${filterable} ${clearable} ${disabled}></${el.tag}>`
},
'el-slider': el => {
const { tag, disabled, vModel } = attrBuilder(el)
const { disabled, vModel } = attrBuilder(el)
const min = el.min ? `:min='${el.min}'` : ''
const max = el.max ? `:max='${el.max}'` : ''
const step = el.step ? `:step='${el.step}'` : ''
const range = el.range ? 'range' : ''
const showStops = el['show-stops'] ? `:show-stops="${el['show-stops']}"` : ''
return `<${tag} ${min} ${max} ${step} ${vModel} ${range} ${showStops} ${disabled}></${tag}>`
return `<${el.tag} ${min} ${max} ${step} ${vModel} ${range} ${showStops} ${disabled}></${el.tag}>`
},
'el-time-picker': el => {
const {
tag, disabled, vModel, clearable, placeholder, width
disabled, vModel, clearable, placeholder, width
} = attrBuilder(el)
const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : ''
const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : ''
@@ -235,11 +223,11 @@ const tags = {
const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : ''
const pickerOptions = el['picker-options'] ? `:picker-options='${JSON.stringify(el['picker-options'])}'` : ''
return `<${tag} ${vModel} ${isRange} ${format} ${valueFormat} ${pickerOptions} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${disabled}></${tag}>`
return `<${el.tag} ${vModel} ${isRange} ${format} ${valueFormat} ${pickerOptions} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${disabled}></${el.tag}>`
},
'el-date-picker': el => {
const {
tag, disabled, vModel, clearable, placeholder, width
disabled, vModel, clearable, placeholder, width
} = attrBuilder(el)
const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : ''
const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : ''
@@ -249,54 +237,46 @@ const tags = {
const type = el.type === 'date' ? '' : `type="${el.type}"`
const readonly = el.readonly ? 'readonly' : ''
return `<${tag} ${type} ${vModel} ${format} ${valueFormat} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${readonly} ${disabled}></${tag}>`
return `<${el.tag} ${type} ${vModel} ${format} ${valueFormat} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${readonly} ${disabled}></${el.tag}>`
},
'el-rate': el => {
const { tag, disabled, vModel } = attrBuilder(el)
const { disabled, vModel } = attrBuilder(el)
const max = el.max ? `:max='${el.max}'` : ''
const allowHalf = el['allow-half'] ? 'allow-half' : ''
const showText = el['show-text'] ? 'show-text' : ''
const showScore = el['show-score'] ? 'show-score' : ''
return `<${tag} ${vModel} ${max} ${allowHalf} ${showText} ${showScore} ${disabled}></${tag}>`
return `<${el.tag} ${vModel} ${allowHalf} ${showText} ${showScore} ${disabled}></${el.tag}>`
},
'el-color-picker': el => {
const { tag, disabled, vModel } = attrBuilder(el)
const { disabled, vModel } = attrBuilder(el)
const size = `size="${el.size}"`
const showAlpha = el['show-alpha'] ? 'show-alpha' : ''
const colorFormat = el['color-format'] ? `color-format="${el['color-format']}"` : ''
return `<${tag} ${vModel} ${size} ${showAlpha} ${colorFormat} ${disabled}></${tag}>`
return `<${el.tag} ${vModel} ${size} ${showAlpha} ${colorFormat} ${disabled}></${el.tag}>`
},
'el-upload': el => {
const { tag } = el.__config__
const disabled = el.disabled ? ':disabled=\'true\'' : ''
const action = el.action ? `:action="${el.__vModel__}Action"` : ''
const action = el.action ? `:action="${el.vModel}Action"` : ''
const multiple = el.multiple ? 'multiple' : ''
const listType = el['list-type'] !== 'text' ? `list-type="${el['list-type']}"` : ''
const accept = el.accept ? `accept="${el.accept}"` : ''
const name = el.name !== 'file' ? `name="${el.name}"` : ''
const autoUpload = el['auto-upload'] === false ? ':auto-upload="false"' : ''
const beforeUpload = `:before-upload="${el.__vModel__}BeforeUpload"`
const fileList = `:file-list="${el.__vModel__}fileList"`
const ref = `ref="${el.__vModel__}"`
const beforeUpload = `:before-upload="${el.vModel}BeforeUpload"`
const fileList = `:file-list="${el.vModel}fileList"`
const ref = `ref="${el.vModel}"`
let child = buildElUploadChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${tag} ${ref} ${fileList} ${action} ${autoUpload} ${multiple} ${beforeUpload} ${listType} ${accept} ${name} ${disabled}>${child}</${tag}>`
},
tinymce: el => {
const { tag, vModel, placeholder } = attrBuilder(el)
const height = el.height ? `:height="${el.height}"` : ''
const branding = el.branding ? `:branding="${el.branding}"` : ''
return `<${tag} ${vModel} ${placeholder} ${height} ${branding}></${tag}>`
return `<${el.tag} ${ref} ${fileList} ${action} ${autoUpload} ${multiple} ${beforeUpload} ${listType} ${accept} ${name} ${disabled}>${child}</${el.tag}>`
}
}
function attrBuilder(el) {
return {
tag: el.__config__.tag,
vModel: `v-model="${confGlobal.formModel}.${el.__vModel__}"`,
vModel: `v-model="${confGlobal.formModel}.${el.vModel}"`,
clearable: el.clearable ? 'clearable' : '',
placeholder: el.placeholder ? `placeholder="${el.placeholder}"` : '',
width: el.style && el.style.width ? ':style="{width: \'100%\'}"' : '',
@@ -305,92 +285,72 @@ function attrBuilder(el) {
}
// el-buttin 子级
function buildElButtonChild(scheme) {
function buildElButtonChild(conf) {
const children = []
const slot = scheme.__slot__ || {}
if (slot.default) {
children.push(slot.default)
if (conf.default) {
children.push(conf.default)
}
return children.join('\n')
}
// el-input 子级
function buildElInputChild(scheme) {
// el-input innerHTML
function buildElInputChild(conf) {
const children = []
const slot = scheme.__slot__
if (slot && slot.prepend) {
children.push(`<template slot="prepend">${slot.prepend}</template>`)
if (conf.prepend) {
children.push(`<template slot="prepend">${conf.prepend}</template>`)
}
if (slot && slot.append) {
children.push(`<template slot="append">${slot.append}</template>`)
if (conf.append) {
children.push(`<template slot="append">${conf.append}</template>`)
}
return children.join('\n')
}
// el-select 子级
function buildElSelectChild(scheme) {
function buildElSelectChild(conf) {
const children = []
const slot = scheme.__slot__
if (slot && slot.options && slot.options.length) {
children.push(`<el-option v-for="(item, index) in ${scheme.__vModel__}Options" :key="index" :label="item.label" :value="item.value" :disabled="item.disabled"></el-option>`)
if (conf.options && conf.options.length) {
children.push(`<el-option v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.label" :value="item.value" :disabled="item.disabled"></el-option>`)
}
return children.join('\n')
}
// el-radio-group 子级
function buildElRadioGroupChild(scheme) {
function buildElRadioGroupChild(conf) {
const children = []
const slot = scheme.__slot__
const config = scheme.__config__
if (slot && slot.options && slot.options.length) {
const tag = config.optionType === 'button' ? 'el-radio-button' : 'el-radio'
const border = config.border ? 'border' : ''
children.push(`<${tag} v-for="(item, index) in ${scheme.__vModel__}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`)
if (conf.options && conf.options.length) {
const tag = conf.optionType === 'button' ? 'el-radio-button' : 'el-radio'
const border = conf.border ? 'border' : ''
children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`)
}
return children.join('\n')
}
// el-checkbox-group 子级
function buildElCheckboxGroupChild(scheme) {
function buildElCheckboxGroupChild(conf) {
const children = []
const slot = scheme.__slot__
const config = scheme.__config__
if (slot && slot.options && slot.options.length) {
const tag = config.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox'
const border = config.border ? 'border' : ''
children.push(`<${tag} v-for="(item, index) in ${scheme.__vModel__}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`)
if (conf.options && conf.options.length) {
const tag = conf.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox'
const border = conf.border ? 'border' : ''
children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`)
}
return children.join('\n')
}
// el-upload 子级
function buildElUploadChild(scheme) {
function buildElUploadChild(conf) {
const list = []
const config = scheme.__config__
if (scheme['list-type'] === 'picture-card') list.push('<i class="el-icon-plus"></i>')
else list.push(`<el-button size="small" type="primary" icon="el-icon-upload">${config.buttonText}</el-button>`)
if (config.showTip) list.push(`<div slot="tip" class="el-upload__tip">只能上传不超过 ${config.fileSize}${config.sizeUnit}${scheme.accept}文件</div>`)
if (conf['list-type'] === 'picture-card') list.push('<i class="el-icon-plus"></i>')
else list.push(`<el-button size="small" type="primary" icon="el-icon-upload">${conf.buttonText}</el-button>`)
if (conf.showTip) list.push(`<div slot="tip" class="el-upload__tip">只能上传不超过 ${conf.fileSize}${conf.sizeUnit}${conf.accept}文件</div>`)
return list.join('\n')
}
/**
* 组装html代码。【入口函数】
* @param {Object} formConfig 整个表单配置
* @param {String} type 生成类型,文件或弹窗等
*/
export function makeUpHtml(formConfig, type) {
export function makeUpHtml(conf, type) {
const htmlList = []
confGlobal = formConfig
// 判断布局是否都沾满了24个栅格以备后续简化代码结构
someSpanIsNot24 = formConfig.fields.some(item => item.__config__.span !== 24)
// 遍历渲染每个组件成html
formConfig.fields.forEach(el => {
htmlList.push(layouts[el.__config__.layout](el))
confGlobal = conf
someSpanIsNot24 = conf.fields.some(item => item.span !== 24)
conf.fields.forEach(el => {
htmlList.push(layouts[el.layout](el))
})
const htmlStr = htmlList.join('\n')
// 将组件代码放进form标签
let temp = buildFormTemplate(formConfig, htmlStr, type)
// dialog标签包裹代码
let temp = buildFormTemplate(conf, htmlStr, type)
if (type === 'dialog') {
temp = dialogWrapper(temp)
}

View File

@@ -1,6 +1,6 @@
import { isArray } from 'util'
import { exportDefault, titleCase, deepClone } from '@/utils/index'
import ruleTrigger from './ruleTrigger'
import { exportDefault, titleCase } from '@/utils/index'
import { trigger } from './config'
const units = {
KB: '1024',
@@ -13,91 +13,69 @@ const inheritAttrs = {
dialog: 'inheritAttrs: false,'
}
/**
* 组装js 【入口函数】
* @param {Object} formConfig 整个表单配置
* @param {String} type 生成类型,文件或弹窗等
*/
export function makeUpJs(formConfig, type) {
confGlobal = formConfig = deepClone(formConfig)
export function makeUpJs(conf, type) {
confGlobal = conf = JSON.parse(JSON.stringify(conf))
const dataList = []
const ruleList = []
const optionsList = []
const propsList = []
const methodList = mixinMethod(type)
const uploadVarList = []
const created = []
formConfig.fields.forEach(el => {
buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList, created)
conf.fields.forEach(el => {
buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList)
})
const script = buildexport(
formConfig,
conf,
type,
dataList.join('\n'),
ruleList.join('\n'),
optionsList.join('\n'),
uploadVarList.join('\n'),
propsList.join('\n'),
methodList.join('\n'),
created.join('\n')
methodList.join('\n')
)
confGlobal = null
return script
}
// 构建组件属性
function buildAttributes(scheme, dataList, ruleList, optionsList, methodList, propsList, uploadVarList, created) {
const config = scheme.__config__
const slot = scheme.__slot__
buildData(scheme, dataList)
buildRules(scheme, ruleList)
function buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList) {
buildData(el, dataList)
buildRules(el, ruleList)
// 特殊处理options属性
if (scheme.options || (slot && slot.options && slot.options.length)) {
buildOptions(scheme, optionsList)
if (config.dataType === 'dynamic') {
const model = `${scheme.__vModel__}Options`
if (el.options && el.options.length) {
buildOptions(el, optionsList)
if (el.dataType === 'dynamic') {
const model = `${el.vModel}Options`
const options = titleCase(model)
const methodName = `get${options}`
buildOptionMethod(methodName, model, methodList, scheme)
callInCreated(methodName, created)
buildOptionMethod(`get${options}`, model, methodList)
}
}
// 处理props
if (scheme.props && scheme.props.props) {
buildProps(scheme, propsList)
if (el.props && el.props.props) {
buildProps(el, propsList)
}
// 处理el-upload的action
if (scheme.action && config.tag === 'el-upload') {
if (el.action && el.tag === 'el-upload') {
uploadVarList.push(
`${scheme.__vModel__}Action: '${scheme.action}',
${scheme.__vModel__}fileList: [],`
`${el.vModel}Action: '${el.action}',
${el.vModel}fileList: [],`
)
methodList.push(buildBeforeUpload(scheme))
// 非自动上传时,生成手动上传的函数
if (!scheme['auto-upload']) {
methodList.push(buildSubmitUpload(scheme))
methodList.push(buildBeforeUpload(el))
if (!el['auto-upload']) {
methodList.push(buildSubmitUpload(el))
}
}
// 构建子级组件属性
if (config.children) {
config.children.forEach(item => {
buildAttributes(item, dataList, ruleList, optionsList, methodList, propsList, uploadVarList, created)
if (el.children) {
el.children.forEach(el2 => {
buildAttributes(el2, dataList, ruleList, optionsList, methodList, propsList, uploadVarList)
})
}
}
// 在Created调用函数
function callInCreated(methodName, created) {
created.push(`this.${methodName}()`)
}
// 混入处理函数
function mixinMethod(type) {
const list = []; const
minxins = {
@@ -120,7 +98,7 @@ function mixinMethod(type) {
close: `close() {
this.$emit('update:visible', false)
},`,
handelConfirm: `handelConfirm() {
handleConfirm: `handleConfirm() {
this.$refs['${confGlobal.formRef}'].validate(valid => {
if(!valid) return
this.close()
@@ -139,75 +117,73 @@ function mixinMethod(type) {
return list
}
// 构建data
function buildData(scheme, dataList) {
const config = scheme.__config__
if (scheme.__vModel__ === undefined) return
const defaultValue = JSON.stringify(config.defaultValue)
dataList.push(`${scheme.__vModel__}: ${defaultValue},`)
function buildData(conf, dataList) {
if (conf.vModel === undefined) return
let defaultValue
if (typeof (conf.defaultValue) === 'string' && !conf.multiple) {
defaultValue = `'${conf.defaultValue}'`
} else {
defaultValue = `${JSON.stringify(conf.defaultValue)}`
}
dataList.push(`${conf.vModel}: ${defaultValue},`)
}
// 构建校验规则
function buildRules(scheme, ruleList) {
const config = scheme.__config__
if (scheme.__vModel__ === undefined) return
function buildRules(conf, ruleList) {
if (conf.vModel === undefined) return
const rules = []
if (ruleTrigger[config.tag]) {
if (config.required) {
const type = isArray(config.defaultValue) ? 'type: \'array\',' : ''
let message = isArray(config.defaultValue) ? `请至少选择一个${config.label}` : scheme.placeholder
if (message === undefined) message = `${config.label}不能为空`
rules.push(`{ required: true, ${type} message: '${message}', trigger: '${ruleTrigger[config.tag]}' }`)
if (trigger[conf.tag]) {
if (conf.required) {
const type = isArray(conf.defaultValue) ? 'type: \'array\',' : ''
let message = isArray(conf.defaultValue) ? `请至少选择一个${conf.vModel}` : conf.placeholder
if (message === undefined) message = `${conf.label}不能为空`
rules.push(`{ required: true, ${type} message: '${message}', trigger: '${trigger[conf.tag]}' }`)
}
if (config.regList && isArray(config.regList)) {
config.regList.forEach(item => {
if (conf.regList && isArray(conf.regList)) {
conf.regList.forEach(item => {
if (item.pattern) {
rules.push(
`{ pattern: ${eval(item.pattern)}, message: '${item.message}', trigger: '${ruleTrigger[config.tag]}' }`
)
rules.push(`{ pattern: ${eval(item.pattern)}, message: '${item.message}', trigger: '${trigger[conf.tag]}' }`)
}
})
}
ruleList.push(`${scheme.__vModel__}: [${rules.join(',')}],`)
ruleList.push(`${conf.vModel}: [${rules.join(',')}],`)
}
}
// 构建options
function buildOptions(scheme, optionsList) {
if (scheme.__vModel__ === undefined) return
// el-cascader直接有options属性其他组件都是定义在slot中所以有两处判断
let { options } = scheme
if (!options) options = scheme.__slot__.options
if (scheme.__config__.dataType === 'dynamic') { options = [] }
const str = `${scheme.__vModel__}Options: ${JSON.stringify(options)},`
function buildOptions(conf, optionsList) {
if (conf.vModel === undefined) return
if (conf.dataType === 'dynamic') { conf.options = [] }
const str = `${conf.vModel}Options: ${JSON.stringify(conf.options)},`
optionsList.push(str)
}
function buildProps(scheme, propsList) {
const str = `${scheme.__vModel__}Props: ${JSON.stringify(scheme.props.props)},`
function buildProps(conf, propsList) {
if (conf.dataType === 'dynamic') {
conf.valueKey !== 'value' && (conf.props.props.value = conf.valueKey)
conf.labelKey !== 'label' && (conf.props.props.label = conf.labelKey)
conf.childrenKey !== 'children' && (conf.props.props.children = conf.childrenKey)
}
const str = `${conf.vModel}Props: ${JSON.stringify(conf.props.props)},`
propsList.push(str)
}
// el-upload的BeforeUpload
function buildBeforeUpload(scheme) {
const config = scheme.__config__
const unitNum = units[config.sizeUnit]; let rightSizeCode = ''; let acceptCode = ''; const
function buildBeforeUpload(conf) {
const unitNum = units[conf.sizeUnit]; let rightSizeCode = ''; let acceptCode = ''; const
returnList = []
if (config.fileSize) {
rightSizeCode = `let isRightSize = file.size / ${unitNum} < ${config.fileSize}
if (conf.fileSize) {
rightSizeCode = `let isRightSize = file.size / ${unitNum} < ${conf.fileSize}
if(!isRightSize){
this.$message.error('文件大小超过 ${config.fileSize}${config.sizeUnit}')
this.$message.error('文件大小超过 ${conf.fileSize}${conf.sizeUnit}')
}`
returnList.push('isRightSize')
}
if (scheme.accept) {
acceptCode = `let isAccept = new RegExp('${scheme.accept}').test(file.type)
if (conf.accept) {
acceptCode = `let isAccept = new RegExp('${conf.accept}').test(file.type)
if(!isAccept){
this.$message.error('应该选择${scheme.accept}类型的文件')
this.$message.error('应该选择${conf.accept}类型的文件')
}`
returnList.push('isAccept')
}
const str = `${scheme.__vModel__}BeforeUpload(file) {
const str = `${conf.vModel}BeforeUpload(file) {
${rightSizeCode}
${acceptCode}
return ${returnList.join('&&')}
@@ -215,31 +191,22 @@ function buildBeforeUpload(scheme) {
return returnList.length ? str : ''
}
// el-upload的submit
function buildSubmitUpload(scheme) {
function buildSubmitUpload(conf) {
const str = `submitUpload() {
this.$refs['${scheme.__vModel__}'].submit()
this.$refs['${conf.vModel}'].submit()
},`
return str
}
function buildOptionMethod(methodName, model, methodList, scheme) {
const config = scheme.__config__
function buildOptionMethod(methodName, model, methodList) {
const str = `${methodName}() {
// 注意this.$axios是通过Vue.prototype.$axios = axios挂载产生的
this.$axios({
method: '${config.method}',
url: '${config.url}'
}).then(resp => {
var { data } = resp
this.${model} = data.${config.dataKey}
})
// TODO 发起请求获取数据
this.${model}
},`
methodList.push(str)
}
// js整体拼接
function buildexport(conf, type, data, rules, selectOptions, uploadVar, props, methods, created) {
function buildexport(conf, type, data, rules, selectOptions, uploadVar, props, methods) {
const str = `${exportDefault}{
${inheritAttrs[type]}
components: {},
@@ -259,9 +226,7 @@ function buildexport(conf, type, data, rules, selectOptions, uploadVar, props, m
},
computed: {},
watch: {},
created () {
${created}
},
created () {},
mounted () {},
methods: {
${methods}

View File

@@ -3,7 +3,7 @@
</template>
<script>
import echarts from 'echarts'
import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import resize from './mixins/resize'

View File

@@ -3,7 +3,7 @@
</template>
<script>
import echarts from 'echarts'
import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import resize from './mixins/resize'

View File

@@ -3,7 +3,7 @@
</template>
<script>
import echarts from 'echarts'
import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import resize from './mixins/resize'

View File

@@ -3,7 +3,7 @@
</template>
<script>
import echarts from 'echarts'
import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import resize from './mixins/resize'

View File

@@ -1,146 +1,146 @@
<template>
<div class="app-container">
<el-row>
<el-col :span="24" class="card-box">
<el-card>
<div slot="header"><span>基本信息</span></div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<table cellspacing="0" style="width: 100%">
<tbody>
<tr>
<td class="el-table__cell is-leaf"><div class="cell">Redis版本</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.redis_version }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">运行模式</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.redis_mode == "standalone" ? "单机" : "集群" }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">端口</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.tcp_port }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">客户端数</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.connected_clients }}</div></td>
</tr>
<tr>
<td class="el-table__cell is-leaf"><div class="cell">运行时间()</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.uptime_in_days }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">使用内存</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.used_memory_human }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">使用CPU</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">内存配置</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.maxmemory_human }}</div></td>
</tr>
<tr>
<td class="el-table__cell is-leaf"><div class="cell">AOF是否开启</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.aof_enabled == "0" ? "否" : "是" }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">RDB是否成功</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.rdb_last_bgsave_status }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">Key数量</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.dbSize">{{ cache.dbSize }} </div></td>
<td class="el-table__cell is-leaf"><div class="cell">网络入口/出口</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.instantaneous_input_kbps }}kps/{{cache.info.instantaneous_output_kbps}}kps</div></td>
</tr>
</tbody>
</table>
</div>
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card>
<div slot="header"><span>命令统计</span></div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="commandstats" style="height: 420px" />
</div>
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card>
<div slot="header">
<span>内存信息</span>
</div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="usedmemory" style="height: 420px" />
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { getCache } from "@/api/monitor/cache";
import echarts from "echarts";
export default {
name: "Cache",
data() {
return {
// 统计命令信息
commandstats: null,
// 使用内存
usedmemory: null,
// cache信息
cache: []
}
},
created() {
this.getList();
this.openLoading();
},
methods: {
/** 查缓存询信息 */
getList() {
getCache().then((response) => {
this.cache = response.data;
this.$modal.closeLoading();
this.commandstats = echarts.init(this.$refs.commandstats, "macarons");
this.commandstats.setOption({
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)",
},
series: [
{
name: "命令",
type: "pie",
roseType: "radius",
radius: [15, 95],
center: ["50%", "38%"],
data: response.data.commandStats,
animationEasing: "cubicInOut",
animationDuration: 1000,
}
]
});
this.usedmemory = echarts.init(this.$refs.usedmemory, "macarons");
this.usedmemory.setOption({
tooltip: {
formatter: "{b} <br/>{a} : " + this.cache.info.used_memory_human,
},
series: [
{
name: "峰值",
type: "gauge",
min: 0,
max: 1000,
detail: {
formatter: this.cache.info.used_memory_human,
},
data: [
{
value: parseFloat(this.cache.info.used_memory_human),
name: "内存消耗",
}
]
}
]
});
});
},
// 打开加载层
openLoading() {
this.$modal.loading("正在加载缓存监控数据,请稍候!");
}
}
};
</script>
<template>
<div class="app-container">
<el-row>
<el-col :span="24" class="card-box">
<el-card>
<div slot="header"><span>基本信息</span></div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<table cellspacing="0" style="width: 100%">
<tbody>
<tr>
<td class="el-table__cell is-leaf"><div class="cell">Redis版本</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.redis_version }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">运行模式</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.redis_mode == "standalone" ? "单机" : "集群" }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">端口</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.tcp_port }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">客户端数</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.connected_clients }}</div></td>
</tr>
<tr>
<td class="el-table__cell is-leaf"><div class="cell">运行时间()</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.uptime_in_days }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">使用内存</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.used_memory_human }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">使用CPU</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">内存配置</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.maxmemory_human }}</div></td>
</tr>
<tr>
<td class="el-table__cell is-leaf"><div class="cell">AOF是否开启</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.aof_enabled == "0" ? "否" : "是" }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">RDB是否成功</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.rdb_last_bgsave_status }}</div></td>
<td class="el-table__cell is-leaf"><div class="cell">Key数量</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.dbSize">{{ cache.dbSize }} </div></td>
<td class="el-table__cell is-leaf"><div class="cell">网络入口/出口</div></td>
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.instantaneous_input_kbps }}kps/{{cache.info.instantaneous_output_kbps}}kps</div></td>
</tr>
</tbody>
</table>
</div>
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card>
<div slot="header"><span>命令统计</span></div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="commandstats" style="height: 420px" />
</div>
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card>
<div slot="header">
<span>内存信息</span>
</div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="usedmemory" style="height: 420px" />
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { getCache } from "@/api/monitor/cache";
import * as echarts from "echarts";
export default {
name: "Cache",
data() {
return {
// 统计命令信息
commandstats: null,
// 使用内存
usedmemory: null,
// cache信息
cache: []
}
},
created() {
this.getList();
this.openLoading();
},
methods: {
/** 查缓存询信息 */
getList() {
getCache().then((response) => {
this.cache = response.data;
this.$modal.closeLoading();
this.commandstats = echarts.init(this.$refs.commandstats, "macarons");
this.commandstats.setOption({
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)",
},
series: [
{
name: "命令",
type: "pie",
roseType: "radius",
radius: [15, 95],
center: ["50%", "38%"],
data: response.data.commandStats,
animationEasing: "cubicInOut",
animationDuration: 1000,
}
]
});
this.usedmemory = echarts.init(this.$refs.usedmemory, "macarons");
this.usedmemory.setOption({
tooltip: {
formatter: "{b} <br/>{a} : " + this.cache.info.used_memory_human,
},
series: [
{
name: "峰值",
type: "gauge",
min: 0,
max: 1000,
detail: {
formatter: this.cache.info.used_memory_human,
},
data: [
{
value: parseFloat(this.cache.info.used_memory_human),
name: "内存消耗",
}
]
}
]
});
});
},
// 打开加载层
openLoading() {
this.$modal.loading("正在加载缓存监控数据,请稍候!");
}
}
};
</script>

View File

@@ -87,7 +87,7 @@ export default {
confirmPassword: "",
code: "",
uuid: "",
user_type: "sys_user"
userType: "sys_user"
},
registerRules: {
username: [

View File

@@ -78,9 +78,9 @@ services:
# 时区上海
TZ: Asia/Shanghai
# 管理后台用户名
MINIO_ACCESS_KEY: ruoyi
MINIO_ROOT_USER: ruoyi
# 管理后台密码最小8个字符
MINIO_SECRET_KEY: ruoyi123
MINIO_ROOT_PASSWORD: ruoyi123
# https需要指定域名
#MINIO_SERVER_URL: "https://xxx.com:9000"
#MINIO_BROWSER_REDIRECT_URL: "https://xxx.com:9001"