80 Commits

Author SHA1 Message Date
RuoYi
24be1a436e 若依 3.1.0 2021-08-01 09:01:15 +08:00
RuoYi
370c53edfa 文件服务本地资源允许跨域访问 2021-07-31 21:51:40 +08:00
RuoYi
f112133ddf 是否开启用户注册功能sql脚本 2021-07-31 21:49:44 +08:00
RuoYi
93ee021b6e XSS过滤排除非json类型 2021-07-31 12:18:24 +08:00
RuoYi
e57d2ea17c 升级nacos到最新版2.0.3 2021-07-31 09:54:28 +08:00
RuoYi
ea038a5437 优化代码生成模板 2021-07-30 22:33:09 +08:00
RuoYi
329aa68644 新增是否开启用户注册功能 2021-07-30 20:03:59 +08:00
Ricky
887430874d 优化代码生成 2021-07-30 18:41:46 +08:00
Ricky
52f8693e1d 优化代码生成 2021-07-30 18:35:52 +08:00
RuoYi
8057dcc4fc 定时任务屏蔽http(s)远程调用 2021-07-30 11:06:11 +08:00
RuoYi
66e0f9a53d 启用部门状态排除顶级节点 2021-07-30 11:05:54 +08:00
RuoYi
7a35c474d6 统一网关错误码响应 2021-07-29 14:51:27 +08:00
RuoYi
892065003d 修复导出含params属性对象参数问题 2021-07-29 10:38:34 +08:00
RuoYi
ede8353503 升级common-pool到最新版本2.10.0 2021-07-28 20:34:41 +08:00
RuoYi
60796ca8fb rollback pr 2021-07-28 20:33:46 +08:00
RuoYi
25e9112ccc 升级minio到最新版本8.2.2 2021-07-28 20:12:50 +08:00
RuoYi
f6c5c91eb1 升级commons.io到最新版本v2.11.0 2021-07-28 14:00:32 +08:00
若依
740da06972 !91 【轻量级PR】 [SysUser] 返回给前端数据的时候,隐藏密码和加盐,防止别人猜测加密方式 或者 暴力破击
Merge pull request !91 from dazer007/master
2021-07-28 05:51:09 +00:00
RuoYi
2a363f9c17 升级tobato到最新版本1.27.2 2021-07-28 13:33:49 +08:00
RuoYi
3c649a8814 升级minio到最新版本8.3.0 2021-07-28 13:30:46 +08:00
RuoYi
a144bf2bff 升级dynamic-ds到最新版本3.4.1 2021-07-28 13:28:50 +08:00
RuoYi
83ee4223be 升级spring-boot-mybatis到最新版2.2.0 2021-07-28 13:24:29 +08:00
RuoYi
8f6c864e96 升级spring-boot-admin到最新版2.4.3 2021-07-28 13:19:50 +08:00
RuoYi
12209ae5a4 升级spring-boot到最新版本2.5.3 2021-07-28 13:17:20 +08:00
duandazhi
4e0301a7b2 [SysUser] 返回给前端数据的时候,隐藏密码和加盐,防止别人猜测加密方式 或者 暴力破击 2021-07-28 13:07:06 +08:00
RuoYi
00fa1c3158 支持配置XSS跨站脚本过滤 2021-07-28 13:05:18 +08:00
若依
04edd66199 !90 【轻量级PR】SysUserController remove 解决把自己删除的bug
Merge pull request !90 from dazer007/secerity-fix-remove-self-ok
2021-07-28 05:01:49 +00:00
若依
d8896c9054 !89 update ruoyi-ui/src/utils/zipdownload.js.
Merge pull request !89 from ytzjf/N/A
2021-07-28 05:01:46 +00:00
duandazhi
dd70c1950e sysuercontroller remove self 问题修复 2021-07-28 10:24:14 +08:00
RuoYi
954d208ac6 支持配置XSS跨站脚本过滤 2021-07-28 09:58:59 +08:00
ytzjf
98d25fa16e update ruoyi-ui/src/utils/zipdownload.js.
BLOB下载时清除URL对象引用
2021-07-28 01:57:11 +00:00
RuoYi
3af7af265b 验证码配置 2021-07-27 20:47:57 +08:00
RuoYi
436c2154ad 支持配置验证码开关&类型 2021-07-27 20:39:46 +08:00
RuoYi
42e8baa85c 添加新群号 2021-07-27 20:38:30 +08:00
RuoYi
0dff71bd4d 跳转路由高亮相对应的菜单栏 2021-07-27 13:09:30 +08:00
RuoYi
20ce9da509 修复任意账户越权问题 2021-07-27 13:08:37 +08:00
RuoYi
a044b0d205 升级element-ui到最新版本2.15.3 2021-07-26 10:11:08 +08:00
RuoYi
698200ecc2 角色&菜单新增字段属性提示信息 2021-07-26 10:10:39 +08:00
RuoYi
883d89ee0b 内链设置meta信息 2021-07-26 10:08:42 +08:00
RuoYi
fb9a480f3c 密码框新增显示切换密码图标 2021-07-26 10:08:28 +08:00
RuoYi
e288710251 导入用户样式调整 2021-07-26 10:06:08 +08:00
RuoYi
1019aa58ce 顶部菜单样式调整 2021-07-26 10:03:52 +08:00
RuoYi
e9621469b2 更多操作按钮添加权限控制 2021-07-25 11:47:55 +08:00
RuoYi
57c4910605 富文本新增上传文件大小限制 2021-07-25 11:39:12 +08:00
RuoYi
9920957796 状态码401返回Promise.reject 2021-07-25 11:36:31 +08:00
RuoYi
e798f46876 顶部菜单排除隐藏的默认路由 2021-07-24 19:15:16 +08:00
RuoYi
caa7537607 修复定时任务日志执行状态显示 2021-07-17 16:51:37 +08:00
若依
8b23c97fdb !83 【轻量级PR】RuoYiFileApplication 重命名为RuoYiFileApplication
Merge pull request !83 from dazer007/RuoYFileApplication-rename-
2021-07-17 08:48:40 +00:00
duandazhi
af49ad54f6 RuoYFileApplication-rename 2021-07-16 12:26:07 +08:00
RuoYi
816479e092 定时任务新增更多操作 2021-07-15 17:36:14 +08:00
RuoYi
7e72849d05 修复多图时无法删除相应图片问题 2021-07-13 10:32:48 +08:00
RuoYi
99206a5d65 删除富文本video事件 2021-07-13 10:32:23 +08:00
RuoYi
114c9f3af6 菜单路由配置支持内链访问 2021-07-11 19:30:24 +08:00
RuoYi
e14fb70c26 自定义弹窗拖拽指令 2021-07-09 21:14:16 +08:00
RuoYi
201149a131 富文本默认上传返回url类型 2021-07-09 21:13:54 +08:00
RuoYi
2bc80c4c07 授权用户添加事务 2021-07-09 21:13:09 +08:00
RuoYi
ee7607bcca 全局注册通用组件 2021-07-09 21:12:37 +08:00
RuoYi
7e2dbc5608 ImageUpload组件支持多图片上传 2021-07-09 21:11:07 +08:00
RuoYi
8eac239e48 FileUpload组件支持多文件上传 2021-07-09 21:10:08 +08:00
若依
6650397e38 !79 【轻量级 PR】:修改登录失效返回值code401
Merge pull request !79 from bug制造者/master
2021-07-09 12:57:28 +00:00
bug制造者
1ca4c26d81 修改登录失效返回值code401
之前返回的code200,前端就会$message一下错误信息,不会弹出401重新登录跳转的弹框。
2021-07-07 09:16:05 +00:00
RuoYi
0b0da91139 角色管理新增分配用户功能 2021-07-06 14:02:37 +08:00
RuoYi
3d47ec2e73 用户管理新增分配角色功能 2021-07-02 10:59:22 +08:00
RuoYi
1f21102204 升级pagehelper到最新版1.3.1 2021-06-25 17:42:41 +08:00
RuoYi
58b4600144 用户信息长度校验限制 2021-06-25 17:41:40 +08:00
RuoYi
6146d63f2c 修复日志列表取消字段排序时的报错问题 2021-06-25 17:40:05 +08:00
RuoYi
dbf42739ca 全局挂载字典标签组件 2021-06-25 17:39:43 +08:00
Ricky
57f56c2769 增加字典标签样式回显 2021-06-22 14:47:28 +08:00
RuoYi
5c130cfda6 封装iframe组件 2021-06-17 20:19:31 +08:00
RuoYi
df284ff6f5 日志列表支持排序操作 2021-06-17 20:18:59 +08:00
RuoYi
1b7550f9a2 升级commons.fileupload到最新版本v1.4 2021-06-16 10:06:09 +08:00
RuoYi
195df24b4b 升级commons.io到最新版本v2.10.0 2021-06-16 10:05:53 +08:00
RuoYi
68de85aa5f 升级nacos到最新版2.0.2 2021-06-15 10:42:03 +08:00
RuoYi
8b6784d8e2 升级element-ui到最新版本2.15.2 2021-06-15 10:41:31 +08:00
RuoYi
b73c14400c 升级spring-boot到最新版本2.5.1 2021-06-12 14:05:27 +08:00
RuoYi
54922af7db 升级dynamic-ds到最新版本3.4.0 2021-06-12 14:05:07 +08:00
RuoYi
23944b2f9c 系统布局配置支持动态标题开关 2021-06-11 11:04:06 +08:00
RuoYi
9d0240b831 分页组件新增pagerCount属性 2021-06-11 11:03:54 +08:00
RuoYi
1aef90d506 修复用户搜索分页变量错误 2021-06-11 11:03:33 +08:00
RuoYi
41b7e62b02 优化部门父级启用状态 2021-06-11 10:49:26 +08:00
148 changed files with 4763 additions and 1595 deletions

70
pom.xml
View File

