Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into featskywalking

 Conflicts:
	src/main/resources/application-local.yaml
This commit is contained in:
YunaiV 2021-04-22 00:55:54 +08:00
commit 22a09d079f
393 changed files with 20123 additions and 7040 deletions

View File

@ -1,6 +1,6 @@
## 平台简介
**芋道** 是基于 [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) **重构**,一套**全部开源**的**企业级**的快速开发平台,毫无保留给个人及企业免费使用。
**芋道**,一套**全部开源**的**企业级**的快速开发平台,毫无保留给个人及企业免费使用。
> 有任何问题,或者想要的功能,可以在 _Issues_ 中提给艿艿。
@ -17,39 +17,63 @@
* 基础设施
* 研发工具
> 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
>
> 额外新增的功能,我们使用 🚀 标记。
🙂 所有功能,都通过 **单元测试** 保证高质量。
### 系统功能
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置
1. 在线用户:当前系统中活跃用户状态监控
1. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分
1. 菜单管理:配置系统菜单,操作权限,按钮权限标识等
1. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限
1. 岗位管理:配置系统用户所属担任职务
1. 字典管理:对系统中经常使用的一些较为固定的数据进行维护
1. 通知公告:系统通知公告信息发布维护
1. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询
1. 登录日志:系统登录日志记录查询包含登录异常
| | 功能 | 描述 |
| --- | --- | --- |
| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 |
| | 在线用户 | 当前系统中活跃用户状态监控,支持手动踢下线 |
| | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 |
| | 菜单管理 | 配置系统菜单,操作权限,按钮权限标识等 |
| | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 |
| | 岗位管理 | 配置系统用户所属担任职务 |
| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 |
| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、云片等主流短信平台 |
| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 |
| | 登录日志 | 系统登录日志记录查询,包含登录异常 |
| 🚀 | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务 |
| | 通知公告 | 系统通知公告信息发布维护 |
计划新增功能:
* 邮件
* 钉钉、飞书等通知
### 基础设施
1. 配置管理:对系统动态配置常用参数
1. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志
1. API 日志:包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题
1. MySQL 监控监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈
1. Redis 监控:监控 Redis 数据库的使用情况,使用的 Redis Key 管理
1. Java 监控:基于 Spring Boot Admin 实现 Java 应用的监控
1. 链路追踪:基于 SkyWalking 实现性能监控,特别是链路的追踪
1. 分布式锁:基于 Redis 实现分布式锁,满足并发场景
1. 幂等组件:基于 Redis 实现幂等组件,解决重复请求问题
1. 服务保障:基于 Resilience4j 实现服务的稳定性,包括限流、熔断等功能
1. 日志服务:轻量级日志中心,查看远程服务器的日志
| | 功能 | 描述 |
| --- | --- | --- |
| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 |
| | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 |
| 🚀 | 文件服务 | 支持本地文件存储,同时支持兼容 Amazon S3 协议的云服务、开源组件 |
| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 |
| | MySQL 监控 | 监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈 |
| | Redis 监控 |监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
| 🚀 |Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 |
| 🚀 | 链路追踪 | 基于 SkyWalking 实现性能监控,特别是链路的追踪 |
| 🚀 | 分布式锁 | 基于 Redis 实现分布式锁,满足并发场景 |
| 🚀 | 幂等组件 | 基于 Redis 实现幂等组件,解决重复请求问题 |
| 🚀 | 服务保障 | 基于 Resilience4j 实现服务的稳定性,包括限流、熔断等功能 |
| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 |
| 🚀 | 单元测试 |基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 |
计划新增:
* 工作流
### 研发工具
1. 表单构建:拖动表单元素生成相应的 HTML 代码
1. 代码生成前后端代码的生成Java、Vue、SQL支持 CRUD 下载
1. 系统接口:基于 Swagger 自动生成相关的 RESTful API 接口文档
1. 数据库文档:基于 Screw 自动生成数据库文档
| | 功能 | 描述 |
| --- | --- | --- |
| 🚀 | 代码生成 |前后端代码的生成Java、Vue、SQL、单元测试支持 CRUD 下载 |
| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 |
| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 |
| | 表单构建 | 拖动表单元素生成相应的 HTML 代码 |
## 在线体验
@ -63,14 +87,15 @@
## 技术栈
**后端**
### 后端
| 框架 | 说明 | 版本 | 学习指南 |
| --- | --- | --- | --- |
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.4.2 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 | |
| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.4 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
| [MyBatis-Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.4.1 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.4.1 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
| [Redis](https://redis.io/) | key-value 数据库 | 5.0 | |
| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.1.46 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.4.2 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
@ -78,14 +103,16 @@
| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.1.7 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
| [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.2 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
| [Resilience4j](https://github.com/quartz-scheduler) | 服务保障组件 | 1.7.0 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) |
| [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.0 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) |
| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.6.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |
| [Spring Boot Admin](https://github.com/skywalking) | Spring Boot 监控平台 | 8.6.0 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 8.6.0 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.11.4 | |
| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.4.1 | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) |
| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码| 1.16.14 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |
| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.16.14 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |
| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.7.0 | - |
| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 3.6.28 | - |
**前端**
### 前端
| 框架 | 说明 | 版本 |
| --- | --- | --- |
@ -125,7 +152,7 @@
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/b6115bc8c31de52951982e509930b20684a.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-6d73c2140ce694e3de4c05035fdc1868d4c.png"/></td>
<td> - </td>
</tr>
</table>

4
lombok.config Normal file
View File

@ -0,0 +1,4 @@
config.stopBubbling = true
lombok.tostring.callsuper=true
lombok.equalsandhashcode.callsuper=true
lombok.accessors.chain=true

99
pom.xml
View File

@ -22,14 +22,16 @@
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
<!-- 统一依赖管理 -->
<spring.boot.version>2.4.2</spring.boot.version>
<spring.boot.version>2.4.4</spring.boot.version>
<!-- Web 相关 -->
<knife4j.version>3.0.2</knife4j.version>
<swagger-annotations.version>1.5.22</swagger-annotations.version>
<!-- DB 相关 -->
<mysql-connector-java.version>5.1.46</mysql-connector-java.version>
<druid.version>1.2.4</druid.version>
<mybatis-plus.version>3.4.1</mybatis-plus.version>
<redisson.version>3.14.1</redisson.version>
<mybatis-plus.version>3.4.2</mybatis-plus.version>
<dynamic-datasource.version>3.3.2</dynamic-datasource.version>
<redisson.version>3.15.1</redisson.version>
<!-- Config 配置中心相关 -->
<apollo.version>1.7.0</apollo.version>
<!-- 服务保障相关 -->
@ -42,10 +44,16 @@
<!-- 工具类相关 -->
<lombok.version>1.16.14</lombok.version>
<mapstruct.version>1.4.1.Final</mapstruct.version>
<hutool.version>5.5.6</hutool.version>
<hutool.version>5.6.1</hutool.version>
<easyexcel.verion>2.2.7</easyexcel.verion>
<velocity.version>2.2</velocity.version>
<screw.version>1.0.5</screw.version>
<podam.version>7.2.6.RELEASE</podam.version>
<jedis-mock.version>0.1.16</jedis-mock.version>
<!-- 三方云服务相关 -->
<aliyun-java-sdk-core.version>4.5.18</aliyun-java-sdk-core.version>
<aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version>
<yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version>
</properties>
<!-- 依赖声明 -->
@ -101,8 +109,21 @@
<artifactId>mapstruct</artifactId>
<groupId>org.mapstruct</groupId> <!-- 避免冲突 -->
</exclusion>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
<exclusion>
<artifactId>swagger-annotations</artifactId>
<groupId>io.swagger</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger-annotations.version}</version>
</dependency>
<!-- DB 相关 -->
<dependency>
@ -120,6 +141,11 @@
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
<version>${dynamic-datasource.version}</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
@ -145,6 +171,12 @@
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
<version>${lock4j.version}</version>
<exclusions>
<exclusion>
<artifactId>redisson-spring-boot-starter</artifactId>
<groupId>org.redisson</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@ -181,6 +213,12 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>asm</artifactId>
<groupId>org.ow2.asm</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@ -192,14 +230,14 @@
<dependency>
<groupId>com.github.fppt</groupId> <!-- 单元测试,我们采用内嵌的 Redis 数据库 -->
<artifactId>jedis-mock</artifactId>
<version>0.1.16</version>
<version>${jedis-mock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>uk.co.jemos.podam</groupId> <!-- 单元测试,随机生成 POJO 类 -->
<artifactId>podam</artifactId>
<version>7.2.6.RELEASE</version>
<version>${podam.version}</version>
<scope>test</scope>
</dependency>
@ -215,6 +253,12 @@
<artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
@ -223,27 +267,7 @@
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-extra</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-captcha</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
@ -265,6 +289,27 @@
<version>${screw.version}</version>
</dependency>
<!-- 三方云服务相关 -->
<!-- SMS SDK begin -->
<dependency>
<groupId>com.yunpian.sdk</groupId>
<artifactId>yunpian-java-sdk</artifactId>
<version>${yunpian-java-sdk.version}</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>${aliyun-java-sdk-core.version}</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>${aliyun-java-sdk-dysmsapi.version}</version>
</dependency>
<!-- SMS SDK end -->
</dependencies>

View File

@ -1,27 +0,0 @@
package com.ruoyi.common.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 数据权限过滤注解
*
* @author ruoyi
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
/**
* 部门表的别名
*/
public String deptAlias() default "";
/**
* 用户表的别名
*/
public String userAlias() default "";
}

View File

@ -1,145 +0,0 @@
package com.ruoyi.framework.aspectj;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.web.service.TokenService;
/**
* 数据过滤处理
*
* @author ruoyi
*/
@Aspect
@Component
public class DataScopeAspect {
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "1";
/**
* 自定数据权限
*/
public static final String DATA_SCOPE_CUSTOM = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 部门及以下数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "5";
/**
* 数据权限过滤关键字
*/
public static final String DATA_SCOPE = "dataScope";
// 配置织入点
@Pointcut("@annotation(com.ruoyi.common.annotation.DataScope)")
public void dataScopePointCut() {
}
@Before("dataScopePointCut()")
public void doBefore(JoinPoint point) throws Throwable {
handleDataScope(point);
}
protected void handleDataScope(final JoinPoint joinPoint) {
// 获得注解
DataScope controllerDataScope = getAnnotationLog(joinPoint);
if (controllerDataScope == null) {
return;
}
// 获取当前的用户
LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());
if (StringUtils.isNotNull(loginUser)) {
SysUser currentUser = loginUser.getUser();
// 如果是超级管理员则不过滤数据
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) {
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
controllerDataScope.userAlias());
}
}
}
/**
* 数据范围过滤
*
* @param joinPoint 切点
* @param user 用户
* @param userAlias 别名
*/
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias) {
StringBuilder sqlString = new StringBuilder();
for (SysRole role : user.getRoles()) {
String dataScope = role.getDataScope();
if (DATA_SCOPE_ALL.equals(dataScope)) {
sqlString = new StringBuilder();
break;
} else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {
sqlString.append(StringUtils.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
role.getRoleId()));
} else if (DATA_SCOPE_DEPT.equals(dataScope)) {
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
} else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
sqlString.append(StringUtils.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
deptAlias, user.getDeptId(), user.getDeptId()));
} else if (DATA_SCOPE_SELF.equals(dataScope)) {
if (StringUtils.isNotBlank(userAlias)) {
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
} else {
// 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(" OR 1=0 ");
}
}
}
if (StringUtils.isNotBlank(sqlString.toString())) {
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {
BaseEntity baseEntity = (BaseEntity) params;
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
}
}
}
/**
* 是否存在注解如果存在就获取
*/
private DataScope getAnnotationLog(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(DataScope.class);
}
return null;
}
}

View File

@ -1,28 +0,0 @@
package com.ruoyi.common.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.ruoyi.common.enums.DataSourceType;
/**
* 自定义多数据源切换注解
* <p>
* 优先级先方法后类如果方法覆盖了类上的数据源类型以方法的为准否则以类上的为准
*
* @author ruoyi
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {
/**
* 切换数据源名称
*/
public DataSourceType value() default DataSourceType.MASTER;
}

View File

@ -1,64 +0,0 @@
package com.ruoyi.framework.aspectj;
import java.util.Objects;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import com.ruoyi.common.annotation.DataSource;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder;
/**
* 多数据源处理
*
* @author ruoyi
*/
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
protected Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)"
+ "|| @within(com.ruoyi.common.annotation.DataSource)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
DataSource dataSource = getDataSource(point);
if (StringUtils.isNotNull(dataSource)) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
} finally {
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
/**
* 获取需要切换的数据源
*/
public DataSource getDataSource(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if (Objects.nonNull(dataSource)) {
return dataSource;
}
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}

View File

@ -1,19 +0,0 @@
package com.ruoyi.common.enums;
/**
* 数据源
*
* @author ruoyi
*/
public enum DataSourceType
{
/**
* 主库
*/
MASTER,
/**
* 从库
*/
SLAVE
}

View File

@ -1,24 +0,0 @@
package com.ruoyi.framework.datasource;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态数据源
*
* @author ruoyi
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}

View File

@ -1,41 +0,0 @@
package com.ruoyi.framework.datasource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 数据源切换处理
*
* @author ruoyi
*/
public class DynamicDataSourceContextHolder {
public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
/**
* 使用ThreadLocal维护变量ThreadLocal为每个使用该变量的线程提供独立的变量副本
* 所以每一个线程都可以独立地改变自己的副本而不会影响其它线程所对应的副本
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源的变量
*/
public static void setDataSourceType(String dsType) {
log.info("切换到{}数据源", dsType);
CONTEXT_HOLDER.set(dsType);
}
/**
* 获得数据源的变量
*/
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}

View File