@@ -6,37 +6,37 @@
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
<name>ruoyi</name>
<url>http://www.ruoyi.vip</url>
<description>若依微服务系统</description>
<properties>
<ruoyi.version>3.0.0</ruoyi.version>
<ruoyi.version>3.1.0</ruoyi.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-boot.version>2.5.0</spring-boot.version>
<spring-boot.version>2.5.3</spring-boot.version>
<spring-cloud.version>2020.0.3</spring-cloud.version>
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
<alibaba.nacos.version>2.0.1</alibaba.nacos.version>
<spring-boot-admin.version>2.4.1</spring-boot-admin.version>
<spring-boot.mybatis>2.1.4</spring-boot.mybatis>
<alibaba.nacos.version>2.0.3</alibaba.nacos.version>
<spring-boot-admin.version>2.4.3</spring-boot-admin.version>
<spring-boot.mybatis>2.2.0</spring-boot.mybatis>
<swagger.fox.version>3.0.0</swagger.fox.version>
<swagger.core.version>1.6.2</swagger.core.version>
<tobato.version>1.26.5</tobato.version>
<tobato.version>1.27.2</tobato.version>
<kaptcha.version>2.3.2</kaptcha.version>
<pagehelper.boot.version>1.3.0</pagehelper.boot.version>
<pagehelper.boot.version>1.3.1</pagehelper.boot.version>
<druid.version>1.2.6</druid.version>
<dynamic-ds.version>3.3.2</dynamic-ds.version>
<commons.io.version>2.5</commons.io.version>
<commons.fileupload.version>1.3.3</commons.fileupload.version>
<dynamic-ds.version>3.4.1</dynamic-ds.version>
<commons.io.version>2.11.0</commons.io.version>
<commons.fileupload.version>1.4</commons.fileupload.version>
<velocity.version>1.7</velocity.version>
<fastjson.version>1.2.76</fastjson.version>
<minio.version>8.2.1</minio.version>
<poi.version>4.1.2</poi.version>
<common-pool.version>2.6.2</common-pool.version>
<minio.version>8.2.2</minio.version>
<poi.version>4.1.2</poi.version>
<common-pool.version>2.10.0</common-pool.version>
<commons-collections.version>3.2.2</commons-collections.version>
</properties>
@@ -53,7 +53,7 @@
<scope>import</scope>
</dependency>
<!-- SpringCloud Alibaba 微服务 -->
<!-- SpringCloud Alibaba 微服务 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
@@ -77,28 +77,28 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- SpringBoot 监控客户端 -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>${spring-boot-admin.version}</version>
</dependency>
<!-- FastDFS 分布式文件系统 -->
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>${tobato.version}</version>
</dependency>
<!-- Mybatis 依赖配置 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${spring-boot.mybatis}</version>
</dependency>
<!-- Swagger 依赖配置 -->
<dependency>
<groupId>io.swagger</groupId>
@@ -172,7 +172,7 @@
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- 公共资源池 -->
<dependency>
<groupId>org.apache.commons</groupId>
@@ -181,71 +181,71 @@
</dependency>
<!-- 核心模块 -->
<dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-core</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<!-- 接口模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-swagger</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<!-- 安全模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-security</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<!-- 权限范围 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-datascope</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<!-- 多数据源 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-datasource</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<!-- 日志记录 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-log</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<!-- 缓存服务 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-redis</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<!-- 系统接口 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-api-system</artifactId>
<version>${ruoyi.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>ruoyi-auth</module>
<module>ruoyi-gateway</module>
<module>ruoyi-visual</module>
<module>ruoyi-modules</module>
<module>ruoyi-api</module>
<module>ruoyi-common</module>
<module>ruoyi-auth</module>
<module>ruoyi-gateway</module>
<module>ruoyi-visual</module>
<module>ruoyi-modules</module>
<module>ruoyi-api</module>
<module>ruoyi-common</module>
</modules>
<packaging>pom</packaging>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-api</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,9 +3,11 @@ package com.ruoyi.system.api;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestHeader;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.constant.ServiceNameConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.domain.SysLogininfor;
import com.ruoyi.system.api.domain.SysOperLog;
import com.ruoyi.system.api.factory.RemoteLogFallbackFactory;
@@ -21,20 +23,19 @@ public interface RemoteLogService
* 保存系统日志
*
* @param sysOperLog 日志实体
* @param source 请求来源
* @return 结果
*/
@PostMapping("/operlog")
R<Boolean> saveLog(@RequestBody SysOperLog sysOperLog);
public R<Boolean> saveLog(@RequestBody SysOperLog sysOperLog, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 保存访问记录
*
* @param username 用户名称
* @param status 状态
* @param message 消息
* @param sysLogininfor 访问实体
* @param source 请求来源
* @return 结果
*/
@PostMapping("/logininfor")
R<Boolean> saveLogininfor(@RequestParam("username") String username, @RequestParam("status") String status,
@RequestParam("message") String message);
public R<Boolean> saveLogininfor(@RequestBody SysLogininfor sysLogininfor, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}

View File

@@ -3,8 +3,13 @@ package com.ruoyi.system.api;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.constant.ServiceNameConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.factory.RemoteUserFallbackFactory;
import com.ruoyi.system.api.model.LoginUser;
@@ -20,8 +25,19 @@ public interface RemoteUserService
* 通过用户名查询用户信息
*
* @param username 用户名
* @param source 请求来源
* @return 结果
*/
@GetMapping(value = "/user/info/{username}")
public R<LoginUser> getUserInfo(@PathVariable("username") String username);
@GetMapping("/user/info/{username}")
public R<LoginUser> getUserInfo(@PathVariable("username") String username, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 注册用户信息
*
* @param sysUser 用户信息
* @param source 请求来源
* @return 结果
*/
@PostMapping("/user/register")
public R<Boolean> registerUserInfo(@RequestBody SysUser sysUser, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}

View File

@@ -1,4 +1,4 @@
package com.ruoyi.system.domain;
package com.ruoyi.system.api.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;

View File

@@ -57,9 +57,6 @@ public class SysUser extends BaseEntity
/** 密码 */
private String password;
/** 盐加密 */
private String salt;
/** 帐号状态0正常 1停用 */
@Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用")
private String status;
@@ -91,6 +88,9 @@ public class SysUser extends BaseEntity
/** 岗位组 */
private Long[] postIds;
/** 角色ID */
private Long roleId;
public SysUser()
{
@@ -208,16 +208,6 @@ public class SysUser extends BaseEntity
this.password = password;
}
public String getSalt()
{
return salt;
}
public void setSalt(String salt)
{
this.salt = salt;
}
public String getStatus()
{
return status;
@@ -297,7 +287,16 @@ public class SysUser extends BaseEntity
{
this.postIds = postIds;
}
public Long getRoleId()
{
return roleId;
}
public void setRoleId(Long roleId)
{
this.roleId = roleId;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@@ -310,7 +309,6 @@ public class SysUser extends BaseEntity
.append("sex", getSex())
.append("avatar", getAvatar())
.append("password", getPassword())
.append("salt", getSalt())
.append("status", getStatus())
.append("delFlag", getDelFlag())
.append("loginIp", getLoginIp())

View File

@@ -6,6 +6,7 @@ import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.RemoteLogService;
import com.ruoyi.system.api.domain.SysLogininfor;
import com.ruoyi.system.api.domain.SysOperLog;
/**
@@ -25,13 +26,13 @@ public class RemoteLogFallbackFactory implements FallbackFactory<RemoteLogServic
return new RemoteLogService()
{
@Override
public R<Boolean> saveLog(SysOperLog sysOperLog)
public R<Boolean> saveLog(SysOperLog sysOperLog, String source)
{
return null;
}
@Override
public R<Boolean> saveLogininfor(String username, String status, String message)
public R<Boolean> saveLogininfor(SysLogininfor sysLogininfor, String source)
{
return null;
}

View File

@@ -6,6 +6,7 @@ import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.api.RemoteUserService;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.model.LoginUser;
/**
@@ -25,10 +26,16 @@ public class RemoteUserFallbackFactory implements FallbackFactory<RemoteUserServ
return new RemoteUserService()
{
@Override
public R<LoginUser> getUserInfo(String username)
public R<LoginUser> getUserInfo(String username, String source)
{
return R.fail("获取用户失败:" + throwable.getMessage());
}
@Override
public R<Boolean> registerUserInfo(SysUser sysUser, String source)
{
return R.fail("注册用户失败:" + throwable.getMessage());
}
};
}
}

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.auth.form.LoginBody;
import com.ruoyi.auth.form.RegisterBody;
import com.ruoyi.auth.service.SysLoginService;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.StringUtils;
@@ -63,4 +64,12 @@ public class TokenController
}
return R.ok();
}
@PostMapping("register")
public R<?> register(@RequestBody RegisterBody registerBody)
{
// 用户注册
sysLoginService.register(registerBody.getUsername(), registerBody.getPassword());
return R.ok();
}
}

View File

@@ -0,0 +1,11 @@
package com.ruoyi.auth.form;
/**
* 用户注册对象
*
* @author ruoyi
*/
public class RegisterBody extends LoginBody
{
}

View File

@@ -3,14 +3,18 @@ package com.ruoyi.auth.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.enums.UserStatus;
import com.ruoyi.common.core.exception.BaseException;
import com.ruoyi.common.core.utils.SecurityUtils;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.ip.IpUtils;
import com.ruoyi.system.api.RemoteLogService;
import com.ruoyi.system.api.RemoteUserService;
import com.ruoyi.system.api.domain.SysLogininfor;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.model.LoginUser;
@@ -36,25 +40,25 @@ public class SysLoginService
// 用户名或密码为空 错误
if (StringUtils.isAnyBlank(username, password))
{
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写");
recordLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写");
throw new BaseException("用户/密码必须填写");
}
// 密码如果不在指定范围内 错误
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围");
recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围");
throw new BaseException("用户密码不在指定范围");
}
// 用户名不在指定范围内 错误
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
throw new BaseException("用户名不在指定范围");
}
// 查询用户信息
R<LoginUser> userResult = remoteUserService.getUserInfo(username);
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
if (R.FAIL == userResult.getCode())
{
@@ -63,33 +67,93 @@ public class SysLoginService
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
{
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
throw new BaseException("登录用户:" + username + " 不存在");
}
LoginUser userInfo = userResult.getData();
SysUser user = userResult.getData().getSysUser();
if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
throw new BaseException("对不起,您的账号:" + username + " 已被删除");
}
if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
throw new BaseException("对不起,您的账号:" + username + " 已停用");
}
if (!SecurityUtils.matchesPassword(password, user.getPassword()))
{
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户密码错误");
recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码错误");
throw new BaseException("用户不存在/密码错误");
}
remoteLogService.saveLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
return userInfo;
}
public void logout(String loginName)
{
remoteLogService.saveLogininfor(loginName, Constants.LOGOUT, "退出成功");
recordLogininfor(loginName, Constants.LOGOUT, "退出成功");
}
/**
* 注册
*/
public void register(String username, String password)
{
// 用户名或密码为空 错误
if (StringUtils.isAnyBlank(username, password))
{
throw new BaseException("用户/密码必须填写");
}
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
throw new BaseException("账户长度必须在2到20个字符之间");
}
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
throw new BaseException("密码长度必须在5到20个字符之间");
}
// 注册用户信息
SysUser sysUser = new SysUser();
sysUser.setUserName(username);
sysUser.setNickName(username);
sysUser.setPassword(SecurityUtils.encryptPassword(password));
R<?> registerResult = remoteUserService.registerUserInfo(sysUser, SecurityConstants.INNER);
if (R.FAIL == registerResult.getCode())
{
throw new BaseException(registerResult.getMsg());
}
recordLogininfor(username, Constants.REGISTER, "注册成功");
}
/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
public void recordLogininfor(String username, String status, String message)
{
SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username);
logininfor.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
logininfor.setMsg(message);
// 日志状态
if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
{
logininfor.setStatus("0");
}
else if (Constants.LOGIN_FAIL.equals(status))
{
logininfor.setStatus("1");
}
remoteLogService.saveLogininfor(logininfor, SecurityConstants.INNER);
}
}

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -7,33 +7,8 @@ package com.ruoyi.common.core.constant;
*/
public class CacheConstants
{
/**
* 令牌自定义标识
*/
public static final String HEADER = "Authorization";
/**
* 令牌前缀
*/
public static final String TOKEN_PREFIX = "Bearer ";
/**
* 权限缓存前缀
*/
public final static String LOGIN_TOKEN_KEY = "login_tokens:";
/**
* 用户ID字段
*/
public static final String DETAILS_USER_ID = "user_id";
/**
* 用户名字段
*/
public static final String DETAILS_USERNAME = "username";
/**
* 授权信息字段
*/
public static final String AUTHORIZATION_HEADER = "authorization";
}

View File

@@ -17,6 +17,11 @@ public class Constants
*/
public static final String GBK = "GBK";
/**
* RMI 远程方法调用
*/
public static final String LOOKUP_RMI = "rmi://";
/**
* http请求
*/

View File

@@ -0,0 +1,44 @@
package com.ruoyi.common.core.constant;
/**
* 权限相关通用常量
*
* @author ruoyi
*/
public class SecurityConstants
{
/**
* 令牌自定义标识
*/
public static final String TOKEN_AUTHENTICATION = "Authorization";
/**
* 令牌前缀
*/
public static final String TOKEN_PREFIX = "Bearer ";
/**
* 用户ID字段
*/
public static final String DETAILS_USER_ID = "user_id";
/**
* 用户名字段
*/
public static final String DETAILS_USERNAME = "username";
/**
* 授权信息字段
*/
public static final String AUTHORIZATION_HEADER = "authorization";
/**
* 请求来源
*/
public static final String FROM_SOURCE = "from-source";
/**
* 内部请求
*/
public static final String INNER = "inner";
}

View File

@@ -57,6 +57,9 @@ public class UserConstants
/** ParentView组件标识 */
public final static String PARENT_VIEW = "ParentView";
/** InnerLink组件标识 */
public final static String INNER_LINK = "InnerLink";
/** 校验返回结果码 */
public final static String UNIQUE = "0";

View File

@@ -0,0 +1,16 @@
package com.ruoyi.common.core.exception;
/**
* 内部认证异常
*
* @author ruoyi
*/
public class InnerAuthException extends RuntimeException
{
private static final long serialVersionUID = 1L;
public InnerAuthException(String message)
{
super(message);
}
}

View File

@@ -2,7 +2,7 @@ package com.ruoyi.common.core.utils;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.text.Convert;
/**
@@ -17,7 +17,7 @@ public class SecurityUtils
*/
public static String getUsername()
{
String username = ServletUtils.getRequest().getHeader(CacheConstants.DETAILS_USERNAME);
String username = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USERNAME);
return ServletUtils.urlDecode(username);
}
@@ -26,7 +26,7 @@ public class SecurityUtils
*/
public static Long getUserId()
{
return Convert.toLong(ServletUtils.getRequest().getHeader(CacheConstants.DETAILS_USER_ID));
return Convert.toLong(ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USER_ID));
}
/**
@@ -42,10 +42,18 @@ public class SecurityUtils
*/
public static String getToken(HttpServletRequest request)
{
String token = ServletUtils.getRequest().getHeader(CacheConstants.HEADER);
if (StringUtils.isNotEmpty(token) && token.startsWith(CacheConstants.TOKEN_PREFIX))
String token = request.getHeader(SecurityConstants.TOKEN_AUTHENTICATION);
return replaceTokenPrefix(token);
}
/**
* 替换token前缀
*/
public static String replaceTokenPrefix(String token)
{
if (StringUtils.isNotEmpty(token) && token.startsWith(SecurityConstants.TOKEN_PREFIX))
{
token = token.replace(CacheConstants.TOKEN_PREFIX, "");
token = token.replace(SecurityConstants.TOKEN_PREFIX, "");
}
return token;
}

View File

@@ -10,11 +10,19 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.text.Convert;
import reactor.core.publisher.Mono;
/**
* 客户端工具类
@@ -213,4 +221,62 @@ public class ServletUtils
return "";
}
}
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param value 响应内容
* @return Mono<Void>
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value)
{
return webFluxResponseWriter(response, HttpStatus.OK, value, R.FAIL);
}
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param code 响应状态码
* @param value 响应内容
* @return Mono<Void>
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value, int code)
{
return webFluxResponseWriter(response, HttpStatus.OK, value, code);
}
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param status http状态码
* @param code 响应状态码
* @param value 响应内容
* @return Mono<Void>
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, HttpStatus status, Object value, int code)
{
return webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE, status, value, code);
}
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param contentType content-type
* @param status http状态码
* @param code 响应状态码
* @param value 响应内容
* @return Mono<Void>
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String contentType, HttpStatus status, Object value, int code)
{
response.setStatusCode(status);
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType);
R<?> result = R.fail(code, value.toString());
DataBuffer dataBuffer = response.bufferFactory().wrap(JSONObject.toJSONString(result).getBytes());
return response.writeWith(Mono.just(dataBuffer));
}
}

View File

@@ -4,6 +4,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.springframework.util.AntPathMatcher;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.text.StrFormatter;
/**
@@ -282,6 +283,17 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
return StrFormatter.format(template, params);
}
/**
* 是否为http(s)://开头
*
* @param link 链接
* @return 结果
*/
public static boolean ishttp(String link)
{
return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS);
}
/**
* 驼峰转下划线命名
*/

View File

@@ -18,7 +18,7 @@ import com.ruoyi.common.core.utils.StringUtils;
*
* @author ruoyi
*/
public class FileUtils extends org.apache.commons.io.FileUtils
public class FileUtils
{
/** 字符常量:斜杠 {@code '/'} */
public static final char SLASH = '/';

View File

@@ -0,0 +1,155 @@
package com.ruoyi.common.core.utils.html;
import com.ruoyi.common.core.utils.StringUtils;
/**
* 转义和反转义工具类
*
* @author ruoyi
*/
public class EscapeUtil
{
public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)";
private static final char[][] TEXT = new char[64][];
static
{
for (int i = 0; i < 64; i++)
{
TEXT[i] = new char[] { (char) i };
}
// special HTML characters
TEXT['\''] = "&#039;".toCharArray(); // 单引号
TEXT['"'] = "&#34;".toCharArray(); // 双引号
TEXT['&'] = "&#38;".toCharArray(); // &符
TEXT['<'] = "&#60;".toCharArray(); // 小于号
TEXT['>'] = "&#62;".toCharArray(); // 大于号
}
/**
* 转义文本中的HTML字符为安全的字符
*
* @param text 被转义的文本
* @return 转义后的文本
*/
public static String escape(String text)
{
return encode(text);
}
/**
* 还原被转义的HTML特殊字符
*
* @param content 包含转义符的HTML内容
* @return 转换后的字符串
*/
public static String unescape(String content)
{
return decode(content);
}
/**
* 清除所有HTML标签但是不删除标签内的内容
*
* @param content 文本
* @return 清除标签后的文本
*/
public static String clean(String content)
{
return new HTMLFilter().filter(content);
}
/**
* Escape编码
*
* @param text 被编码的文本
* @return 编码后的字符
*/
private static String encode(String text)
{
int len;
if ((text == null) || ((len = text.length()) == 0))
{
return StringUtils.EMPTY;
}
StringBuilder buffer = new StringBuilder(len + (len >> 2));
char c;
for (int i = 0; i < len; i++)
{
c = text.charAt(i);
if (c < 64)
{
buffer.append(TEXT[c]);
}
else
{
buffer.append(c);
}
}
return buffer.toString();
}
/**
* Escape解码
*
* @param content 被转义的内容
* @return 解码后的字符串
*/
public static String decode(String content)
{
if (StringUtils.isEmpty(content))
{
return content;
}
StringBuilder tmp = new StringBuilder(content.length());
int lastPos = 0, pos = 0;
char ch;
while (lastPos < content.length())
{
pos = content.indexOf("%", lastPos);
if (pos == lastPos)
{
if (content.charAt(pos + 1) == 'u')
{
ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16);
tmp.append(ch);
lastPos = pos + 6;
}
else
{
ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16);
tmp.append(ch);
lastPos = pos + 3;
}
}
else
{
if (pos == -1)
{
tmp.append(content.substring(lastPos));
lastPos = content.length();
}
else
{
tmp.append(content.substring(lastPos, pos));
lastPos = pos;
}
}
}
return tmp.toString();
}
public static void main(String[] args)
{
String html = "<script>alert(1);</script>";
// String html = "<scr<script>ipt>alert(\"XSS\")</scr<script>ipt>";
// String html = "<123";
// String html = "123>";
System.out.println(EscapeUtil.clean(html));
System.out.println(EscapeUtil.escape(html));
System.out.println(EscapeUtil.unescape(html));
}
}

View File

@@ -0,0 +1,570 @@
package com.ruoyi.common.core.utils.html;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* HTML过滤器用于去除XSS漏洞隐患。
*
* @author ruoyi
*/
public final class HTMLFilter
{
/**
* regex flag union representing /si modifiers in php
**/
private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
private static final Pattern P_COMMENTS = Pattern.compile("<!--(.*?)-->", Pattern.DOTALL);
private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI);
private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL);
private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI);
private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI);
private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI);
private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI);
private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI);
private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?");
private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?");
private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?");
private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))");
private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL);
private static final Pattern P_END_ARROW = Pattern.compile("^>");
private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)");
private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)");
private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)");
private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)");
private static final Pattern P_AMP = Pattern.compile("&");
private static final Pattern P_QUOTE = Pattern.compile("\"");
private static final Pattern P_LEFT_ARROW = Pattern.compile("<");
private static final Pattern P_RIGHT_ARROW = Pattern.compile(">");
private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>");
// @xxx could grow large... maybe use sesat's ReferenceMap
private static final ConcurrentMap<String, Pattern> P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, Pattern> P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>();
/**
* set of allowed html elements, along with allowed attributes for each element
**/
private final Map<String, List<String>> vAllowed;
/**
* counts of open tags for each (allowable) html element
**/
private final Map<String, Integer> vTagCounts = new HashMap<>();
/**
* html elements which must always be self-closing (e.g. "<img />")
**/
private final String[] vSelfClosingTags;
/**
* html elements which must always have separate opening and closing tags (e.g. "<b></b>")
**/
private final String[] vNeedClosingTags;
/**
* set of disallowed html elements
**/
private final String[] vDisallowed;
/**
* attributes which should be checked for valid protocols
**/
private final String[] vProtocolAtts;
/**
* allowed protocols
**/
private final String[] vAllowedProtocols;
/**
* tags which should be removed if they contain no content (e.g. "<b></b>" or "<b />")
**/
private final String[] vRemoveBlanks;
/**
* entities allowed within html markup
**/
private final String[] vAllowedEntities;
/**
* flag determining whether comments are allowed in input String.
*/
private final boolean stripComment;
private final boolean encodeQuotes;
/**
* flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "<b text </b>"
* becomes "<b> text </b>"). If set to false, unbalanced angle brackets will be html escaped.
*/
private final boolean alwaysMakeTags;
/**
* Default constructor.
*/
public HTMLFilter()
{
vAllowed = new HashMap<>();
final ArrayList<String> a_atts = new ArrayList<>();
a_atts.add("href");
a_atts.add("target");
vAllowed.put("a", a_atts);
final ArrayList<String> img_atts = new ArrayList<>();
img_atts.add("src");
img_atts.add("width");
img_atts.add("height");
img_atts.add("alt");
vAllowed.put("img", img_atts);
final ArrayList<String> no_atts = new ArrayList<>();
vAllowed.put("b", no_atts);
vAllowed.put("strong", no_atts);
vAllowed.put("i", no_atts);
vAllowed.put("em", no_atts);
vSelfClosingTags = new String[] { "img" };
vNeedClosingTags = new String[] { "a", "b", "strong", "i", "em" };
vDisallowed = new String[] {};
vAllowedProtocols = new String[] { "http", "mailto", "https" }; // no ftp.
vProtocolAtts = new String[] { "src", "href" };
vRemoveBlanks = new String[] { "a", "b", "strong", "i", "em" };
vAllowedEntities = new String[] { "amp", "gt", "lt", "quot" };
stripComment = true;
encodeQuotes = true;
alwaysMakeTags = false;
}
/**
* Map-parameter configurable constructor.
*
* @param conf map containing configuration. keys match field names.
*/
@SuppressWarnings("unchecked")
public HTMLFilter(final Map<String, Object> conf)
{
assert conf.containsKey("vAllowed") : "configuration requires vAllowed";
assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags";
assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags";
assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed";
assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols";
assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts";
assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks";
assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities";
vAllowed = Collections.unmodifiableMap((HashMap<String, List<String>>) conf.get("vAllowed"));
vSelfClosingTags = (String[]) conf.get("vSelfClosingTags");
vNeedClosingTags = (String[]) conf.get("vNeedClosingTags");
vDisallowed = (String[]) conf.get("vDisallowed");
vAllowedProtocols = (String[]) conf.get("vAllowedProtocols");
vProtocolAtts = (String[]) conf.get("vProtocolAtts");
vRemoveBlanks = (String[]) conf.get("vRemoveBlanks");
vAllowedEntities = (String[]) conf.get("vAllowedEntities");
stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true;
encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true;
alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true;
}
private void reset()
{
vTagCounts.clear();
}
// ---------------------------------------------------------------
// my versions of some PHP library functions
public static String chr(final int decimal)
{
return String.valueOf((char) decimal);
}
public static String htmlSpecialChars(final String s)
{
String result = s;
result = regexReplace(P_AMP, "&amp;", result);
result = regexReplace(P_QUOTE, "&quot;", result);
result = regexReplace(P_LEFT_ARROW, "&lt;", result);
result = regexReplace(P_RIGHT_ARROW, "&gt;", result);
return result;
}
// ---------------------------------------------------------------
/**
* given a user submitted input String, filter out any invalid or restricted html.
*
* @param input text (i.e. submitted by a user) than may contain html
* @return "clean" version of input, with only valid, whitelisted html elements allowed
*/
public String filter(final String input)
{
reset();
String s = input;
s = escapeComments(s);
s = balanceHTML(s);
s = checkTags(s);
s = processRemoveBlanks(s);
// s = validateEntities(s);
return s;
}
public boolean isAlwaysMakeTags()
{
return alwaysMakeTags;
}
public boolean isStripComments()
{
return stripComment;
}
private String escapeComments(final String s)
{
final Matcher m = P_COMMENTS.matcher(s);
final StringBuffer buf = new StringBuffer();
if (m.find())
{
final String match = m.group(1); // (.*?)
m.appendReplacement(buf, Matcher.quoteReplacement("<!--" + htmlSpecialChars(match) + "-->"));
}
m.appendTail(buf);
return buf.toString();
}
private String balanceHTML(String s)
{
if (alwaysMakeTags)
{
//
// try and form html
//
s = regexReplace(P_END_ARROW, "", s);
// 不追加结束标签
s = regexReplace(P_BODY_TO_END, "<$1>", s);
s = regexReplace(P_XML_CONTENT, "$1<$2", s);
}
else
{
//
// escape stray brackets
//
s = regexReplace(P_STRAY_LEFT_ARROW, "&lt;$1", s);
s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2&gt;<", s);
//
// the last regexp causes '<>' entities to appear
// (we need to do a lookahead assertion so that the last bracket can
// be used in the next pass of the regexp)
//
s = regexReplace(P_BOTH_ARROWS, "", s);
}
return s;
}
private String checkTags(String s)
{
Matcher m = P_TAGS.matcher(s);
final StringBuffer buf = new StringBuffer();
while (m.find())
{
String replaceStr = m.group(1);
replaceStr = processTag(replaceStr);
m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr));
}
m.appendTail(buf);
// these get tallied in processTag
// (remember to reset before subsequent calls to filter method)
final StringBuilder sBuilder = new StringBuilder(buf.toString());
for (String key : vTagCounts.keySet())
{
for (int ii = 0; ii < vTagCounts.get(key); ii++)
{
sBuilder.append("</").append(key).append(">");
}
}
s = sBuilder.toString();
return s;
}
private String processRemoveBlanks(final String s)
{
String result = s;
for (String tag : vRemoveBlanks)
{
if (!P_REMOVE_PAIR_BLANKS.containsKey(tag))
{
P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?></" + tag + ">"));
}
result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result);
if (!P_REMOVE_SELF_BLANKS.containsKey(tag))
{
P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>"));
}
result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result);
}
return result;
}
private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s)
{
Matcher m = regex_pattern.matcher(s);
return m.replaceAll(replacement);
}
private String processTag(final String s)
{
// ending tags
Matcher m = P_END_TAG.matcher(s);
if (m.find())
{
final String name = m.group(1).toLowerCase();
if (allowed(name))
{
if (false == inArray(name, vSelfClosingTags))
{
if (vTagCounts.containsKey(name))
{
vTagCounts.put(name, vTagCounts.get(name) - 1);
return "</" + name + ">";
}
}
}
}
// starting tags
m = P_START_TAG.matcher(s);
if (m.find())
{
final String name = m.group(1).toLowerCase();
final String body = m.group(2);
String ending = m.group(3);
// debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" );
if (allowed(name))
{
final StringBuilder params = new StringBuilder();
final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body);
final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body);
final List<String> paramNames = new ArrayList<>();
final List<String> paramValues = new ArrayList<>();
while (m2.find())
{
paramNames.add(m2.group(1)); // ([a-z0-9]+)
paramValues.add(m2.group(3)); // (.*?)
}
while (m3.find())
{
paramNames.add(m3.group(1)); // ([a-z0-9]+)
paramValues.add(m3.group(3)); // ([^\"\\s']+)
}
String paramName, paramValue;
for (int ii = 0; ii < paramNames.size(); ii++)
{
paramName = paramNames.get(ii).toLowerCase();
paramValue = paramValues.get(ii);
// debug( "paramName='" + paramName + "'" );
// debug( "paramValue='" + paramValue + "'" );
// debug( "allowed? " + vAllowed.get( name ).contains( paramName ) );
if (allowedAttribute(name, paramName))
{
if (inArray(paramName, vProtocolAtts))
{
paramValue = processParamProtocol(paramValue);
}
params.append(' ').append(paramName).append("=\"").append(paramValue).append("\"");
}
}
if (inArray(name, vSelfClosingTags))
{
ending = " /";
}
if (inArray(name, vNeedClosingTags))
{
ending = "";
}
if (ending == null || ending.length() < 1)
{
if (vTagCounts.containsKey(name))
{
vTagCounts.put(name, vTagCounts.get(name) + 1);
}
else
{
vTagCounts.put(name, 1);
}
}
else
{
ending = " /";
}
return "<" + name + params + ending + ">";
}
else
{
return "";
}
}
// comments
m = P_COMMENT.matcher(s);
if (!stripComment && m.find())
{
return "<" + m.group() + ">";
}
return "";
}
private String processParamProtocol(String s)
{
s = decodeEntities(s);
final Matcher m = P_PROTOCOL.matcher(s);
if (m.find())
{
final String protocol = m.group(1);
if (!inArray(protocol, vAllowedProtocols))
{
// bad protocol, turn into local anchor link instead
s = "#" + s.substring(protocol.length() + 1);
if (s.startsWith("#//"))
{
s = "#" + s.substring(3);
}
}
}
return s;
}
private String decodeEntities(String s)
{
StringBuffer buf = new StringBuffer();
Matcher m = P_ENTITY.matcher(s);
while (m.find())
{
final String match = m.group(1);
final int decimal = Integer.decode(match).intValue();
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();
buf = new StringBuffer();
m = P_ENTITY_UNICODE.matcher(s);
while (m.find())
{
final String match = m.group(1);
final int decimal = Integer.valueOf(match, 16).intValue();
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();
buf = new StringBuffer();
m = P_ENCODE.matcher(s);
while (m.find())
{
final String match = m.group(1);
final int decimal = Integer.valueOf(match, 16).intValue();
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();
s = validateEntities(s);
return s;
}
private String validateEntities(final String s)
{
StringBuffer buf = new StringBuffer();
// validate entities throughout the string
Matcher m = P_VALID_ENTITIES.matcher(s);
while (m.find())
{
final String one = m.group(1); // ([^&;]*)
final String two = m.group(2); // (?=(;|&|$))
m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two)));
}
m.appendTail(buf);
return encodeQuotes(buf.toString());
}
private String encodeQuotes(final String s)
{
if (encodeQuotes)
{
StringBuffer buf = new StringBuffer();
Matcher m = P_VALID_QUOTES.matcher(s);
while (m.find())
{
final String one = m.group(1); // (>|^)
final String two = m.group(2); // ([^<]+?)
final String three = m.group(3); // (<|$)
// 不替换双引号为&quot;防止json格式无效 regexReplace(P_QUOTE, "&quot;", two)
m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three));
}
m.appendTail(buf);
return buf.toString();
}
else
{
return s;
}
}
private String checkEntity(final String preamble, final String term)
{
return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&amp;" + preamble;
}
private boolean isValidEntity(final String entity)
{
return inArray(entity, vAllowedEntities);
}
private static boolean inArray(final String s, final String[] array)
{
for (String item : array)
{
if (item != null && item.equals(s))
{
return true;
}
}
return false;
}
private boolean allowed(final String name)
{
return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed);
}
private boolean allowedAttribute(final String name, final String paramName)
{
return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName));
}
}