@ -1,116 +0,0 @@
package com.ruoyi.framework.config;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.config.properties.DruidProperties;
import com.ruoyi.framework.datasource.DynamicDataSource;
/**
* druid 配置多数据源
*
* @author ruoyi
*/
@Configuration
public class DruidConfig {
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DruidProperties druidProperties) {
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource(DruidProperties druidProperties) {
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
return new DynamicDataSource(masterDataSource, targetDataSources);
}
/**
* 设置数据源
*
* @param targetDataSources 备选数据源集合
* @param sourceName 数据源名称
* @param beanName bean名称
*/
public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName) {
try {
DataSource dataSource = SpringUtils.getBean(beanName);
targetDataSources.put(sourceName, dataSource);
} catch (Exception e) {
}
}
/**
* 去除监控页面底部的广告
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@Bean
@ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) {
// 获取web监控页面的参数
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
// 提取common.js的配置路径
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
final String filePath = "support/http/resources/js/common.js";
// 创建filter进行过滤
Filter filter = new Filter() {
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
// 重置缓冲区响应头不会被重置
response.resetBuffer();
// 获取common.js
String text = Utils.readFromResource(filePath);
// 正则替换banner, 除去底部的广告信息
text = text.replaceAll("<a.*?banner\"></a><br/>", "");
text = text.replaceAll("powered.*?shrek.wang</a>", "");
response.getWriter().write(text);
}
@Override
public void destroy() {
}
};
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(filter);
registrationBean.addUrlPatterns(commonJsPattern);
return registrationBean;
}
}

View File

@ -1,37 +0,0 @@
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: password
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false

7
ruoyi-ui/.env.demo1024 Normal file
View File

@ -0,0 +1,7 @@
NODE_ENV = production
# 测试环境配置
ENV = 'staging'
# 芋道管理系统/测试环境
VUE_APP_BASE_API = 'http://127.0.0.1:48080'

View File

@ -8,6 +8,7 @@
"dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"build:demo1024": "vue-cli-service build --mode demo1024",
"preview": "node build/index.js --preview",
"lint": "eslint --ext .js,.vue src"
},

View File

@ -8,3 +8,19 @@ export function exportHtml() {
responseType: 'blob'
})
}
export function exportWord() {
return request({
url: '/infra/db-doc/export-word',
method: 'get',
responseType: 'blob'
})
}
export function exportMarkdown() {
return request({
url: '/infra/db-doc/export-markdown',
method: 'get',
responseType: 'blob'
})
}

View File

@ -0,0 +1,18 @@
import request from '@/utils/request'
// 删除文件
export function deleteFile(id) {
return request({
url: '/infra/file/delete?id=' + id,
method: 'delete'
})
}
// 获得文件分页
export function getFilePage(query) {
return request({
url: '/infra/file/page',
method: 'get',
params: query
})
}

View File

@ -46,7 +46,7 @@ export function addDept(data) {
export function updateDept(data) {
return request({
url: '/system/dept/update',
method: 'post',
method: 'put',
data: data
})
}
@ -55,6 +55,6 @@ export function updateDept(data) {
export function delDept(id) {
return request({
url: '/system/dept/delete?id=' + id,
method: 'post'
method: 'delete'
})
}

View File

@ -38,7 +38,7 @@ export function addData(data) {
export function updateData(data) {
return request({
url: '/system/dict-data/update',
method: 'post',
method: 'put',
data: data
})
}
@ -47,7 +47,7 @@ export function updateData(data) {
export function delData(dictCode) {
return request({
url: '/system/dict-data/delete?id=' + dictCode,
method: 'post'
method: 'delete'
})
}

View File

@ -30,7 +30,7 @@ export function addType(data) {
export function updateType(data) {
return request({
url: '/system/dict-type/update',
method: 'post',
method: 'put',
data: data
})
}
@ -39,7 +39,7 @@ export function updateType(data) {
export function delType(dictId) {
return request({
url: '/system/dict-type/delete?id=' + dictId,
method: 'post'
method: 'delete'
})
}

View File

@ -0,0 +1,54 @@
import request from '@/utils/request'
// 创建错误码
export function createErrorCode(data) {
return request({
url: '/system/error-code/create',
method: 'post',
data: data
})
}
// 更新错误码
export function updateErrorCode(data) {
return request({
url: '/system/error-code/update',
method: 'put',
data: data
})
}
// 删除错误码
export function deleteErrorCode(id) {
return request({
url: '/system/error-code/delete?id=' + id,
method: 'delete'
})
}
// 获得错误码
export function getErrorCode(id) {
return request({
url: '/system/error-code/get?id=' + id,
method: 'get'
})
}
// 获得错误码分页
export function getErrorCodePage(query) {
return request({
url: '/system/error-code/page',
method: 'get',
params: query
})
}
// 导出错误码 Excel
export function exportErrorCodeExcel(query) {
return request({
url: '/system/error-code/export-excel',
method: 'get',
params: query,
responseType: 'blob'
})
}

View File

@ -38,7 +38,7 @@ export function addMenu(data) {
export function updateMenu(data) {
return request({
url: '/system/menu/update',
method: 'post',
method: 'put',
data: data
})
}
@ -47,6 +47,6 @@ export function updateMenu(data) {
export function delMenu(id) {
return request({
url: '/system/menu/delete?id=' + id,
method: 'post'
method: 'delete'
})
}

View File

@ -30,7 +30,7 @@ export function addNotice(data) {
export function updateNotice(data) {
return request({
url: '/system/notice/update',
method: 'post',
method: 'put',
data: data
})
}
@ -39,6 +39,6 @@ export function updateNotice(data) {
export function delNotice(noticeId) {
return request({
url: '/system/notice/delete?id=' + noticeId,
method: 'post'
method: 'delete'
})
}

View File

@ -38,7 +38,7 @@ export function addPost(data) {
export function updatePost(data) {
return request({
url: '/system/post/update',
method: 'post',
method: 'put',
data: data
})
}
@ -47,7 +47,7 @@ export function updatePost(data) {
export function delPost(postId) {
return request({
url: '/system/post/delete?id=' + postId,
method: 'post'
method: 'delete'
})
}

View File

@ -38,7 +38,7 @@ export function addRole(data) {
export function updateRole(data) {
return request({
url: '/system/role/update',
method: 'post',
method: 'put',
data: data
})
}
@ -51,7 +51,7 @@ export function changeRoleStatus(id, status) {
}
return request({
url: '/system/role/update-status',
method: 'post',
method: 'put',
data: data
})
}
@ -60,7 +60,7 @@ export function changeRoleStatus(id, status) {
export function delRole(roleId) {
return request({
url: '/system/role/delete?id=' + roleId,
method: 'post'
method: 'delete'
})
}

View File

@ -0,0 +1,52 @@
import request from '@/utils/request'
// 创建短信渠道
export function createSmsChannel(data) {
return request({
url: '/system/sms-channel/create',
method: 'post',
data: data
})
}
// 更新短信渠道
export function updateSmsChannel(data) {
return request({
url: '/system/sms-channel/update',
method: 'put',
data: data
})
}
// 删除短信渠道
export function deleteSmsChannel(id) {
return request({
url: '/system/sms-channel/delete?id=' + id,
method: 'delete'
})
}
// 获得短信渠道
export function getSmsChannel(id) {
return request({
url: '/system/sms-channel/get?id=' + id,
method: 'get'
})
}
// 获得短信渠道分页
export function getSmsChannelPage(query) {
return request({
url: '/system/sms-channel/page',
method: 'get',
params: query
})
}
// 获得短信渠道精简列表
export function getSimpleSmsChannels() {
return request({
url: '/system/sms-channel/list-all-simple',
method: 'get',
})
}

View File

@ -0,0 +1,20 @@
import request from '@/utils/request'
// 获得短信日志分页
export function getSmsLogPage(query) {
return request({
url: '/system/sms-log/page',
method: 'get',
params: query
})
}
// 导出短信日志 Excel
export function exportSmsLogExcel(query) {
return request({
url: '/system/sms-log/export-excel',
method: 'get',
params: query,
responseType: 'blob'
})
}

View File

@ -0,0 +1,64 @@
import request from '@/utils/request'
// 创建短信模板
export function createSmsTemplate(data) {
return request({
url: '/system/sms-template/create',
method: 'post',
data: data
})
}
// 更新短信模板
export function updateSmsTemplate(data) {
return request({
url: '/system/sms-template/update',
method: 'put',
data: data
})
}
// 删除短信模板
export function deleteSmsTemplate(id) {
return request({
url: '/system/sms-template/delete?id=' + id,
method: 'delete'
})
}
// 获得短信模板
export function getSmsTemplate(id) {
return request({
url: '/system/sms-template/get?id=' + id,
method: 'get'
})
}
// 获得短信模板分页
export function getSmsTemplatePage(query) {
return request({
url: '/system/sms-template/page',
method: 'get',
params: query
})
}
// 创建短信模板
export function sendSms(data) {
return request({
url: '/system/sms-template/send-sms',
method: 'post',
data: data
})
}
// 导出短信模板 Excel
export function exportSmsTemplateExcel(query) {
return request({
url: '/system/sms-template/export-excel',
method: 'get',
params: query,
responseType: 'blob'
})
}

View File

@ -31,7 +31,7 @@ export function addUser(data) {
export function updateUser(data) {
return request({
url: '/system/user/update',
method: 'post',
method: 'put',
data: data
})
}
@ -62,7 +62,7 @@ export function resetUserPwd(id, password) {
}
return request({
url: '/system/user/update-password',
method: 'post',
method: 'put',
data: data
})
}
@ -75,7 +75,7 @@ export function changeUserStatus(id, status) {
}
return request({
url: '/system/user/update-status',
method: 'post',
method: 'put',
data: data
})
}
@ -83,7 +83,7 @@ export function changeUserStatus(id, status) {
// 查询用户个人信息
export function getUserProfile() {
return request({
url: '/system/user/profile',
url: '/system/user/profile/get',
method: 'get'
})
}
@ -91,7 +91,7 @@ export function getUserProfile() {
// 修改用户个人信息
export function updateUserProfile(data) {
return request({
url: '/system/user/profile',
url: '/system/user/profile/update',
method: 'put',
data: data
})
@ -104,9 +104,9 @@ export function updateUserPwd(oldPassword, newPassword) {
newPassword
}
return request({
url: '/system/user/profile/updatePwd',
url: '/system/user/profile/update-password',
method: 'put',
params: data
data: data
})
}
@ -114,7 +114,7 @@ export function updateUserPwd(oldPassword, newPassword) {
export function uploadAvatar(data) {
return request({
url: '/system/user/profile/avatar',
method: 'post',
method: 'put',
data: data
})
}

View File

@ -25,7 +25,10 @@ import {
download,
handleTree,
downloadExcel,
downloadZip
downloadWord,
downloadZip,
downloadHtml,
downloadMarkdown,
} from "@/utils/ruoyi";
import Pagination from "@/components/Pagination";
// 自定义表格工具扩展
@ -48,6 +51,9 @@ Vue.prototype.getDictDataLabel = getDictDataLabel
Vue.prototype.DICT_TYPE = DICT_TYPE
Vue.prototype.download = download
Vue.prototype.downloadExcel = downloadExcel
Vue.prototype.downloadWord = downloadWord
Vue.prototype.downloadHtml = downloadHtml
Vue.prototype.downloadMarkdown = downloadMarkdown
Vue.prototype.downloadZip = downloadZip
Vue.prototype.handleTree = handleTree

View File

@ -53,7 +53,7 @@ const user = {
getInfo(state.token).then(res => {
res = res.data; // 读取 data 数据
const user = res.user
const avatar = user.avatar === "" ? require("@/assets/images/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar;
const avatar = user.avatar === "" ? require("@/assets/images/profile.jpg") : user.avatar;
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
commit('SET_ROLES', res.roles)
commit('SET_PERMISSIONS', res.permissions)

View File

@ -17,6 +17,11 @@ export const DICT_TYPE = {
SYS_OPERATE_TYPE: 'sys_operate_type',
SYS_LOGIN_RESULT: 'sys_login_result',
SYS_CONFIG_TYPE: 'sys_config_type',
SYS_SMS_CHANNEL_CODE: 'sys_sms_channel_code',
SYS_SMS_TEMPLATE_TYPE: 'sys_sms_template_type',
SYS_SMS_SEND_STATUS: 'sys_sms_send_status',
SYS_SMS_RECEIVE_STATUS: 'sys_sms_receive_status',
SYS_ERROR_CODE_TYPE: 'sys_error_code_type',
INF_REDIS_TIMEOUT_TYPE: 'inf_redis_timeout_type',
INF_JOB_STATUS: 'inf_job_status',

View File

@ -73,6 +73,18 @@ service.interceptors.response.use(res => {
type: 'error'
})
return Promise.reject(new Error(msg))
} else if (code === 901) {
Message({
type: 'error',
duration: 0,
dangerouslyUseHTMLString: true,
message: '<div>演示模式,不发进行写操作</div>'
+ '<div> &nbsp; </div>'
+ '<div>参考 https://www.iocoder.cn/Yudao/build-debugger-environment 教程</div>'
+ '<div> &nbsp; </div>'
+ '<div>5 分钟搭建本地环境</div>',
})
return Promise.reject(new Error(msg))
} else if (code !== 200) {
Notification.error({
title: msg

View File

@ -120,11 +120,26 @@ export function downloadExcel(data, fileName) {
download0(data, fileName, 'application/vnd.ms-excel');
}
// 下载 Word 方法
export function downloadWord(data, fileName) {
download0(data, fileName, 'application/msword');
}
// 下载 Zip 方法
export function downloadZip(data, fileName) {
download0(data, fileName, 'application/zip');
}
// 下载 Html 方法
export function downloadHtml(data, fileName) {
download0(data, fileName, 'text/html');
}
// 下载 Markdown 方法
export function downloadMarkdown(data, fileName) {
download0(data, fileName, 'text/markdown');
}
function download0(data, fileName, mineType) {
// 创建 blob
let blob = new Blob([data], {type: mineType});

View File

@ -2,24 +2,12 @@
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="参数名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入参数名称"
clearable
size="small"
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.name" placeholder="请输入参数名称" clearable size="small" style="width: 240px"
@keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="参数键名" prop="key">
<el-input
v-model="queryParams.key"
placeholder="请输入参数键名"
clearable
size="small"
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.key" placeholder="请输入参数键名" clearable size="small" style="width: 240px"
@keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="系统内置" prop="type">
<el-select v-model="queryParams.type" placeholder="系统内置" clearable size="small">
@ -56,7 +44,7 @@
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['infra:config:add']"
v-hasPermi="['infra:config:create']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
@ -95,31 +83,15 @@
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['infra:config:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['infra:config:remove']"
>删除</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['infra:config:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:config:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNo"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 添加或修改参数配置对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>

View File

@ -0,0 +1,202 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="文件路径" prop="id">
<el-input v-model="queryParams.id" placeholder="请输入文件路径" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="文件类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择文件类型" clearable size="small">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker v-model="dateRangeCreateTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">上传文件</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="文件路径" align="center" prop="id" width="300" />
<el-table-column label="文件类型" align="center" prop="type" width="80" />
<el-table-column label="文件内容" align="center" prop="content">
<template slot-scope="scope">
<img v-if="scope.row.type === 'jpg' || scope.row.type === 'png' || scope.row.type === 'gif'"
width="200px" :src="getFileUrl + scope.row.id">
<i v-else>非图片无法预览</i>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:file:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
<el-upload ref="upload" :limit="1" accept=".jpg, .png, .gif" :auto-upload="false" drag
:headers="upload.headers" :action="upload.url" :data="upload.data" :disabled="upload.isUploading"
:on-change="handleFileChange"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess">
<i class="el-icon-upload"></i>
<div class="el-upload__text">
将文件拖到此处 <em>点击上传</em>
</div>
<div class="el-upload__tip" style="color:red" slot="tip">提示仅允许导入 jpgpnggif 格式文件</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { deleteFile, getFilePage } from "@/api/infra/file";
import {getToken} from "@/utils/auth";
export default {
name: "File",
data() {
return {
getFileUrl: process.env.VUE_APP_BASE_API + '/api/infra/file/get/',
//
loading: true,
//
showSearch: true,
//
total: 0,
//
list: [],
//
title: "",
dateRangeCreateTime: [],
//
queryParams: {
pageNo: 1,
pageSize: 10,
id: null,
type: null,
},
//
upload: {
open: false, //
title: "", //
isUploading: false, //
url: process.env.VUE_APP_BASE_API + '/api/' + "/infra/file/upload", //
headers: { Authorization: "Bearer " + getToken() }, //
data: {} //
},
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
//
let params = {...this.queryParams};
this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
//
getFilePage(params).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
content: undefined,
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRangeCreateTime = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.upload.open = true;
this.upload.title = "上传文件";
},
/** 处理上传的文件发生变化 */
handleFileChange(file, fileList) {
this.upload.data.path = file.name;
},
/** 处理文件上传中 */
handleFileUploadProgress(event, file, fileList) {
this.upload.isUploading = true; //
},
/** 发起文件上窜 */
submitFileForm() {
this.$refs.upload.submit();
},
/** 文件上传成功处理 */
handleFileSuccess(response, file, fileList) {
//
this.upload.open = false;
this.upload.isUploading = false;
this.$refs.upload.clearFiles();
//
this.msgSuccess("上传成功");
this.getList();
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$confirm('是否确认删除文件编号为"' + id + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return deleteFile(id);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
})
},
}
};
</script>