View File

@@ -67,6 +67,18 @@ public class PageDomain
public void setIsAsc(String isAsc)
{
this.isAsc = isAsc;
if (StringUtils.isNotEmpty(isAsc))
{
// 兼容前端排序类型
if ("ascending".equals(isAsc))
{
isAsc = "asc";
}
else if ("descending".equals(isAsc))
{
isAsc = "desc";
}
this.isAsc = isAsc;
}
}
}

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,6 +3,7 @@ package com.ruoyi.common.log.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.system.api.RemoteLogService;
import com.ruoyi.system.api.domain.SysOperLog;
@@ -23,6 +24,6 @@ public class AsyncLogService
@Async
public void saveSysLog(SysOperLog sysOperLog)
{
remoteLogService.saveLog(sysOperLog);
remoteLogService.saveLog(sysOperLog, SecurityConstants.INNER);
}
}

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -74,6 +74,17 @@ public class RedisService
return redisTemplate.expire(key, timeout, unit);
}
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key)
{
return redisTemplate.hasKey(key);
}
/**
* 获得缓存的基本对象。
*

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,19 @@
package com.ruoyi.common.security.annotation;
import java.lang.annotation.*;
/**
* 内部认证注解
*
* @author ruoyi
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InnerAuth
{
/**
* 是否校验用户信息
*/
boolean isUser() default false;
}

View File

@@ -0,0 +1,51 @@
package com.ruoyi.common.security.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.exception.InnerAuthException;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.annotation.InnerAuth;
/**
* 内部服务调用验证处理
*
* @author ruoyi
*/
@Aspect
@Component
public class InnerAuthAspect implements Ordered
{
@Around("@annotation(innerAuth)")
public Object innerAround(ProceedingJoinPoint point, InnerAuth innerAuth) throws Throwable
{
String source = ServletUtils.getRequest().getHeader(SecurityConstants.FROM_SOURCE);
// 内部请求验证
if (!StringUtils.equals(SecurityConstants.INNER, source))
{
throw new InnerAuthException("没有内部访问权限,不允许访问");
}
String userid = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USER_ID);
String username = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USERNAME);
// 用户信息验证
if (innerAuth.isUser() && (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)))
{
throw new InnerAuthException("没有设置用户信息,不允许访问 ");
}
return point.proceed();
}
/**
* 确保在权限认证aop执行前执行
*/
@Override
public int getOrder()
{
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}

View File

@@ -2,11 +2,11 @@ package com.ruoyi.common.security.feign;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import com.ruoyi.common.core.utils.ip.IpUtils;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.ip.IpUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
@@ -26,20 +26,20 @@ public class FeignRequestInterceptor implements RequestInterceptor
{
Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
// 传递用户信息请求头,防止丢失
String userId = headers.get(CacheConstants.DETAILS_USER_ID);
String userId = headers.get(SecurityConstants.DETAILS_USER_ID);
if (StringUtils.isNotEmpty(userId))
{
requestTemplate.header(CacheConstants.DETAILS_USER_ID, userId);
requestTemplate.header(SecurityConstants.DETAILS_USER_ID, userId);
}
String userName = headers.get(CacheConstants.DETAILS_USERNAME);
String userName = headers.get(SecurityConstants.DETAILS_USERNAME);
if (StringUtils.isNotEmpty(userName))
{
requestTemplate.header(CacheConstants.DETAILS_USERNAME, userName);
requestTemplate.header(SecurityConstants.DETAILS_USERNAME, userName);
}
String authentication = headers.get(CacheConstants.AUTHORIZATION_HEADER);
String authentication = headers.get(SecurityConstants.AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(authentication))
{
requestTemplate.header(CacheConstants.AUTHORIZATION_HEADER, authentication);
requestTemplate.header(SecurityConstants.AUTHORIZATION_HEADER, authentication);
}
// 配置客户端IP

View File

@@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.ruoyi.common.core.exception.BaseException;
import com.ruoyi.common.core.exception.CustomException;
import com.ruoyi.common.core.exception.DemoModeException;
import com.ruoyi.common.core.exception.InnerAuthException;
import com.ruoyi.common.core.exception.PreAuthorizeException;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.web.domain.AjaxResult;
@@ -73,7 +74,7 @@ public class GlobalExceptionHandler
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return AjaxResult.error(message);
}
/**
* 权限异常
*/
@@ -82,7 +83,16 @@ public class GlobalExceptionHandler
{
return AjaxResult.error("没有权限,请联系管理员授权");
}
/**
* 内部认证异常
*/
@ExceptionHandler(InnerAuthException.class)
public AjaxResult InnerAuthException(InnerAuthException e)
{
return AjaxResult.error(e.getMessage());
}
/**
* 演示模式异常
*/

View File

@@ -73,6 +73,16 @@ public class TokenService
{
// 获取请求携带的令牌
String token = SecurityUtils.getToken(request);
return getLoginUser(token);
}
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public LoginUser getLoginUser(String token)
{
if (StringUtils.isNotEmpty(token))
{
String userKey = getTokenKey(token);

View File

@@ -1,4 +1,5 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ruoyi.common.security.service.TokenService,\
com.ruoyi.common.security.aspect.PreAuthorizeAspect,\
com.ruoyi.common.security.aspect.InnerAuthAspect,\
com.ruoyi.common.security.handler.GlobalExceptionHandler

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,46 @@
package com.ruoyi.gateway.config.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* 验证码配置
*
* @author ruoyi
*/
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.captcha")
public class CaptchaProperties
{
/**
* 验证码开关
*/
private Boolean enabled;
/**
* 验证码类型math 数组计算 char 字符)
*/
private String type;
public Boolean getEnabled()
{
return enabled;
}
public void setEnabled(Boolean enabled)
{
this.enabled = enabled;
}
public String getType()
{
return type;
}
public void setType(String type)
{
this.type = type;
}
}

View File

@@ -13,7 +13,7 @@ import org.springframework.context.annotation.Configuration;
*/
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "ignore")
@ConfigurationProperties(prefix = "security.ignore")
public class IgnoreWhiteProperties
{
/**

View File

@@ -0,0 +1,48 @@
package com.ruoyi.gateway.config.properties;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* XSS跨站脚本配置
*
* @author ruoyi
*/
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.xss")
public class XssProperties
{
/**
* Xss开关
*/
private Boolean enabled;
/**
* 排除路径
*/
private List<String> excludeUrls = new ArrayList<>();
public Boolean getEnabled()
{
return enabled;
}
public void setEnabled(Boolean enabled)
{
this.enabled = enabled;
}
public List<String> getExcludeUrls()
{
return excludeUrls;
}
public void setExcludeUrls(List<String> excludeUrls)
{
this.excludeUrls = excludeUrls;
}
}

View File

@@ -7,19 +7,16 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.constant.HttpStatus;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.utils.SecurityUtils;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.redis.service.RedisService;
@@ -35,7 +32,7 @@ import reactor.core.publisher.Mono;
public class AuthFilter implements GlobalFilter, Ordered
{
private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);
private final static long EXPIRE_TIME = Constants.TOKEN_EXPIRE * 60;
// 排除过滤的 uri 地址nacos自行添加
@@ -44,61 +41,75 @@ public class AuthFilter implements GlobalFilter, Ordered
@Resource(name = "stringRedisTemplate")
private ValueOperations<String, String> sops;
@Autowired
private RedisService redisService;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
String url = exchange.getRequest().getURI().getPath();
ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest.Builder mutate = request.mutate();
String url = request.getURI().getPath();
// 跳过不需要验证的路径
if (StringUtils.matches(url, ignoreWhite.getWhites()))
{
return chain.filter(exchange);
}
String token = getToken(exchange.getRequest());
if (StringUtils.isBlank(token))
String token = getToken(request);
if (StringUtils.isEmpty(token))
{
return setUnauthorizedResponse(exchange, "令牌不能为空");
return unauthorizedResponse(exchange, "令牌不能为空");
}
String userStr = sops.get(getTokenKey(token));
if (StringUtils.isNull(userStr))
if (StringUtils.isEmpty(userStr))
{
return setUnauthorizedResponse(exchange, "登录状态已过期");
return unauthorizedResponse(exchange, "登录状态已过期");
}
JSONObject obj = JSONObject.parseObject(userStr);
String userid = obj.getString("userid");
String username = obj.getString("username");
if (StringUtils.isBlank(userid) || StringUtils.isBlank(username))
JSONObject cacheObj = JSONObject.parseObject(userStr);
String userid = cacheObj.getString("userid");
String username = cacheObj.getString("username");
if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username))
{
return setUnauthorizedResponse(exchange, "令牌验证失败");
return unauthorizedResponse(exchange, "令牌验证失败");
}
// 设置过期时间
redisService.expire(getTokenKey(token), EXPIRE_TIME);
// 设置用户信息到请求
ServerHttpRequest mutableReq = exchange.getRequest().mutate().header(CacheConstants.DETAILS_USER_ID, userid)
.header(CacheConstants.DETAILS_USERNAME, ServletUtils.urlEncode(username)).build();
ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
return chain.filter(mutableExchange);
addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
// 内部请求来源参数清除
removeHeader(mutate, SecurityConstants.FROM_SOURCE);
return chain.filter(exchange.mutate().request(mutate.build()).build());
}
private Mono<Void> setUnauthorizedResponse(ServerWebExchange exchange, String msg)
private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value)
{
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
response.setStatusCode(HttpStatus.OK);
log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
return response.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = response.bufferFactory();
return bufferFactory.wrap(JSON.toJSONBytes(R.fail(msg)));
}));
if (value == null)
{
return;
}
String valueStr = value.toString();
String valueEncode = ServletUtils.urlEncode(valueStr);
mutate.header(name, valueEncode);
}
private void removeHeader(ServerHttpRequest.Builder mutate, String name)
{
mutate.headers(httpHeaders -> httpHeaders.remove(name)).build();
}
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg)
{
log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED);
}
/**
* 获取缓存key
*/
private String getTokenKey(String token)
{
return CacheConstants.LOGIN_TOKEN_KEY + token;
@@ -109,12 +120,8 @@ public class AuthFilter implements GlobalFilter, Ordered
*/
private String getToken(ServerHttpRequest request)
{
String token = request.getHeaders().getFirst(CacheConstants.HEADER);
if (StringUtils.isNotEmpty(token) && token.startsWith(CacheConstants.TOKEN_PREFIX))
{
token = token.replace(CacheConstants.TOKEN_PREFIX, "");
}
return token;
String token = request.getHeaders().getFirst(SecurityConstants.TOKEN_AUTHENTICATION);
return SecurityUtils.replaceTokenPrefix(token);
}
@Override

View File

@@ -5,11 +5,8 @@ import java.util.List;
import java.util.regex.Pattern;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.ruoyi.common.core.web.domain.AjaxResult;
import reactor.core.publisher.Mono;
import com.ruoyi.common.core.utils.ServletUtils;
/**
* 黑名单过滤器
@@ -27,10 +24,7 @@ public class BlackListUrlFilter extends AbstractGatewayFilterFactory<BlackListUr
String url = exchange.getRequest().getURI().getPath();
if (config.matchBlacklist(url))
{
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return exchange.getResponse().writeWith(
Mono.just(response.bufferFactory().wrap(JSON.toJSONBytes(AjaxResult.error("请求地址不允许访问")))));
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求地址不允许访问");
}
return chain.filter(exchange);

View File

@@ -16,6 +16,11 @@ import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 获取body请求数据解决流不能重复读取问题
*
* @author ruoyi
*/
@Component
public class CacheRequestFilter extends AbstractGatewayFilterFactory<CacheRequestFilter.Config>
{

View File

@@ -9,15 +9,13 @@ import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFac
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.gateway.config.properties.CaptchaProperties;
import com.ruoyi.gateway.service.ValidateCodeService;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 验证码过滤器
@@ -27,11 +25,14 @@ import reactor.core.publisher.Mono;
@Component
public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
{
private final static String AUTH_URL = "/auth/login";
private final static String[] VALIDATE_URL = new String[] { "/auth/login", "/auth/register" };
@Autowired
private ValidateCodeService validateCodeService;
@Autowired
private CaptchaProperties captchaProperties;
private static final String CODE = "code";
private static final String UUID = "uuid";
@@ -42,8 +43,8 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// 非登录请求,不处理
if (!StringUtils.containsIgnoreCase(request.getURI().getPath(), AUTH_URL))
// 非登录/注册请求或验证码关闭,不处理
if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled())
{
return chain.filter(exchange);
}
@@ -56,10 +57,7 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
}
catch (Exception e)
{
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return exchange.getResponse().writeWith(
Mono.just(response.bufferFactory().wrap(JSON.toJSONBytes(AjaxResult.error(e.getMessage())))));
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
}
return chain.filter(exchange);
};

View File

@@ -0,0 +1,120 @@
package com.ruoyi.gateway.filter;
import java.nio.charset.StandardCharsets;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.html.EscapeUtil;
import com.ruoyi.gateway.config.properties.XssProperties;
import io.netty.buffer.ByteBufAllocator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 跨站脚本过滤器
*
* @author ruoyi
*/
@Component
@ConditionalOnProperty(value = "security.xss.enabled", havingValue = "true")
public class XssFilter implements GlobalFilter, Ordered
{
// 跨站脚本的 xss 配置nacos自行添加
@Autowired
private XssProperties xss;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
ServerHttpRequest request = exchange.getRequest();
// GET DELETE 不过滤
HttpMethod method = request.getMethod();
if (method == null || method.matches("GET") || method.matches("DELETE"))
{
return chain.filter(exchange);
}
// 非json类型不过滤
if (!isJsonRequest(exchange))
{
return chain.filter(exchange);
}
// excludeUrls 不过滤
String url = request.getURI().getPath();
if (StringUtils.matches(url, xss.getExcludeUrls()))
{
return chain.filter(exchange);
}
ServerHttpRequestDecorator httpRequestDecorator = requestDecorator(exchange);
return chain.filter(exchange.mutate().request(httpRequestDecorator).build());
}
private ServerHttpRequestDecorator requestDecorator(ServerWebExchange exchange)
{
ServerHttpRequestDecorator serverHttpRequestDecorator = new ServerHttpRequestDecorator(exchange.getRequest())
{
@Override
public Flux<DataBuffer> getBody()
{
Flux<DataBuffer> body = super.getBody();
return body.map(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
DataBufferUtils.release(dataBuffer);
String bodyStr = new String(content, StandardCharsets.UTF_8);
// 防xss攻击过滤
bodyStr = EscapeUtil.clean(bodyStr);
// 转成字节
byte[] bytes = bodyStr.getBytes();
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
buffer.write(bytes);
return buffer;
});
}
@Override
public HttpHeaders getHeaders()
{
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
// 由于修改了请求体的body导致content-length长度不确定因此需要删除原先的content-length
httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
return httpHeaders;
}
};
return serverHttpRequestDecorator;
}
/**
* 是否是Json请求
*
* @param request
*/
public boolean isJsonRequest(ServerWebExchange exchange)
{
String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
}
@Override
public int getOrder()
{
return -100;
}
}

View File

@@ -6,14 +6,10 @@ import org.slf4j.LoggerFactory;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import com.alibaba.fastjson.JSON;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.ServletUtils;
import reactor.core.publisher.Mono;
/**
@@ -55,12 +51,6 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler
log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage());
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
response.setStatusCode(HttpStatus.OK);
return response.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = response.bufferFactory();
return bufferFactory.wrap(JSON.toJSONBytes(R.fail(msg)));
}));
return ServletUtils.webFluxResponseWriter(response, msg);
}
}

View File

@@ -1,10 +1,8 @@
package com.ruoyi.gateway.handler;
import java.nio.charset.StandardCharsets;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import com.ruoyi.common.core.utils.ServletUtils;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
@@ -19,11 +17,7 @@ public class SentinelFallbackHandler implements WebExceptionHandler
{
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange)
{
ServerHttpResponse serverHttpResponse = exchange.getResponse();
serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
byte[] datas = "{\"code\":429,\"msg\":\"请求超过最大数,请稍后再试\"}".getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
return serverHttpResponse.writeWith(Mono.just(buffer));
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍后再试");
}
@Override

View File

@@ -16,6 +16,7 @@ import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.sign.Base64;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.gateway.config.properties.CaptchaProperties;
import com.ruoyi.gateway.service.ValidateCodeService;
/**
@@ -35,8 +36,8 @@ public class ValidateCodeServiceImpl implements ValidateCodeService
@Autowired
private RedisService redisService;
// 验证码类型
private String captchaType = "math";
@Autowired
private CaptchaProperties captchaProperties;
/**
* 生成验证码
@@ -44,6 +45,14 @@ public class ValidateCodeServiceImpl implements ValidateCodeService
@Override
public AjaxResult createCapcha() throws IOException, CaptchaException
{
AjaxResult ajax = AjaxResult.success();
boolean captchaOnOff = captchaProperties.getEnabled();
ajax.put("captchaOnOff", captchaOnOff);
if (!captchaOnOff)
{
return ajax;
}
// 保存验证码信息
String uuid = IdUtils.simpleUUID();
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
@@ -51,6 +60,7 @@ public class ValidateCodeServiceImpl implements ValidateCodeService
String capStr = null, code = null;
BufferedImage image = null;
String captchaType = captchaProperties.getType();
// 生成验证码
if ("math".equals(captchaType))
{
@@ -77,7 +87,6 @@ public class ValidateCodeServiceImpl implements ValidateCodeService
return AjaxResult.error(e.getMessage());
}
AjaxResult ajax = AjaxResult.success();
ajax.put("uuid", uuid);
ajax.put("img", Base64.encode(os.toByteArray()));
return ajax;

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,31 +1,31 @@
package com.ruoyi.file;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import com.ruoyi.common.swagger.annotation.EnableCustomSwagger2;
/**
* 文件服务
*
* @author ruoyi
*/
@EnableCustomSwagger2
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
public class RuoYFileApplication
{
public static void main(String[] args)
{
SpringApplication.run(RuoYFileApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 文件服务模块启动成功 ლ(´ڡ`ლ)゙ \n" +
" .-------. ____ __ \n" +
" | _ _ \\ \\ \\ / / \n" +
" | ( ' ) | \\ _. / ' \n" +
" |(_ o _) / _( )_ .' \n" +
" | (_,_).' __ ___(_ o _)' \n" +
" | |\\ \\ | || |(_,_)' \n" +
" | | \\ `' /| `-' / \n" +
" | | \\ / \\ / \n" +
" ''-' `'-' `-..-' ");
}
}
package com.ruoyi.file;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import com.ruoyi.common.swagger.annotation.EnableCustomSwagger2;
/**
* 文件服务
*
* @author ruoyi
*/
@EnableCustomSwagger2
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
public class RuoYiFileApplication
{
public static void main(String[] args)
{
SpringApplication.run(RuoYiFileApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 文件服务模块启动成功 ლ(´ڡ`ლ)゙ \n" +
" .-------. ____ __ \n" +
" | _ _ \\ \\ \\ / / \n" +
" | ( ' ) | \\ _. / ' \n" +
" |(_ o _) / _( )_ .' \n" +
" | (_,_).' __ ___(_ o _)' \n" +
" | |\\ \\ | || |(_,_)' \n" +
" | | \\ `' /| `-' / \n" +
" | | \\ / \\ / \n" +
" ''-' `'-' `-..-' ");
}
}

View File

@@ -3,6 +3,7 @@ package com.ruoyi.file.config;
import java.io.File;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -33,4 +34,17 @@ public class ResourcesConfig implements WebMvcConfigurer
registry.addResourceHandler(localFilePrefix + "/**")
.addResourceLocations("file:" + localFilePath + File.separator);
}
/**
* 开启跨域
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路由
registry.addMapping(localFilePrefix + "/**")
// 设置允许跨域请求的域名
.allowedOrigins("*")
// 设置允许的方法
.allowedMethods("GET");
}
}

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -10,6 +10,7 @@ import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
@@ -27,7 +28,6 @@ import com.ruoyi.common.core.exception.CustomException;
import com.ruoyi.common.core.text.CharsetKit;
import com.ruoyi.common.core.utils.SecurityUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.file.FileUtils;
import com.ruoyi.gen.domain.GenTable;
import com.ruoyi.gen.domain.GenTableColumn;
import com.ruoyi.gen.mapper.GenTableColumnMapper;

View File

@@ -280,7 +280,8 @@ public class VelocityUtils
*/
public static String getParentMenuId(JSONObject paramsObj)
{
if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID))
if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)
&& StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID)))
{
return paramsObj.getString(GenConstants.PARENT_MENU_ID);
}

View File