View File

@ -2,22 +2,11 @@
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
<el-form-item label="部门名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入部门名称"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.name" placeholder="请输入部门名称" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="菜单状态" clearable size="small">
<el-option
v-for="dict in statusDictDatas"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
<el-option v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</el-form-item>
<el-form-item>
@ -28,24 +17,13 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:dept:add']"
>新增</el-button>
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['system:dept:create']">新增</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table
v-loading="loading"
:data="deptList"
row-key="id"
default-expand-all
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
>
<el-table v-loading="loading" :data="deptList" row-key="id" default-expand-all
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
<el-table-column prop="name" label="部门名称" width="260"></el-table-column>
<el-table-column prop="sort" label="排序" width="200"></el-table-column>
<el-table-column prop="status" label="状态" :formatter="statusFormat" width="100"></el-table-column>
@ -56,28 +34,12 @@
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:dept:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-plus"
@click="handleAdd(scope.row)"
v-hasPermi="['system:dept:add']"
>新增</el-button>
<el-button
v-if="scope.row.parentId !== 0"
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:dept:remove']"
>删除</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:dept:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-plus" @click="handleAdd(scope.row)"
v-hasPermi="['system:dept:create']">新增</el-button>
<el-button v-if="scope.row.parentId !== 0" size="mini" type="text" icon="el-icon-delete"
@click="handleDelete(scope.row)" v-hasPermi="['system:dept:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
@ -119,11 +81,8 @@
<el-col :span="12">
<el-form-item label="部门状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in statusDictDatas"
:key="parseInt(dict.value)"
:label="parseInt(dict.value)"
>{{dict.label}}</el-radio>
<el-radio v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="parseInt(dict.value)">
{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>

View File

@ -3,31 +3,15 @@
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="字典名称" prop="dictType">
<el-select v-model="queryParams.dictType" size="small">
<el-option
v-for="item in typeOptions"
:key="item.id"
:label="item.name"
:value="item.type"
/>
<el-option v-for="item in typeOptions" :key="item.id" :label="item.name" :value="item.type"/>
</el-select>
</el-form-item>
<el-form-item label="字典标签" prop="label">
<el-input
v-model="queryParams.label"
placeholder="请输入字典标签"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.label" placeholder="请输入字典标签" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="数据状态" clearable size="small">
<el-option
v-for="dict in statusOptions"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
<el-option v-for="dict in statusOptions" :key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item>
@ -38,22 +22,12 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:dict:add']"
>新增</el-button>
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['system:dict:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['system:dict:export']"
>导出</el-button>
<el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport"
v-hasPermi="['system:dict:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@ -72,31 +46,16 @@
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:dict:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:dict:remove']"
>删除</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:dict:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['system:dict:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNo"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 添加或修改参数配置对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
@ -115,11 +74,7 @@
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in statusDictDatas"
:key="parseInt(dict.value)"
:label="parseInt(dict.value)"
>{{dict.label}}</el-radio>
<el-radio v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">

View File

@ -2,52 +2,19 @@
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="字典名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入字典名称"
clearable
size="small"
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.name" placeholder="请输入字典名称" clearable size="small" style="width: 240px" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="字典类型" prop="type">
<el-input
v-model="queryParams.type"
placeholder="请输入字典类型"
clearable
size="small"
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.type" placeholder="请输入字典类型" clearable size="small" style="width: 240px" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="字典状态"
clearable
size="small"
style="width: 240px"
>
<el-option
v-for="dict in statusDictDatas"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
<el-select v-model="queryParams.status" placeholder="字典状态" clearable size="small" style="width: 240px">
<el-option v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="dateRange"
size="small"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
<el-date-picker v-model="dateRangeCreateTime" size="small" style="width: 240px" value-format="yyyy-MM-dd" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@ -57,22 +24,12 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:dict:add']"
>新增</el-button>
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['system:dict:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['system:dict:export']"
>导出</el-button>
<el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport"
v-hasPermi="['system:dict:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@ -96,31 +53,16 @@
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:dict:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:dict:remove']"
>删除</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:dict:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['system:dict:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNo"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 添加或修改参数配置对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
@ -133,11 +75,7 @@
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in statusDictDatas"
:key="parseInt(dict.value)"
:label="parseInt(dict.value)"
>{{dict.label}}</el-radio>
<el-radio v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
@ -177,7 +115,7 @@ export default {
//
statusOptions: [],
//
dateRange: [],
dateRangeCreateTime: [],
//
queryParams: {
pageNo: 1,
@ -211,15 +149,15 @@ export default {
/** 查询字典类型列表 */
getList() {
this.loading = true;
listType(this.addDateRange(this.queryParams, [
this.dateRange[0] ? this.dateRange[0] + ' 00:00:00' : undefined,
this.dateRange[1] ? this.dateRange[1] + ' 23:59:59' : undefined,
])).then(response => {
this.typeList = response.data.list;
this.total = response.data.total;
this.loading = false;
}
);
//
let params = {...this.queryParams};
this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
//
listType(params).then(response => {
this.typeList = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
//
statusFormat(row, column) {
@ -248,7 +186,7 @@ export default {
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = [];
this.dateRangeCreateTime = [];
this.resetForm("queryForm");
this.handleQuery();
},
@ -304,19 +242,21 @@ export default {
},
/** 导出按钮操作 */
handleExport() {
const queryParams = this.addDateRange(this.queryParams, [
this.dateRange[0] ? this.dateRange[0] + ' 00:00:00' : undefined,
this.dateRange[1] ? this.dateRange[1] + ' 23:59:59' : undefined,
]);
this.$confirm('是否确认导出所有类型数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return exportType(queryParams);
}).then(response => {
this.downloadExcel(response, '数据类型.xls');
})
//
let params = {...this.queryParams};
params.pageNo = undefined;
params.pageSize = undefined;
this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
//
this.$confirm('是否确认导出所有字典类型数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return exportType(params);
}).then(response => {
this.downloadExcel(response, '字典类型.xls');
})
}
}
};

View File

@ -0,0 +1,256 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="错误码类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择错误码类型" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_ERROR_CODE_TYPE)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="应用名" prop="applicationName">
<el-input v-model="queryParams.applicationName" placeholder="请输入应用名" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="错误码编码" prop="code">
<el-input v-model="queryParams.code" placeholder="请输入错误码编码" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="错误码提示" prop="message">
<el-input v-model="queryParams.message" placeholder="请输入错误码提示" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker v-model="dateRangeCreateTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['system:error-code:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
v-hasPermi="['system:error-code:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="类型" align="center" prop="type" width="80">
<template slot-scope="scope">
<span>{{ getDictDataLabel(DICT_TYPE.SYS_ERROR_CODE_TYPE, scope.row.type) }}</span>
</template>
</el-table-column>
<el-table-column label="应用名" align="center" prop="applicationName" width="200" />
<el-table-column label="错误码编码" align="center" prop="code" width="100" />
<el-table-column label="错误码提示" align="center" prop="message" width="300" />
<el-table-column label="备注" align="center" prop="memo" width="200" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:error-code:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['system:error-code:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="应用名" prop="applicationName">
<el-input v-model="form.applicationName" placeholder="请输入应用名" />
</el-form-item>
<el-form-item label="错误码编码" prop="code">
<el-input v-model="form.code" placeholder="请输入错误码编码" />
</el-form-item>
<el-form-item label="错误码提示" prop="message">
<el-input v-model="form.message" placeholder="请输入错误码提示" />
</el-form-item>
<el-form-item label="备注" prop="memo">
<el-input v-model="form.memo" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { createErrorCode, updateErrorCode, deleteErrorCode, getErrorCode, getErrorCodePage, exportErrorCodeExcel } from "@/api/system/errorCode";
export default {
name: "ErrorCode",
components: {
},
data() {
return {
//
loading: true,
//
showSearch: true,
//
total: 0,
//
list: [],
//
title: "",
//
open: false,
dateRangeCreateTime: [],
//
queryParams: {
pageNo: 1,
pageSize: 10,
type: null,
applicationName: null,
code: null,
message: null,
},
//
form: {},
//
rules: {
applicationName: [{ required: true, message: "应用名不能为空", trigger: "blur" }],
code: [{ required: true, message: "错误码编码不能为空", trigger: "blur" }],
message: [{ required: true, message: "错误码提示不能为空", trigger: "blur" }],
}
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
//
let params = {...this.queryParams};
this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
//
getErrorCodePage(params).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
applicationName: undefined,
code: undefined,
message: undefined,
memo: undefined,
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRangeCreateTime = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加错误码";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getErrorCode(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改错误码";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
//
if (this.form.id != null) {
updateErrorCode(this.form).then(response => {
this.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
//
createErrorCode(this.form).then(response => {
this.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$confirm('是否确认删除错误码编号为"' + id + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return deleteErrorCode(id);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
})
},
/** 导出按钮操作 */
handleExport() {
//
let params = {...this.queryParams};
params.pageNo = undefined;
params.pageSize = undefined;
this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
//
this.$confirm('是否确认导出所有错误码数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return exportErrorCodeExcel(params);
}).then(response => {
this.downloadExcel(response, '错误码.xls');
})
}
}
};
</script>

View File

@ -2,56 +2,22 @@
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="登录地址" prop="userIp">
<el-input
v-model="queryParams.userIp"
placeholder="请输入登录地址"
clearable
style="width: 240px;"
size="small"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.userIp" placeholder="请输入登录地址" clearable style="width: 240px;" size="small"
@keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="用户名称" prop="username">
<el-input
v-model="queryParams.username"
placeholder="请输入用户名称"
clearable
style="width: 240px;"
size="small"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.username" placeholder="请输入用户名称" clearable style="width: 240px;" size="small"
@keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="结果"
clearable
size="small"
style="width: 240px"
>
<el-option
:key="true"
label="成功"
:value="true"
/>
<el-option
:key="false"
label="失败"
:value="false"
/>
<el-select v-model="queryParams.status" placeholder="结果" clearable size="small" style="width: 240px">
<el-option :key="true" label="成功" :value="true"/>
<el-option :key="false" label="失败" :value="false"/>
</el-select>
</el-form-item>
<el-form-item label="登录时间">
<el-date-picker
v-model="dateRange"
size="small"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
<el-date-picker v-model="dateRange" size="small" style="width: 240px" value-format="yyyy-MM-dd"
type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@ -61,13 +27,8 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="warning"
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['system:login-log:export']"
>导出</el-button>
<el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport"
v-hasPermi="['system:login-log:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@ -76,7 +37,7 @@
<el-table-column label="访问编号" align="center" prop="id" />
<el-table-column label="日志类型" align="center" prop="logType">
<template slot-scope="scope">
<span>{{ scope.row.logType === 1 ? '登录' : '退出' }}</span>
<span>{{ scope.row.logType === 100 ? '登录' : '退出' }}</span>
</template>
</el-table-column>
<el-table-column label="用户名称" align="center" prop="username" />
@ -95,13 +56,8 @@
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNo"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
</div>
</template>

View File

@ -2,22 +2,11 @@
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
<el-form-item label="菜单名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入菜单名称"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.name" placeholder="请输入菜单名称" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="菜单状态" clearable size="small">
<el-option
v-for="dict in statusDictDatas"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
<el-option v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</el-form-item>
<el-form-item>
@ -28,23 +17,14 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:menu:add']"
>新增</el-button>
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['system:menu:create']">新增</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table
v-loading="loading"
:data="menuList"
row-key="id"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
>
<el-table v-loading="loading" :data="menuList" row-key="id"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
<el-table-column prop="name" label="菜单名称" :show-overflow-tooltip="true" width="200"></el-table-column>
<el-table-column prop="icon" label="图标" align="center" width="100">
<template slot-scope="scope">
@ -62,26 +42,12 @@
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:menu:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-plus"
@click="handleAdd(scope.row)"
v-hasPermi="['system:menu:add']"
>新增</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:menu:remove']"
>删除</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:menu:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-plus" @click="handleAdd(scope.row)"
v-hasPermi="['system:menu:create']">新增</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['system:menu:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
@ -92,43 +58,25 @@
<el-row>
<el-col :span="24">
<el-form-item label="上级菜单">
<treeselect
v-model="form.parentId"
:options="menuOptions"
:normalizer="normalizer"
:show-count="true"
placeholder="选择上级菜单"
/>
<treeselect v-model="form.parentId" :options="menuOptions" :normalizer="normalizer" :show-count="true"
placeholder="选择上级菜单"/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="form.type">
<el-radio
v-for="dict in menuTypeDictDatas"
:key="parseInt(dict.value)"
:label="parseInt(dict.value)"
>{{dict.label}}</el-radio>
<el-radio v-for="dict in menuTypeDictDatas" :key="parseInt(dict.value)" :label="parseInt(dict.value)">
{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item v-if="form.type != '3'" label="菜单图标">
<el-popover
placement="bottom-start"
width="460"
trigger="click"
@show="$refs['iconSelect'].reset()"
>
<el-popover placement="bottom-start" width="460" trigger="click" @show="$refs['iconSelect'].reset()">
<IconSelect ref="iconSelect" @selected="selected" />
<el-input slot="reference" v-model="form.icon" placeholder="点击选择图标" readonly>
<svg-icon
v-if="form.icon"
slot="prefix"
:icon-class="form.icon"
class="el-input__icon"
style="height: 32px;width: 16px;"
/>
<svg-icon v-if="form.icon" slot="prefix" :icon-class="form.icon" class="el-input__icon"
style="height: 32px;width: 16px;"/>
<i v-else slot="prefix" class="el-icon-search el-input__icon" />
</el-input>
</el-popover>

View File

@ -2,31 +2,14 @@
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公告标题" prop="title">
<el-input
v-model="queryParams.title"
placeholder="请输入公告标题"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.title" placeholder="请输入公告标题" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="操作人员" prop="createBy">
<el-input
v-model="queryParams.createBy"
placeholder="请输入操作人员"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.createBy" placeholder="请输入操作人员" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="queryParams.type" placeholder="公告类型" clearable size="small">
<el-option
v-for="dict in noticeTypeDictDatas"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
<el-option v-for="dict in noticeTypeDictDatas" :key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</el-form-item>
<el-form-item>
@ -37,39 +20,16 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:notice:add']"
>新增</el-button>
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['system:notice:create']"s>新增</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="noticeList">
<el-table-column label="序号" align="center" prop="id" width="100" />
<el-table-column
label="公告标题"
align="center"
prop="title"
:show-overflow-tooltip="true"
/>
<el-table-column
label="公告类型"
align="center"
prop="type"
:formatter="typeFormat"
width="100"
/>
<el-table-column
label="状态"
align="center"
prop="status"
:formatter="statusFormat"
width="100"
/>
<el-table-column label="公告标题" align="center" prop="title" :show-overflow-tooltip="true"/>
<el-table-column label="公告类型" align="center" prop="type" :formatter="typeFormat" width="100"/>
<el-table-column label="状态" align="center" prop="status" :formatter="statusFormat" width="100"/>
<el-table-column label="创建者" align="center" prop="createBy" width="100" />
<el-table-column label="创建时间" align="center" prop="createTime" width="100">
<template slot-scope="scope">
@ -78,31 +38,16 @@
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:notice:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:notice:remove']"
>删除</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:notice:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['system:notice:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNo"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 添加或修改公告对话框 -->
<el-dialog :title="title" :visible.sync="open" width="780px" append-to-body>

View File

@ -2,72 +2,28 @@
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="系统模块" prop="title">
<el-input
v-model="queryParams.title"
placeholder="请输入系统模块"
clearable
style="width: 240px;"
size="small"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.title" placeholder="请输入系统模块" clearable style="width: 240px;" size="small"
@keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="操作人员" prop="operName">
<el-input
v-model="queryParams.operName"
placeholder="请输入操作人员"
clearable
style="width: 240px;"
size="small"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.operName" placeholder="请输入操作人员" clearable style="width: 240px;" size="small"
@keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select
v-model="queryParams.type"
placeholder="操作类型"
clearable
size="small"
style="width: 240px"
>
<el-option
v-for="dict in this.getDictDatas(DICT_TYPE.SYS_OPERATE_TYPE)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
<el-select v-model="queryParams.type" placeholder="操作类型" clearable size="small" style="width: 240px">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_OPERATE_TYPE)" :key="parseInt(dict.value)"
:label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.success"
placeholder="操作状态"
clearable
size="small"
style="width: 240px"
>
<el-option
:key="true"
label="成功"
:value="true"
/>
<el-option
:key="false"
label="失败"
:value="false"
/>
<el-select v-model="queryParams.success" placeholder="操作状态" clearable size="small" style="width: 240px">
<el-option :key="true" label="成功" :value="true"/>
<el-option :key="false" label="失败" :value="false"/>
</el-select>
</el-form-item>
<el-form-item label="操作时间">
<el-date-picker
v-model="dateRange"
size="small"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
<el-date-picker v-model="dateRange" size="small" style="width: 240px" value-format="yyyy-MM-dd"
type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@ -77,13 +33,8 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="warning"
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['system:config:export']"
>导出</el-button>
<el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport"
v-hasPermi="['system:operate-log:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@ -212,7 +163,6 @@ export default {
businessType: undefined,
status: undefined
},
};
},
created() {

View File

@ -2,31 +2,14 @@
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="岗位编码" prop="code">
<el-input
v-model="queryParams.code"
placeholder="请输入岗位编码"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.code" placeholder="请输入岗位编码" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="岗位名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入岗位名称"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.name" placeholder="请输入岗位名称" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="岗位状态" clearable size="small">
<el-option
v-for="dict in statusDictDatas"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
<el-option v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</el-form-item>
<el-form-item>
@ -37,22 +20,12 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:post:add']"
>新增</el-button>
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['system:post:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['system:post:export']"
>导出</el-button>
<el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport"
v-hasPermi="['system:post:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@ -70,31 +43,16 @@
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:post:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:post:remove']"
>删除</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:post:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['system:post:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNo"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 添加或修改岗位对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
@ -110,11 +68,8 @@
</el-form-item>
<el-form-item label="岗位状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in statusDictDatas"
:key="parseInt(dict.value)"
:label="parseInt(dict.value)"
>{{dict.label}}</el-radio>
<el-radio v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="parseInt(dict.value)">
{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">

View File

@ -2,52 +2,21 @@
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" v-show="showSearch" :inline="true">
<el-form-item label="角色名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入角色名称"
clearable
size="small"
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.name" placeholder="请输入角色名称" clearable size="small" style="width: 240px"
@keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="角色标识" prop="code">
<el-input
v-model="queryParams.code"
placeholder="请输入角色标识"
clearable
size="small"
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
<el-input v-model="queryParams.code" placeholder="请输入角色标识" clearable size="small" style="width: 240px"
@keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="角色状态"
clearable
size="small"
style="width: 240px"
>
<el-option
v-for="dict in statusDictDatas"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
<el-select v-model="queryParams.status" placeholder="角色状态" clearable size="small" style="width: 240px">
<el-option v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="dateRange"
size="small"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
<el-date-picker v-model="dateRange" size="small" style="width: 240px" value-format="yyyy-MM-dd" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@ -57,22 +26,12 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:role:add']"
>新增</el-button>
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['system:role:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['system:role:export']"
>导出</el-button>
<el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport"
v-hasPermi="['system:role:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@ -85,12 +44,7 @@
<el-table-column label="显示顺序" prop="sort" width="100" />
<el-table-column label="状态" align="center" width="100">
<template slot-scope="scope">
<el-switch
v-model="scope.row.status"
:active-value="0"
:inactive-value="1"
@change="handleStatusChange(scope.row)"
></el-switch>
<el-switch v-model="scope.row.status" :active-value="0" :inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
@ -100,45 +54,20 @@
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:role:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-circle-check"
@click="handleMenu(scope.row)"
v-hasPermi="['system:permission:assign-role-menu']"
>菜单权限</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-circle-check"
@click="handleDataScope(scope.row)"
v-hasPermi="['system:permission:assign-role-data-scope']"
>数据权限</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:role:remove']"
>删除</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:role:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-circle-check" @click="handleMenu(scope.row)"
v-hasPermi="['system:permission:assign-role-menu']">菜单权限</el-button>
<el-button size="mini" type="text" icon="el-icon-circle-check" @click="handleDataScope(scope.row)"
v-hasPermi="['system:permission:assign-role-data-scope']">数据权限</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['system:role:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 添加或修改角色配置对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>

View File

@ -0,0 +1,542 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="短信签名" prop="signature">
<el-input v-model="queryParams.signature" placeholder="请输入短信签名" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="启用状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择启用状态" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_COMMON_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker v-model="dateRangeCreateTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['system:sms-channel:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
v-hasPermi="['system:sms-channel:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="短信签名" align="center" prop="signature" />
<el-table-column label="渠道编码" align="center" prop="code">
<template slot-scope="scope">
<span>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.code) }}</span>
</template>
</el-table-column>>
<el-table-column label="启用状态" align="center" prop="status">
<template slot-scope="scope">
<span>{{ getDictDataLabel(DICT_TYPE.SYS_COMMON_STATUS, scope.row.status) }}</span>
</template>
</el-table-column>>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="短信 API 的账号" align="center" prop="apiKey" />
<el-table-column label="短信 API 的秘钥" align="center" prop="apiSecret" />
<el-table-column label="短信发送回调 URL" align="center" prop="callbackUrl" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:sms-channel:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['system:sms-channel:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="短信签名" prop="signature">
<el-input v-model="form.signature" placeholder="请输入短信签名" />
</el-form-item>
<el-form-item label="渠道编码" prop="code">
<el-input v-model="form.code" placeholder="请输入渠道编码" />
</el-form-item>
<el-form-item label="启用状态">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.SYS_COMMON_STATUS)"
:key="dict.value" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="短信 API 的账号" prop="apiKey">
<el-input v-model="form.apiKey" placeholder="请输入短信 API 的账号" />
</el-form-item>
<el-form-item label="短信 API 的秘钥" prop="apiSecret">
<el-input v-model="form.apiSecret" placeholder="请输入短信 API 的秘钥" />
</el-form-item>
<el-form-item label="短信发送回调 URL" prop="callbackUrl">
<el-input v-model="form.callbackUrl" placeholder="请输入短信发送回调 URL" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { createSmsChannel, updateSmsChannel, deleteSmsChannel, getSmsChannel, getSmsChannelPage,
getSimpleSmsChannels } from "@/api/system/sms/smsChannel";
export default {
name: "SmsChannel",
components: {
},
data() {
return {
//
loading: true,
//
showSearch: true,
//
total: 0,
//
list: [],
//
title: "",
//
open: false,
dateRangeCreateTime: [],
//
queryParams: {
pageNo: 1,
pageSize: 10,
signature: null,
status: null,
},
//
form: {},
//
rules: {
signature: [{ required: true, message: "短信签名不能为空", trigger: "blur" }],
code: [{ required: true, message: "渠道编码不能为空", trigger: "blur" }],
status: [{ required: true, message: "启用状态不能为空", trigger: "blur" }],
apiKey: [{ required: true, message: "短信 API 的账号不能为空", trigger: "blur" }],
},
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
//
let params = {...this.queryParams};
this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
//
getSmsChannelPage(params).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
signature: undefined,
code: undefined,
status: undefined,
remark: undefined,
apiKey: undefined,
apiSecret: undefined,
callbackUrl: undefined,
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRangeCreateTime = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加短信渠道";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getSmsChannel(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改短信渠道";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
//
if (this.form.id != null) {
updateSmsChannel(this.form).then(response => {
this.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
//
createSmsChannel(this.form).then(response => {
this.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$confirm('是否确认删除短信渠道编号为"' + id + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return deleteSmsChannel(id);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
})
},
/** 导出按钮操作 */
handleExport() {
//
let params = {...this.queryParams};
params.pageNo = undefined;
params.pageSize = undefined;
this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
//
this.$confirm('是否确认导出所有短信渠道数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return exportSmsChannelExcel(params);
}).then(response => {
this.downloadExcel(response, '短信渠道.xls');
})
}
}
};
</script><template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="短信签名" prop="signature">
<el-input v-model="queryParams.signature" placeholder="请输入短信签名" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="启用状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择启用状态" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_COMMON_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker v-model="dateRangeCreateTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['system:sms-channel:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
v-hasPermi="['system:sms-channel:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="短信签名" align="center" prop="signature" />
<el-table-column label="渠道编码" align="center" prop="code">
<template slot-scope="scope">
<span>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.code) }}</span>
</template>
</el-table-column>>
<el-table-column label="启用状态" align="center" prop="status">
<template slot-scope="scope">
<span>{{ getDictDataLabel(DICT_TYPE.SYS_COMMON_STATUS, scope.row.status) }}</span>
</template>
</el-table-column>>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:sms-channel:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['system:sms-channel:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="130px">
<el-form-item label="短信签名" prop="signature">
<el-input v-model="form.signature" placeholder="请输入短信签名" />
</el-form-item>
<el-form-item label="渠道编码" prop="code">
<el-select v-model="form.code" placeholder="请选择渠道编码" :disabled="form.id > 0">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_SMS_CHANNEL_CODE)"
:key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="启用状态">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.SYS_COMMON_STATUS)"
:key="dict.value" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="短信 API 的账号" prop="apiKey">
<el-input v-model="form.apiKey" placeholder="请输入短信 API 的账号" />
</el-form-item>
<el-form-item v-if="form.code !== 'YUN_PIAN'" label="短信 API 的秘钥" prop="apiSecret">
<el-input v-model="form.apiSecret" placeholder="请输入短信 API 的秘钥" />
</el-form-item>
<el-form-item label="短信发送回调 URL" prop="callbackUrl">
<el-input v-model="form.callbackUrl" placeholder="请输入短信发送回调 URL" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { createSmsChannel, updateSmsChannel, deleteSmsChannel, getSmsChannel, getSmsChannelPage } from "@/api/system/sms/smsChannel";
export default {
name: "SmsChannel",
components: {
},
data() {
return {
//
loading: true,
//
showSearch: true,
//
total: 0,
//
list: [],
//
title: "",
//
open: false,
dateRangeCreateTime: [],
//
queryParams: {
pageNo: 1,
pageSize: 10,
signature: null,
status: null,
},
//
form: {},
//
apiKeyEnableChannelCodes: ['YUN_PIAN'],
rules: {
signature: [{ required: true, message: "短信签名不能为空", trigger: "blur" }],
code: [{ required: true, message: "渠道编码不能为空", trigger: "blur" }],
status: [{ required: true, message: "启用状态不能为空", trigger: "blur" }],
apiKey: [{ required: true, message: "短信 API 的账号不能为空", trigger: "blur" }],
apiSecret: [{ required: true, message: "短信 API 的秘钥不能为空", trigger: "blur" }],
}
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
//
let params = {...this.queryParams};
this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
//
getSmsChannelPage(params).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
signature: undefined,
code: undefined,
status: undefined,
remark: undefined,
apiKey: undefined,
apiSecret: undefined,
callbackUrl: undefined,
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRangeCreateTime = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加短信渠道";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getSmsChannel(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改短信渠道";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
//
if (this.form.id != null) {
updateSmsChannel(this.form).then(response => {
this.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
//
createSmsChannel(this.form).then(response => {
this.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$confirm('是否确认删除短信渠道编号为"' + id + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return deleteSmsChannel(id);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
})
},
/** 导出按钮操作 */
handleExport() {
//
let params = {...this.queryParams};
params.pageNo = undefined;
params.pageSize = undefined;
this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
//
this.$confirm('是否确认导出所有短信渠道数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return exportSmsChannelExcel(params);
}).then(response => {
this.downloadExcel(response, '短信渠道.xls');
})
}
}
};
</script>

View File

@ -0,0 +1,297 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="手机号" prop="mobile">
<el-input v-model="queryParams.mobile" placeholder="请输入手机号" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="短信渠道" prop="channelId">
<el-select v-model="queryParams.channelId" placeholder="请选择短信渠道" clearable size="small">
<el-option v-for="channel in channelOptions"
:key="channel.id" :value="channel.id"
:label="channel.signature + '【' + getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, channel.code) + '】'" />
</el-select>
</el-form-item>
<el-form-item label="模板编号" prop="templateId">
<el-input v-model="queryParams.templateId" placeholder="请输入模板编号" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="发送状态" prop="sendStatus">
<el-select v-model="queryParams.sendStatus" placeholder="请选择发送状态" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_SMS_SEND_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="发送时间">
<el-date-picker v-model="dateRangeSendTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
<el-form-item label="接收状态" prop="receiveStatus">
<el-select v-model="queryParams.receiveStatus" placeholder="请选择接收状态" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_SMS_RECEIVE_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="接收时间">
<el-date-picker v-model="dateRangeReceiveTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['system:sms-log:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
v-hasPermi="['system:sms-log:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="手机号" align="center" prop="mobile" width="120">
<template slot-scope="scope">
<div>{{ scope.row.mobile }}</div>
<div v-if="scope.row.userType && scope.row.userId">
{{ getDictDataLabel(DICT_TYPE.USER_TYPE, scope.row.userType) + '(' + scope.row.userId + ')' }}
</div>
</template>
</el-table-column>
<el-table-column label="短信内容" align="center" prop="templateContent" width="300" />
<el-table-column label="发送状态" align="center" width="180">
<template slot-scope="scope">
<div>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_SEND_STATUS, scope.row.sendStatus) }}</div>
<div>{{ parseTime(scope.row.sendTime) }}</div>
</template>
</el-table-column>
<el-table-column label="接收状态" align="center" width="180">
<template slot-scope="scope">
<div>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_RECEIVE_STATUS, scope.row.receiveStatus) }}</div>
<div>{{ parseTime(scope.row.receiveTime) }}</div>
</template>
</el-table-column>
<el-table-column label="短信渠道" align="center" width="120">
<template slot-scope="scope">
<div>{{ formatChannelSignature(scope.row.channelId) }}</div>
<div>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}</div>
</template>
</el-table-column>
<el-table-column label="模板编号" align="center" prop="templateId" />
<el-table-column label="短信类型" align="center" prop="templateType">
<template slot-scope="scope">
<span>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_TEMPLATE_TYPE, scope.row.templateType) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row,scope.index)"
v-hasPermi="['system:sms-log:query']">详细</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 短信日志详细 -->
<el-dialog title="短信日志详细" :visible.sync="open" width="700px" append-to-body>
<el-form ref="form" :model="form" label-width="140px" size="mini">
<el-row>
<el-col :span="24">
<el-form-item label="日志主键:">{{ form.id }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="短信渠道:">
{{ formatChannelSignature(form.channelId) }}{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, form.channelCode) }}
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="短信模板:">
{{ form.templateId }} | {{ form.templateCode}} | {{ getDictDataLabel(DICT_TYPE.SYS_SMS_TEMPLATE_TYPE, form.templateType) }}
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="API 的模板编号:">{{ form.apiTemplateId }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="用户信息:">{{ form.mobile }}
<span v-if="form.userType && form.userId"> | {{ getDictDataLabel(DICT_TYPE.USER_TYPE, form.userType) }} | {{ form.userId }}</span>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="短信内容:">{{ form.templateContent }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="短信参数:">{{ form.templateParams }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="创建时间:">{{ parseTime(form.createTime) }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="发送状态:">{{ getDictDataLabel(DICT_TYPE.SYS_SMS_SEND_STATUS, form.sendStatus) }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="发送时间:">{{ parseTime(form.sendTime) }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="发送结果:">{{ form.sendCode }} | {{ form.sendMsg }}
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="API 发送结果:">{{ form.apiSendCode }} | {{ form.apiSendMsg }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="API 短信编号:">{{ form.apiSerialNo }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="API 请求编号:">{{ form.apiRequestId }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="接收状态:">{{ getDictDataLabel(DICT_TYPE.SYS_SMS_RECEIVE_STATUS, form.receiveStatus) }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="接收时间:">{{ parseTime(form.receiveTime) }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="API 接收结果:">{{ form.apiReceiveCode }} | {{ form.apiReceiveMsg }}
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="open = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getSmsLogPage, exportSmsLogExcel } from "@/api/system/sms/smsLog";
import { getSimpleSmsChannels } from "@/api/system/sms/smsChannel";
export default {
name: "SmsLog",
components: {
},
data() {
return {
//
loading: true,
//
showSearch: true,
//
total: 0,
//
list: [],
//
title: "",
//
open: false,
dateRangeSendTime: [],
dateRangeReceiveTime: [],
//
form: {},
//
queryParams: {
pageNo: 1,
pageSize: 10,
channelId: null,
templateId: null,
mobile: null,
sendStatus: null,
receiveStatus: null,
},
//
channelOptions: [],
};
},
created() {
this.getList();
//
getSimpleSmsChannels().then(response => {
this.channelOptions = response.data;
})
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
//
let params = {...this.queryParams};
this.addBeginAndEndTime(params, this.dateRangeSendTime, 'sendTime');
this.addBeginAndEndTime(params, this.dateRangeReceiveTime, 'receiveTime');
//
getSmsLogPage(params).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRangeSendTime = [];
this.dateRangeReceiveTime = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 导出按钮操作 */
handleExport() {
//
let params = {...this.queryParams};
params.pageNo = undefined;
params.pageSize = undefined;
this.addBeginAndEndTime(params, this.dateRangeSendTime, 'sendTime');
this.addBeginAndEndTime(params, this.dateRangeReceiveTime, 'receiveTime');
//
this.$confirm('是否确认导出所有短信日志数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return exportSmsLogExcel(params);
}).then(response => {
this.downloadExcel(response, '短信日志.xls');
})
},
/** 详细按钮操作 */
handleView(row) {
this.open = true;
this.form = row;
},
/** 格式化短信渠道 */
formatChannelSignature(channelId) {
for (const channel of this.channelOptions) {
if (channel.id === channelId) {
return channel.signature;
}
}
return '找不到签名:' + channelId;
}
}
};
</script>

View File

@ -0,0 +1,405 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="150px">
<el-form-item label="短信类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择短信类型" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_SMS_TEMPLATE_TYPE)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="开启状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择开启状态" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_COMMON_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="模板编码" prop="code">
<el-input v-model="queryParams.code" placeholder="请输入模板编码" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="短信 API 的模板编号" prop="apiTemplateId">
<el-input v-model="queryParams.apiTemplateId" placeholder="请输入短信 API 的模板编号" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="短信渠道" prop="channelId">
<el-select v-model="queryParams.channelId" placeholder="请选择短信渠道" clearable size="small">
<el-option v-for="channel in channelOptions"
:key="channel.id" :value="channel.id"
:label="channel.signature + '【' + getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, channel.code) + '】'" />
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker v-model="dateRangeCreateTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['system:sms-template:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
v-hasPermi="['system:sms-template:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="模板编码" align="center" prop="code" />
<el-table-column label="模板名称" align="center" prop="name" />
<el-table-column label="模板内容" align="center" prop="content" width="300" />
<el-table-column label="短信类型" align="center" prop="type">
<template slot-scope="scope">
<span>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_TEMPLATE_TYPE, scope.row.type) }}</span>
</template>
</el-table-column>
<el-table-column label="开启状态" align="center" prop="status">
<template slot-scope="scope">
<span>{{ getDictDataLabel(DICT_TYPE.SYS_COMMON_STATUS, scope.row.status) }}</span>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="短信 API 的模板编号" align="center" prop="apiTemplateId" width="180" />
<el-table-column label="短信渠道" align="center" width="120">
<template slot-scope="scope">
<div>{{ formatChannelSignature(scope.row.channelId) }}</div>
<div>{{ getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, scope.row.channelCode) }}</div>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-share" @click="handleSendSms(scope.row)"
v-hasPermi="['system:sms-template:send-sms']">测试</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['system:sms-template:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['system:sms-template:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="140px">
<el-form-item label="短信渠道编号" prop="channelId">
<el-select v-model="form.channelId" placeholder="请选择短信渠道编号">
<el-option v-for="channel in channelOptions"
:key="channel.id" :value="channel.id"
:label="channel.signature + '【' + getDictDataLabel(DICT_TYPE.SYS_SMS_CHANNEL_CODE, channel.code) + '】'" />
</el-select>
</el-form-item>
<el-form-item label="短信类型" prop="type">
<el-select v-model="form.type" placeholder="请选择短信类型">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYS_SMS_TEMPLATE_TYPE)"
:key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
</el-select>
</el-form-item>
<el-form-item label="模板编号" prop="code">
<el-input v-model="form.code" placeholder="请输入模板编号" />
</el-form-item>
<el-form-item label="模板名称" prop="name">
<el-input v-model="form.name" placeholder="请输入模板名称" />
</el-form-item>
<el-form-item label="模板内容" prop="content">
<el-input type="textarea" v-model="form.content" placeholder="请输入模板内容" />
</el-form-item>
<el-form-item label="开启状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.SYS_COMMON_STATUS)"
:key="dict.value" :label="parseInt(dict.value)">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="短信 API 模板编号" prop="apiTemplateId">
<el-input v-model="form.apiTemplateId" placeholder="请输入短信 API 的模板编号" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<!-- 对话框(发送短信) -->
<el-dialog title="测试发送短信" :visible.sync="sendSmsOpen" width="500px" append-to-body>
<el-form ref="sendSmsForm" :model="sendSmsForm" :rules="sendSmsRules" label-width="140px">
<el-form-item label="模板内容" prop="content">
<el-input v-model="sendSmsForm.content" type="textarea" placeholder="请输入模板内容" readonly />
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input v-model="sendSmsForm.mobile" placeholder="请输入手机号" />
</el-form-item>
<el-form-item v-for="param in sendSmsForm.params" :label="'参数 {' + param + '}'" :prop="'templateParams.' + param">
<el-input v-model="sendSmsForm.templateParams[param]" :placeholder="'请输入 ' + param + ' 参数'" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitSendSmsForm"> </el-button>
<el-button @click="cancelSendSms"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { createSmsTemplate, updateSmsTemplate, deleteSmsTemplate, getSmsTemplate, getSmsTemplatePage,
exportSmsTemplateExcel, sendSms } from "@/api/system/sms/smsTemplate";
import { getSimpleSmsChannels } from "@/api/system/sms/smsChannel";
export default {
name: "SmsTemplate",
data() {
return {
//
loading: true,
//
showSearch: true,
//
total: 0,
//
list: [],
//
title: "",
//
open: false,
dateRangeCreateTime: [],
//
queryParams: {
pageNo: 1,
pageSize: 10,
type: null,
status: null,
code: null,
content: null,
apiTemplateId: null,
channelId: null,
},
//
form: {},
//
rules: {
type: [{ required: true, message: "短信类型不能为空", trigger: "change" }],
status: [{ required: true, message: "开启状态不能为空", trigger: "blur" }],
code: [{ required: true, message: "模板编码不能为空", trigger: "blur" }],
name: [{ required: true, message: "模板名称不能为空", trigger: "blur" }],
content: [{ required: true, message: "模板内容不能为空", trigger: "blur" }],
apiTemplateId: [{ required: true, message: "短信 API 的模板编号不能为空", trigger: "blur" }],
channelId: [{ required: true, message: "短信渠道编号不能为空", trigger: "change" }],
},
//
channelOptions: [],
//
sendSmsOpen: false,
sendSmsForm: {
params: [], //
},
sendSmsRules: {
mobile: [{ required: true, message: "手机不能为空", trigger: "blur" }],
templateCode: [{ required: true, message: "手机不能为空", trigger: "blur" }],
templateParams: { }
}
};
},
created() {
this.getList();
//
getSimpleSmsChannels().then(response => {
this.channelOptions = response.data;
})
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
//
let params = {...this.queryParams};
this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
//
getSmsTemplatePage(params).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
type: undefined,
status: undefined,
code: undefined,
name: undefined,
content: undefined,
remark: undefined,
apiTemplateId: undefined,
channelId: undefined,
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRangeCreateTime = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加短信模板";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getSmsTemplate(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改短信模板";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
//
if (this.form.id != null) {
updateSmsTemplate(this.form).then(response => {
this.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
//
createSmsTemplate(this.form).then(response => {
this.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$confirm('是否确认删除短信模板编号为"' + id + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return deleteSmsTemplate(id);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
})
},
/** 导出按钮操作 */
handleExport() {
//
let params = {...this.queryParams};
params.pageNo = undefined;
params.pageSize = undefined;
this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
//
this.$confirm('是否确认导出所有短信模板数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return exportSmsTemplateExcel(params);
}).then(response => {
this.downloadExcel(response, '短信模板.xls');
})
},
/** 发送短息按钮 */
handleSendSms(row) {
this.resetSendSms(row);
//
this.sendSmsForm.content = row.content;
this.sendSmsForm.params = row.params;
this.sendSmsForm.templateCode = row.code;
this.sendSmsForm.templateParams = row.params.reduce(function(obj, item) {
obj[item] = undefined;
return obj;
}, {});
// row rules
this.sendSmsRules.templateParams = row.params.reduce(function(obj, item) {
obj[item] = { required: true, message: '参数 ' + item + " 不能为空", trigger: "change" };
return obj;
}, {});
//
this.sendSmsOpen = true;
},
/** 重置发送短信的表单 */
resetSendSms() {
// row
this.sendSmsForm = {
content: undefined,
params: undefined,
mobile: undefined,
templateCode: undefined,
templateParams: {}
};
this.resetForm("sendSmsForm");
},
/** 取消发送短信 */
cancelSendSms() {
this.sendSmsOpen = false;
this.resetSendSms();
},
/** 提交按钮 */
submitSendSmsForm() {
this.$refs["sendSmsForm"].validate(valid => {
if (!valid) {
return;
}
//
sendSms(this.sendSmsForm).then(response => {
this.msgSuccess("提交发送成功!发送结果,见发送日志编号:" + response.data);
this.sendSmsOpen = false;
});
});
},
/** 格式化短信渠道 */
formatChannelSignature(channelId) {
for (const channel of this.channelOptions) {
if (channel.id === channelId) {
return channel.signature;
}
}
return '找不到签名:' + channelId;
}
}
};
</script>

File diff suppressed because it is too large Load Diff

View File

@ -13,11 +13,11 @@
<ul class="list-group list-group-striped">
<li class="list-group-item">
<svg-icon icon-class="user" />用户名称
<div class="pull-right">{{ user.userName }}</div>
<div class="pull-right">{{ user.username }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="phone" />手机号码
<div class="pull-right">{{ user.phonenumber }}</div>
<div class="pull-right">{{ user.mobile }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="email" />用户邮箱
@ -25,15 +25,19 @@
</li>
<li class="list-group-item">
<svg-icon icon-class="tree" />所属部门
<div class="pull-right" v-if="user.dept">{{ user.dept.deptName }} / {{ postGroup }}</div>
<div class="pull-right" v-if="user.dept">{{ user.dept.name }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="tree" />所属岗位
<div class="pull-right" v-if="user.posts">{{ user.posts.map(post => post.name).join(',') }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="peoples" />所属角色
<div class="pull-right">{{ roleGroup }}</div>
<div class="pull-right">{{ user.roles.map(post => post.name).join(',') }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="date" />创建日期
<div class="pull-right">{{ user.createTime }}</div>
<div class="pull-right">{{ parseTime(user.createTime) }}</div>
</li>
</ul>
</div>
@ -82,8 +86,6 @@ export default {
getUser() {
getUserProfile().then(response => {
this.user = response.data;
this.roleGroup = response.roleGroup;
this.postGroup = response.postGroup;
});
}
}

View File

@ -1,18 +1,18 @@
<template>
<el-form ref="form" :model="user" :rules="rules" label-width="80px">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="user.nickName" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="user.phonenumber" maxlength="11" />
<el-input v-model="user.nickname" />
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input v-model="user.mobile" maxlength="11" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="user.email" maxlength="50" />
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="user.sex">
<el-radio label="0"></el-radio>
<el-radio label="1"></el-radio>
<el-radio :label="1"></el-radio>
<el-radio :label="2"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
@ -35,7 +35,7 @@ export default {
return {
//
rules: {
nickName: [
nickname: [
{ required: true, message: "用户昵称不能为空", trigger: "blur" }
],
email: [
@ -46,7 +46,7 @@ export default {
trigger: ["blur", "change"]
}
],
phonenumber: [
mobile: [
{ required: true, message: "手机号码不能为空", trigger: "blur" },
{
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,

View File

@ -1,10 +1,21 @@
<template>
<div v-loading="loading" :style="'height:'+ height">
<iframe :src="src" frameborder="no" style="width: 100%;height: 100%" scrolling="auto" />
<div class="app-container">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleExportHtml">导出 HTML</el-button>
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleExportWord">导出 Word</el-button>
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleExportMarkdown">导出 Markdown</el-button>
</el-col>
</el-row>
<!-- 展示文档 -->
<div v-loading="loading" :style="'height:'+ height">
<iframe :src="src" frameborder="no" style="width: 100%;height: 100%" scrolling="auto" />
</div>
</div>
</template>
<script>
import {exportHtml} from "@/api/infra/dbDoc";
import { exportHtml, exportWord, exportMarkdown} from "@/api/infra/dbDoc";
export default {
name: "DBDoc",
@ -25,10 +36,31 @@ export default {
};
},
created() {
// Html
exportHtml().then(response => {
// var blob = new Blob(['<a id="a"><b id="b">hey!</b></a>'], {type : 'text/html'});
this.src = window.URL.createObjectURL(response);
let blob = new Blob([response], {type : 'text/html'});
this.src = window.URL.createObjectURL(blob);
})
},
methods: {
/** 处理导出 HTML */
handleExportHtml() {
exportHtml().then(response => {
this.downloadHtml(response, '数据库文档.html');
})
},
/** 处理导出 Word */
handleExportWord() {
exportWord().then(response => {
this.downloadWord(response, '数据库文档.doc');
})
},
/** 处理导出 Markdown */
handleExportMarkdown() {
exportMarkdown().then(response => {
this.downloadMarkdown(response, '数据库文档.md');
})
}
}
};
</script>

View File

@ -178,3 +178,11 @@ CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIG
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
commit;
-- 初始化默认任务 用户 Session 超时 Job
INSERT INTO QRTZ_JOB_DETAILS VALUES ('schedulerName', 'sysUserSessionTimeoutJob', 'DEFAULT', NULL, 'cn.iocoder.dashboard.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000047400104A4F425F48414E444C45525F4E414D457400187379735573657253657373696F6E54696D656F75744A6F627800);
commit;
INSERT INTO QRTZ_TRIGGERS VALUES ('schedulerName', 'sysUserSessionTimeoutJob', 'DEFAULT', 'sysUserSessionTimeoutJob', 'DEFAULT', NULL, 1615706340000, 1615706280000, 5, 'WAITING', 'CRON', 1615706125000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D707400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B0200007870000007D074000F4A4F425F52455452595F434F554E547371007E0009000000037800);
commit;
INSERT INTO QRTZ_CRON_TRIGGERS VALUES ('schedulerName', 'sysUserSessionTimeoutJob', 'DEFAULT', '0 * * * * ? *', 'Asia/Shanghai');
commit;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,20 @@
package cn.iocoder.dashboard.common.core;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Key Value 的键值对
*
* @author 芋道源码
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class KeyValue<K, V> {
private K key;
private V value;
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.dashboard.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 通用状态枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum DefaultBitFieldEnum {
NO(0, ""),
YES(1, "");
/**
* 状态值
*/
private final Integer val;
/**
* 状态名
*/
private final String name;
}

View File

@ -1,12 +1,13 @@
package cn.iocoder.dashboard.common.exception;
import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.dashboard.common.exception.enums.ServiceErrorCodeRange;
import lombok.Data;
/**
* 错误码对象
*
* 全局错误码占用 [0, 999]参见 {@link GlobalException}
* 全局错误码占用 [0, 999], 参见 {@link GlobalErrorCodeConstants}
* 业务异常错误码占用 [1 000 000 000, +)参见 {@link ServiceErrorCodeRange}
*
* TODO 错误码设计成对象的原因为未来的 i18 国际化做准备
@ -21,11 +22,11 @@ public class ErrorCode {
/**
* 错误提示
*/
private final String message;
private final String msg;
public ErrorCode(Integer code, String message) {
this.code = code;
this.message = message;
this.msg = message;
}
}

View File

@ -1,41 +0,0 @@
package cn.iocoder.dashboard.common.exception;
import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 全局异常 Exception
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GlobalException extends RuntimeException {
/**
* 全局错误码
*
* @see GlobalErrorCodeConstants
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 空构造方法避免反序列化问题
*/
public GlobalException() {
}
public GlobalException(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
}
public GlobalException(Integer code, String message) {
this.code = code;
this.message = message;
}
}

View File

@ -30,7 +30,7 @@ public final class ServiceException extends RuntimeException {
public ServiceException(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
this.message = errorCode.getMsg();
}
public ServiceException(Integer code, String message) {

View File

@ -32,6 +32,7 @@ public interface GlobalErrorCodeConstants {
// ========== 自定义错误段 ==========
ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求
ErrorCode DEMO_DENY = new ErrorCode(901, "演示模式,禁止写操作");
ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");

View File

@ -2,6 +2,7 @@ package cn.iocoder.dashboard.common.exception.util;
import cn.iocoder.dashboard.common.exception.ErrorCode;
import cn.iocoder.dashboard.common.exception.ServiceException;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -46,12 +47,12 @@ public class ServiceExceptionUtil {
// ========== ServiceException 的集成 ==========
public static ServiceException exception(ErrorCode errorCode) {
String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMessage());
String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMsg());
return exception0(errorCode.getCode(), messagePattern);
}
public static ServiceException exception(ErrorCode errorCode, Object... params) {
String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMessage());
String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMsg());
return exception0(errorCode.getCode(), messagePattern, params);
}
@ -91,7 +92,8 @@ public class ServiceExceptionUtil {
* @param params 参数
* @return 格式化后的提示
*/
private static String doFormat(int code, String messagePattern, Object... params) {
@VisibleForTesting
public static String doFormat(int code, String messagePattern, Object... params) {
StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
int i = 0;
int j;

View File

@ -1,7 +1,6 @@
package cn.iocoder.dashboard.common.pojo;
import cn.iocoder.dashboard.common.exception.ErrorCode;
import cn.iocoder.dashboard.common.exception.GlobalException;
import cn.iocoder.dashboard.common.exception.ServiceException;
import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
import com.fasterxml.jackson.annotation.JsonIgnore;
@ -9,6 +8,7 @@ import lombok.Data;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.util.Objects;
/**
* 通用返回
@ -16,7 +16,7 @@ import java.io.Serializable;
* @param <T> 数据泛型
*/
@Data
public final class CommonResult<T> implements Serializable {
public class CommonResult<T> implements Serializable {
/**
* 错误码
@ -31,7 +31,7 @@ public final class CommonResult<T> implements Serializable {
/**
* 错误提示用户可阅读
*
* @see ErrorCode#getMessage() ()
* @see ErrorCode#getMsg() ()
*/
private String msg;
@ -57,7 +57,7 @@ public final class CommonResult<T> implements Serializable {
}
public static <T> CommonResult<T> error(ErrorCode errorCode) {
return error(errorCode.getCode(), errorCode.getMessage());
return error(errorCode.getCode(), errorCode.getMsg());
}
public static <T> CommonResult<T> success(T data) {
@ -68,9 +68,13 @@ public final class CommonResult<T> implements Serializable {
return result;
}
public static boolean isSuccess(Integer code) {
return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode());
}
@JsonIgnore // 避免 jackson 序列化
public boolean isSuccess() {
return GlobalErrorCodeConstants.SUCCESS.getCode().equals(code);
return isSuccess(code);
}
@JsonIgnore // 避免 jackson 序列化
@ -81,16 +85,12 @@ public final class CommonResult<T> implements Serializable {
// ========= Exception 异常体系集成 =========
/**
* 判断是否有异常如果有则抛出 {@link GlobalException} {@link ServiceException} 异常
* 判断是否有异常如果有则抛出 {@link ServiceException} 异常
*/
public void checkError() throws GlobalException, ServiceException {
public void checkError() throws ServiceException {
if (isSuccess()) {
return;
}
// 全局异常
if (GlobalErrorCodeConstants.isMatch(code)) {
throw new GlobalException(code, msg);
}
// 业务异常
throw new ServiceException(code, msg);
}
@ -99,8 +99,4 @@ public final class CommonResult<T> implements Serializable {
return error(serviceException.getCode(), serviceException.getMessage());
}
public static <T> CommonResult<T> error(GlobalException globalException) {
return error(globalException.getCode(), globalException.getMessage());
}
}

View File

@ -13,14 +13,17 @@ import java.io.Serializable;
@Data
public class PageParam implements Serializable {
private static final Integer PAGE_NO = 1;
private static final Integer PAGE_SIZE = 10;
@ApiModelProperty(value = "页码,从 1 开始", required = true,example = "1")
@NotNull(message = "页码不能为空")
@Min(value = 1, message = "页码最小值为 1")
private Integer pageNo;
private Integer pageNo = PAGE_NO;
@ApiModelProperty(value = "每页条数,最大值为 100", required = true, example = "10")
@NotNull(message = "每页条数不能为空")
@Range(min = 1, max = 100, message = "条数范围为 [1, 100]")
private Integer pageSize;
private Integer pageSize = PAGE_SIZE;
}

View File

@ -1,12 +1,38 @@
package cn.iocoder.dashboard.framework.datasource.config;
import cn.iocoder.dashboard.framework.datasource.core.filter.DruidAdRemoveFilter;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* 数据库匹配类
* 数据库配置类
*
* @author 芋道源码
*/
@Configuration
@EnableTransactionManagement(proxyTargetClass = true) // 启动事务管理
public class DataSourceConfiguration {
/**
* 创建 DruidAdRemoveFilter 过滤器过滤 common.js 的广告
*/
@Bean
@ConditionalOnProperty(name = "spring.datasource.druid.web-stat-filter.enabled", havingValue = "true")
public FilterRegistrationBean<DruidAdRemoveFilter> druidAdRemoveFilterFilter(DruidStatProperties properties) {
// 获取 druid web 监控页面的参数
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
// 提取 common.js 的配置路径
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
// 创建 DruidAdRemoveFilter Bean
FilterRegistrationBean<DruidAdRemoveFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new DruidAdRemoveFilter());
registrationBean.addUrlPatterns(commonJsPattern);
return registrationBean;
}
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.dashboard.framework.datasource.core.enums;
/**
* 对应于多数据源中不同数据源配置
*
* 通过在方法上使用 {@link com.baomidou.dynamic.datasource.annotation.DS} 注解设置使用的数据源
* 注意默认是 {@link #MASTER} 数据源
*
* 对应官方文档为 http://dynamic-datasource.com/guide/customize/Annotation.html
*/
public interface DataSourceEnum {
/**
* 主库推荐使用 {@link com.baomidou.dynamic.datasource.annotation.Master} 注解
*/
String MASTER = "master";
/**
* 从库推荐使用 {@link com.baomidou.dynamic.datasource.annotation.Slave} 注解
*/
String SLAVE = "slave";
}

View File

@ -0,0 +1,38 @@
package cn.iocoder.dashboard.framework.datasource.core.filter;
import com.alibaba.druid.util.Utils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Druid 底部广告过滤器
*
* @author 芋道源码
*/
public class DruidAdRemoveFilter extends OncePerRequestFilter {
/**
* common.js 的路径
*/
private static final String COMMON_JS_ILE_PATH = "support/http/resources/js/common.js";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
chain.doFilter(request, response);
// 重置缓冲区响应头不会被重置
response.resetBuffer();
// 获取 common.js
String text = Utils.readFromResource(COMMON_JS_ILE_PATH);
// 正则替换 banner, 除去底部的广告信息
text = text.replaceAll("<a.*?banner\"></a><br/>", "");
text = text.replaceAll("powered.*?shrek.wang</a>", "");
response.getWriter().write(text);
}
}

View File

@ -0,0 +1,36 @@
package cn.iocoder.dashboard.framework.errorcode.config;
import cn.iocoder.dashboard.framework.errorcode.core.generator.ErrorCodeAutoGenerator;
import cn.iocoder.dashboard.framework.errorcode.core.loader.ErrorCodeLoader;
import cn.iocoder.dashboard.framework.errorcode.core.service.ErrorCodeFrameworkService;
import cn.iocoder.dashboard.framework.errorcode.core.loader.ErrorCodeLoaderImpl;
import cn.iocoder.dashboard.framework.errorcode.core.generator.ErrorCodeAutoGeneratorImpl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 错误码配置类
*/
@Configuration
@EnableConfigurationProperties(ErrorCodeProperties.class)
@EnableScheduling // 开启调度任务的功能因为 ErrorCodeRemoteLoader 通过定时刷新错误码
public class ErrorCodeConfiguration {
@Bean
public ErrorCodeAutoGenerator errorCodeAutoGenerator(@Value("${spring.application.name}") String applicationName,
ErrorCodeProperties errorCodeProperties,
ErrorCodeFrameworkService errorCodeFrameworkService) {
return new ErrorCodeAutoGeneratorImpl(applicationName, errorCodeProperties.getConstantsClassList(),
errorCodeFrameworkService);
}
@Bean
public ErrorCodeLoader errorCodeLoader(@Value("${spring.application.name}") String applicationName,
ErrorCodeFrameworkService errorCodeFrameworkService) {
return new ErrorCodeLoaderImpl(applicationName, errorCodeFrameworkService);
}
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.dashboard.framework.errorcode.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 错误码的配置属性类
*
* @author dlyan
*/
@ConfigurationProperties("yudao.error-code")
@Data
@Validated
public class ErrorCodeProperties {
/**
* 错误码枚举类
*/
@NotNull(message = "错误码枚举类不能为空")
private List<String> constantsClassList;
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.dashboard.framework.errorcode.core.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 错误码自动生成 DTO
*
* @author dylan
*/
@Data
@Accessors(chain = true)
public class ErrorCodeAutoGenerateReqDTO {
/**
* 应用名
*/
@NotNull(message = "应用名不能为空")
private String applicationName;
/**
* 错误码编码
*/
@NotNull(message = "错误码编码不能为空")
private Integer code;
/**
* 错误码错误提示
*/
@NotEmpty(message = "错误码错误提示不能为空")
private String message;
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.dashboard.framework.errorcode.core.dto;
import lombok.Data;
import java.util.Date;
/**
* 错误码的 Response DTO
*
* @author 芋道源码
*/
@Data
public class ErrorCodeRespDTO {
/**
* 错误码编码
*/
private Integer code;
/**
* 错误码错误提示
*/
private String message;
/**
* 更新时间
*/
private Date updateTime;
}

View File

@ -0,0 +1,15 @@
package cn.iocoder.dashboard.framework.errorcode.core.generator;
/**
* 错误码的自动生成器
*
* @author dylan
*/
public interface ErrorCodeAutoGenerator {
/**
* 将配置类到错误码写入数据库
*/
void execute();
}

View File

@ -0,0 +1,98 @@
package cn.iocoder.dashboard.framework.errorcode.core.generator;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.dashboard.common.exception.ErrorCode;
import cn.iocoder.dashboard.framework.errorcode.core.dto.ErrorCodeAutoGenerateReqDTO;
import cn.iocoder.dashboard.framework.errorcode.core.service.ErrorCodeFrameworkService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* ErrorCodeAutoGenerator 的实现类
* 目的是扫描指定的 {@link #constantsClassList} 写入到 system 服务中
*
* @author dylan
*/
@RequiredArgsConstructor
@Slf4j
public class ErrorCodeAutoGeneratorImpl implements ErrorCodeAutoGenerator {
/**
* 应用分组
*/
private final String applicationName;
/**
* 错误码枚举类
*/
private final List<String> constantsClassList;
/**
* 错误码 Service
*/
private final ErrorCodeFrameworkService errorCodeService;
@Override
@EventListener(ApplicationReadyEvent.class)
@Async // 异步保证项目的启动过程毕竟非关键流程
public void execute() {
// 第一步解析错误码
List<ErrorCodeAutoGenerateReqDTO> autoGenerateDTOs = parseErrorCode();
log.info("[execute][解析到错误码数量为 ({}) 个]", autoGenerateDTOs.size());
// 第二步写入到 system 服务
errorCodeService.autoGenerateErrorCodes(autoGenerateDTOs);
log.info("[execute][写入到 system 组件完成]");
}
/**
* 解析 constantsClassList 变量转换成错误码数组
*
* @return 错误码数组
*/
private List<ErrorCodeAutoGenerateReqDTO> parseErrorCode() {
// 校验 errorCodeConstantsClass 参数
if (CollUtil.isEmpty(constantsClassList)) {
log.info("[execute][未配置 yudao.error-code.constants-class-list 配置项,不进行自动写入到 system 服务中]");
return new ArrayList<>();
}
// 解析错误码
List<ErrorCodeAutoGenerateReqDTO> autoGenerateDTOs = new ArrayList<>();
constantsClassList.forEach(constantsClass -> {
// 解析错误码枚举类
Class<?> errorCodeConstantsClazz = ClassUtil.loadClass(constantsClass);
// 解析错误码
autoGenerateDTOs.addAll(parseErrorCode(errorCodeConstantsClazz));
});
return autoGenerateDTOs;
}
/**
* 解析错误码类获得错误码数组
*
* @return 错误码数组
*/
private List<ErrorCodeAutoGenerateReqDTO> parseErrorCode(Class<?> constantsClass) {
List<ErrorCodeAutoGenerateReqDTO> autoGenerateDTOs = new ArrayList<>();
Arrays.stream(constantsClass.getFields()).forEach(field -> {
if (field.getType() != ErrorCode.class) {
return;
}
// 转换成 ErrorCodeAutoGenerateReqDTO 对象
ErrorCode errorCode = (ErrorCode) ReflectUtil.getFieldValue(constantsClass, field);
autoGenerateDTOs.add(new ErrorCodeAutoGenerateReqDTO().setApplicationName(applicationName)
.setCode(errorCode.getCode()).setMessage(errorCode.getMsg()));
});
return autoGenerateDTOs;
}
}

View File

@ -0,0 +1,24 @@
package cn.iocoder.dashboard.framework.errorcode.core.loader;
import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
/**
* 错误码加载器
*
* 注意错误码最终加载到 {@link cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil} MESSAGES 变量中
*
* @author dlyan
*/
public interface ErrorCodeLoader {
/**
* 添加错误码
*
* @param code 错误码的编号
* @param msg 错误码的提示
*/
default void putErrorCode(Integer code, String msg) {
ServiceExceptionUtil.put(code, msg);
}
}

View File

@ -0,0 +1,73 @@
package cn.iocoder.dashboard.framework.errorcode.core.loader;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.dashboard.framework.errorcode.core.dto.ErrorCodeRespDTO;
import cn.iocoder.dashboard.framework.errorcode.core.service.ErrorCodeFrameworkService;
import cn.iocoder.dashboard.util.date.DateUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled;
import java.util.Date;
import java.util.List;
/**
* ErrorCodeLoader 的实现类 infra 的数据库中加载错误码
*
* 考虑到错误码会刷新所以按照 {@link #REFRESH_ERROR_CODE_PERIOD} 频率增量加载错误码
*
* @author dlyan
*/
@RequiredArgsConstructor
@Slf4j
public class ErrorCodeLoaderImpl implements ErrorCodeLoader {
/**
* 刷新错误码的频率单位毫秒
*/
private static final int REFRESH_ERROR_CODE_PERIOD = 60 * 1000;
/**
* 应用分组
*/
private final String applicationName;
/**
* 错误码 Service
*/
private final ErrorCodeFrameworkService errorCodeService;
/**
* 缓存错误码的最大更新时间用于后续的增量轮询判断是否有更新
*/
private Date maxUpdateTime;
@EventListener(ApplicationReadyEvent.class)
public void loadErrorCodes() {
this.loadErrorCodes0();
}
@Scheduled(fixedDelay = REFRESH_ERROR_CODE_PERIOD, initialDelay = REFRESH_ERROR_CODE_PERIOD)
public void refreshErrorCodes() {
this.loadErrorCodes0();
}
private void loadErrorCodes0() {
// 加载错误码
List<ErrorCodeRespDTO> errorCodeRespDTOs = errorCodeService.getErrorCodeList(applicationName, maxUpdateTime);
if (CollUtil.isEmpty(errorCodeRespDTOs)) {
return;
}
log.info("[loadErrorCodes0][加载到 ({}) 个错误码]", errorCodeRespDTOs.size());
// 刷新错误码的缓存
errorCodeRespDTOs.forEach(errorCodeRespDTO -> {
// 写入到错误码的缓存
putErrorCode(errorCodeRespDTO.getCode(), errorCodeRespDTO.getMessage());
// 记录下更新时间方便增量更新
maxUpdateTime = DateUtils.max(maxUpdateTime, errorCodeRespDTO.getUpdateTime());
});
}
}

View File

@ -0,0 +1,35 @@
package cn.iocoder.dashboard.framework.errorcode.core.service;
import cn.iocoder.dashboard.framework.errorcode.core.dto.ErrorCodeAutoGenerateReqDTO;
import cn.iocoder.dashboard.framework.errorcode.core.dto.ErrorCodeRespDTO;
import javax.validation.Valid;
import java.util.Date;
import java.util.List;
/**
* 错误码 Framework Service 接口
*
* @author 芋道源码
*/
public interface ErrorCodeFrameworkService {
/**
* 自动创建错误码
*
* @param autoGenerateDTOs 错误码信息
*/
void autoGenerateErrorCodes(@Valid List<ErrorCodeAutoGenerateReqDTO> autoGenerateDTOs);
/**
* 增量获得错误码数组
*
* 如果 minUpdateTime 为空时则获取所有错误码
*
* @param applicationName 应用名
* @param minUpdateTime 最小更新时间
* @return 错误码数组
*/
List<ErrorCodeRespDTO> getErrorCodeList(String applicationName, Date minUpdateTime);
}

View File

@ -13,6 +13,11 @@ import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import lombok.extern.slf4j.Slf4j;
/**
* Excel {@link SysDictDataDO} 数据字典转换器
*
* @author 芋道源码
*/
@Slf4j
public class DictConvert implements Converter<Object> {

View File

@ -0,0 +1,39 @@
package cn.iocoder.dashboard.framework.excel.core.convert;
import cn.iocoder.dashboard.util.json.JsonUtils;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
/**
* Excel Json 转换器
*
* @author 芋道源码
*/
public class JsonConvert implements Converter<Object> {
@Override
public Class<?> supportJavaTypeKey() {
throw new UnsupportedOperationException("暂不支持,也不需要");
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
throw new UnsupportedOperationException("暂不支持,也不需要");
}
@Override
public Object convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
throw new UnsupportedOperationException("暂不支持,也不需要");
}
@Override
public CellData<String> convertToExcelData(Object value, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
// 生成 Excel 小表格
return new CellData<>(JsonUtils.toJsonString(value));
}
}

View File

@ -1,6 +1,6 @@
package cn.iocoder.dashboard.framework.file.config;
import cn.iocoder.dashboard.modules.system.controller.common.SysFileController;
import cn.iocoder.dashboard.modules.infra.controller.file.InfFileController;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@ -13,7 +13,7 @@ import javax.validation.constraints.NotNull;
public class FileProperties {
/**
* 对应 {@link SysFileController#}
* 对应 {@link InfFileController#}
*/
@NotNull(message = "基础文件路径不能为空")
private String basePath;

View File

@ -1,18 +1,35 @@
package cn.iocoder.dashboard.framework.jackson.config;
import cn.iocoder.dashboard.framework.jackson.deser.LocalDateTimeDeserializer;
import cn.iocoder.dashboard.framework.jackson.ser.LocalDateTimeSerializer;
import cn.iocoder.dashboard.util.json.JsonUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
@Configuration
public class JacksonConfig {
@Bean
@SuppressWarnings("InstantiationOfUtilityClass")
public JsonUtils jsonUtils(ObjectMapper objectMapper) {
SimpleModule simpleModule = new SimpleModule();
/*
* 1. 新增Long类型序列化规则数值超过2^53-1在JS会出现精度丢失问题因此Long自动序列化为字符串类型
* 2. 新增LocalDateTime序列化反序列化规则
*/
simpleModule
// .addSerializer(Long.class, ToStringSerializer.instance)
// .addSerializer(Long.TYPE, ToStringSerializer.instance)
.addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE)
.addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE);
objectMapper.registerModules(simpleModule);
JsonUtils.init(objectMapper);
return new JsonUtils();
}
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.dashboard.framework.jackson.deser;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
* LocalDateTime反序列化规则
* <p>
* 会将毫秒级时间戳反序列化为LocalDateTime
*/
public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
public static final LocalDateTimeDeserializer INSTANCE = new LocalDateTimeDeserializer();
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
}
}

View File

@ -0,0 +1,24 @@
package cn.iocoder.dashboard.framework.jackson.ser;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
* LocalDateTime序列化规则
* <p>
* 会将LocalDateTime序列化为毫秒级时间戳
*/
public class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
public static final LocalDateTimeSerializer INSTANCE = new LocalDateTimeSerializer();
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
}
}

View File

@ -81,7 +81,7 @@ public class ApiAccessLogFilter extends OncePerRequestFilter {
Map<String, String> queryString, String requestBody, Exception ex) {
// 处理用户信息
accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request));
accessLog.setUserType(WebFrameworkUtils.getUesrType(request));
accessLog.setUserType(WebFrameworkUtils.getUserType(request));
// 设置访问结果
CommonResult<?> result = WebFrameworkUtils.getCommonResult(request);
if (result != null) {

View File

@ -3,6 +3,7 @@ package cn.iocoder.dashboard.framework.logger.apilog.core.service;
import cn.iocoder.dashboard.framework.logger.apilog.core.service.dto.ApiAccessLogCreateDTO;
import javax.validation.Valid;
import java.util.concurrent.Future;
/**
* API 访问日志 Framework Service 接口
@ -15,7 +16,8 @@ public interface ApiAccessLogFrameworkService {
* 创建 API 访问日志
*
* @param createDTO 创建信息
* @return 是否创建成功
*/
void createApiAccessLogAsync(@Valid ApiAccessLogCreateDTO createDTO);
Future<Boolean> createApiAccessLogAsync(@Valid ApiAccessLogCreateDTO createDTO);
}

View File

@ -3,6 +3,7 @@ package cn.iocoder.dashboard.framework.logger.apilog.core.service;
import cn.iocoder.dashboard.framework.logger.apilog.core.service.dto.ApiErrorLogCreateDTO;
import javax.validation.Valid;
import java.util.concurrent.Future;
/**
* API 错误日志 Framework Service 接口
@ -15,7 +16,8 @@ public interface ApiErrorLogFrameworkService {
* 创建 API 错误日志
*
* @param createDTO 创建信息
* @return 是否创建成功
*/
void createApiErrorLogAsync(@Valid ApiErrorLogCreateDTO createDTO);
Future<Boolean> createApiErrorLogAsync(@Valid ApiErrorLogCreateDTO createDTO);
}

View File

@ -2,13 +2,16 @@ package cn.iocoder.dashboard.framework.logger.operatelog.core.service;
import cn.iocoder.dashboard.modules.system.controller.logger.vo.operatelog.SysOperateLogCreateReqVO;
import java.util.concurrent.Future;
public interface OperateLogFrameworkService {
/**
* 要不记录操作日志
* 异步记录操作日志
*
* @param reqVO 操作日志请求
* @return true: 记录成功,false: 记录失败
*/
void createOperateLogAsync(SysOperateLogCreateReqVO reqVO);
Future<Boolean> createOperateLogAsync(SysOperateLogCreateReqVO reqVO);
}

View File

@ -1,5 +1,7 @@
package cn.iocoder.dashboard.framework.mybatis.config;
import cn.iocoder.dashboard.framework.mybatis.core.handler.DefaultDBFieldHandler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.annotations.Mapper;
@ -13,7 +15,8 @@ import org.springframework.context.annotation.Configuration;
* @author 芋道源码
*/
@Configuration
@MapperScan(value = "${yudao.info.base-package}", annotationClass = Mapper.class)
@MapperScan(value = "${yudao.info.base-package}", annotationClass = Mapper.class,
lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载目前仅用于单元测试
public class MybatisConfiguration {
@Bean
@ -23,4 +26,9 @@ public class MybatisConfiguration {
return mybatisPlusInterceptor;
}
@Bean
public MetaObjectHandler defaultMetaObjectHandler(){
return new DefaultDBFieldHandler(); // 自动填充参数类
}
}

View File

@ -1,5 +1,7 @@
package cn.iocoder.dashboard.framework.mybatis.core.dataobject;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
@ -15,19 +17,27 @@ public class BaseDO implements Serializable {
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 最后更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 创建者 TODO 芋艿迁移成编号
* 创建者目前使用 SysUser id 编号
*
* 使用 String 类型的原因是未来可能会存在非数值的情况留好拓展性
*/
private String createBy;
@TableField(fill = FieldFill.INSERT)
private String creator;
/**
* 更新者 TODO 芋艿迁移成编号
* 更新者目前使用 SysUser id 编号
*
* 使用 String 类型的原因是未来可能会存在非数值的情况留好拓展性
*/
private String updateBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updater;
/**
* 是否删除
*/

View File

@ -0,0 +1,63 @@
package cn.iocoder.dashboard.framework.mybatis.core.handler;
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.dashboard.framework.security.core.LoginUser;
import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import java.util.Date;
import java.util.Objects;
/**
* 通用参数填充实现类
*
* 如果没有显式的对通用参数进行赋值这里会对通用参数进行填充赋值
*
* @author hexiaowu
*/
public class DefaultDBFieldHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO) {
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
BaseDO baseDO = (BaseDO) metaObject.getOriginalObject();
Date current = new Date();
// 创建时间为空则以当前时间为插入时间
if (Objects.isNull(baseDO.getCreateTime())) {
baseDO.setCreateTime(current);
}
// 更新时间为空则以当前时间为更新时间
if (Objects.isNull(baseDO.getUpdateTime())) {
baseDO.setUpdateTime(current);
}
// 当前登录用户不为空创建人为空则当前登录用户为创建人
if (Objects.nonNull(loginUser) && Objects.isNull(baseDO.getCreator())) {
baseDO.setCreator(loginUser.getId().toString());
}
// 当前登录用户不为空更新人为空则当前登录用户为更新人
if (Objects.nonNull(loginUser) && Objects.isNull(baseDO.getUpdater())) {
baseDO.setUpdater(loginUser.getId().toString());
}
}
}
@Override
public void updateFill(MetaObject metaObject) {
Object modifyTime = getFieldValByName("updateTime", metaObject);
Object modifier = getFieldValByName("updater", metaObject);
// 获取登录用户信息
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
// 更新时间为空则以当前时间为更新时间
if (Objects.isNull(modifyTime)) {
setFieldValByName("updateTime", new Date(), metaObject);
}
// 当前登录用户不为空更新人为空则当前登录用户为更新人
if (Objects.nonNull(loginUser) && Objects.isNull(modifier)) {
setFieldValByName("updater", loginUser.getId().toString(), metaObject);
}
}
}

View File

@ -24,12 +24,16 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
}
default List<T> selectList() {
return selectList(new QueryWrapper<>());
}
default T selectOne(String field, Object value) {
return selectOne(new QueryWrapper<T>().eq(field, value));
}
default Integer selectCount(String field, Object value) {
return selectCount(new QueryWrapper<T>().eq(field, value));
}
default List<T> selectList() {
return selectList(new QueryWrapper<>());
}
}

View File

@ -1,14 +1,21 @@
package cn.iocoder.dashboard.framework.redis.config;
import cn.hutool.system.SystemUtil;
import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener;
import cn.iocoder.dashboard.framework.redis.core.stream.AbstractStreamMessageListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.stream.Consumer;
import org.springframework.data.redis.connection.stream.ObjectRecord;
import org.springframework.data.redis.connection.stream.ReadOffset;
import org.springframework.data.redis.connection.stream.StreamOffset;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.stream.StreamMessageListenerContainer;
import java.util.List;
@ -19,6 +26,9 @@ import java.util.List;
@Slf4j
public class RedisConfig {
/**
* 创建 RedisTemplate Bean使用 JSON 序列化方式
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 创建 RedisTemplate 对象
@ -27,14 +37,19 @@ public class RedisConfig {
template.setConnectionFactory(factory);
// 使用 String 序列化方式序列化 KEY
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 使用 JSON 序列化方式库是 Jackson 序列化 VALUE
template.setValueSerializer(RedisSerializer.json());
template.setHashValueSerializer(RedisSerializer.json());
return template;
}
/**
* 创建 Redis Pub/Sub 广播消费的容器
*/
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory factory,
List<AbstractChannelMessageListener<?>> listeners) {
public RedisMessageListenerContainer redisMessageListenerContainer(
RedisConnectionFactory factory, List<AbstractChannelMessageListener<?>> listeners) {
// 创建 RedisMessageListenerContainer 对象
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
// 设置 RedisConnection 工厂
@ -48,4 +63,57 @@ public class RedisConfig {
return container;
}
/**
* 创建 Redis Stream 集群消费的容器
*
* Redis Stream xreadgroup 命令https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html
*/
@Bean(initMethod = "start", destroyMethod = "stop")
public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer(
RedisTemplate<String, Object> redisTemplate, List<AbstractStreamMessageListener<?>> listeners) {
// 第一步创建 StreamMessageListenerContainer 容器
// 创建 options 配置
StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, ObjectRecord<String, String>> containerOptions =
StreamMessageListenerContainer.StreamMessageListenerContainerOptions.builder()
.batchSize(10) // 一次性最多拉取多少条消息
.targetType(String.class) // 目标类型统一使用 String通过自己封装的 AbstractStreamMessageListener 去反序列化
.build();
// 创建 container 对象
StreamMessageListenerContainer<String, ObjectRecord<String, String>> container = StreamMessageListenerContainer.create(
redisTemplate.getRequiredConnectionFactory(), containerOptions);
// 第二步注册监听器消费对应的 Stream 主题
String consumerName = buildConsumerName();
// String consumerName = "110";
listeners.forEach(listener -> {
// 创建 listener 对应的消费者分组
try {
redisTemplate.opsForStream().createGroup(listener.getStreamKey(), listener.getGroup());
} catch (Exception ignore) {}
// 设置 listener 对应的 redisTemplate
listener.setRedisTemplate(redisTemplate);
// 创建 Consumer 对象
Consumer consumer = Consumer.from(listener.getGroup(), consumerName);
// 设置 Consumer 消费进度以最小消费进度为准
StreamOffset<String> streamOffset = StreamOffset.create(listener.getStreamKey(), ReadOffset.lastConsumed());
// 设置 Consumer 监听
StreamMessageListenerContainer.StreamReadRequestBuilder<String> builder = StreamMessageListenerContainer.StreamReadRequest
.builder(streamOffset).consumer(consumer)
.autoAcknowledge(false) // 不自动 ack
.cancelOnError(throwable -> false); // 默认配置发生异常就取消消费显然不符合预期因此我们设置为 false
container.register(builder.build(), listener);
});
return container;
}
/**
* 构建消费者名字使用本地 IP + 进程编号的方式
* 参考自 RocketMQ clientId 的实现
*
* @return 消费者名字
*/
private static String buildConsumerName() {
return String.format("%s@%d", SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID());
}
}

View File

@ -1,11 +1,10 @@
package cn.iocoder.dashboard.framework.redis.core.pubsub;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.TypeUtil;
import cn.iocoder.dashboard.util.json.JsonUtils;
import lombok.SneakyThrows;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
import java.lang.reflect.Type;
@ -62,21 +61,11 @@ public abstract class AbstractChannelMessageListener<T extends ChannelMessage> i
*/
@SuppressWarnings("unchecked")
private Class<T> getMessageClass() {
Class<?> targetClass = getClass();
while (targetClass.getSuperclass() != null) {
// 如果不是 AbstractMessageListener 父类继续向上查找
if (targetClass.getSuperclass() != AbstractChannelMessageListener.class) {
targetClass = targetClass.getSuperclass();
continue;
}
// 如果是 AbstractMessageListener 父类则解析泛型
Type[] types = ((ParameterizedTypeImpl) targetClass.getGenericSuperclass()).getActualTypeArguments();
if (ArrayUtil.isEmpty(types)) {
throw new IllegalStateException(String.format("类型(%s) 需要设置消息类型", getClass().getName()));
}
return (Class<T>) types[0];
Type type = TypeUtil.getTypeArgument(getClass(), 0);
if (type == null) {
throw new IllegalStateException(String.format("类型(%s) 需要设置消息类型", getClass().getName()));
}
throw new IllegalStateException(String.format("类型(%s) 找不到 AbstractMessageListener 父类", getClass().getName()));
return (Class<T>) type;
}
}

View File

@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* Redis Channel Message 接口
*
* @author 芋道源码
*/
public interface ChannelMessage {
@ -12,7 +14,7 @@ public interface ChannelMessage {
*
* @return Channel
*/
@JsonIgnore // 必须序列化
@JsonIgnore // 避免序列化
String getChannel();
}

View File

@ -0,0 +1,88 @@
package cn.iocoder.dashboard.framework.redis.core.stream;
import cn.hutool.core.util.TypeUtil;
import cn.iocoder.dashboard.util.json.JsonUtils;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.stream.ObjectRecord;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.stream.StreamListener;
import java.lang.reflect.Type;
/**
* Redis Stream 监听器抽象类用于实现集群消费
*
* @param <T> 消息类型一定要填写噢不然会报错
*
* @author 芋道源码
*/
public abstract class AbstractStreamMessageListener<T extends StreamMessage>
implements StreamListener<String, ObjectRecord<String, String>> {
/**
* 消息类型
*/
private final Class<T> messageType;
/**
* Redis Channel
*/
@Getter
private final String streamKey;
/**
* Redis 消费者分组默认使用 spring.application.name 名字
*/
@Value("${spring.application.name}")
@Getter
private String group;
/**
*
*/
@Setter
private RedisTemplate<String, ?> redisTemplate;
@SneakyThrows
protected AbstractStreamMessageListener() {
this.messageType = getMessageClass();
this.streamKey = messageType.newInstance().getStreamKey();
}
@Override
public void onMessage(ObjectRecord<String, String> message) {
// 消费消息
T messageObj = JsonUtils.parseObject(message.getValue(), messageType);
this.onMessage(messageObj);
// ack 消息消费完成
redisTemplate.opsForStream().acknowledge(group, message);
// TODO 芋艿需要额外考虑以下几个点
// 1. 处理异常的情况
// 2. 发送日志以及事务的结合
// 3. 消费日志以及通用的幂等性
// 4. 消费失败的重试https://zhuanlan.zhihu.com/p/60501638
}
/**
* 处理消息
*
* @param message 消息
*/
public abstract void onMessage(T message);
/**
* 通过解析类上的泛型获得消息类型
*
* @return 消息类型
*/
@SuppressWarnings("unchecked")
private Class<T> getMessageClass() {
Type type = TypeUtil.getTypeArgument(getClass(), 0);
if (type == null) {
throw new IllegalStateException(String.format("类型(%s) 需要设置消息类型", getClass().getName()));
}
return (Class<T>) type;
}
}

View File

@ -0,0 +1,20 @@
package cn.iocoder.dashboard.framework.redis.core.stream;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* Redis Stream Message 接口
*
* @author 芋道源码
*/
public interface StreamMessage {
/**
* 获得 Redis Stream Key
*
* @return Channel
*/
@JsonIgnore // 避免序列化
String getStreamKey();
}

View File

@ -1,7 +1,10 @@
package cn.iocoder.dashboard.framework.redis.core.util;
import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage;
import cn.iocoder.dashboard.framework.redis.core.stream.StreamMessage;
import cn.iocoder.dashboard.util.json.JsonUtils;
import org.springframework.data.redis.connection.stream.RecordId;
import org.springframework.data.redis.connection.stream.StreamRecords;
import org.springframework.data.redis.core.RedisTemplate;
/**
@ -17,8 +20,21 @@ public class RedisMessageUtils {
* @param redisTemplate Redis 操作模板
* @param message 消息
*/
public static <T extends ChannelMessage> void sendChannelMessage(RedisTemplate<?, ?> redisTemplate, T message) {
public static <T extends ChannelMessage> void sendChannelMessage(RedisTemplate<?, ?> redisTemplate, T message) {
redisTemplate.convertAndSend(message.getChannel(), JsonUtils.toJsonString(message));
}
/**
* 发送 Redis 消息基于 Redis Stream 实现
*
* @param redisTemplate Redis 操作模板
* @param message 消息
* @return 消息记录的编号对象
*/
public static <T extends StreamMessage> RecordId sendStreamMessage(RedisTemplate<String, ?> redisTemplate, T message) {
return redisTemplate.opsForStream().add(StreamRecords.newRecord()
.ofObject(JsonUtils.toJsonString(message)) // 设置内容
.withStreamKey(message.getStreamKey())); // 设置 stream key
}
}

View File

@ -1 +0,0 @@
<http://www.iocoder.cn/Spring-Boot/Spring-Security/?github>

View File

@ -0,0 +1 @@
<https://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao>

View File

@ -128,13 +128,13 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// 设置每个请求的权限
.authorizeRequests()
// 登陆的接口可匿名访问
.antMatchers(webProperties.getApiPrefix() + "/login").anonymous()
.antMatchers(api("/login")).anonymous()
// 通用的接口可匿名访问
.antMatchers( webProperties.getApiPrefix() + "/system/captcha/**").anonymous()
.antMatchers(api("/system/captcha/**")).anonymous()
// 静态资源可匿名访问
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
// 文件的获取接口可匿名访问
.antMatchers(webProperties.getApiPrefix() + "/system/file/get/**").anonymous()
.antMatchers(api("/infra/file/get/**")).anonymous()
// Swagger 接口文档
.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
@ -148,13 +148,19 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
.antMatchers("/actuator/**").anonymous()
// Druid 监控
.antMatchers("/druid/**").anonymous()
// 短信回调 API
.antMatchers(api("/system/sms/callback/**")).anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
httpSecurity.logout().logoutUrl(api("/logout")).logoutSuccessHandler(logoutSuccessHandler);
// 添加 JWT Filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
private String api(String url) {
return webProperties.getApiPrefix() + url;
}
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.dashboard.framework.security.core.handler;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.dashboard.common.pojo.CommonResult;
import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
import cn.iocoder.dashboard.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils;
@ -36,6 +37,6 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
securityFrameworkService.logout(token);
}
// 返回成功
ServletUtils.writeJSON(response, null);
ServletUtils.writeJSON(response, CommonResult.success(null));
}
}

View File

@ -2,7 +2,10 @@ package cn.iocoder.dashboard.framework.security.core.util;
import cn.iocoder.dashboard.framework.security.core.LoginUser;
import cn.iocoder.dashboard.framework.web.core.util.WebFrameworkUtils;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
@ -40,9 +43,20 @@ public class SecurityFrameworkUtils {
/**
* 获取当前用户
*
* @return 当前用户
*/
@Nullable
public static LoginUser getLoginUser() {
return (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
SecurityContext context = SecurityContextHolder.getContext();
if (context == null) {
return null;
}
Authentication authentication = context.getAuthentication();
if (authentication == null) {
return null;
}
return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
}
/**
@ -50,8 +64,10 @@ public class SecurityFrameworkUtils {
*
* @return 用户编号
*/
@Nullable
public static Long getLoginUserId() {
return getLoginUser().getId();
LoginUser loginUser = getLoginUser();
return loginUser != null ? loginUser.getId() : null;
}
/**
@ -59,8 +75,10 @@ public class SecurityFrameworkUtils {
*
* @return 角色编号数组
*/
@Nullable
public static Set<Long> getLoginUserRoleIds() {
return getLoginUser().getRoleIds();
LoginUser loginUser = getLoginUser();
return loginUser != null ? loginUser.getRoleIds() : null;
}
/**

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