@@ -78,7 +78,7 @@ public class ${ClassName}Controller extends BaseController
@GetMapping(value = "/{${pkColumn.javaField}}")
public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField})
{
return AjaxResult.success(${className}Service.select${ClassName}ById(${pkColumn.javaField}));
return AjaxResult.success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}));
}
/**
@@ -111,6 +111,6 @@ public class ${ClassName}Controller extends BaseController
@DeleteMapping("/{${pkColumn.javaField}s}")
public AjaxResult remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s)
{
return toAjax(${className}Service.delete${ClassName}ByIds(${pkColumn.javaField}s));
return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s));
}
}

View File

@@ -17,10 +17,10 @@ public interface ${ClassName}Mapper
/**
* 查询${functionName}
*
* @param ${pkColumn.javaField} ${functionName}ID
* @param ${pkColumn.javaField} ${functionName}主键
* @return ${functionName}
*/
public ${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField});
public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});
/**
* 查询${functionName}列表
@@ -49,27 +49,27 @@ public interface ${ClassName}Mapper
/**
* 删除${functionName}
*
* @param ${pkColumn.javaField} ${functionName}ID
* @param ${pkColumn.javaField} ${functionName}主键
* @return 结果
*/
public int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField});
public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});
/**
* 批量删除${functionName}
*
* @param ${pkColumn.javaField}s 需要删除的数据ID
* @param ${pkColumn.javaField}s 需要删除的数据主键集合
* @return 结果
*/
public int delete${ClassName}ByIds(${pkColumn.javaType}[] ${pkColumn.javaField}s);
public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s);
#if($table.sub)
/**
* 批量删除${subTable.functionName}
*
* @param customerIds 需要删除的数据ID
* @param ${pkColumn.javaField}s 需要删除的数据主键集合
* @return 结果
*/
public int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s);
public int delete${subClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s);
/**
* 批量新增${subTable.functionName}
@@ -81,7 +81,7 @@ public interface ${ClassName}Mapper
/**
* 通过${functionName}ID删除${subTable.functionName}信息
* 通过${functionName}主键删除${subTable.functionName}信息
*
* @param ${pkColumn.javaField} ${functionName}ID
* @return 结果

View File

@@ -14,10 +14,10 @@ public interface I${ClassName}Service
/**
* 查询${functionName}
*
* @param ${pkColumn.javaField} ${functionName}ID
* @param ${pkColumn.javaField} ${functionName}主键
* @return ${functionName}
*/
public ${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField});
public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});
/**
* 查询${functionName}列表
@@ -46,16 +46,16 @@ public interface I${ClassName}Service
/**
* 批量删除${functionName}
*
* @param ${pkColumn.javaField}s 需要删除的${functionName}ID
* @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合
* @return 结果
*/
public int delete${ClassName}ByIds(${pkColumn.javaType}[] ${pkColumn.javaField}s);
public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s);
/**
* 删除${functionName}信息
*
* @param ${pkColumn.javaField} ${functionName}ID
* @param ${pkColumn.javaField} ${functionName}主键
* @return 结果
*/
public int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField});
public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});
}

View File

@@ -34,13 +34,13 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service
/**
* 查询${functionName}
*
* @param ${pkColumn.javaField} ${functionName}ID
* @param ${pkColumn.javaField} ${functionName}主键
* @return ${functionName}
*/
@Override
public ${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField})
public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField})
{
return ${className}Mapper.select${ClassName}ById(${pkColumn.javaField});
return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField});
}
/**
@@ -108,34 +108,34 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service
/**
* 批量删除${functionName}
*
* @param ${pkColumn.javaField}s 需要删除的${functionName}ID
* @param ${pkColumn.javaField}s 需要删除的${functionName}主键
* @return 结果
*/
#if($table.sub)
@Transactional
#end
@Override
public int delete${ClassName}ByIds(${pkColumn.javaType}[] ${pkColumn.javaField}s)
public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s)
{
#if($table.sub)
${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s);
#end
return ${className}Mapper.delete${ClassName}ByIds(${pkColumn.javaField}s);
return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s);
}
/**
* 删除${functionName}信息
*
* @param ${pkColumn.javaField} ${functionName}ID
* @param ${pkColumn.javaField} ${functionName}主键
* @return 结果
*/
@Override
public int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField})
public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField})
{
#if($table.sub)
${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField});
#end
return ${className}Mapper.delete${ClassName}ById(${pkColumn.javaField});
return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField});
}
#if($table.sub)
@@ -147,7 +147,7 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service
public void insert${subClassName}(${ClassName} ${className})
{
List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List();
Long ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}();
${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}();
if (StringUtils.isNotNull(${subclassName}List))
{
List<${subClassName}> list = new ArrayList<${subClassName}>();

View File

@@ -258,46 +258,10 @@
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName}, export${BusinessName} } from "@/api/${moduleName}/${businessName}";
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "imageUpload")
import ImageUpload from '@/components/ImageUpload';
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "fileUpload")
import FileUpload from '@/components/FileUpload';
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "editor")
import Editor from '@/components/Editor';
#break
#end
#end
export default {
name: "${BusinessName}",
components: {
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "imageUpload")
ImageUpload,
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "fileUpload")
FileUpload,
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "editor")
Editor,
#break
#end
#end
Treeselect
},
data() {

View File

@@ -309,47 +309,9 @@
<script>
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "imageUpload")
import ImageUpload from '@/components/ImageUpload';
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "fileUpload")
import FileUpload from '@/components/FileUpload';
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "editor")
import Editor from '@/components/Editor';
#break
#end
#end
export default {
name: "${BusinessName}",
components: {
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "imageUpload")
ImageUpload,
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "fileUpload")
FileUpload,
#break
#end
#end
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "editor")
Editor,
#break
#end
#end
},
data() {
return {
// 遮罩层

View File

@@ -58,7 +58,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</where>
</select>
<select id="select${ClassName}ById" parameterType="${pkColumn.javaType}" resultMap="#if($table.sub)${ClassName}${subClassName}Result#else${ClassName}Result#end">
<select id="select${ClassName}By${pkColumn.capJavaField}" parameterType="${pkColumn.javaType}" resultMap="#if($table.sub)${ClassName}${subClassName}Result#else${ClassName}Result#end">
#if($table.crud || $table.tree)
<include refid="select${ClassName}Vo"/>
where ${pkColumn.columnName} = #{${pkColumn.javaField}}
@@ -102,11 +102,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where ${pkColumn.columnName} = #{${pkColumn.javaField}}
</update>
<delete id="delete${ClassName}ById" parameterType="${pkColumn.javaType}">
<delete id="delete${ClassName}By${pkColumn.capJavaField}" parameterType="${pkColumn.javaType}">
delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}}
</delete>
<delete id="delete${ClassName}ByIds" parameterType="String">
<delete id="delete${ClassName}By${pkColumn.capJavaField}s" parameterType="String">
delete from ${tableName} where ${pkColumn.columnName} in
<foreach item="${pkColumn.javaField}" collection="array" open="(" separator="," close=")">
#{${pkColumn.javaField}}
@@ -121,7 +121,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach>
</delete>
<delete id="delete${subClassName}By${subTableFkClassName}" parameterType="Long">
<delete id="delete${subClassName}By${subTableFkClassName}" parameterType="${pkColumn.javaType}">
delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}}
</delete>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -13,8 +13,10 @@ import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.exception.job.TaskException;
import com.ruoyi.common.core.utils.SecurityUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.poi.ExcelUtil;
import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.common.core.web.domain.AjaxResult;
@@ -79,14 +81,22 @@ public class SysJobController extends BaseController
@PreAuthorize(hasPermi = "monitor:job:add")
@Log(title = "定时任务", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody SysJob sysJob) throws SchedulerException, TaskException
public AjaxResult add(@RequestBody SysJob job) throws SchedulerException, TaskException
{
if (!CronUtils.isValid(sysJob.getCronExpression()))
if (!CronUtils.isValid(job.getCronExpression()))
{
return AjaxResult.error("cron表达式不正确");
return error("新增任务'" + job.getJobName() + "'失败Cron表达式不正确");
}
sysJob.setCreateBy(SecurityUtils.getUsername());
return toAjax(jobService.insertJob(sysJob));
else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi://'调用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
{
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)//'调用");
}
job.setCreateBy(SecurityUtils.getUsername());
return toAjax(jobService.insertJob(job));
}
/**
@@ -95,14 +105,22 @@ public class SysJobController extends BaseController
@PreAuthorize(hasPermi = "monitor:job:edit")
@Log(title = "定时任务", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody SysJob sysJob) throws SchedulerException, TaskException
public AjaxResult edit(@RequestBody SysJob job) throws SchedulerException, TaskException
{
if (!CronUtils.isValid(sysJob.getCronExpression()))
if (!CronUtils.isValid(job.getCronExpression()))
{
return AjaxResult.error("cron表达式不正确");
return error("修改任务'" + job.getJobName() + "'失败Cron表达式不正确");
}
sysJob.setUpdateBy(SecurityUtils.getUsername());
return toAjax(jobService.updateJob(sysJob));
else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
{
return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi://'调用");
}
else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
{
return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)//'调用");
}
job.setUpdateBy(SecurityUtils.getUsername());
return toAjax(jobService.updateJob(job));
}
/**

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -8,20 +8,18 @@ import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.ip.IpUtils;
import com.ruoyi.common.core.utils.poi.ExcelUtil;
import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.core.web.page.TableDataInfo;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.security.annotation.InnerAuth;
import com.ruoyi.common.security.annotation.PreAuthorize;
import com.ruoyi.system.domain.SysLogininfor;
import com.ruoyi.system.api.domain.SysLogininfor;
import com.ruoyi.system.service.ISysLogininforService;
/**
@@ -72,26 +70,10 @@ public class SysLogininforController extends BaseController
return AjaxResult.success();
}
@InnerAuth
@PostMapping
public AjaxResult add(@RequestParam("username") String username, @RequestParam("status") String status,
@RequestParam("message") String message)
public AjaxResult add(@RequestBody SysLogininfor logininfor)
{
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
// 封装对象
SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username);
logininfor.setIpaddr(ip);
logininfor.setMsg(message);
// 日志状态
if (Constants.LOGIN_SUCCESS.equals(status) || Constants.LOGOUT.equals(status))
{
logininfor.setStatus("0");
}
else if (Constants.LOGIN_FAIL.equals(status))
{
logininfor.setStatus("1");
}
return toAjax(logininforService.insertLogininfor(logininfor));
}
}

View File

@@ -11,7 +11,6 @@ import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.utils.SecurityUtils;
import com.ruoyi.common.core.utils.StringUtils;
@@ -94,8 +93,7 @@ public class SysMenuController extends BaseController
{
return AjaxResult.error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
}
else if (UserConstants.YES_FRAME.equals(menu.getIsFrame())
&& !StringUtils.startsWithAny(menu.getPath(), Constants.HTTP, Constants.HTTPS))
else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath()))
{
return AjaxResult.error("新增菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头");
}
@@ -115,8 +113,7 @@ public class SysMenuController extends BaseController
{
return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
}
else if (UserConstants.YES_FRAME.equals(menu.getIsFrame())
&& !StringUtils.startsWithAny(menu.getPath(), Constants.HTTP, Constants.HTTPS))
else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath()))
{
return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头");
}

View File

@@ -17,6 +17,7 @@ import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.core.web.page.TableDataInfo;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.security.annotation.InnerAuth;
import com.ruoyi.common.security.annotation.PreAuthorize;
import com.ruoyi.system.api.domain.SysOperLog;
import com.ruoyi.system.service.ISysOperLogService;
@@ -69,6 +70,7 @@ public class SysOperlogController extends BaseController
return AjaxResult.success();
}
@InnerAuth
@PostMapping
public AjaxResult add(@RequestBody SysOperLog operLog)
{

View File

@@ -75,9 +75,12 @@ public class SysProfileController extends BaseController
{
return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
LoginUser loginUser = tokenService.getLoginUser();
SysUser sysUser = loginUser.getSysUser();
user.setUserId(sysUser.getUserId());
user.setPassword(null);
if (userService.updateUserProfile(user) > 0)
{
LoginUser loginUser = tokenService.getLoginUser();
// 更新缓存用户信息
loginUser.getSysUser().setNickName(user.getNickName());
loginUser.getSysUser().setPhonenumber(user.getPhonenumber());

View File

@@ -23,7 +23,10 @@ import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.security.annotation.PreAuthorize;
import com.ruoyi.system.api.domain.SysRole;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.domain.SysUserRole;
import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService;
/**
* 角色信息
@@ -37,6 +40,9 @@ public class SysRoleController extends BaseController
@Autowired
private ISysRoleService roleService;
@Autowired
private ISysUserService userService;
@PreAuthorize(hasPermi = "system:role:list")
@GetMapping("/list")
public TableDataInfo list(SysRole role)
@@ -153,4 +159,60 @@ public class SysRoleController extends BaseController
{
return AjaxResult.success(roleService.selectRoleAll());
}
/**
* 查询已分配用户角色列表
*/
@PreAuthorize(hasPermi = "system:role:list")
@GetMapping("/authUser/allocatedList")
public TableDataInfo allocatedList(SysUser user)
{
startPage();
List<SysUser> list = userService.selectAllocatedList(user);
return getDataTable(list);
}
/**
* 查询未分配用户角色列表
*/
@PreAuthorize(hasPermi = "system:role:list")
@GetMapping("/authUser/unallocatedList")
public TableDataInfo unallocatedList(SysUser user)
{
startPage();
List<SysUser> list = userService.selectUnallocatedList(user);
return getDataTable(list);
}
/**
* 取消授权用户
*/
@PreAuthorize(hasPermi = "system:role:edit")
@Log(title = "角色管理", businessType = BusinessType.GRANT)
@PutMapping("/authUser/cancel")
public AjaxResult cancelAuthUser(@RequestBody SysUserRole userRole)
{
return toAjax(roleService.deleteAuthUser(userRole));
}
/**
* 批量取消授权用户
*/
@PreAuthorize(hasPermi = "system:role:edit")
@Log(title = "角色管理", businessType = BusinessType.GRANT)
@PutMapping("/authUser/cancelAll")
public AjaxResult cancelAuthUserAll(Long roleId, Long[] userIds)
{
return toAjax(roleService.deleteAuthUsers(roleId, userIds));
}
/**
* 批量选择用户授权
*/
@PreAuthorize(hasPermi = "system:role:edit")
@Log(title = "角色管理", businessType = BusinessType.GRANT)
@PutMapping("/authUser/selectAll")
public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds)
{
return toAjax(roleService.insertAuthUsers(roleId, userIds));
}
}

View File

@@ -5,6 +5,7 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -26,10 +27,12 @@ import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.core.web.page.TableDataInfo;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.security.annotation.InnerAuth;
import com.ruoyi.common.security.annotation.PreAuthorize;
import com.ruoyi.system.api.domain.SysRole;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.model.LoginUser;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysPermissionService;
import com.ruoyi.system.service.ISysPostService;
import com.ruoyi.system.service.ISysRoleService;
@@ -56,6 +59,9 @@ public class SysUserController extends BaseController
@Autowired
private ISysPermissionService permissionService;
@Autowired
private ISysConfigService configService;
/**
* 获取用户列表
*/
@@ -100,6 +106,7 @@ public class SysUserController extends BaseController
/**
* 获取当前用户信息
*/
@InnerAuth
@GetMapping("/info/{username}")
public R<LoginUser> info(@PathVariable("username") String username)
{
@@ -119,6 +126,25 @@ public class SysUserController extends BaseController
return R.ok(sysUserVo);
}
/**
* 注册用户信息
*/
@InnerAuth
@PostMapping("/register")
public R<Boolean> register(@RequestBody SysUser sysUser)
{
String username = sysUser.getUserName();
if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser"))))
{
return R.fail("当前系统没有开启注册功能!");
}
if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(username)))
{
return R.fail("保存用户'" + username + "'失败,注册账号已存在");
}
return R.ok(userService.registerUser(sysUser));
}
/**
* 获取用户信息
*
@@ -217,6 +243,10 @@ public class SysUserController extends BaseController
@DeleteMapping("/{userIds}")
public AjaxResult remove(@PathVariable Long[] userIds)
{
if (ArrayUtils.contains(userIds, SecurityUtils.getUserId()))
{
return AjaxResult.error("当前用户不能删除");
}
return toAjax(userService.deleteUserByIds(userIds));
}
@@ -246,4 +276,31 @@ public class SysUserController extends BaseController
user.setUpdateBy(SecurityUtils.getUsername());
return toAjax(userService.updateUserStatus(user));
}
/**
* 根据用户编号获取授权角色
*/
@PreAuthorize(hasPermi = "system:user:query")
@GetMapping("/authRole/{userId}")
public AjaxResult authRole(@PathVariable("userId") Long userId)
{
AjaxResult ajax = AjaxResult.success();
SysUser user = userService.selectUserById(userId);
List<SysRole> roles = roleService.selectRolesByUserId(userId);
ajax.put("user", user);
ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
return ajax;
}
/**
* 用户授权角色
*/
@PreAuthorize(hasPermi = "system:user:edit")
@Log(title = "用户管理", businessType = BusinessType.GRANT)
@PutMapping("/authRole")
public AjaxResult insertAuthRole(Long userId, Long[] roleIds)
{
userService.insertUserAuth(userId, roleIds);
return success();
}
}

View File

@@ -1,5 +1,7 @@
package com.ruoyi.system.domain.vo;
import com.ruoyi.common.core.utils.StringUtils;
/**
* 路由显示信息
*
@@ -22,6 +24,11 @@ public class MetaVo
*/
private boolean noCache;
/**
* 内链地址http(s)://开头)
*/
private String link;
public MetaVo()
{
}
@@ -39,6 +46,24 @@ public class MetaVo
this.noCache = noCache;
}
public MetaVo(String title, String icon, String link)
{
this.title = title;
this.icon = icon;
this.link = link;
}
public MetaVo(String title, String icon, boolean noCache, String link)
{
this.title = title;
this.icon = icon;
this.noCache = noCache;
if (StringUtils.ishttp(link))
{
this.link = link;
}
}
public boolean isNoCache()
{
return noCache;
@@ -68,4 +93,14 @@ public class MetaVo
{
this.icon = icon;
}
public String getLink()
{
return link;
}
public void setLink(String link)
{
this.link = link;
}
}

View File

@@ -96,11 +96,11 @@ public interface SysDeptMapper
public int updateDept(SysDept dept);
/**
* 修改所在部门的父级部门状态
* 修改所在部门正常状态
*
* @param dept 部门
* @param deptIds 部门ID组
*/
public void updateDeptStatus(SysDept dept);
public void updateDeptStatusNormal(Long[] deptIds);
/**
* 修改子元素关系

View File

@@ -1,7 +1,7 @@
package com.ruoyi.system.mapper;
import java.util.List;
import com.ruoyi.system.domain.SysLogininfor;
import com.ruoyi.system.api.domain.SysLogininfor;
/**
* 系统访问日志情况信息 数据层

View File

@@ -1,9 +1,7 @@
package com.ruoyi.system.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.system.api.domain.SysUser;
/**
@@ -20,6 +18,21 @@ public interface SysUserMapper
* @return 用户信息集合信息
*/
public List<SysUser> selectUserList(SysUser sysUser);
/**
* 根据条件分页查询未已配用户角色列表
*
* @param user 用户信息
* @return 用户信息集合信息
*/
public List<SysUser> selectAllocatedList(SysUser user);
/**
* 根据条件分页查询未分配用户角色列表
*
* @param user 用户信息
* @return 用户信息集合信息
*/
public List<SysUser> selectUnallocatedList(SysUser user);
/**
* 通过用户名查询用户

View File

@@ -1,7 +1,7 @@
package com.ruoyi.system.service;
import java.util.List;
import com.ruoyi.system.domain.SysLogininfor;
import com.ruoyi.system.api.domain.SysLogininfor;
/**
* 系统访问日志情况信息 服务层

View File

@@ -2,8 +2,8 @@ package com.ruoyi.system.service;
import java.util.List;
import java.util.Set;
import com.ruoyi.system.api.domain.SysRole;
import com.ruoyi.system.domain.SysUserRole;
/**
* 角色业务层
@@ -21,7 +21,15 @@ public interface ISysRoleService
public List<SysRole> selectRoleList(SysRole role);
/**
* 根据用户ID查询角色
* 根据用户ID查询角色列表
*
* @param userId 用户ID
* @return 角色列表
*/
public List<SysRole> selectRolesByUserId(Long userId);
/**
* 根据用户ID查询角色权限
*
* @param userId 用户ID
* @return 权限列表
@@ -129,4 +137,29 @@ public interface ISysRoleService
* @return 结果
*/
public int deleteRoleByIds(Long[] roleIds);
/**
* 取消授权用户角色
*
* @param userRole 用户和角色关联信息
* @return 结果
*/
public int deleteAuthUser(SysUserRole userRole);
/**
* 批量取消授权用户角色
*
* @param roleId 角色ID
* @param userIds 需要取消授权的用户数据ID
* @return 结果
*/
public int deleteAuthUsers(Long roleId, Long[] userIds);
/**
* 批量选择授权用户角色
*
* @param roleId 角色ID
* @param userIds 需要删除的用户数据ID
* @return 结果
*/
public int insertAuthUsers(Long roleId, Long[] userIds);
}

View File

@@ -1,7 +1,6 @@
package com.ruoyi.system.service;
import java.util.List;
import com.ruoyi.system.api.domain.SysUser;
/**
@@ -19,6 +18,22 @@ public interface ISysUserService
*/
public List<SysUser> selectUserList(SysUser user);
/**
* 根据条件分页查询已分配用户角色列表
*
* @param user 用户信息
* @return 用户信息集合信息
*/
public List<SysUser> selectAllocatedList(SysUser user);
/**
* 根据条件分页查询未分配用户角色列表
*
* @param user 用户信息
* @return 用户信息集合信息
*/
public List<SysUser> selectUnallocatedList(SysUser user);
/**
* 通过用户名查询用户
*
@@ -90,6 +105,14 @@ public interface ISysUserService
*/
public int insertUser(SysUser user);
/**
* 注册用户信息
*
* @param user 用户信息
* @return 结果
*/
public boolean registerUser(SysUser user);
/**
* 修改用户信息
*
@@ -97,6 +120,14 @@ public interface ISysUserService
* @return 结果
*/
public int updateUser(SysUser user);
/**
* 用户授权角色
*
* @param userId 用户ID
* @param roleIds 角色组
*/
public void insertUserAuth(Long userId, Long[] roleIds);
/**
* 修改用户状态

View File

@@ -8,6 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.exception.CustomException;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.datascope.annotation.DataScope;
import com.ruoyi.system.api.domain.SysDept;
@@ -208,10 +209,11 @@ public class SysDeptServiceImpl implements ISysDeptService
updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors);
}
int result = deptMapper.updateDept(dept);
if (UserConstants.DEPT_NORMAL.equals(dept.getStatus()))
if (UserConstants.DEPT_NORMAL.equals(dept.getStatus()) && StringUtils.isNotEmpty(dept.getAncestors())
&& !StringUtils.equals("0", dept.getAncestors()))
{
// 如果该部门是启用状态,则启用该部门的所有上级部门
updateParentDeptStatus(dept);
updateParentDeptStatusNormal(dept);
}
return result;
}
@@ -221,12 +223,11 @@ public class SysDeptServiceImpl implements ISysDeptService
*
* @param dept 当前部门
*/
private void updateParentDeptStatus(SysDept dept)
private void updateParentDeptStatusNormal(SysDept dept)
{
String updateBy = dept.getUpdateBy();
dept = deptMapper.selectDeptById(dept.getDeptId());
dept.setUpdateBy(updateBy);
deptMapper.updateDeptStatus(dept);
String ancestors = dept.getAncestors();
Long[] deptIds = Convert.toLongArray(ancestors);
deptMapper.updateDeptStatusNormal(deptIds);
}
/**

View File

@@ -3,7 +3,7 @@ package com.ruoyi.system.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.system.domain.SysLogininfor;
import com.ruoyi.system.api.domain.SysLogininfor;
import com.ruoyi.system.mapper.SysLogininforMapper;
import com.ruoyi.system.service.ISysLogininforService;

View File

@@ -10,6 +10,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.utils.SecurityUtils;
import com.ruoyi.common.core.utils.StringUtils;
@@ -150,7 +151,7 @@ public class SysMenuServiceImpl implements ISysMenuService
router.setName(getRouteName(menu));
router.setPath(getRouterPath(menu));
router.setComponent(getComponent(menu));
router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache())));
router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
List<SysMenu> cMenus = menu.getChildren();
if (!cMenus.isEmpty() && cMenus.size() > 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType()))
{
@@ -166,7 +167,21 @@ public class SysMenuServiceImpl implements ISysMenuService
children.setPath(menu.getPath());
children.setComponent(menu.getComponent());
children.setName(StringUtils.capitalize(menu.getPath()));
children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache())));
children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
childrenList.add(children);
router.setChildren(childrenList);
}
else if (menu.getParentId().intValue() == 0 && isInnerLink(menu))
{
router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
router.setPath("/inner");
List<RouterVo> childrenList = new ArrayList<RouterVo>();
RouterVo children = new RouterVo();
String routerPath = StringUtils.replaceEach(menu.getPath(), new String[] { Constants.HTTP, Constants.HTTPS }, new String[] { "", "" });
children.setPath(routerPath);
children.setComponent(UserConstants.INNER_LINK);
children.setName(StringUtils.capitalize(routerPath));
children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));
childrenList.add(children);
router.setChildren(childrenList);
}
@@ -338,6 +353,11 @@ public class SysMenuServiceImpl implements ISysMenuService
public String getRouterPath(SysMenu menu)
{
String routerPath = menu.getPath();
// 内链打开外网方式
if (menu.getParentId().intValue() != 0 && isInnerLink(menu))
{
routerPath = StringUtils.replaceEach(routerPath, new String[] { Constants.HTTP, Constants.HTTPS }, new String[] { "", "" });
}
// 非外链并且是一级目录(类型为目录)
if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType())
&& UserConstants.NO_FRAME.equals(menu.getIsFrame()))
@@ -365,6 +385,10 @@ public class SysMenuServiceImpl implements ISysMenuService
{
component = menu.getComponent();
}
else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 && isInnerLink(menu))
{
component = UserConstants.INNER_LINK;
}
else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu))
{
component = UserConstants.PARENT_VIEW;
@@ -384,6 +408,17 @@ public class SysMenuServiceImpl implements ISysMenuService
&& menu.getIsFrame().equals(UserConstants.NO_FRAME);
}
/**
* 是否为内链组件
*
* @param menu 菜单信息
* @return 结果
*/
public boolean isInnerLink(SysMenu menu)
{
return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath());
}
/**
* 是否为parent_view组件
*

View File

@@ -16,6 +16,7 @@ import com.ruoyi.common.datascope.annotation.DataScope;
import com.ruoyi.system.api.domain.SysRole;
import com.ruoyi.system.domain.SysRoleDept;
import com.ruoyi.system.domain.SysRoleMenu;
import com.ruoyi.system.domain.SysUserRole;
import com.ruoyi.system.mapper.SysRoleDeptMapper;
import com.ruoyi.system.mapper.SysRoleMapper;
import com.ruoyi.system.mapper.SysRoleMenuMapper;
@@ -55,6 +56,31 @@ public class SysRoleServiceImpl implements ISysRoleService
return roleMapper.selectRoleList(role);
}
/**
* 根据用户ID查询角色
*
* @param userId 用户ID
* @return 角色列表
*/
@Override
public List<SysRole> selectRolesByUserId(Long userId)
{
List<SysRole> userRoles = roleMapper.selectRolePermissionByUserId(userId);
List<SysRole> roles = selectRoleAll();
for (SysRole role : roles)
{
for (SysRole userRole : userRoles)
{
if (role.getRoleId().longValue() == userRole.getRoleId().longValue())
{
role.setFlag(true);
break;
}
}
}
return roles;
}
/**
* 根据用户ID查询权限
*
@@ -325,4 +351,51 @@ public class SysRoleServiceImpl implements ISysRoleService
roleDeptMapper.deleteRoleDept(roleIds);
return roleMapper.deleteRoleByIds(roleIds);
}
/**
* 取消授权用户角色
*
* @param userRole 用户和角色关联信息
* @return 结果
*/
@Override
public int deleteAuthUser(SysUserRole userRole)
{
return userRoleMapper.deleteUserRoleInfo(userRole);
}
/**
* 批量取消授权用户角色
*
* @param roleId 角色ID
* @param userIds 需要取消授权的用户数据ID
* @return 结果
*/
@Override
public int deleteAuthUsers(Long roleId, Long[] userIds)
{
return userRoleMapper.deleteUserRoleInfos(roleId, userIds);
}
/**
* 批量选择授权用户角色
*
* @param roleId 角色ID
* @param userIds 需要删除的用户数据ID
* @return 结果
*/
@Override
public int insertAuthUsers(Long roleId, Long[] userIds)
{
// 新增用户与角色管理
List<SysUserRole> list = new ArrayList<SysUserRole>();
for (Long userId : userIds)
{
SysUserRole ur = new SysUserRole();
ur.setUserId(userId);
ur.setRoleId(roleId);
list.add(ur);
}
return userRoleMapper.batchUserRole(list);
}
}

View File

@@ -66,6 +66,32 @@ public class SysUserServiceImpl implements ISysUserService
return userMapper.selectUserList(user);
}
/**
* 根据条件分页查询已分配用户角色列表
*
* @param user 用户信息
* @return 用户信息集合信息
*/
@Override
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysUser> selectAllocatedList(SysUser user)
{
return userMapper.selectAllocatedList(user);
}
/**
* 根据条件分页查询未分配用户角色列表
*
* @param user 用户信息
* @return 用户信息集合信息
*/
@Override
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysUser> selectUnallocatedList(SysUser user)
{
return userMapper.selectUnallocatedList(user);
}
/**
* 通过用户名查询用户
*
@@ -220,6 +246,17 @@ public class SysUserServiceImpl implements ISysUserService
return rows;
}
/**
* 注册用户信息
*
* @param user 用户信息
* @return 结果
*/
public boolean registerUser(SysUser user)
{
return userMapper.insertUser(user) > 0;
}
/**
* 修改保存用户信息
*
@@ -242,6 +279,20 @@ public class SysUserServiceImpl implements ISysUserService
return userMapper.updateUser(user);
}
/**
* 用户授权角色
*
* @param userId 用户ID
* @param roleIds 角色组
*/
@Override
@Transactional
public void insertUserAuth(Long userId, Long[] roleIds)
{
userRoleMapper.deleteUserRoleByUserId(userId);
insertUserRole(userId, roleIds);
}
/**
* 修改用户状态
*
@@ -356,6 +407,32 @@ public class SysUserServiceImpl implements ISysUserService
}
}
/**
* 新增用户角色信息
*
* @param userId 用户ID
* @param roleIds 角色组
*/
public void insertUserRole(Long userId, Long[] roleIds)
{
if (StringUtils.isNotNull(roleIds))
{
// 新增用户与角色管理
List<SysUserRole> list = new ArrayList<SysUserRole>();
for (Long roleId : roleIds)
{
SysUserRole ur = new SysUserRole();
ur.setUserId(userId);
ur.setRoleId(roleId);
list.add(ur);
}
if (list.size() > 0)
{
userRoleMapper.batchUserRole(list);
}
}
}
/**
* 通过用户ID删除用户
*

View File

@@ -140,14 +140,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach>
</update>
<update id="updateDeptStatus" parameterType="SysDept">
update sys_dept
<set>
<if test="status != null and status != ''">status = #{status},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = sysdate()
</set>
where find_in_set(#{deptId}, ancestors)
<update id="updateDeptStatusNormal" parameterType="Long">
update sys_dept set status = '0' where dept_id in
<foreach collection="array" item="deptId" open="(" separator="," close=")">
#{deptId}
</foreach>
</update>
<delete id="deleteDeptById" parameterType="Long">

View File

@@ -81,6 +81,41 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
${params.dataScope}
</select>
<select id="selectAllocatedList" parameterType="SysUser" resultMap="SysUserResult">
select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time
from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
left join sys_user_role ur on u.user_id = ur.user_id
left join sys_role r on r.role_id = ur.role_id
where u.del_flag = '0' and r.role_id = #{roleId}
<if test="userName != null and userName != ''">
AND u.user_name like concat('%', #{userName}, '%')
</if>
<if test="phonenumber != null and phonenumber != ''">
AND u.phonenumber like concat('%', #{phonenumber}, '%')
</if>
<!-- 数据范围过滤 -->
${params.dataScope}
</select>
<select id="selectUnallocatedList" parameterType="SysUser" resultMap="SysUserResult">
select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time
from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
left join sys_user_role ur on u.user_id = ur.user_id
left join sys_role r on r.role_id = ur.role_id
where u.del_flag = '0' and (r.role_id != #{roleId} or r.role_id IS NULL)
and u.user_id not in (select u.user_id from sys_user u inner join sys_user_role ur on u.user_id = ur.user_id and ur.role_id = #{roleId})
<if test="userName != null and userName != ''">
AND u.user_name like concat('%', #{userName}, '%')
</if>
<if test="phonenumber != null and phonenumber != ''">
AND u.phonenumber like concat('%', #{phonenumber}, '%')
</if>
<!-- 数据范围过滤 -->
${params.dataScope}
</select>
<select id="selectUserByUserName" parameterType="String" resultMap="SysUserResult">
<include refid="selectUserVo"/>
where u.user_name = #{userName}

View File

@@ -1,3 +1,6 @@
# 页面标题
VUE_APP_TITLE = 若依管理系统
# 开发环境配置
ENV = 'development'

View File

@@ -1,3 +1,6 @@
# 页面标题
VUE_APP_TITLE = 若依管理系统
# 生产环境配置
ENV = 'production'

View File

@@ -1,3 +1,6 @@
# 页面标题
VUE_APP_TITLE = 若依管理系统
NODE_ENV = production
# 测试环境配置

View File

@@ -1,6 +1,6 @@
{
"name": "ruoyi",
"version": "3.0.0",
"version": "3.1.0",
"description": "若依管理系统",
"author": "若依",
"license": "MIT",
@@ -41,7 +41,7 @@
"clipboard": "2.0.6",
"core-js": "3.8.1",
"echarts": "4.9.0",
"element-ui": "2.15.0",
"element-ui": "2.15.3",
"file-saver": "2.0.4",
"fuse.js": "6.4.3",
"highlight.js": "9.18.5",
@@ -56,6 +56,7 @@
"vue-count-to": "1.0.13",
"vue-cropper": "0.5.5",
"vue-router": "3.4.9",
"vue-meta": "^2.4.0",
"vuedraggable": "2.24.3",
"vuex": "3.6.0"
},

View File

@@ -6,6 +6,14 @@
<script>
export default {
name: 'App'
name: 'App',
metaInfo() {
return {
title: this.$store.state.settings.dynamicTitle && this.$store.state.settings.title,
titleTemplate: title => {
return title ? `${title} - ${process.env.VUE_APP_TITLE}` : process.env.VUE_APP_TITLE
}
}
}
}
</script>

View File

@@ -9,6 +9,18 @@ export function login(username, password, code, uuid) {
})
}
// 注册方法
export function register(data) {
return request({
url: '/auth/register',
headers: {
isToken: false
},
method: 'post',
data: data
})
}
// 刷新方法
export function refreshToken() {
return request({

View File

@@ -64,3 +64,47 @@ export function delRole(roleId) {
method: 'delete'
})
}
// 查询角色已授权用户列表
export function allocatedUserList(query) {
return request({
url: '/system/role/authUser/allocatedList',
method: 'get',
params: query
})
}
// 查询角色未授权用户列表
export function unallocatedUserList(query) {
return request({
url: '/system/role/authUser/unallocatedList',
method: 'get',
params: query
})
}
// 取消用户授权角色
export function authUserCancel(data) {
return request({
url: '/system/role/authUser/cancel',
method: 'put',
data: data
})
}
// 批量取消用户授权角色
export function authUserCancelAll(data) {
return request({
url: '/system/role/authUser/cancelAll',
method: 'put',
params: data
})
}
// 授权用户选择
export function authUserSelectAll(data) {
return request({
url: '/system/role/authUser/selectAll',
method: 'put',
params: data
})
}

View File

@@ -108,3 +108,20 @@ export function uploadAvatar(data) {
data: data
})
}
// 查询授权角色
export function getAuthRole(userId) {
return request({
url: '/system/user/authRole/' + userId,
method: 'get'
})
}
// 保存授权角色
export function updateAuthRole(data) {
return request({
url: '/system/user/authRole',
method: 'put',
params: data
})
}

View File

@@ -53,6 +53,13 @@
margin-left: 20px;
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
}
.el-dialog:not(.is-fullscreen){
margin-top: 6vh !important;
}
@@ -105,12 +112,32 @@
position: absolute;
}
@media ( max-width : 768px) {
.pagination-container .el-pagination > .el-pagination__jump {
display: none !important;
}
.pagination-container .el-pagination > .el-pagination__sizes {
display: none !important;
}
}
.el-table .fixed-width .el-button--mini {
padding-left: 0;
padding-right: 0;
width: inherit;
}
/** 表格更多操作下拉样式 */
.el-table .el-dropdown-link {
cursor: pointer;
color: #1890ff;
margin-left: 5px;
}
.el-table .el-dropdown, .el-icon-arrow-down {
font-size: 12px;
}
.el-tree-node__content > .el-checkbox {
margin-right: 8px;
}

View File

@@ -0,0 +1,51 @@
<template>
<div>
<template v-for="(item, index) in options">
<template v-if="values.includes(item.dictValue)">
<span
v-if="item.listClass == 'default' || item.listClass == ''"
:key="item.dictValue"
:index="index"
:class="item.cssClass"
>{{ item.dictLabel }}</span
>
<el-tag
v-else
:key="item.dictValue"
:index="index"
:type="item.listClass == 'primary' ? '' : item.listClass"
:class="item.cssClass"
>
{{ item.dictLabel }}
</el-tag>
</template>
</template>
</div>
</template>
<script>
export default {
name: "DictTag",
props: {
options: {
type: Array,
default: null,
},
value: [String, Array],
},
computed: {
values() {
if (this.value) {
return Array.isArray(this.value) ? this.value : [this.value];
} else {
return [];
}
},
},
};
</script>
<style scoped>
.el-tag + .el-tag {
margin-left: 10px;
}
</style>

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