Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
1d9931e75f
|
|
@ -115,4 +115,4 @@ com.ruoyi
|
||||||
|
|
||||||
## 若依微服务交流群
|
## 若依微服务交流群
|
||||||
|
|
||||||
QQ群: [](https://jq.qq.com/?_wv=1027&k=yqInfq0S) [](https://jq.qq.com/?_wv=1027&k=Oy1mb3p8) [](https://jq.qq.com/?_wv=1027&k=rvxkJtXK) [](https://jq.qq.com/?_wv=1027&k=0Ck3PvTe) 点击按钮入群。
|
QQ群: [](https://jq.qq.com/?_wv=1027&k=yqInfq0S) [](https://jq.qq.com/?_wv=1027&k=Oy1mb3p8) [](https://jq.qq.com/?_wv=1027&k=rvxkJtXK) [](https://jq.qq.com/?_wv=1027&k=0Ck3PvTe) [](https://jq.qq.com/?_wv=1027&k=FnHHP4TT) 点击按钮入群。
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@echo off
|
@echo off
|
||||||
echo.
|
echo.
|
||||||
echo [信息] 清理生成路径。
|
echo [信息] 清理工程target生成路径。
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
%~d0
|
%~d0
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@echo off
|
@echo off
|
||||||
echo.
|
echo.
|
||||||
echo [信息] 运行auth工程。
|
echo [信息] 使用Jar命令运行Auth工程。
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
cd %~dp0
|
cd %~dp0
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@echo off
|
@echo off
|
||||||
echo.
|
echo.
|
||||||
echo [信息] 运行gateway工程。
|
echo [信息] 使用Jar命令运行Gateway工程。
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
cd %~dp0
|
cd %~dp0
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@echo off
|
@echo off
|
||||||
echo.
|
echo.
|
||||||
echo [信息] 运行modules-file工程。
|
echo [信息] 使用Jar命令运行Modules-File工程。
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
cd %~dp0
|
cd %~dp0
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@echo off
|
@echo off
|
||||||
echo.
|
echo.
|
||||||
echo [信息] 运行modules-gen工程。
|
echo [信息] 使用Jar命令运行Modules-Gen工程。
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
cd %~dp0
|
cd %~dp0
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@echo off
|
@echo off
|
||||||
echo.
|
echo.
|
||||||
echo [信息] 运行modules-job工程。
|
echo [信息] 使用Jar命令运行Modules-Job工程。
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
cd %~dp0
|
cd %~dp0
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@echo off
|
@echo off
|
||||||
echo.
|
echo.
|
||||||
echo [信息] 运行modules-system工程。
|
echo [信息] 使用Jar命令运行Modules-System工程。
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
cd %~dp0
|
cd %~dp0
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@echo off
|
@echo off
|
||||||
echo.
|
echo.
|
||||||
echo [信息] 运行monitor工程。
|
echo [信息] 使用Jar命令运行Monitor工程。
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
cd %~dp0
|
cd %~dp0
|
||||||
|
|
|
||||||
46
pom.xml
46
pom.xml
|
|
@ -6,38 +6,40 @@
|
||||||
|
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi</artifactId>
|
<artifactId>ruoyi</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
|
|
||||||
<name>ruoyi</name>
|
<name>ruoyi</name>
|
||||||
<url>http://www.ruoyi.vip</url>
|
<url>http://www.ruoyi.vip</url>
|
||||||
<description>若依微服务系统</description>
|
<description>若依微服务系统</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<ruoyi.version>3.0.0</ruoyi.version>
|
<ruoyi.version>3.2.0</ruoyi.version>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
<spring-boot.version>2.5.1</spring-boot.version>
|
<spring-boot.version>2.5.5</spring-boot.version>
|
||||||
<spring-cloud.version>2020.0.3</spring-cloud.version>
|
<spring-cloud.version>2020.0.4</spring-cloud.version>
|
||||||
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
|
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
|
||||||
<alibaba.nacos.version>2.0.2</alibaba.nacos.version>
|
<alibaba.nacos.version>2.0.3</alibaba.nacos.version>
|
||||||
<spring-boot-admin.version>2.4.1</spring-boot-admin.version>
|
<spring-boot-admin.version>2.5.2</spring-boot-admin.version>
|
||||||
<spring-boot.mybatis>2.1.4</spring-boot.mybatis>
|
<spring-boot.mybatis>2.2.0</spring-boot.mybatis>
|
||||||
<swagger.fox.version>3.0.0</swagger.fox.version>
|
<swagger.fox.version>3.0.0</swagger.fox.version>
|
||||||
<swagger.core.version>1.6.2</swagger.core.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>
|
<kaptcha.version>2.3.2</kaptcha.version>
|
||||||
<pagehelper.boot.version>1.3.1</pagehelper.boot.version>
|
<pagehelper.boot.version>1.4.0</pagehelper.boot.version>
|
||||||
<druid.version>1.2.6</druid.version>
|
<druid.version>1.2.8</druid.version>
|
||||||
<dynamic-ds.version>3.4.0</dynamic-ds.version>
|
<dynamic-ds.version>3.4.1</dynamic-ds.version>
|
||||||
<commons.io.version>2.10.0</commons.io.version>
|
<commons.io.version>2.11.0</commons.io.version>
|
||||||
<commons.fileupload.version>1.4</commons.fileupload.version>
|
<commons.fileupload.version>1.4</commons.fileupload.version>
|
||||||
<velocity.version>1.7</velocity.version>
|
<velocity.version>1.7</velocity.version>
|
||||||
<fastjson.version>1.2.76</fastjson.version>
|
<fastjson.version>1.2.78</fastjson.version>
|
||||||
<minio.version>8.2.1</minio.version>
|
<jjwt.version>0.9.1</jjwt.version>
|
||||||
|
<minio.version>8.2.2</minio.version>
|
||||||
<poi.version>4.1.2</poi.version>
|
<poi.version>4.1.2</poi.version>
|
||||||
<common-pool.version>2.6.2</common-pool.version>
|
<common-pool.version>2.10.0</common-pool.version>
|
||||||
<commons-collections.version>3.2.2</commons-collections.version>
|
<commons-collections.version>3.2.2</commons-collections.version>
|
||||||
|
<transmittable-thread-local.version>2.12.2</transmittable-thread-local.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<!-- 依赖声明 -->
|
<!-- 依赖声明 -->
|
||||||
|
|
@ -173,6 +175,20 @@
|
||||||
<version>${fastjson.version}</version>
|
<version>${fastjson.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JWT -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt</artifactId>
|
||||||
|
<version>${jjwt.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 线程传递值 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>transmittable-thread-local</artifactId>
|
||||||
|
<version>${transmittable-thread-local.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- 公共资源池 -->
|
<!-- 公共资源池 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi</artifactId>
|
<artifactId>ruoyi</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-api</artifactId>
|
<artifactId>ruoyi-api</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@ package com.ruoyi.system.api;
|
||||||
import org.springframework.cloud.openfeign.FeignClient;
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
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.constant.ServiceNameConstants;
|
||||||
import com.ruoyi.common.core.domain.R;
|
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.domain.SysOperLog;
|
||||||
import com.ruoyi.system.api.factory.RemoteLogFallbackFactory;
|
import com.ruoyi.system.api.factory.RemoteLogFallbackFactory;
|
||||||
|
|
||||||
|
|
@ -21,20 +23,19 @@ public interface RemoteLogService
|
||||||
* 保存系统日志
|
* 保存系统日志
|
||||||
*
|
*
|
||||||
* @param sysOperLog 日志实体
|
* @param sysOperLog 日志实体
|
||||||
|
* @param source 请求来源
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@PostMapping("/operlog")
|
@PostMapping("/operlog")
|
||||||
R<Boolean> saveLog(@RequestBody SysOperLog sysOperLog);
|
public R<Boolean> saveLog(@RequestBody SysOperLog sysOperLog, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存访问记录
|
* 保存访问记录
|
||||||
*
|
*
|
||||||
* @param username 用户名称
|
* @param sysLogininfor 访问实体
|
||||||
* @param status 状态
|
* @param source 请求来源
|
||||||
* @param message 消息
|
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@PostMapping("/logininfor")
|
@PostMapping("/logininfor")
|
||||||
R<Boolean> saveLogininfor(@RequestParam("username") String username, @RequestParam("status") String status,
|
public R<Boolean> saveLogininfor(@RequestBody SysLogininfor sysLogininfor, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||||
@RequestParam("message") String message);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,13 @@ package com.ruoyi.system.api;
|
||||||
import org.springframework.cloud.openfeign.FeignClient;
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
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.constant.ServiceNameConstants;
|
||||||
import com.ruoyi.common.core.domain.R;
|
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.factory.RemoteUserFallbackFactory;
|
||||||
import com.ruoyi.system.api.model.LoginUser;
|
import com.ruoyi.system.api.model.LoginUser;
|
||||||
|
|
||||||
|
|
@ -20,8 +25,19 @@ public interface RemoteUserService
|
||||||
* 通过用户名查询用户信息
|
* 通过用户名查询用户信息
|
||||||
*
|
*
|
||||||
* @param username 用户名
|
* @param username 用户名
|
||||||
|
* @param source 请求来源
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@GetMapping(value = "/user/info/{username}")
|
@GetMapping("/user/info/{username}")
|
||||||
public R<LoginUser> getUserInfo(@PathVariable("username") String 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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.ruoyi.system.domain;
|
package com.ruoyi.system.api.domain;
|
||||||
|
|
||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
import javax.validation.constraints.Size;
|
import javax.validation.constraints.Size;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.ruoyi.system.domain;
|
package com.ruoyi.system.api.domain;
|
||||||
|
|
||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
import javax.validation.constraints.Size;
|
import javax.validation.constraints.Size;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.ruoyi.system.domain;
|
package com.ruoyi.system.api.domain;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
|
@ -204,6 +204,7 @@ public class SysRole extends BaseEntity
|
||||||
this.deptIds = deptIds;
|
this.deptIds = deptIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||||
.append("roleId", getRoleId())
|
.append("roleId", getRoleId())
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,6 @@ public class SysUser extends BaseEntity
|
||||||
/** 密码 */
|
/** 密码 */
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
/** 盐加密 */
|
|
||||||
private String salt;
|
|
||||||
|
|
||||||
/** 帐号状态(0正常 1停用) */
|
/** 帐号状态(0正常 1停用) */
|
||||||
@Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用")
|
@Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
@ -91,6 +88,9 @@ public class SysUser extends BaseEntity
|
||||||
/** 岗位组 */
|
/** 岗位组 */
|
||||||
private Long[] postIds;
|
private Long[] postIds;
|
||||||
|
|
||||||
|
/** 角色ID */
|
||||||
|
private Long roleId;
|
||||||
|
|
||||||
public SysUser()
|
public SysUser()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
@ -208,16 +208,6 @@ public class SysUser extends BaseEntity
|
||||||
this.password = password;
|
this.password = password;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSalt()
|
|
||||||
{
|
|
||||||
return salt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSalt(String salt)
|
|
||||||
{
|
|
||||||
this.salt = salt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStatus()
|
public String getStatus()
|
||||||
{
|
{
|
||||||
return status;
|
return status;
|
||||||
|
|
@ -298,6 +288,15 @@ public class SysUser extends BaseEntity
|
||||||
this.postIds = postIds;
|
this.postIds = postIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getRoleId()
|
||||||
|
{
|
||||||
|
return roleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoleId(Long roleId)
|
||||||
|
{
|
||||||
|
this.roleId = roleId;
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||||
|
|
@ -310,7 +309,6 @@ public class SysUser extends BaseEntity
|
||||||
.append("sex", getSex())
|
.append("sex", getSex())
|
||||||
.append("avatar", getAvatar())
|
.append("avatar", getAvatar())
|
||||||
.append("password", getPassword())
|
.append("password", getPassword())
|
||||||
.append("salt", getSalt())
|
|
||||||
.append("status", getStatus())
|
.append("status", getStatus())
|
||||||
.append("delFlag", getDelFlag())
|
.append("delFlag", getDelFlag())
|
||||||
.append("loginIp", getLoginIp())
|
.append("loginIp", getLoginIp())
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import org.springframework.cloud.openfeign.FallbackFactory;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import com.ruoyi.common.core.domain.R;
|
import com.ruoyi.common.core.domain.R;
|
||||||
import com.ruoyi.system.api.RemoteLogService;
|
import com.ruoyi.system.api.RemoteLogService;
|
||||||
|
import com.ruoyi.system.api.domain.SysLogininfor;
|
||||||
import com.ruoyi.system.api.domain.SysOperLog;
|
import com.ruoyi.system.api.domain.SysOperLog;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -25,13 +26,13 @@ public class RemoteLogFallbackFactory implements FallbackFactory<RemoteLogServic
|
||||||
return new RemoteLogService()
|
return new RemoteLogService()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public R<Boolean> saveLog(SysOperLog sysOperLog)
|
public R<Boolean> saveLog(SysOperLog sysOperLog, String source)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public R<Boolean> saveLogininfor(String username, String status, String message)
|
public R<Boolean> saveLogininfor(SysLogininfor sysLogininfor, String source)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import org.springframework.cloud.openfeign.FallbackFactory;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import com.ruoyi.common.core.domain.R;
|
import com.ruoyi.common.core.domain.R;
|
||||||
import com.ruoyi.system.api.RemoteUserService;
|
import com.ruoyi.system.api.RemoteUserService;
|
||||||
|
import com.ruoyi.system.api.domain.SysUser;
|
||||||
import com.ruoyi.system.api.model.LoginUser;
|
import com.ruoyi.system.api.model.LoginUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -25,10 +26,16 @@ public class RemoteUserFallbackFactory implements FallbackFactory<RemoteUserServ
|
||||||
return new RemoteUserService()
|
return new RemoteUserService()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public R<LoginUser> getUserInfo(String username)
|
public R<LoginUser> getUserInfo(String username, String source)
|
||||||
{
|
{
|
||||||
return R.fail("获取用户失败:" + throwable.getMessage());
|
return R.fail("获取用户失败:" + throwable.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<Boolean> registerUserInfo(SysUser sysUser, String source)
|
||||||
|
{
|
||||||
|
return R.fail("注册用户失败:" + throwable.getMessage());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi</artifactId>
|
<artifactId>ruoyi</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,14 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import com.ruoyi.auth.form.LoginBody;
|
import com.ruoyi.auth.form.LoginBody;
|
||||||
|
import com.ruoyi.auth.form.RegisterBody;
|
||||||
import com.ruoyi.auth.service.SysLoginService;
|
import com.ruoyi.auth.service.SysLoginService;
|
||||||
import com.ruoyi.common.core.domain.R;
|
import com.ruoyi.common.core.domain.R;
|
||||||
|
import com.ruoyi.common.core.utils.JwtUtils;
|
||||||
import com.ruoyi.common.core.utils.StringUtils;
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
|
import com.ruoyi.common.security.auth.AuthUtil;
|
||||||
import com.ruoyi.common.security.service.TokenService;
|
import com.ruoyi.common.security.service.TokenService;
|
||||||
|
import com.ruoyi.common.security.utils.SecurityUtils;
|
||||||
import com.ruoyi.system.api.model.LoginUser;
|
import com.ruoyi.system.api.model.LoginUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -39,12 +43,12 @@ public class TokenController
|
||||||
@DeleteMapping("logout")
|
@DeleteMapping("logout")
|
||||||
public R<?> logout(HttpServletRequest request)
|
public R<?> logout(HttpServletRequest request)
|
||||||
{
|
{
|
||||||
LoginUser loginUser = tokenService.getLoginUser(request);
|
String token = SecurityUtils.getToken(request);
|
||||||
if (StringUtils.isNotNull(loginUser))
|
if (StringUtils.isNotEmpty(token))
|
||||||
{
|
{
|
||||||
String username = loginUser.getUsername();
|
String username = JwtUtils.getUserName(token);
|
||||||
// 删除用户缓存记录
|
// 删除用户缓存记录
|
||||||
tokenService.delLoginUser(loginUser.getToken());
|
AuthUtil.logoutByToken(token);
|
||||||
// 记录用户退出日志
|
// 记录用户退出日志
|
||||||
sysLoginService.logout(username);
|
sysLoginService.logout(username);
|
||||||
}
|
}
|
||||||
|
|
@ -63,4 +67,12 @@ public class TokenController
|
||||||
}
|
}
|
||||||
return R.ok();
|
return R.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("register")
|
||||||
|
public R<?> register(@RequestBody RegisterBody registerBody)
|
||||||
|
{
|
||||||
|
// 用户注册
|
||||||
|
sysLoginService.register(registerBody.getUsername(), registerBody.getPassword());
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.ruoyi.auth.form;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户注册对象
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public class RegisterBody extends LoginBody
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -3,14 +3,18 @@ package com.ruoyi.auth.service;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import com.ruoyi.common.core.constant.Constants;
|
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.constant.UserConstants;
|
||||||
import com.ruoyi.common.core.domain.R;
|
import com.ruoyi.common.core.domain.R;
|
||||||
import com.ruoyi.common.core.enums.UserStatus;
|
import com.ruoyi.common.core.enums.UserStatus;
|
||||||
import com.ruoyi.common.core.exception.BaseException;
|
import com.ruoyi.common.core.exception.ServiceException;
|
||||||
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.StringUtils;
|
||||||
|
import com.ruoyi.common.core.utils.ip.IpUtils;
|
||||||
|
import com.ruoyi.common.security.utils.SecurityUtils;
|
||||||
import com.ruoyi.system.api.RemoteLogService;
|
import com.ruoyi.system.api.RemoteLogService;
|
||||||
import com.ruoyi.system.api.RemoteUserService;
|
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.domain.SysUser;
|
||||||
import com.ruoyi.system.api.model.LoginUser;
|
import com.ruoyi.system.api.model.LoginUser;
|
||||||
|
|
||||||
|
|
@ -36,60 +40,120 @@ public class SysLoginService
|
||||||
// 用户名或密码为空 错误
|
// 用户名或密码为空 错误
|
||||||
if (StringUtils.isAnyBlank(username, password))
|
if (StringUtils.isAnyBlank(username, password))
|
||||||
{
|
{
|
||||||
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写");
|
recordLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写");
|
||||||
throw new BaseException("用户/密码必须填写");
|
throw new ServiceException("用户/密码必须填写");
|
||||||
}
|
}
|
||||||
// 密码如果不在指定范围内 错误
|
// 密码如果不在指定范围内 错误
|
||||||
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|
||||||
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
|
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
|
||||||
{
|
{
|
||||||
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围");
|
recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围");
|
||||||
throw new BaseException("用户密码不在指定范围");
|
throw new ServiceException("用户密码不在指定范围");
|
||||||
}
|
}
|
||||||
// 用户名不在指定范围内 错误
|
// 用户名不在指定范围内 错误
|
||||||
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|
||||||
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
|
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
|
||||||
{
|
{
|
||||||
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
|
recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
|
||||||
throw new BaseException("用户名不在指定范围");
|
throw new ServiceException("用户名不在指定范围");
|
||||||
}
|
}
|
||||||
// 查询用户信息
|
// 查询用户信息
|
||||||
R<LoginUser> userResult = remoteUserService.getUserInfo(username);
|
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
|
||||||
|
|
||||||
if (R.FAIL == userResult.getCode())
|
if (R.FAIL == userResult.getCode())
|
||||||
{
|
{
|
||||||
throw new BaseException(userResult.getMsg());
|
throw new ServiceException(userResult.getMsg());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
|
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
|
||||||
{
|
{
|
||||||
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
|
recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
|
||||||
throw new BaseException("登录用户:" + username + " 不存在");
|
throw new ServiceException("登录用户:" + username + " 不存在");
|
||||||
}
|
}
|
||||||
LoginUser userInfo = userResult.getData();
|
LoginUser userInfo = userResult.getData();
|
||||||
SysUser user = userResult.getData().getSysUser();
|
SysUser user = userResult.getData().getSysUser();
|
||||||
if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
|
if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
|
||||||
{
|
{
|
||||||
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
|
recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
|
||||||
|
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
|
||||||
throw new BaseException("对不起,您的账号:" + username + " 已被删除");
|
|
||||||
}
|
}
|
||||||
if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
|
if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
|
||||||
{
|
{
|
||||||
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
|
recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
|
||||||
throw new BaseException("对不起,您的账号:" + username + " 已停用");
|
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
|
||||||
}
|
}
|
||||||
if (!SecurityUtils.matchesPassword(password, user.getPassword()))
|
if (!SecurityUtils.matchesPassword(password, user.getPassword()))
|
||||||
{
|
{
|
||||||
remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户密码错误");
|
recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码错误");
|
||||||
throw new BaseException("用户不存在/密码错误");
|
throw new ServiceException("用户不存在/密码错误");
|
||||||
}
|
}
|
||||||
remoteLogService.saveLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
|
recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
|
||||||
return userInfo;
|
return userInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logout(String loginName)
|
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 ServiceException("用户/密码必须填写");
|
||||||
|
}
|
||||||
|
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|
||||||
|
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ServiceException("账户长度必须在2到20个字符之间");
|
||||||
|
}
|
||||||
|
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|
||||||
|
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
|
||||||
|
{
|
||||||
|
throw new ServiceException("密码长度必须在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 ServiceException(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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi</artifactId>
|
<artifactId>ruoyi</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-common</artifactId>
|
<artifactId>ruoyi-common</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
@ -41,6 +41,12 @@
|
||||||
<artifactId>spring-web</artifactId>
|
<artifactId>spring-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Transmittable ThreadLocal -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>transmittable-thread-local</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Apache Commons Pool2 -->
|
<!-- Apache Commons Pool2 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
|
|
@ -71,6 +77,18 @@
|
||||||
<artifactId>fastjson</artifactId>
|
<artifactId>fastjson</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Jwt -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Jaxb -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Apache Lang3 -->
|
<!-- Apache Lang3 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import com.ruoyi.common.core.utils.poi.ExcelHandlerAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义导出Excel数据注解
|
* 自定义导出Excel数据注解
|
||||||
|
|
@ -103,7 +104,17 @@ public @interface Excel
|
||||||
/**
|
/**
|
||||||
* 导出字段对齐方式(0:默认;1:靠左;2:居中;3:靠右)
|
* 导出字段对齐方式(0:默认;1:靠左;2:居中;3:靠右)
|
||||||
*/
|
*/
|
||||||
Align align() default Align.AUTO;
|
public Align align() default Align.AUTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义数据处理器
|
||||||
|
*/
|
||||||
|
public Class<?> handler() default ExcelHandlerAdapter.class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义数据处理器参数
|
||||||
|
*/
|
||||||
|
public String[] args() default {};
|
||||||
|
|
||||||
public enum Align
|
public enum Align
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -8,32 +8,17 @@ package com.ruoyi.common.core.constant;
|
||||||
public class CacheConstants
|
public class CacheConstants
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 令牌自定义标识
|
* 缓存有效期,默认720(分钟)
|
||||||
*/
|
*/
|
||||||
public static final String HEADER = "Authorization";
|
public final static long EXPIRATION = 720;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 令牌前缀
|
* 缓存刷新时间,默认120(分钟)
|
||||||
*/
|
*/
|
||||||
public static final String TOKEN_PREFIX = "Bearer ";
|
public final static long REFRESH_TIME = 120;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 权限缓存前缀
|
* 权限缓存前缀
|
||||||
*/
|
*/
|
||||||
public final static String LOGIN_TOKEN_KEY = "login_tokens:";
|
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";
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,16 @@ public class Constants
|
||||||
*/
|
*/
|
||||||
public static final String GBK = "GBK";
|
public static final String GBK = "GBK";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RMI 远程方法调用
|
||||||
|
*/
|
||||||
|
public static final String LOOKUP_RMI = "rmi://";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LDAP 远程方法调用
|
||||||
|
*/
|
||||||
|
public static final String LOOKUP_LDAP = "ldap://";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* http请求
|
* http请求
|
||||||
*/
|
*/
|
||||||
|
|
@ -87,10 +97,6 @@ public class Constants
|
||||||
*/
|
*/
|
||||||
public static final long CAPTCHA_EXPIRATION = 2;
|
public static final long CAPTCHA_EXPIRATION = 2;
|
||||||
|
|
||||||
/**
|
|
||||||
* 令牌有效期(分钟)
|
|
||||||
*/
|
|
||||||
public final static long TOKEN_EXPIRE = 720;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 参数管理 cache key
|
* 参数管理 cache key
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.ruoyi.common.core.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限相关通用常量
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public class SecurityConstants
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 用户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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户标识
|
||||||
|
*/
|
||||||
|
public static final String USER_KEY = "user_key";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录用户
|
||||||
|
*/
|
||||||
|
public static final String LOGIN_USER = "login_user";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.ruoyi.common.core.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token的Key常量
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public class TokenConstants
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 令牌自定义标识
|
||||||
|
*/
|
||||||
|
public static final String AUTHENTICATION = "Authorization";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 令牌前缀
|
||||||
|
*/
|
||||||
|
public static final String PREFIX = "Bearer ";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 令牌秘钥
|
||||||
|
*/
|
||||||
|
public final static String SECRET = "abcdefghijklmnopqrstuvwxyz";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -57,6 +57,9 @@ public class UserConstants
|
||||||
/** ParentView组件标识 */
|
/** ParentView组件标识 */
|
||||||
public final static String PARENT_VIEW = "ParentView";
|
public final static String PARENT_VIEW = "ParentView";
|
||||||
|
|
||||||
|
/** InnerLink组件标识 */
|
||||||
|
public final static String INNER_LINK = "InnerLink";
|
||||||
|
|
||||||
/** 校验返回结果码 */
|
/** 校验返回结果码 */
|
||||||
public final static String UNIQUE = "0";
|
public final static String UNIQUE = "0";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package com.ruoyi.common.core.context;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||||
|
import com.ruoyi.common.core.constant.SecurityConstants;
|
||||||
|
import com.ruoyi.common.core.text.Convert;
|
||||||
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前线程变量中的 用户id、用户名称、Token等信息
|
||||||
|
* 注意: 必须在网关通过请求头的方法传入,同时在HeaderInterceptor拦截器设置值。 否则这里无法获取
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public class SecurityContextHolder
|
||||||
|
{
|
||||||
|
private static final TransmittableThreadLocal<Map<String, Object>> THREAD_LOCAL = new TransmittableThreadLocal<>();
|
||||||
|
|
||||||
|
public static void set(String key, Object value)
|
||||||
|
{
|
||||||
|
Map<String, Object> map = getLocalMap();
|
||||||
|
map.put(key, value == null ? StringUtils.EMPTY : value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String get(String key)
|
||||||
|
{
|
||||||
|
Map<String, Object> map = getLocalMap();
|
||||||
|
return Convert.toStr(map.getOrDefault(key, StringUtils.EMPTY));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T get(String key, Class<T> clazz)
|
||||||
|
{
|
||||||
|
Map<String, Object> map = getLocalMap();
|
||||||
|
return StringUtils.cast(map.getOrDefault(key, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, Object> getLocalMap()
|
||||||
|
{
|
||||||
|
Map<String, Object> map = THREAD_LOCAL.get();
|
||||||
|
if (map == null)
|
||||||
|
{
|
||||||
|
map = new ConcurrentHashMap<String, Object>();
|
||||||
|
THREAD_LOCAL.set(map);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setLocalMap(Map<String, Object> threadLocalMap)
|
||||||
|
{
|
||||||
|
THREAD_LOCAL.set(threadLocalMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Long getUserId()
|
||||||
|
{
|
||||||
|
return Convert.toLong(get(SecurityConstants.DETAILS_USER_ID), 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setUserId(String account)
|
||||||
|
{
|
||||||
|
set(SecurityConstants.DETAILS_USER_ID, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getUserName()
|
||||||
|
{
|
||||||
|
return get(SecurityConstants.DETAILS_USERNAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setUserName(String username)
|
||||||
|
{
|
||||||
|
set(SecurityConstants.DETAILS_USERNAME, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getUserKey()
|
||||||
|
{
|
||||||
|
return get(SecurityConstants.USER_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setUserKey(String userKey)
|
||||||
|
{
|
||||||
|
set(SecurityConstants.USER_KEY, userKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void remove()
|
||||||
|
{
|
||||||
|
THREAD_LOCAL.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
package com.ruoyi.common.core.exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义异常
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
public class CustomException extends RuntimeException
|
|
||||||
{
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private Integer code;
|
|
||||||
|
|
||||||
private String message;
|
|
||||||
|
|
||||||
public CustomException(String message)
|
|
||||||
{
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomException(String message, Integer code)
|
|
||||||
{
|
|
||||||
this.message = message;
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomException(String message, Throwable e)
|
|
||||||
{
|
|
||||||
super(message, e);
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMessage()
|
|
||||||
{
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getCode()
|
|
||||||
{
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.ruoyi.common.core.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局异常
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public class GlobalException extends RuntimeException
|
||||||
|
{
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误提示
|
||||||
|
*/
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误明细,内部调试错误
|
||||||
|
*
|
||||||
|
* 和 {@link CommonResult#getDetailMessage()} 一致的设计
|
||||||
|
*/
|
||||||
|
private String detailMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 空构造方法,避免反序列化问题
|
||||||
|
*/
|
||||||
|
public GlobalException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlobalException(String message)
|
||||||
|
{
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDetailMessage()
|
||||||
|
{
|
||||||
|
return detailMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlobalException setDetailMessage(String detailMessage)
|
||||||
|
{
|
||||||
|
this.detailMessage = detailMessage;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage()
|
||||||
|
{
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlobalException setMessage(String message)
|
||||||
|
{
|
||||||
|
this.message = message;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package com.ruoyi.common.core.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务异常
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public final class ServiceException extends RuntimeException
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误码
|
||||||
|
*/
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误提示
|
||||||
|
*/
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误明细,内部调试错误
|
||||||
|
*
|
||||||
|
* 和 {@link CommonResult#getDetailMessage()} 一致的设计
|
||||||
|
*/
|
||||||
|
private String detailMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 空构造方法,避免反序列化问题
|
||||||
|
*/
|
||||||
|
public ServiceException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceException(String message)
|
||||||
|
{
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceException(String message, Integer code)
|
||||||
|
{
|
||||||
|
this.message = message;
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDetailMessage()
|
||||||
|
{
|
||||||
|
return detailMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage()
|
||||||
|
{
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCode()
|
||||||
|
{
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceException setMessage(String message)
|
||||||
|
{
|
||||||
|
this.message = message;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceException setDetailMessage(String detailMessage)
|
||||||
|
{
|
||||||
|
this.detailMessage = detailMessage;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.ruoyi.common.core.exception.auth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未能通过的登录认证异常
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public class NotLoginException extends RuntimeException
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public NotLoginException(String message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.ruoyi.common.core.exception.auth;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未能通过的权限认证异常
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public class NotPermissionException extends RuntimeException
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public NotPermissionException(String permission)
|
||||||
|
{
|
||||||
|
super(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotPermissionException(String[] permissions)
|
||||||
|
{
|
||||||
|
super(StringUtils.join(permissions, ","));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.ruoyi.common.core.exception.auth;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未能通过的角色认证异常
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public class NotRoleException extends RuntimeException
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public NotRoleException(String role)
|
||||||
|
{
|
||||||
|
super(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotRoleException(String[] roles)
|
||||||
|
{
|
||||||
|
super(StringUtils.join(roles, ","));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.ruoyi.common.core.exception;
|
package com.ruoyi.common.core.exception.base;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础异常
|
* 基础异常
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.ruoyi.common.core.exception.file;
|
package com.ruoyi.common.core.exception.file;
|
||||||
|
|
||||||
import com.ruoyi.common.core.exception.BaseException;
|
import com.ruoyi.common.core.exception.base.BaseException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件信息异常类
|
* 文件信息异常类
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.ruoyi.common.core.exception.user;
|
package com.ruoyi.common.core.exception.user;
|
||||||
|
|
||||||
import com.ruoyi.common.core.exception.BaseException;
|
import com.ruoyi.common.core.exception.base.BaseException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户信息异常类
|
* 用户信息异常类
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
package com.ruoyi.common.core.utils;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import com.ruoyi.common.core.constant.SecurityConstants;
|
||||||
|
import com.ruoyi.common.core.constant.TokenConstants;
|
||||||
|
import com.ruoyi.common.core.text.Convert;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jwt工具类
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public class JwtUtils
|
||||||
|
{
|
||||||
|
public static String secret = TokenConstants.SECRET;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从数据声明生成令牌
|
||||||
|
*
|
||||||
|
* @param claims 数据声明
|
||||||
|
* @return 令牌
|
||||||
|
*/
|
||||||
|
public static String createToken(Map<String, Object> claims)
|
||||||
|
{
|
||||||
|
String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从令牌中获取数据声明
|
||||||
|
*
|
||||||
|
* @param token 令牌
|
||||||
|
* @return 数据声明
|
||||||
|
*/
|
||||||
|
public static Claims parseToken(String token)
|
||||||
|
{
|
||||||
|
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据令牌获取用户标识
|
||||||
|
*
|
||||||
|
* @param token 令牌
|
||||||
|
* @return 用户ID
|
||||||
|
*/
|
||||||
|
public static String getUserKey(String token)
|
||||||
|
{
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
return getValue(claims, SecurityConstants.USER_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据令牌获取用户标识
|
||||||
|
*
|
||||||
|
* @param claims 身份信息
|
||||||
|
* @return 用户ID
|
||||||
|
*/
|
||||||
|
public static String getUserKey(Claims claims)
|
||||||
|
{
|
||||||
|
return getValue(claims, SecurityConstants.USER_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据令牌获取用户ID
|
||||||
|
*
|
||||||
|
* @param token 令牌
|
||||||
|
* @return 用户ID
|
||||||
|
*/
|
||||||
|
public static String getUserId(String token)
|
||||||
|
{
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
return getValue(claims, SecurityConstants.DETAILS_USER_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据身份信息获取用户ID
|
||||||
|
*
|
||||||
|
* @param claims 身份信息
|
||||||
|
* @return 用户ID
|
||||||
|
*/
|
||||||
|
public static String getUserId(Claims claims)
|
||||||
|
{
|
||||||
|
return getValue(claims, SecurityConstants.DETAILS_USER_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据令牌获取用户名
|
||||||
|
*
|
||||||
|
* @param token 令牌
|
||||||
|
* @return 用户名
|
||||||
|
*/
|
||||||
|
public static String getUserName(String token)
|
||||||
|
{
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
return getValue(claims, SecurityConstants.DETAILS_USERNAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据身份信息获取用户名
|
||||||
|
*
|
||||||
|
* @param claims 身份信息
|
||||||
|
* @return 用户名
|
||||||
|
*/
|
||||||
|
public static String getUserName(Claims claims)
|
||||||
|
{
|
||||||
|
return getValue(claims, SecurityConstants.DETAILS_USERNAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据身份信息获取键值
|
||||||
|
*
|
||||||
|
* @param claims 身份信息
|
||||||
|
* @param key 键
|
||||||
|
* @return 值
|
||||||
|
*/
|
||||||
|
public static String getValue(Claims claims, String key)
|
||||||
|
{
|
||||||
|
return Convert.toStr(claims.get(key), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,11 +10,19 @@ import java.util.Map;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
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.RequestAttributes;
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.ruoyi.common.core.constant.Constants;
|
import com.ruoyi.common.core.constant.Constants;
|
||||||
|
import com.ruoyi.common.core.domain.R;
|
||||||
import com.ruoyi.common.core.text.Convert;
|
import com.ruoyi.common.core.text.Convert;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户端工具类
|
* 客户端工具类
|
||||||
|
|
@ -55,6 +63,22 @@ public class ServletUtils
|
||||||
return Convert.toInt(getRequest().getParameter(name), defaultValue);
|
return Convert.toInt(getRequest().getParameter(name), defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Boolean参数
|
||||||
|
*/
|
||||||
|
public static Boolean getParameterToBool(String name)
|
||||||
|
{
|
||||||
|
return Convert.toBool(getRequest().getParameter(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Boolean参数
|
||||||
|
*/
|
||||||
|
public static Boolean getParameterToBool(String name, Boolean defaultValue)
|
||||||
|
{
|
||||||
|
return Convert.toBool(getRequest().getParameter(name), defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取request
|
* 获取request
|
||||||
*/
|
*/
|
||||||
|
|
@ -106,6 +130,16 @@ public class ServletUtils
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getHeader(HttpServletRequest request, String name)
|
||||||
|
{
|
||||||
|
String value = request.getHeader(name);
|
||||||
|
if (StringUtils.isEmpty(value))
|
||||||
|
{
|
||||||
|
return StringUtils.EMPTY;
|
||||||
|
}
|
||||||
|
return urlDecode(value);
|
||||||
|
}
|
||||||
|
|
||||||
public static Map<String, String> getHeaders(HttpServletRequest request)
|
public static Map<String, String> getHeaders(HttpServletRequest request)
|
||||||
{
|
{
|
||||||
Map<String, String> map = new LinkedHashMap<>();
|
Map<String, String> map = new LinkedHashMap<>();
|
||||||
|
|
@ -192,7 +226,7 @@ public class ServletUtils
|
||||||
}
|
}
|
||||||
catch (UnsupportedEncodingException e)
|
catch (UnsupportedEncodingException e)
|
||||||
{
|
{
|
||||||
return "";
|
return StringUtils.EMPTY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,7 +244,65 @@ public class ServletUtils
|
||||||
}
|
}
|
||||||
catch (UnsupportedEncodingException e)
|
catch (UnsupportedEncodingException e)
|
||||||
{
|
{
|
||||||
return "";
|
return StringUtils.EMPTY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.springframework.util.AntPathMatcher;
|
import org.springframework.util.AntPathMatcher;
|
||||||
|
import com.ruoyi.common.core.constant.Constants;
|
||||||
import com.ruoyi.common.core.text.StrFormatter;
|
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);
|
return StrFormatter.format(template, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为http(s)://开头
|
||||||
|
*
|
||||||
|
* @param link 链接
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public static boolean ishttp(String link)
|
||||||
|
{
|
||||||
|
return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 驼峰转下划线命名
|
* 驼峰转下划线命名
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,7 @@ public class FileUtils
|
||||||
.append(percentEncodedFileName);
|
.append(percentEncodedFileName);
|
||||||
|
|
||||||
response.setHeader("Content-disposition", contentDispositionValue.toString());
|
response.setHeader("Content-disposition", contentDispositionValue.toString());
|
||||||
|
response.setHeader("download-filename", percentEncodedFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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['\''] = "'".toCharArray(); // 单引号
|
||||||
|
TEXT['"'] = """.toCharArray(); // 双引号
|
||||||
|
TEXT['&'] = "&".toCharArray(); // &符
|
||||||
|
TEXT['<'] = "<".toCharArray(); // 小于号
|
||||||
|
TEXT['>'] = ">".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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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, "&", result);
|
||||||
|
result = regexReplace(P_QUOTE, """, result);
|
||||||
|
result = regexReplace(P_LEFT_ARROW, "<", result);
|
||||||
|
result = regexReplace(P_RIGHT_ARROW, ">", 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, "<$1", s);
|
||||||
|
s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", 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); // (<|$)
|
||||||
|
// 不替换双引号为",防止json格式无效 regexReplace(P_QUOTE, """, 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 : "&" + 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.ruoyi.common.core.utils.poi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Excel数据格式处理适配器
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public interface ExcelHandlerAdapter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 格式化
|
||||||
|
*
|
||||||
|
* @param value 单元格数据值
|
||||||
|
* @param args excel注解args参数组
|
||||||
|
*
|
||||||
|
* @return 处理后的值
|
||||||
|
*/
|
||||||
|
Object format(Object value, String[] args);
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -35,6 +36,7 @@ import org.apache.poi.ss.usermodel.Sheet;
|
||||||
import org.apache.poi.ss.usermodel.VerticalAlignment;
|
import org.apache.poi.ss.usermodel.VerticalAlignment;
|
||||||
import org.apache.poi.ss.usermodel.Workbook;
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||||
|
import org.apache.poi.ss.util.CellRangeAddress;
|
||||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||||
import org.apache.poi.util.IOUtils;
|
import org.apache.poi.util.IOUtils;
|
||||||
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
||||||
|
|
@ -102,6 +104,16 @@ public class ExcelUtil<T>
|
||||||
*/
|
*/
|
||||||
private List<Object[]> fields;
|
private List<Object[]> fields;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前行号
|
||||||
|
*/
|
||||||
|
private int rownum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标题
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 最大高度
|
* 最大高度
|
||||||
*/
|
*/
|
||||||
|
|
@ -127,7 +139,7 @@ public class ExcelUtil<T>
|
||||||
this.clazz = clazz;
|
this.clazz = clazz;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(List<T> list, String sheetName, Type type)
|
public void init(List<T> list, String sheetName, String title, Type type)
|
||||||
{
|
{
|
||||||
if (list == null)
|
if (list == null)
|
||||||
{
|
{
|
||||||
|
|
@ -136,8 +148,27 @@ public class ExcelUtil<T>
|
||||||
this.list = list;
|
this.list = list;
|
||||||
this.sheetName = sheetName;
|
this.sheetName = sheetName;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
this.title = title;
|
||||||
createExcelField();
|
createExcelField();
|
||||||
createWorkbook();
|
createWorkbook();
|
||||||
|
createTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建excel第一行标题
|
||||||
|
*/
|
||||||
|
public void createTitle()
|
||||||
|
{
|
||||||
|
if (StringUtils.isNotEmpty(title))
|
||||||
|
{
|
||||||
|
Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0);
|
||||||
|
titleRow.setHeightInPoints(30);
|
||||||
|
Cell titleCell = titleRow.createCell(0);
|
||||||
|
titleCell.setCellStyle(styles.get("title"));
|
||||||
|
titleCell.setCellValue(title);
|
||||||
|
sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(),
|
||||||
|
this.fields.size() - 1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -148,33 +179,36 @@ public class ExcelUtil<T>
|
||||||
*/
|
*/
|
||||||
public List<T> importExcel(InputStream is) throws Exception
|
public List<T> importExcel(InputStream is) throws Exception
|
||||||
{
|
{
|
||||||
return importExcel(StringUtils.EMPTY, is);
|
return importExcel(is, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对excel表单默认第一个索引名转换成list
|
||||||
|
*
|
||||||
|
* @param is 输入流
|
||||||
|
* @param titleNum 标题占用行数
|
||||||
|
* @return 转换后集合
|
||||||
|
*/
|
||||||
|
public List<T> importExcel(InputStream is, int titleNum) throws Exception
|
||||||
|
{
|
||||||
|
return importExcel(StringUtils.EMPTY, is, titleNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对excel表单指定表格索引名转换成list
|
* 对excel表单指定表格索引名转换成list
|
||||||
*
|
*
|
||||||
* @param sheetName 表格索引名
|
* @param sheetName 表格索引名
|
||||||
|
* @param titleNum 标题占用行数
|
||||||
* @param is 输入流
|
* @param is 输入流
|
||||||
* @return 转换后集合
|
* @return 转换后集合
|
||||||
*/
|
*/
|
||||||
public List<T> importExcel(String sheetName, InputStream is) throws Exception
|
public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception
|
||||||
{
|
{
|
||||||
this.type = Type.IMPORT;
|
this.type = Type.IMPORT;
|
||||||
this.wb = WorkbookFactory.create(is);
|
this.wb = WorkbookFactory.create(is);
|
||||||
List<T> list = new ArrayList<T>();
|
List<T> list = new ArrayList<T>();
|
||||||
Sheet sheet = null;
|
// 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet
|
||||||
if (StringUtils.isNotEmpty(sheetName))
|
Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);
|
||||||
{
|
|
||||||
// 如果指定sheet名,则取指定sheet中的内容.
|
|
||||||
sheet = wb.getSheet(sheetName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 如果传入的sheet名不存在则默认指向第1个sheet.
|
|
||||||
sheet = wb.getSheetAt(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sheet == null)
|
if (sheet == null)
|
||||||
{
|
{
|
||||||
throw new IOException("文件sheet不存在");
|
throw new IOException("文件sheet不存在");
|
||||||
|
|
@ -188,7 +222,7 @@ public class ExcelUtil<T>
|
||||||
// 定义一个map用于存放excel列的序号和field.
|
// 定义一个map用于存放excel列的序号和field.
|
||||||
Map<String, Integer> cellMap = new HashMap<String, Integer>();
|
Map<String, Integer> cellMap = new HashMap<String, Integer>();
|
||||||
// 获取表头
|
// 获取表头
|
||||||
Row heard = sheet.getRow(0);
|
Row heard = sheet.getRow(titleNum);
|
||||||
for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++)
|
for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++)
|
||||||
{
|
{
|
||||||
Cell cell = heard.getCell(i);
|
Cell cell = heard.getCell(i);
|
||||||
|
|
@ -203,25 +237,18 @@ public class ExcelUtil<T>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 有数据时才处理 得到类的所有field.
|
// 有数据时才处理 得到类的所有field.
|
||||||
Field[] allFields = clazz.getDeclaredFields();
|
List<Object[]> fields = this.getFields();
|
||||||
// 定义一个map用于存放列的序号和field.
|
Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>();
|
||||||
Map<Integer, Field> fieldsMap = new HashMap<Integer, Field>();
|
for (Object[] objects : fields)
|
||||||
for (int col = 0; col < allFields.length; col++)
|
|
||||||
{
|
{
|
||||||
Field field = allFields[col];
|
Excel attr = (Excel) objects[1];
|
||||||
Excel attr = field.getAnnotation(Excel.class);
|
|
||||||
if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
|
|
||||||
{
|
|
||||||
// 设置类的私有字段属性可访问.
|
|
||||||
field.setAccessible(true);
|
|
||||||
Integer column = cellMap.get(attr.name());
|
Integer column = cellMap.get(attr.name());
|
||||||
if (column != null)
|
if (column != null)
|
||||||
{
|
{
|
||||||
fieldsMap.put(column, field);
|
fieldsMap.put(column, objects);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
for (int i = titleNum + 1; i <= rows; i++)
|
||||||
for (int i = 1; i <= rows; i++)
|
|
||||||
{
|
{
|
||||||
// 从第2行开始取数据,默认第一行是表头.
|
// 从第2行开始取数据,默认第一行是表头.
|
||||||
Row row = sheet.getRow(i);
|
Row row = sheet.getRow(i);
|
||||||
|
|
@ -231,14 +258,15 @@ public class ExcelUtil<T>
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
T entity = null;
|
T entity = null;
|
||||||
for (Map.Entry<Integer, Field> entry : fieldsMap.entrySet())
|
for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet())
|
||||||
{
|
{
|
||||||
Object val = this.getCellValue(row, entry.getKey());
|
Object val = this.getCellValue(row, entry.getKey());
|
||||||
|
|
||||||
// 如果不存在实例则新建.
|
// 如果不存在实例则新建.
|
||||||
entity = (entity == null ? clazz.newInstance() : entity);
|
entity = (entity == null ? clazz.newInstance() : entity);
|
||||||
// 从map中得到对应列的field.
|
// 从map中得到对应列的field.
|
||||||
Field field = fieldsMap.get(entry.getKey());
|
Field field = (Field) entry.getValue()[0];
|
||||||
|
Excel attr = (Excel) entry.getValue()[1];
|
||||||
// 取得类型,并根据对象类型设置值.
|
// 取得类型,并根据对象类型设置值.
|
||||||
Class<?> fieldType = field.getType();
|
Class<?> fieldType = field.getType();
|
||||||
if (String.class == fieldType)
|
if (String.class == fieldType)
|
||||||
|
|
@ -298,7 +326,6 @@ public class ExcelUtil<T>
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotNull(fieldType))
|
if (StringUtils.isNotNull(fieldType))
|
||||||
{
|
{
|
||||||
Excel attr = field.getAnnotation(Excel.class);
|
|
||||||
String propertyName = field.getName();
|
String propertyName = field.getName();
|
||||||
if (StringUtils.isNotEmpty(attr.targetAttr()))
|
if (StringUtils.isNotEmpty(attr.targetAttr()))
|
||||||
{
|
{
|
||||||
|
|
@ -308,6 +335,10 @@ public class ExcelUtil<T>
|
||||||
{
|
{
|
||||||
val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
|
val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
|
||||||
}
|
}
|
||||||
|
else if (!attr.handler().equals(ExcelHandlerAdapter.class))
|
||||||
|
{
|
||||||
|
val = dataFormatHandlerAdapter(val, attr);
|
||||||
|
}
|
||||||
ReflectUtils.invokeSetter(entity, propertyName, val);
|
ReflectUtils.invokeSetter(entity, propertyName, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -327,13 +358,34 @@ public class ExcelUtil<T>
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public void exportExcel(HttpServletResponse response, List<T> list, String sheetName)throws IOException
|
public void exportExcel(HttpServletResponse response, List<T> list, String sheetName)throws IOException
|
||||||
|
{
|
||||||
|
exportExcel(response, list, sheetName, StringUtils.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对list数据源将其里面的数据导入到excel表单
|
||||||
|
*
|
||||||
|
* @param response 返回数据
|
||||||
|
* @param list 导出数据集合
|
||||||
|
* @param sheetName 工作表的名称
|
||||||
|
* @param title 标题
|
||||||
|
* @return 结果
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public void exportExcel(HttpServletResponse response, List<T> list, String sheetName, String title) throws IOException
|
||||||
{
|
{
|
||||||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||||
response.setCharacterEncoding("utf-8");
|
response.setCharacterEncoding("utf-8");
|
||||||
this.init(list, sheetName, Type.EXPORT);
|
this.init(list, sheetName, title, Type.EXPORT);
|
||||||
exportExcel(response.getOutputStream());
|
exportExcel(response.getOutputStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对list数据源将其里面的数据导入到excel表单
|
||||||
|
*
|
||||||
|
* @param sheetName 工作表的名称
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
/**
|
/**
|
||||||
* 对list数据源将其里面的数据导入到excel表单
|
* 对list数据源将其里面的数据导入到excel表单
|
||||||
*
|
*
|
||||||
|
|
@ -341,10 +393,22 @@ public class ExcelUtil<T>
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
public void importTemplateExcel(HttpServletResponse response, String sheetName) throws IOException
|
public void importTemplateExcel(HttpServletResponse response, String sheetName) throws IOException
|
||||||
|
{
|
||||||
|
importTemplateExcel(response, sheetName, StringUtils.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对list数据源将其里面的数据导入到excel表单
|
||||||
|
*
|
||||||
|
* @param sheetName 工作表的名称
|
||||||
|
* @param title 标题
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) throws IOException
|
||||||
{
|
{
|
||||||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||||
response.setCharacterEncoding("utf-8");
|
response.setCharacterEncoding("utf-8");
|
||||||
this.init(null, sheetName, Type.IMPORT);
|
this.init(null, sheetName, title, Type.IMPORT);
|
||||||
exportExcel(response.getOutputStream());
|
exportExcel(response.getOutputStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -377,13 +441,13 @@ public class ExcelUtil<T>
|
||||||
public void writeSheet()
|
public void writeSheet()
|
||||||
{
|
{
|
||||||
// 取出一共有多少个sheet.
|
// 取出一共有多少个sheet.
|
||||||
double sheetNo = Math.ceil(list.size() / sheetSize);
|
int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize));
|
||||||
for (int index = 0; index <= sheetNo; index++)
|
for (int index = 0; index < sheetNo; index++)
|
||||||
{
|
{
|
||||||
createSheet(sheetNo, index);
|
createSheet(sheetNo, index);
|
||||||
|
|
||||||
// 产生一行
|
// 产生一行
|
||||||
Row row = sheet.createRow(0);
|
Row row = sheet.createRow(rownum);
|
||||||
int column = 0;
|
int column = 0;
|
||||||
// 写入各个字段的列头名称
|
// 写入各个字段的列头名称
|
||||||
for (Object[] os : fields)
|
for (Object[] os : fields)
|
||||||
|
|
@ -411,7 +475,7 @@ public class ExcelUtil<T>
|
||||||
int endNo = Math.min(startNo + sheetSize, list.size());
|
int endNo = Math.min(startNo + sheetSize, list.size());
|
||||||
for (int i = startNo; i < endNo; i++)
|
for (int i = startNo; i < endNo; i++)
|
||||||
{
|
{
|
||||||
row = sheet.createRow(i + 1 - startNo);
|
row = sheet.createRow(i + 1 + rownum - startNo);
|
||||||
// 得到导出对象.
|
// 得到导出对象.
|
||||||
T vo = (T) list.get(i);
|
T vo = (T) list.get(i);
|
||||||
int column = 0;
|
int column = 0;
|
||||||
|
|
@ -419,8 +483,6 @@ public class ExcelUtil<T>
|
||||||
{
|
{
|
||||||
Field field = (Field) os[0];
|
Field field = (Field) os[0];
|
||||||
Excel excel = (Excel) os[1];
|
Excel excel = (Excel) os[1];
|
||||||
// 设置实体类私有属性可访问
|
|
||||||
field.setAccessible(true);
|
|
||||||
this.addCell(excel, row, vo, field, column++);
|
this.addCell(excel, row, vo, field, column++);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -439,6 +501,16 @@ public class ExcelUtil<T>
|
||||||
CellStyle style = wb.createCellStyle();
|
CellStyle style = wb.createCellStyle();
|
||||||
style.setAlignment(HorizontalAlignment.CENTER);
|
style.setAlignment(HorizontalAlignment.CENTER);
|
||||||
style.setVerticalAlignment(VerticalAlignment.CENTER);
|
style.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||||
|
Font titleFont = wb.createFont();
|
||||||
|
titleFont.setFontName("Arial");
|
||||||
|
titleFont.setFontHeightInPoints((short) 16);
|
||||||
|
titleFont.setBold(true);
|
||||||
|
style.setFont(titleFont);
|
||||||
|
styles.put("title", style);
|
||||||
|
|
||||||
|
style = wb.createCellStyle();
|
||||||
|
style.setAlignment(HorizontalAlignment.CENTER);
|
||||||
|
style.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||||
style.setBorderRight(BorderStyle.THIN);
|
style.setBorderRight(BorderStyle.THIN);
|
||||||
style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
||||||
style.setBorderLeft(BorderStyle.THIN);
|
style.setBorderLeft(BorderStyle.THIN);
|
||||||
|
|
@ -633,6 +705,10 @@ public class ExcelUtil<T>
|
||||||
{
|
{
|
||||||
cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString());
|
cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString());
|
||||||
}
|
}
|
||||||
|
else if (!attr.handler().equals(ExcelHandlerAdapter.class))
|
||||||
|
{
|
||||||
|
cell.setCellValue(dataFormatHandlerAdapter(value, attr));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 设置列类型
|
// 设置列类型
|
||||||
|
|
@ -779,6 +855,28 @@ public class ExcelUtil<T>
|
||||||
return StringUtils.stripEnd(propertyString.toString(), separator);
|
return StringUtils.stripEnd(propertyString.toString(), separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据处理器
|
||||||
|
*
|
||||||
|
* @param value 数据值
|
||||||
|
* @param excel 数据注解
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String dataFormatHandlerAdapter(Object value, Excel excel)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Object instance = excel.handler().newInstance();
|
||||||
|
Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class });
|
||||||
|
value = formatMethod.invoke(instance, value, excel.args());
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
log.error("不能格式化数据 " + excel.handler(), e.getMessage());
|
||||||
|
}
|
||||||
|
return Convert.toStr(value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 合计统计信息
|
* 合计统计信息
|
||||||
*/
|
*/
|
||||||
|
|
@ -809,10 +907,9 @@ public class ExcelUtil<T>
|
||||||
{
|
{
|
||||||
if (statistics.size() > 0)
|
if (statistics.size() > 0)
|
||||||
{
|
{
|
||||||
Cell cell = null;
|
|
||||||
Row row = sheet.createRow(sheet.getLastRowNum() + 1);
|
Row row = sheet.createRow(sheet.getLastRowNum() + 1);
|
||||||
Set<Integer> keys = statistics.keySet();
|
Set<Integer> keys = statistics.keySet();
|
||||||
cell = row.createCell(0);
|
Cell cell = row.createCell(0);
|
||||||
cell.setCellStyle(styles.get("total"));
|
cell.setCellStyle(styles.get("total"));
|
||||||
cell.setCellValue("合计");
|
cell.setCellValue("合计");
|
||||||
|
|
||||||
|
|
@ -882,7 +979,17 @@ public class ExcelUtil<T>
|
||||||
*/
|
*/
|
||||||
private void createExcelField()
|
private void createExcelField()
|
||||||
{
|
{
|
||||||
this.fields = new ArrayList<Object[]>();
|
this.fields = getFields();
|
||||||
|
this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
|
||||||
|
this.maxHeight = getRowHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字段注解信息
|
||||||
|
*/
|
||||||
|
public List<Object[]> getFields()
|
||||||
|
{
|
||||||
|
List<Object[]> fields = new ArrayList<Object[]>();
|
||||||
List<Field> tempFields = new ArrayList<>();
|
List<Field> tempFields = new ArrayList<>();
|
||||||
tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
|
tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
|
||||||
tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
|
tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
|
||||||
|
|
@ -891,7 +998,12 @@ public class ExcelUtil<T>
|
||||||
// 单注解
|
// 单注解
|
||||||
if (field.isAnnotationPresent(Excel.class))
|
if (field.isAnnotationPresent(Excel.class))
|
||||||
{
|
{
|
||||||
putToField(field, field.getAnnotation(Excel.class));
|
Excel attr = field.getAnnotation(Excel.class);
|
||||||
|
if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
|
||||||
|
{
|
||||||
|
field.setAccessible(true);
|
||||||
|
fields.add(new Object[] { field, attr });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 多注解
|
// 多注解
|
||||||
|
|
@ -899,14 +1011,17 @@ public class ExcelUtil<T>
|
||||||
{
|
{
|
||||||
Excels attrs = field.getAnnotation(Excels.class);
|
Excels attrs = field.getAnnotation(Excels.class);
|
||||||
Excel[] excels = attrs.value();
|
Excel[] excels = attrs.value();
|
||||||
for (Excel excel : excels)
|
for (Excel attr : excels)
|
||||||
{
|
{
|
||||||
putToField(field, excel);
|
if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
|
||||||
|
{
|
||||||
|
field.setAccessible(true);
|
||||||
|
fields.add(new Object[] { field, attr });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
|
}
|
||||||
this.maxHeight = getRowHeight();
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -923,23 +1038,15 @@ public class ExcelUtil<T>
|
||||||
return (short) (maxHeight * 20);
|
return (short) (maxHeight * 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 放到字段集合中
|
|
||||||
*/
|
|
||||||
private void putToField(Field field, Excel attr)
|
|
||||||
{
|
|
||||||
if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
|
|
||||||
{
|
|
||||||
this.fields.add(new Object[] { field, attr });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建一个工作簿
|
* 创建一个工作簿
|
||||||
*/
|
*/
|
||||||
public void createWorkbook()
|
public void createWorkbook()
|
||||||
{
|
{
|
||||||
this.wb = new SXSSFWorkbook(500);
|
this.wb = new SXSSFWorkbook(500);
|
||||||
|
this.sheet = wb.createSheet();
|
||||||
|
wb.setSheetName(0, sheetName);
|
||||||
|
this.styles = createStyles(wb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -948,17 +1055,13 @@ public class ExcelUtil<T>
|
||||||
* @param sheetNo sheet数量
|
* @param sheetNo sheet数量
|
||||||
* @param index 序号
|
* @param index 序号
|
||||||
*/
|
*/
|
||||||
public void createSheet(double sheetNo, int index)
|
public void createSheet(int sheetNo, int index)
|
||||||
|
{
|
||||||
|
// 设置工作表的名称.
|
||||||
|
if (sheetNo > 1 && index > 0)
|
||||||
{
|
{
|
||||||
this.sheet = wb.createSheet();
|
this.sheet = wb.createSheet();
|
||||||
this.styles = createStyles(wb);
|
this.createTitle();
|
||||||
// 设置工作表的名称.
|
|
||||||
if (sheetNo == 0)
|
|
||||||
{
|
|
||||||
wb.setSheetName(index, sheetName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wb.setSheetName(index, sheetName + index);
|
wb.setSheetName(index, sheetName + index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.ruoyi.common.core.utils.sql;
|
package com.ruoyi.common.core.utils.sql;
|
||||||
|
|
||||||
import com.ruoyi.common.core.exception.BaseException;
|
import com.ruoyi.common.core.exception.UtilException;
|
||||||
import com.ruoyi.common.core.utils.StringUtils;
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -22,7 +22,7 @@ public class SqlUtil
|
||||||
{
|
{
|
||||||
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value))
|
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value))
|
||||||
{
|
{
|
||||||
throw new BaseException("参数不符合规范,不能进行查询");
|
throw new UtilException("参数不符合规范,不能进行查询");
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,8 @@ public class BaseController
|
||||||
if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize))
|
if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize))
|
||||||
{
|
{
|
||||||
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
|
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
|
||||||
PageHelper.startPage(pageNum, pageSize, orderBy);
|
Boolean reasonable = pageDomain.getReasonable();
|
||||||
|
PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,9 @@ public class PageDomain
|
||||||
/** 排序的方向desc或者asc */
|
/** 排序的方向desc或者asc */
|
||||||
private String isAsc = "asc";
|
private String isAsc = "asc";
|
||||||
|
|
||||||
|
/** 分页参数合理化 */
|
||||||
|
private Boolean reasonable = true;
|
||||||
|
|
||||||
public String getOrderBy()
|
public String getOrderBy()
|
||||||
{
|
{
|
||||||
if (StringUtils.isEmpty(orderByColumn))
|
if (StringUtils.isEmpty(orderByColumn))
|
||||||
|
|
@ -81,4 +84,18 @@ public class PageDomain
|
||||||
this.isAsc = isAsc;
|
this.isAsc = isAsc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getReasonable()
|
||||||
|
{
|
||||||
|
if (StringUtils.isNull(reasonable))
|
||||||
|
{
|
||||||
|
return Boolean.TRUE;
|
||||||
|
}
|
||||||
|
return reasonable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReasonable(Boolean reasonable)
|
||||||
|
{
|
||||||
|
this.reasonable = reasonable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,11 @@ public class TableSupport
|
||||||
*/
|
*/
|
||||||
public static final String IS_ASC = "isAsc";
|
public static final String IS_ASC = "isAsc";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页参数合理化
|
||||||
|
*/
|
||||||
|
public static final String REASONABLE = "reasonable";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 封装分页对象
|
* 封装分页对象
|
||||||
*/
|
*/
|
||||||
|
|
@ -39,6 +44,7 @@ public class TableSupport
|
||||||
pageDomain.setPageSize(ServletUtils.getParameterToInt(PAGE_SIZE));
|
pageDomain.setPageSize(ServletUtils.getParameterToInt(PAGE_SIZE));
|
||||||
pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN));
|
pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN));
|
||||||
pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC));
|
pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC));
|
||||||
|
pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE));
|
||||||
return pageDomain;
|
return pageDomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-common</artifactId>
|
<artifactId>ruoyi-common</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,13 @@
|
||||||
package com.ruoyi.common.datascope.aspect;
|
package com.ruoyi.common.datascope.aspect;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import org.aspectj.lang.JoinPoint;
|
import org.aspectj.lang.JoinPoint;
|
||||||
import org.aspectj.lang.Signature;
|
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
import org.aspectj.lang.annotation.Before;
|
import org.aspectj.lang.annotation.Before;
|
||||||
import org.aspectj.lang.annotation.Pointcut;
|
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import com.ruoyi.common.core.utils.StringUtils;
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
import com.ruoyi.common.core.web.domain.BaseEntity;
|
import com.ruoyi.common.core.web.domain.BaseEntity;
|
||||||
import com.ruoyi.common.datascope.annotation.DataScope;
|
import com.ruoyi.common.datascope.annotation.DataScope;
|
||||||
import com.ruoyi.common.security.service.TokenService;
|
import com.ruoyi.common.security.utils.SecurityUtils;
|
||||||
import com.ruoyi.system.api.domain.SysRole;
|
import com.ruoyi.system.api.domain.SysRole;
|
||||||
import com.ruoyi.system.api.domain.SysUser;
|
import com.ruoyi.system.api.domain.SysUser;
|
||||||
import com.ruoyi.system.api.model.LoginUser;
|
import com.ruoyi.system.api.model.LoginUser;
|
||||||
|
|
@ -56,32 +51,17 @@ public class DataScopeAspect
|
||||||
*/
|
*/
|
||||||
public static final String DATA_SCOPE = "dataScope";
|
public static final String DATA_SCOPE = "dataScope";
|
||||||
|
|
||||||
@Autowired
|
@Before("@annotation(controllerDataScope)")
|
||||||
private TokenService tokenService;
|
public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
|
||||||
|
|
||||||
// 配置织入点
|
|
||||||
@Pointcut("@annotation(com.ruoyi.common.datascope.annotation.DataScope)")
|
|
||||||
public void dataScopePointCut()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before("dataScopePointCut()")
|
|
||||||
public void doBefore(JoinPoint point) throws Throwable
|
|
||||||
{
|
{
|
||||||
clearDataScope(point);
|
clearDataScope(point);
|
||||||
handleDataScope(point);
|
handleDataScope(point, controllerDataScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleDataScope(final JoinPoint joinPoint)
|
protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
|
||||||
{
|
{
|
||||||
// 获得注解
|
|
||||||
DataScope controllerDataScope = getAnnotationLog(joinPoint);
|
|
||||||
if (controllerDataScope == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 获取当前的用户
|
// 获取当前的用户
|
||||||
LoginUser loginUser = tokenService.getLoginUser();
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||||
if (StringUtils.isNotNull(loginUser))
|
if (StringUtils.isNotNull(loginUser))
|
||||||
{
|
{
|
||||||
SysUser currentUser = loginUser.getSysUser();
|
SysUser currentUser = loginUser.getSysUser();
|
||||||
|
|
@ -155,22 +135,6 @@ public class DataScopeAspect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否存在注解,如果存在就获取
|
|
||||||
*/
|
|
||||||
private DataScope getAnnotationLog(JoinPoint joinPoint)
|
|
||||||
{
|
|
||||||
Signature signature = joinPoint.getSignature();
|
|
||||||
MethodSignature methodSignature = (MethodSignature) signature;
|
|
||||||
Method method = methodSignature.getMethod();
|
|
||||||
|
|
||||||
if (method != null)
|
|
||||||
{
|
|
||||||
return method.getAnnotation(DataScope.class);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拼接权限sql前先清空params.dataScope参数防止注入
|
* 拼接权限sql前先清空params.dataScope参数防止注入
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-common</artifactId>
|
<artifactId>ruoyi-common</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-common</artifactId>
|
<artifactId>ruoyi-common</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,4 +38,9 @@ public @interface Log
|
||||||
* 是否保存请求的参数
|
* 是否保存请求的参数
|
||||||
*/
|
*/
|
||||||
public boolean isSaveRequestData() default true;
|
public boolean isSaveRequestData() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否保存响应的参数
|
||||||
|
*/
|
||||||
|
public boolean isSaveResponseData() default true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,13 @@
|
||||||
package com.ruoyi.common.log.aspect;
|
package com.ruoyi.common.log.aspect;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import org.aspectj.lang.JoinPoint;
|
import org.aspectj.lang.JoinPoint;
|
||||||
import org.aspectj.lang.Signature;
|
|
||||||
import org.aspectj.lang.annotation.AfterReturning;
|
import org.aspectj.lang.annotation.AfterReturning;
|
||||||
import org.aspectj.lang.annotation.AfterThrowing;
|
import org.aspectj.lang.annotation.AfterThrowing;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
import org.aspectj.lang.annotation.Pointcut;
|
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
@ -21,13 +16,13 @@ import org.springframework.stereotype.Component;
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.ruoyi.common.core.utils.SecurityUtils;
|
|
||||||
import com.ruoyi.common.core.utils.ServletUtils;
|
import com.ruoyi.common.core.utils.ServletUtils;
|
||||||
import com.ruoyi.common.core.utils.StringUtils;
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
import com.ruoyi.common.core.utils.ip.IpUtils;
|
import com.ruoyi.common.core.utils.ip.IpUtils;
|
||||||
import com.ruoyi.common.log.annotation.Log;
|
import com.ruoyi.common.log.annotation.Log;
|
||||||
import com.ruoyi.common.log.enums.BusinessStatus;
|
import com.ruoyi.common.log.enums.BusinessStatus;
|
||||||
import com.ruoyi.common.log.service.AsyncLogService;
|
import com.ruoyi.common.log.service.AsyncLogService;
|
||||||
|
import com.ruoyi.common.security.utils.SecurityUtils;
|
||||||
import com.ruoyi.system.api.domain.SysOperLog;
|
import com.ruoyi.system.api.domain.SysOperLog;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -44,21 +39,15 @@ public class LogAspect
|
||||||
@Autowired
|
@Autowired
|
||||||
private AsyncLogService asyncLogService;
|
private AsyncLogService asyncLogService;
|
||||||
|
|
||||||
// 配置织入点
|
|
||||||
@Pointcut("@annotation(com.ruoyi.common.log.annotation.Log)")
|
|
||||||
public void logPointCut()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理完请求后执行
|
* 处理完请求后执行
|
||||||
*
|
*
|
||||||
* @param joinPoint 切点
|
* @param joinPoint 切点
|
||||||
*/
|
*/
|
||||||
@AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
|
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
|
||||||
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult)
|
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
|
||||||
{
|
{
|
||||||
handleLog(joinPoint, null, jsonResult);
|
handleLog(joinPoint, controllerLog, null, jsonResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -67,32 +56,22 @@ public class LogAspect
|
||||||
* @param joinPoint 切点
|
* @param joinPoint 切点
|
||||||
* @param e 异常
|
* @param e 异常
|
||||||
*/
|
*/
|
||||||
@AfterThrowing(value = "logPointCut()", throwing = "e")
|
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
|
||||||
public void doAfterThrowing(JoinPoint joinPoint, Exception e)
|
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
|
||||||
{
|
{
|
||||||
handleLog(joinPoint, e, null);
|
handleLog(joinPoint, controllerLog, e, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult)
|
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 获得注解
|
|
||||||
Log controllerLog = getAnnotationLog(joinPoint);
|
|
||||||
if (controllerLog == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// *========数据库日志=========*//
|
// *========数据库日志=========*//
|
||||||
SysOperLog operLog = new SysOperLog();
|
SysOperLog operLog = new SysOperLog();
|
||||||
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
|
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
|
||||||
// 请求的地址
|
// 请求的地址
|
||||||
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
|
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
|
||||||
operLog.setOperIp(ip);
|
operLog.setOperIp(ip);
|
||||||
// 返回参数
|
|
||||||
operLog.setJsonResult(JSON.toJSONString(jsonResult));
|
|
||||||
|
|
||||||
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
|
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
|
||||||
String username = SecurityUtils.getUsername();
|
String username = SecurityUtils.getUsername();
|
||||||
if (StringUtils.isNotBlank(username))
|
if (StringUtils.isNotBlank(username))
|
||||||
|
|
@ -112,7 +91,7 @@ public class LogAspect
|
||||||
// 设置请求方式
|
// 设置请求方式
|
||||||
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
|
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
|
||||||
// 处理设置注解上的参数
|
// 处理设置注解上的参数
|
||||||
getControllerMethodDescription(joinPoint, controllerLog, operLog);
|
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
|
||||||
// 保存数据库
|
// 保存数据库
|
||||||
asyncLogService.saveSysLog(operLog);
|
asyncLogService.saveSysLog(operLog);
|
||||||
}
|
}
|
||||||
|
|
@ -132,7 +111,7 @@ public class LogAspect
|
||||||
* @param operLog 操作日志
|
* @param operLog 操作日志
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) throws Exception
|
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception
|
||||||
{
|
{
|
||||||
// 设置action动作
|
// 设置action动作
|
||||||
operLog.setBusinessType(log.businessType().ordinal());
|
operLog.setBusinessType(log.businessType().ordinal());
|
||||||
|
|
@ -146,6 +125,11 @@ public class LogAspect
|
||||||
// 获取参数的信息,传入到数据库中。
|
// 获取参数的信息,传入到数据库中。
|
||||||
setRequestValue(joinPoint, operLog);
|
setRequestValue(joinPoint, operLog);
|
||||||
}
|
}
|
||||||
|
// 是否需要保存response,参数和值
|
||||||
|
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
|
||||||
|
{
|
||||||
|
operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -164,22 +148,6 @@ public class LogAspect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否存在注解,如果存在就获取
|
|
||||||
*/
|
|
||||||
private Log getAnnotationLog(JoinPoint joinPoint) throws Exception
|
|
||||||
{
|
|
||||||
Signature signature = joinPoint.getSignature();
|
|
||||||
MethodSignature methodSignature = (MethodSignature) signature;
|
|
||||||
Method method = methodSignature.getMethod();
|
|
||||||
|
|
||||||
if (method != null)
|
|
||||||
{
|
|
||||||
return method.getAnnotation(Log.class);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 参数拼装
|
* 参数拼装
|
||||||
*/
|
*/
|
||||||
|
|
@ -188,13 +156,13 @@ public class LogAspect
|
||||||
String params = "";
|
String params = "";
|
||||||
if (paramsArray != null && paramsArray.length > 0)
|
if (paramsArray != null && paramsArray.length > 0)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < paramsArray.length; i++)
|
for (Object o : paramsArray)
|
||||||
{
|
{
|
||||||
if (StringUtils.isNotNull(paramsArray[i]) && !isFilterObject(paramsArray[i]))
|
if (StringUtils.isNotNull(o) && !isFilterObject(o))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Object jsonObj = JSON.toJSON(paramsArray[i]);
|
Object jsonObj = JSON.toJSON(o);
|
||||||
params += jsonObj.toString() + " ";
|
params += jsonObj.toString() + " ";
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
@ -223,17 +191,17 @@ public class LogAspect
|
||||||
else if (Collection.class.isAssignableFrom(clazz))
|
else if (Collection.class.isAssignableFrom(clazz))
|
||||||
{
|
{
|
||||||
Collection collection = (Collection) o;
|
Collection collection = (Collection) o;
|
||||||
for (Iterator iter = collection.iterator(); iter.hasNext();)
|
for (Object value : collection)
|
||||||
{
|
{
|
||||||
return iter.next() instanceof MultipartFile;
|
return value instanceof MultipartFile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (Map.class.isAssignableFrom(clazz))
|
else if (Map.class.isAssignableFrom(clazz))
|
||||||
{
|
{
|
||||||
Map map = (Map) o;
|
Map map = (Map) o;
|
||||||
for (Iterator iter = map.entrySet().iterator(); iter.hasNext();)
|
for (Object value : map.entrySet())
|
||||||
{
|
{
|
||||||
Map.Entry entry = (Map.Entry) iter.next();
|
Map.Entry entry = (Map.Entry) value;
|
||||||
return entry.getValue() instanceof MultipartFile;
|
return entry.getValue() instanceof MultipartFile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.ruoyi.common.log.service;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import com.ruoyi.common.core.constant.SecurityConstants;
|
||||||
import com.ruoyi.system.api.RemoteLogService;
|
import com.ruoyi.system.api.RemoteLogService;
|
||||||
import com.ruoyi.system.api.domain.SysOperLog;
|
import com.ruoyi.system.api.domain.SysOperLog;
|
||||||
|
|
||||||
|
|
@ -23,6 +24,6 @@ public class AsyncLogService
|
||||||
@Async
|
@Async
|
||||||
public void saveSysLog(SysOperLog sysOperLog)
|
public void saveSysLog(SysOperLog sysOperLog)
|
||||||
{
|
{
|
||||||
remoteLogService.saveLog(sysOperLog);
|
remoteLogService.saveLog(sysOperLog, SecurityConstants.INNER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-common</artifactId>
|
<artifactId>ruoyi-common</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,28 @@ public class RedisService
|
||||||
return redisTemplate.expire(key, timeout, unit);
|
return redisTemplate.expire(key, timeout, unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取有效时间
|
||||||
|
*
|
||||||
|
* @param key Redis键
|
||||||
|
* @return 有效时间
|
||||||
|
*/
|
||||||
|
public long getExpire(final String key)
|
||||||
|
{
|
||||||
|
return redisTemplate.getExpire(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断 key是否存在
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return true 存在 false不存在
|
||||||
|
*/
|
||||||
|
public Boolean hasKey(String key)
|
||||||
|
{
|
||||||
|
return redisTemplate.hasKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得缓存的基本对象。
|
* 获得缓存的基本对象。
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-common</artifactId>
|
<artifactId>ruoyi-common</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
@ -16,6 +16,12 @@
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- Spring Web -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-webmvc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- RuoYi Api System -->
|
<!-- RuoYi Api System -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.ruoyi.common.security.annotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限注解的验证模式
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public enum Logical
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 必须具有所有的元素
|
||||||
|
*/
|
||||||
|
AND,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 只需具有其中一个元素
|
||||||
|
*/
|
||||||
|
OR
|
||||||
|
}
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
package com.ruoyi.common.security.annotation;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 权限注解
|
|
||||||
*
|
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
public @interface PreAuthorize
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 验证用户是否具备某权限
|
|
||||||
*/
|
|
||||||
public String hasPermi() default "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否不具备某权限,与 hasPermi逻辑相反
|
|
||||||
*/
|
|
||||||
public String lacksPermi() default "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否具有以下任意一个权限
|
|
||||||
*/
|
|
||||||
public String[] hasAnyPermi() default {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断用户是否拥有某个角色
|
|
||||||
*/
|
|
||||||
public String hasRole() default "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否不具备某角色,与 isRole逻辑相反
|
|
||||||
*/
|
|
||||||
public String lacksRole() default "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否具有以下任意一个角色
|
|
||||||
*/
|
|
||||||
public String[] hasAnyRoles() default {};
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.ruoyi.common.security.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录认证:只有登录之后才能进入该方法
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||||
|
public @interface RequiresLogin
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.ruoyi.common.security.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限认证:必须具有指定权限才能进入该方法
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||||
|
public @interface RequiresPermissions
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 需要校验的权限码
|
||||||
|
*/
|
||||||
|
String[] value() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证模式:AND | OR,默认AND
|
||||||
|
*/
|
||||||
|
Logical logical() default Logical.AND;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.ruoyi.common.security.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色认证:必须具有指定角色标识才能进入该方法
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||||
|
public @interface RequiresRoles
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 需要校验的角色标识
|
||||||
|
*/
|
||||||
|
String[] value() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证逻辑:AND | OR,默认AND
|
||||||
|
*/
|
||||||
|
Logical logical() default Logical.AND;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,225 +1,97 @@
|
||||||
package com.ruoyi.common.security.aspect;
|
package com.ruoyi.common.security.aspect;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Collection;
|
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
import org.aspectj.lang.Signature;
|
|
||||||
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Around;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.annotation.Pointcut;
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.CollectionUtils;
|
import com.ruoyi.common.security.annotation.RequiresLogin;
|
||||||
import org.springframework.util.PatternMatchUtils;
|
import com.ruoyi.common.security.annotation.RequiresPermissions;
|
||||||
import com.ruoyi.common.core.exception.PreAuthorizeException;
|
import com.ruoyi.common.security.annotation.RequiresRoles;
|
||||||
import com.ruoyi.common.core.utils.StringUtils;
|
import com.ruoyi.common.security.auth.AuthUtil;
|
||||||
import com.ruoyi.common.security.annotation.PreAuthorize;
|
|
||||||
import com.ruoyi.common.security.service.TokenService;
|
|
||||||
import com.ruoyi.system.api.model.LoginUser;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义权限实现
|
* 基于 Spring Aop 的注解鉴权
|
||||||
*
|
*
|
||||||
* @author ruoyi
|
* @author kong
|
||||||
*/
|
*/
|
||||||
@Aspect
|
@Aspect
|
||||||
@Component
|
@Component
|
||||||
public class PreAuthorizeAspect
|
public class PreAuthorizeAspect
|
||||||
{
|
{
|
||||||
@Autowired
|
/**
|
||||||
private TokenService tokenService;
|
* 构建
|
||||||
|
*/
|
||||||
/** 所有权限标识 */
|
public PreAuthorizeAspect()
|
||||||
private static final String ALL_PERMISSION = "*:*:*";
|
|
||||||
|
|
||||||
/** 管理员角色权限标识 */
|
|
||||||
private static final String SUPER_ADMIN = "admin";
|
|
||||||
|
|
||||||
/** 数组为0时 */
|
|
||||||
private static final Integer ARRAY_EMPTY = 0;
|
|
||||||
|
|
||||||
@Around("@annotation(com.ruoyi.common.security.annotation.PreAuthorize)")
|
|
||||||
public Object around(ProceedingJoinPoint point) throws Throwable
|
|
||||||
{
|
{
|
||||||
Signature signature = point.getSignature();
|
|
||||||
MethodSignature methodSignature = (MethodSignature) signature;
|
|
||||||
Method method = methodSignature.getMethod();
|
|
||||||
PreAuthorize annotation = method.getAnnotation(PreAuthorize.class);
|
|
||||||
if (annotation == null)
|
|
||||||
{
|
|
||||||
return point.proceed();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StringUtils.isNotEmpty(annotation.hasPermi()))
|
|
||||||
{
|
|
||||||
if (hasPermi(annotation.hasPermi()))
|
|
||||||
{
|
|
||||||
return point.proceed();
|
|
||||||
}
|
|
||||||
throw new PreAuthorizeException();
|
|
||||||
}
|
|
||||||
else if (StringUtils.isNotEmpty(annotation.lacksPermi()))
|
|
||||||
{
|
|
||||||
if (lacksPermi(annotation.lacksPermi()))
|
|
||||||
{
|
|
||||||
return point.proceed();
|
|
||||||
}
|
|
||||||
throw new PreAuthorizeException();
|
|
||||||
}
|
|
||||||
else if (ARRAY_EMPTY < annotation.hasAnyPermi().length)
|
|
||||||
{
|
|
||||||
if (hasAnyPermi(annotation.hasAnyPermi()))
|
|
||||||
{
|
|
||||||
return point.proceed();
|
|
||||||
}
|
|
||||||
throw new PreAuthorizeException();
|
|
||||||
}
|
|
||||||
else if (StringUtils.isNotEmpty(annotation.hasRole()))
|
|
||||||
{
|
|
||||||
if (hasRole(annotation.hasRole()))
|
|
||||||
{
|
|
||||||
return point.proceed();
|
|
||||||
}
|
|
||||||
throw new PreAuthorizeException();
|
|
||||||
}
|
|
||||||
else if (StringUtils.isNotEmpty(annotation.lacksRole()))
|
|
||||||
{
|
|
||||||
if (lacksRole(annotation.lacksRole()))
|
|
||||||
{
|
|
||||||
return point.proceed();
|
|
||||||
}
|
|
||||||
throw new PreAuthorizeException();
|
|
||||||
}
|
|
||||||
else if (ARRAY_EMPTY < annotation.hasAnyRoles().length)
|
|
||||||
{
|
|
||||||
if (hasAnyRoles(annotation.hasAnyRoles()))
|
|
||||||
{
|
|
||||||
return point.proceed();
|
|
||||||
}
|
|
||||||
throw new PreAuthorizeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return point.proceed();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证用户是否具备某权限
|
* 定义AOP签名 (切入所有使用鉴权注解的方法)
|
||||||
*
|
|
||||||
* @param permission 权限字符串
|
|
||||||
* @return 用户是否具备某权限
|
|
||||||
*/
|
*/
|
||||||
public boolean hasPermi(String permission)
|
public static final String POINTCUT_SIGN = " @annotation(com.ruoyi.common.security.annotation.RequiresLogin) || "
|
||||||
|
+ "@annotation(com.ruoyi.common.security.annotation.RequiresPermissions) || "
|
||||||
|
+ "@annotation(com.ruoyi.common.security.annotation.RequiresRoles)";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 声明AOP签名
|
||||||
|
*/
|
||||||
|
@Pointcut(POINTCUT_SIGN)
|
||||||
|
public void pointcut()
|
||||||
{
|
{
|
||||||
LoginUser userInfo = tokenService.getLoginUser();
|
|
||||||
if (StringUtils.isNull(userInfo) || CollectionUtils.isEmpty(userInfo.getPermissions()))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return hasPermissions(userInfo.getPermissions(), permission);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证用户是否不具备某权限,与 hasPermi逻辑相反
|
* 环绕切入
|
||||||
*
|
*
|
||||||
* @param permission 权限字符串
|
* @param joinPoint 切面对象
|
||||||
* @return 用户是否不具备某权限
|
* @return 底层方法执行后的返回值
|
||||||
|
* @throws Throwable 底层方法抛出的异常
|
||||||
*/
|
*/
|
||||||
public boolean lacksPermi(String permission)
|
@Around("pointcut()")
|
||||||
|
public Object around(ProceedingJoinPoint joinPoint) throws Throwable
|
||||||
{
|
{
|
||||||
return hasPermi(permission) != true;
|
// 注解鉴权
|
||||||
|
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||||
|
checkMethodAnnotation(signature.getMethod());
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 执行原有逻辑
|
||||||
|
Object obj = joinPoint.proceed();
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证用户是否具有以下任意一个权限
|
* 对一个Method对象进行注解检查
|
||||||
*
|
|
||||||
* @param permissions 权限列表
|
|
||||||
* @return 用户是否具有以下任意一个权限
|
|
||||||
*/
|
*/
|
||||||
public boolean hasAnyPermi(String[] permissions)
|
public void checkMethodAnnotation(Method method)
|
||||||
{
|
{
|
||||||
LoginUser userInfo = tokenService.getLoginUser();
|
// 校验 @RequiresLogin 注解
|
||||||
if (StringUtils.isNull(userInfo) || CollectionUtils.isEmpty(userInfo.getPermissions()))
|
RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class);
|
||||||
|
if (requiresLogin != null)
|
||||||
{
|
{
|
||||||
return false;
|
AuthUtil.checkLogin();
|
||||||
}
|
|
||||||
Collection<String> authorities = userInfo.getPermissions();
|
|
||||||
for (String permission : permissions)
|
|
||||||
{
|
|
||||||
if (permission != null && hasPermissions(authorities, permission))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 校验 @RequiresRoles 注解
|
||||||
* 判断用户是否拥有某个角色
|
RequiresRoles requiresRoles = method.getAnnotation(RequiresRoles.class);
|
||||||
*
|
if (requiresRoles != null)
|
||||||
* @param role 角色字符串
|
|
||||||
* @return 用户是否具备某角色
|
|
||||||
*/
|
|
||||||
public boolean hasRole(String role)
|
|
||||||
{
|
{
|
||||||
LoginUser userInfo = tokenService.getLoginUser();
|
AuthUtil.checkRole(requiresRoles);
|
||||||
if (StringUtils.isNull(userInfo) || CollectionUtils.isEmpty(userInfo.getRoles()))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (String roleKey : userInfo.getRoles())
|
|
||||||
{
|
|
||||||
if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(role))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 校验 @RequiresPermissions 注解
|
||||||
* 验证用户是否不具备某角色,与 isRole逻辑相反。
|
RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
|
||||||
*
|
if (requiresPermissions != null)
|
||||||
* @param role 角色名称
|
|
||||||
* @return 用户是否不具备某角色
|
|
||||||
*/
|
|
||||||
public boolean lacksRole(String role)
|
|
||||||
{
|
{
|
||||||
return hasRole(role) != true;
|
AuthUtil.checkPermi(requiresPermissions);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户是否具有以下任意一个角色
|
|
||||||
*
|
|
||||||
* @param roles 角色列表
|
|
||||||
* @return 用户是否具有以下任意一个角色
|
|
||||||
*/
|
|
||||||
public boolean hasAnyRoles(String[] roles)
|
|
||||||
{
|
|
||||||
LoginUser userInfo = tokenService.getLoginUser();
|
|
||||||
if (StringUtils.isNull(userInfo) || CollectionUtils.isEmpty(userInfo.getRoles()))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (String role : roles)
|
|
||||||
{
|
|
||||||
if (hasRole(role))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否包含权限
|
|
||||||
*
|
|
||||||
* @param authorities 权限列表
|
|
||||||
* @param permission 权限字符串
|
|
||||||
* @return 用户是否具备某权限
|
|
||||||
*/
|
|
||||||
private boolean hasPermissions(Collection<String> authorities, String permission)
|
|
||||||
{
|
|
||||||
return authorities.stream().filter(StringUtils::hasText)
|
|
||||||
.anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(x, permission));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,371 @@
|
||||||
|
package com.ruoyi.common.security.auth;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.springframework.util.PatternMatchUtils;
|
||||||
|
import com.ruoyi.common.core.exception.auth.NotLoginException;
|
||||||
|
import com.ruoyi.common.core.exception.auth.NotPermissionException;
|
||||||
|
import com.ruoyi.common.core.exception.auth.NotRoleException;
|
||||||
|
import com.ruoyi.common.core.utils.SpringUtils;
|
||||||
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
|
import com.ruoyi.common.security.annotation.Logical;
|
||||||
|
import com.ruoyi.common.security.annotation.RequiresLogin;
|
||||||
|
import com.ruoyi.common.security.annotation.RequiresPermissions;
|
||||||
|
import com.ruoyi.common.security.annotation.RequiresRoles;
|
||||||
|
import com.ruoyi.common.security.service.TokenService;
|
||||||
|
import com.ruoyi.common.security.utils.SecurityUtils;
|
||||||
|
import com.ruoyi.system.api.model.LoginUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token 权限验证,逻辑实现类
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public class AuthLogic
|
||||||
|
{
|
||||||
|
/** 所有权限标识 */
|
||||||
|
private static final String ALL_PERMISSION = "*:*:*";
|
||||||
|
|
||||||
|
/** 管理员角色权限标识 */
|
||||||
|
private static final String SUPER_ADMIN = "admin";
|
||||||
|
|
||||||
|
public TokenService tokenService = SpringUtils.getBean(TokenService.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话注销
|
||||||
|
*/
|
||||||
|
public void logout()
|
||||||
|
{
|
||||||
|
String token = SecurityUtils.getToken();
|
||||||
|
if (token == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logoutByToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话注销,根据指定Token
|
||||||
|
*/
|
||||||
|
public void logoutByToken(String token)
|
||||||
|
{
|
||||||
|
tokenService.delLoginUser(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检验用户是否已经登录,如未登录,则抛出异常
|
||||||
|
*/
|
||||||
|
public void checkLogin()
|
||||||
|
{
|
||||||
|
getLoginUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户缓存信息, 如果未登录,则抛出异常
|
||||||
|
*
|
||||||
|
* @return 用户缓存信息
|
||||||
|
*/
|
||||||
|
public LoginUser getLoginUser()
|
||||||
|
{
|
||||||
|
String token = SecurityUtils.getToken();
|
||||||
|
if (token == null)
|
||||||
|
{
|
||||||
|
throw new NotLoginException("未提供token");
|
||||||
|
}
|
||||||
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||||
|
if (loginUser == null)
|
||||||
|
{
|
||||||
|
throw new NotLoginException("无效的token");
|
||||||
|
}
|
||||||
|
return loginUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户缓存信息, 如果未登录,则抛出异常
|
||||||
|
*
|
||||||
|
* @param token 前端传递的认证信息
|
||||||
|
* @return 用户缓存信息
|
||||||
|
*/
|
||||||
|
public LoginUser getLoginUser(String token)
|
||||||
|
{
|
||||||
|
return tokenService.getLoginUser(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证当前用户有效期, 如果相差不足360分钟,自动刷新缓存
|
||||||
|
*
|
||||||
|
* @param loginUser 当前用户信息
|
||||||
|
*/
|
||||||
|
public void verifyLoginUserExpire(LoginUser loginUser)
|
||||||
|
{
|
||||||
|
tokenService.verifyToken(loginUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证用户是否具备某权限
|
||||||
|
*
|
||||||
|
* @param permission 权限字符串
|
||||||
|
* @return 用户是否具备某权限
|
||||||
|
*/
|
||||||
|
public boolean hasPermi(String permission)
|
||||||
|
{
|
||||||
|
return hasPermi(getPermiList(), permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证用户是否具备某权限, 如果验证未通过,则抛出异常: NotPermissionException
|
||||||
|
*
|
||||||
|
* @param permission 权限字符串
|
||||||
|
* @return 用户是否具备某权限
|
||||||
|
*/
|
||||||
|
public void checkPermi(String permission)
|
||||||
|
{
|
||||||
|
if (!hasPermi(getPermiList(), permission))
|
||||||
|
{
|
||||||
|
throw new NotPermissionException(permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据注解(@RequiresPermissions)鉴权, 如果验证未通过,则抛出异常: NotPermissionException
|
||||||
|
*
|
||||||
|
* @param requiresPermissions 注解对象
|
||||||
|
*/
|
||||||
|
public void checkPermi(RequiresPermissions requiresPermissions)
|
||||||
|
{
|
||||||
|
if (requiresPermissions.logical() == Logical.AND)
|
||||||
|
{
|
||||||
|
checkPermiAnd(requiresPermissions.value());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
checkPermiOr(requiresPermissions.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证用户是否含有指定权限,必须全部拥有
|
||||||
|
*
|
||||||
|
* @param permissions 权限列表
|
||||||
|
*/
|
||||||
|
public void checkPermiAnd(String... permissions)
|
||||||
|
{
|
||||||
|
Set<String> permissionList = getPermiList();
|
||||||
|
for (String permission : permissions)
|
||||||
|
{
|
||||||
|
if (!hasPermi(permissionList, permission))
|
||||||
|
{
|
||||||
|
throw new NotPermissionException(permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证用户是否含有指定权限,只需包含其中一个
|
||||||
|
*
|
||||||
|
* @param permissions 权限码数组
|
||||||
|
*/
|
||||||
|
public void checkPermiOr(String... permissions)
|
||||||
|
{
|
||||||
|
Set<String> permissionList = getPermiList();
|
||||||
|
for (String permission : permissions)
|
||||||
|
{
|
||||||
|
if (hasPermi(permissionList, permission))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (permissions.length > 0)
|
||||||
|
{
|
||||||
|
throw new NotPermissionException(permissions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断用户是否拥有某个角色
|
||||||
|
*
|
||||||
|
* @param role 角色标识
|
||||||
|
* @return 用户是否具备某角色
|
||||||
|
*/
|
||||||
|
public boolean hasRole(String role)
|
||||||
|
{
|
||||||
|
return hasRole(getRoleList(), role);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断用户是否拥有某个角色, 如果验证未通过,则抛出异常: NotRoleException
|
||||||
|
*
|
||||||
|
* @param role 角色标识
|
||||||
|
*/
|
||||||
|
public void checkRole(String role)
|
||||||
|
{
|
||||||
|
if (!hasRole(role))
|
||||||
|
{
|
||||||
|
throw new NotRoleException(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据注解(@RequiresRoles)鉴权
|
||||||
|
*
|
||||||
|
* @param requiresRoles 注解对象
|
||||||
|
*/
|
||||||
|
public void checkRole(RequiresRoles requiresRoles)
|
||||||
|
{
|
||||||
|
if (requiresRoles.logical() == Logical.AND)
|
||||||
|
{
|
||||||
|
checkRoleAnd(requiresRoles.value());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
checkRoleOr(requiresRoles.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证用户是否含有指定角色,必须全部拥有
|
||||||
|
*
|
||||||
|
* @param roles 角色标识数组
|
||||||
|
*/
|
||||||
|
public void checkRoleAnd(String... roles)
|
||||||
|
{
|
||||||
|
Set<String> roleList = getRoleList();
|
||||||
|
for (String role : roles)
|
||||||
|
{
|
||||||
|
if (!hasRole(roleList, role))
|
||||||
|
{
|
||||||
|
throw new NotRoleException(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证用户是否含有指定角色,只需包含其中一个
|
||||||
|
*
|
||||||
|
* @param roles 角色标识数组
|
||||||
|
*/
|
||||||
|
public void checkRoleOr(String... roles)
|
||||||
|
{
|
||||||
|
Set<String> roleList = getRoleList();
|
||||||
|
for (String role : roles)
|
||||||
|
{
|
||||||
|
if (hasRole(roleList, role))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (roles.length > 0)
|
||||||
|
{
|
||||||
|
throw new NotRoleException(roles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据注解(@RequiresLogin)鉴权
|
||||||
|
*
|
||||||
|
* @param at 注解对象
|
||||||
|
*/
|
||||||
|
public void checkByAnnotation(RequiresLogin at)
|
||||||
|
{
|
||||||
|
this.checkLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据注解(@RequiresRoles)鉴权
|
||||||
|
*
|
||||||
|
* @param at 注解对象
|
||||||
|
*/
|
||||||
|
public void checkByAnnotation(RequiresRoles at)
|
||||||
|
{
|
||||||
|
String[] roleArray = at.value();
|
||||||
|
if (at.logical() == Logical.AND)
|
||||||
|
{
|
||||||
|
this.checkRoleAnd(roleArray);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.checkRoleOr(roleArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据注解(@RequiresPermissions)鉴权
|
||||||
|
*
|
||||||
|
* @param at 注解对象
|
||||||
|
*/
|
||||||
|
public void checkByAnnotation(RequiresPermissions at)
|
||||||
|
{
|
||||||
|
String[] permissionArray = at.value();
|
||||||
|
if (at.logical() == Logical.AND)
|
||||||
|
{
|
||||||
|
this.checkPermiAnd(permissionArray);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.checkPermiOr(permissionArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前账号的角色列表
|
||||||
|
*
|
||||||
|
* @return 角色列表
|
||||||
|
*/
|
||||||
|
public Set<String> getRoleList()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LoginUser loginUser = getLoginUser();
|
||||||
|
return loginUser.getRoles();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前账号的权限列表
|
||||||
|
*
|
||||||
|
* @return 权限列表
|
||||||
|
*/
|
||||||
|
public Set<String> getPermiList()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LoginUser loginUser = getLoginUser();
|
||||||
|
return loginUser.getPermissions();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否包含权限
|
||||||
|
*
|
||||||
|
* @param authorities 权限列表
|
||||||
|
* @param permission 权限字符串
|
||||||
|
* @return 用户是否具备某权限
|
||||||
|
*/
|
||||||
|
public boolean hasPermi(Collection<String> authorities, String permission)
|
||||||
|
{
|
||||||
|
return authorities.stream().filter(StringUtils::hasText)
|
||||||
|
.anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(x, permission));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否包含角色
|
||||||
|
*
|
||||||
|
* @param roles 角色列表
|
||||||
|
* @param role 角色
|
||||||
|
* @return 用户是否具备某角色权限
|
||||||
|
*/
|
||||||
|
public boolean hasRole(Collection<String> roles, String role)
|
||||||
|
{
|
||||||
|
return roles.stream().filter(StringUtils::hasText)
|
||||||
|
.anyMatch(x -> SUPER_ADMIN.contains(x) || PatternMatchUtils.simpleMatch(x, role));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
package com.ruoyi.common.security.auth;
|
||||||
|
|
||||||
|
import com.ruoyi.common.security.annotation.RequiresPermissions;
|
||||||
|
import com.ruoyi.common.security.annotation.RequiresRoles;
|
||||||
|
import com.ruoyi.system.api.model.LoginUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token 权限验证工具类
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public class AuthUtil
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 底层的 AuthLogic 对象
|
||||||
|
*/
|
||||||
|
public static AuthLogic authLogic = new AuthLogic();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话注销
|
||||||
|
*/
|
||||||
|
public static void logout()
|
||||||
|
{
|
||||||
|
authLogic.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话注销,根据指定Token
|
||||||
|
*
|
||||||
|
* @param tokenValue 指定token
|
||||||
|
*/
|
||||||
|
public static void logoutByToken(String token)
|
||||||
|
{
|
||||||
|
authLogic.logoutByToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检验当前会话是否已经登录,如未登录,则抛出异常
|
||||||
|
*/
|
||||||
|
public static void checkLogin()
|
||||||
|
{
|
||||||
|
authLogic.checkLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户信息
|
||||||
|
*/
|
||||||
|
public static LoginUser getLoginUser(String token)
|
||||||
|
{
|
||||||
|
return authLogic.getLoginUser(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证当前用户有效期
|
||||||
|
*/
|
||||||
|
public static void verifyLoginUserExpire(LoginUser loginUser)
|
||||||
|
{
|
||||||
|
authLogic.verifyLoginUserExpire(loginUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前账号是否含有指定角色标识, 返回true或false
|
||||||
|
*
|
||||||
|
* @param role 角色标识
|
||||||
|
* @return 是否含有指定角色标识
|
||||||
|
*/
|
||||||
|
public static boolean hasRole(String role)
|
||||||
|
{
|
||||||
|
return authLogic.hasRole(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
|
||||||
|
*
|
||||||
|
* @param role 角色标识
|
||||||
|
*/
|
||||||
|
public static void checkRole(String role)
|
||||||
|
{
|
||||||
|
authLogic.checkRole(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据注解传入参数鉴权, 如果验证未通过,则抛出异常: NotRoleException
|
||||||
|
*
|
||||||
|
* @param requiresRoles 角色权限注解
|
||||||
|
*/
|
||||||
|
public static void checkRole(RequiresRoles requiresRoles)
|
||||||
|
{
|
||||||
|
authLogic.checkRole(requiresRoles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
|
||||||
|
*
|
||||||
|
* @param roles 角色标识数组
|
||||||
|
*/
|
||||||
|
public static void checkRoleAnd(String... roles)
|
||||||
|
{
|
||||||
|
authLogic.checkRoleAnd(roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
|
||||||
|
*
|
||||||
|
* @param roles 角色标识数组
|
||||||
|
*/
|
||||||
|
public static void checkRoleOr(String... roles)
|
||||||
|
{
|
||||||
|
authLogic.checkRoleOr(roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前账号是否含有指定权限, 返回true或false
|
||||||
|
*
|
||||||
|
* @param permission 权限码
|
||||||
|
* @return 是否含有指定权限
|
||||||
|
*/
|
||||||
|
public static boolean hasPermi(String permission)
|
||||||
|
{
|
||||||
|
return authLogic.hasPermi(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
|
||||||
|
*
|
||||||
|
* @param permission 权限码
|
||||||
|
*/
|
||||||
|
public static void checkPermi(String permission)
|
||||||
|
{
|
||||||
|
authLogic.checkPermi(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据注解传入参数鉴权, 如果验证未通过,则抛出异常: NotPermissionException
|
||||||
|
*
|
||||||
|
* @param requiresPermissions 权限注解
|
||||||
|
*/
|
||||||
|
public static void checkPermi(RequiresPermissions requiresPermissions)
|
||||||
|
{
|
||||||
|
authLogic.checkPermi(requiresPermissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前账号是否含有指定权限 [指定多个,必须全部验证通过]
|
||||||
|
*
|
||||||
|
* @param permissions 权限码数组
|
||||||
|
*/
|
||||||
|
public static void checkPermiAnd(String... permissions)
|
||||||
|
{
|
||||||
|
authLogic.checkPermiAnd(permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
|
||||||
|
*
|
||||||
|
* @param permissions 权限码数组
|
||||||
|
*/
|
||||||
|
public static void checkPermiOr(String... permissions)
|
||||||
|
{
|
||||||
|
authLogic.checkPermiOr(permissions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.ruoyi.common.security.config;
|
||||||
|
|
||||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
import com.ruoyi.common.security.interceptor.HeaderInterceptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拦截器配置
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public class WebMvcConfig implements WebMvcConfigurer
|
||||||
|
{
|
||||||
|
/** 不需要拦截地址 */
|
||||||
|
public static final String[] excludeUrls = { "/login", "/logout", "/refresh" };
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addInterceptors(InterceptorRegistry registry)
|
||||||
|
{
|
||||||
|
registry.addInterceptor(getHeaderInterceptor())
|
||||||
|
.addPathPatterns("/**")
|
||||||
|
.excludePathPatterns(excludeUrls)
|
||||||
|
.order(-10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义请求头拦截器
|
||||||
|
*/
|
||||||
|
public HeaderInterceptor getHeaderInterceptor()
|
||||||
|
{
|
||||||
|
return new HeaderInterceptor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,11 +2,11 @@ package com.ruoyi.common.security.feign;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import com.ruoyi.common.core.utils.ip.IpUtils;
|
|
||||||
import org.springframework.stereotype.Component;
|
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.ServletUtils;
|
||||||
import com.ruoyi.common.core.utils.StringUtils;
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
|
import com.ruoyi.common.core.utils.ip.IpUtils;
|
||||||
import feign.RequestInterceptor;
|
import feign.RequestInterceptor;
|
||||||
import feign.RequestTemplate;
|
import feign.RequestTemplate;
|
||||||
|
|
||||||
|
|
@ -26,20 +26,20 @@ public class FeignRequestInterceptor implements RequestInterceptor
|
||||||
{
|
{
|
||||||
Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
|
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))
|
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))
|
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))
|
if (StringUtils.isNotEmpty(authentication))
|
||||||
{
|
{
|
||||||
requestTemplate.header(CacheConstants.AUTHORIZATION_HEADER, authentication);
|
requestTemplate.header(SecurityConstants.AUTHORIZATION_HEADER, authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 配置客户端IP
|
// 配置客户端IP
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,19 @@
|
||||||
package com.ruoyi.common.security.handler;
|
package com.ruoyi.common.security.handler;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.validation.BindException;
|
import org.springframework.validation.BindException;
|
||||||
|
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
import com.ruoyi.common.core.exception.BaseException;
|
import com.ruoyi.common.core.constant.HttpStatus;
|
||||||
import com.ruoyi.common.core.exception.CustomException;
|
|
||||||
import com.ruoyi.common.core.exception.DemoModeException;
|
import com.ruoyi.common.core.exception.DemoModeException;
|
||||||
import com.ruoyi.common.core.exception.PreAuthorizeException;
|
import com.ruoyi.common.core.exception.InnerAuthException;
|
||||||
|
import com.ruoyi.common.core.exception.ServiceException;
|
||||||
|
import com.ruoyi.common.core.exception.auth.NotPermissionException;
|
||||||
|
import com.ruoyi.common.core.exception.auth.NotRoleException;
|
||||||
import com.ruoyi.common.core.utils.StringUtils;
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
import com.ruoyi.common.core.web.domain.AjaxResult;
|
import com.ruoyi.common.core.web.domain.AjaxResult;
|
||||||
|
|
||||||
|
|
@ -24,31 +28,69 @@ public class GlobalExceptionHandler
|
||||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础异常
|
* 权限码异常
|
||||||
*/
|
*/
|
||||||
@ExceptionHandler(BaseException.class)
|
@ExceptionHandler(NotPermissionException.class)
|
||||||
public AjaxResult baseException(BaseException e)
|
public AjaxResult handleNotPermissionException(NotPermissionException e, HttpServletRequest request)
|
||||||
{
|
{
|
||||||
return AjaxResult.error(e.getDefaultMessage());
|
String requestURI = request.getRequestURI();
|
||||||
|
log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage());
|
||||||
|
return AjaxResult.error(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色权限异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(NotRoleException.class)
|
||||||
|
public AjaxResult handleNotRoleException(NotRoleException e, HttpServletRequest request)
|
||||||
|
{
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage());
|
||||||
|
return AjaxResult.error(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求方式不支持
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||||
|
public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
|
||||||
|
HttpServletRequest request)
|
||||||
|
{
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
|
||||||
|
return AjaxResult.error(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 业务异常
|
* 业务异常
|
||||||
*/
|
*/
|
||||||
@ExceptionHandler(CustomException.class)
|
@ExceptionHandler(ServiceException.class)
|
||||||
public AjaxResult businessException(CustomException e)
|
public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request)
|
||||||
{
|
|
||||||
if (StringUtils.isNull(e.getCode()))
|
|
||||||
{
|
|
||||||
return AjaxResult.error(e.getMessage());
|
|
||||||
}
|
|
||||||
return AjaxResult.error(e.getCode(), e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(Exception.class)
|
|
||||||
public AjaxResult handleException(Exception e)
|
|
||||||
{
|
{
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
|
Integer code = e.getCode();
|
||||||
|
return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拦截未知的运行时异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(RuntimeException.class)
|
||||||
|
public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request)
|
||||||
|
{
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
log.error("请求地址'{}',发生未知异常.", requestURI, e);
|
||||||
|
return AjaxResult.error(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public AjaxResult handleException(Exception e, HttpServletRequest request)
|
||||||
|
{
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
log.error("请求地址'{}',发生系统异常.", requestURI, e);
|
||||||
return AjaxResult.error(e.getMessage());
|
return AjaxResult.error(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +98,7 @@ public class GlobalExceptionHandler
|
||||||
* 自定义验证异常
|
* 自定义验证异常
|
||||||
*/
|
*/
|
||||||
@ExceptionHandler(BindException.class)
|
@ExceptionHandler(BindException.class)
|
||||||
public AjaxResult validatedBindException(BindException e)
|
public AjaxResult handleBindException(BindException e)
|
||||||
{
|
{
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
String message = e.getAllErrors().get(0).getDefaultMessage();
|
String message = e.getAllErrors().get(0).getDefaultMessage();
|
||||||
|
|
@ -67,7 +109,7 @@ public class GlobalExceptionHandler
|
||||||
* 自定义验证异常
|
* 自定义验证异常
|
||||||
*/
|
*/
|
||||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
public Object validExceptionHandler(MethodArgumentNotValidException e)
|
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
|
||||||
{
|
{
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
String message = e.getBindingResult().getFieldError().getDefaultMessage();
|
String message = e.getBindingResult().getFieldError().getDefaultMessage();
|
||||||
|
|
@ -75,19 +117,19 @@ public class GlobalExceptionHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 权限异常
|
* 内部认证异常
|
||||||
*/
|
*/
|
||||||
@ExceptionHandler(PreAuthorizeException.class)
|
@ExceptionHandler(InnerAuthException.class)
|
||||||
public AjaxResult preAuthorizeException(PreAuthorizeException e)
|
public AjaxResult handleInnerAuthException(InnerAuthException e)
|
||||||
{
|
{
|
||||||
return AjaxResult.error("没有权限,请联系管理员授权");
|
return AjaxResult.error(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 演示模式异常
|
* 演示模式异常
|
||||||
*/
|
*/
|
||||||
@ExceptionHandler(DemoModeException.class)
|
@ExceptionHandler(DemoModeException.class)
|
||||||
public AjaxResult demoModeException(DemoModeException e)
|
public AjaxResult handleDemoModeException(DemoModeException e)
|
||||||
{
|
{
|
||||||
return AjaxResult.error("演示模式,不允许操作");
|
return AjaxResult.error("演示模式,不允许操作");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.ruoyi.common.security.interceptor;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.web.method.HandlerMethod;
|
||||||
|
import org.springframework.web.servlet.AsyncHandlerInterceptor;
|
||||||
|
import com.ruoyi.common.core.constant.SecurityConstants;
|
||||||
|
import com.ruoyi.common.core.context.SecurityContextHolder;
|
||||||
|
import com.ruoyi.common.core.utils.ServletUtils;
|
||||||
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
|
import com.ruoyi.common.security.auth.AuthUtil;
|
||||||
|
import com.ruoyi.common.security.utils.SecurityUtils;
|
||||||
|
import com.ruoyi.system.api.model.LoginUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义请求头拦截器,将Header数据封装到线程变量中方便获取
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
public class HeaderInterceptor implements AsyncHandlerInterceptor
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
|
||||||
|
{
|
||||||
|
if (!(handler instanceof HandlerMethod))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SecurityContextHolder.setUserId(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USER_ID));
|
||||||
|
SecurityContextHolder.setUserName(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USERNAME));
|
||||||
|
SecurityContextHolder.setUserKey(ServletUtils.getHeader(request, SecurityConstants.USER_KEY));
|
||||||
|
|
||||||
|
String token = SecurityUtils.getToken();
|
||||||
|
if (StringUtils.isNotEmpty(token))
|
||||||
|
{
|
||||||
|
LoginUser loginUser = AuthUtil.getLoginUser(token);
|
||||||
|
if (StringUtils.isNotNull(loginUser))
|
||||||
|
{
|
||||||
|
AuthUtil.verifyLoginUserExpire(loginUser);
|
||||||
|
SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
SecurityContextHolder.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,13 +7,14 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import com.ruoyi.common.core.constant.CacheConstants;
|
import com.ruoyi.common.core.constant.CacheConstants;
|
||||||
import com.ruoyi.common.core.constant.Constants;
|
import com.ruoyi.common.core.constant.SecurityConstants;
|
||||||
import com.ruoyi.common.core.utils.IdUtils;
|
import com.ruoyi.common.core.utils.IdUtils;
|
||||||
import com.ruoyi.common.core.utils.SecurityUtils;
|
import com.ruoyi.common.core.utils.JwtUtils;
|
||||||
import com.ruoyi.common.core.utils.ServletUtils;
|
import com.ruoyi.common.core.utils.ServletUtils;
|
||||||
import com.ruoyi.common.core.utils.StringUtils;
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
import com.ruoyi.common.core.utils.ip.IpUtils;
|
import com.ruoyi.common.core.utils.ip.IpUtils;
|
||||||
import com.ruoyi.common.redis.service.RedisService;
|
import com.ruoyi.common.redis.service.RedisService;
|
||||||
|
import com.ruoyi.common.security.utils.SecurityUtils;
|
||||||
import com.ruoyi.system.api.model.LoginUser;
|
import com.ruoyi.system.api.model.LoginUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -27,31 +28,41 @@ public class TokenService
|
||||||
@Autowired
|
@Autowired
|
||||||
private RedisService redisService;
|
private RedisService redisService;
|
||||||
|
|
||||||
private final static long EXPIRE_TIME = Constants.TOKEN_EXPIRE * 60;
|
protected static final long MILLIS_SECOND = 1000;
|
||||||
|
|
||||||
|
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
|
||||||
|
|
||||||
|
private final static long expireTime = CacheConstants.EXPIRATION;
|
||||||
|
|
||||||
private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY;
|
private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY;
|
||||||
|
|
||||||
protected static final long MILLIS_SECOND = 1000;
|
private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建令牌
|
* 创建令牌
|
||||||
*/
|
*/
|
||||||
public Map<String, Object> createToken(LoginUser loginUser)
|
public Map<String, Object> createToken(LoginUser loginUser)
|
||||||
{
|
{
|
||||||
// 生成token
|
|
||||||
String token = IdUtils.fastUUID();
|
String token = IdUtils.fastUUID();
|
||||||
|
Long userId = loginUser.getSysUser().getUserId();
|
||||||
|
String userName = loginUser.getSysUser().getUserName();
|
||||||
loginUser.setToken(token);
|
loginUser.setToken(token);
|
||||||
loginUser.setUserid(loginUser.getSysUser().getUserId());
|
loginUser.setUserid(userId);
|
||||||
loginUser.setUsername(loginUser.getSysUser().getUserName());
|
loginUser.setUsername(userName);
|
||||||
loginUser.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
|
loginUser.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
|
||||||
refreshToken(loginUser);
|
refreshToken(loginUser);
|
||||||
|
|
||||||
// 保存或更新用户token
|
// Jwt存储信息
|
||||||
Map<String, Object> map = new HashMap<String, Object>();
|
Map<String, Object> claimsMap = new HashMap<String, Object>();
|
||||||
map.put("access_token", token);
|
claimsMap.put(SecurityConstants.USER_KEY, token);
|
||||||
map.put("expires_in", EXPIRE_TIME);
|
claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId);
|
||||||
redisService.setCacheObject(ACCESS_TOKEN + token, loginUser, EXPIRE_TIME, TimeUnit.SECONDS);
|
claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName);
|
||||||
return map;
|
|
||||||
|
// 接口返回信息
|
||||||
|
Map<String, Object> rspMap = new HashMap<String, Object>();
|
||||||
|
rspMap.put("access_token", JwtUtils.createToken(claimsMap));
|
||||||
|
rspMap.put("expires_in", expireTime);
|
||||||
|
return rspMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -73,13 +84,30 @@ public class TokenService
|
||||||
{
|
{
|
||||||
// 获取请求携带的令牌
|
// 获取请求携带的令牌
|
||||||
String token = SecurityUtils.getToken(request);
|
String token = SecurityUtils.getToken(request);
|
||||||
|
return getLoginUser(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户身份信息
|
||||||
|
*
|
||||||
|
* @return 用户信息
|
||||||
|
*/
|
||||||
|
public LoginUser getLoginUser(String token)
|
||||||
|
{
|
||||||
|
LoginUser user = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
if (StringUtils.isNotEmpty(token))
|
if (StringUtils.isNotEmpty(token))
|
||||||
{
|
{
|
||||||
String userKey = getTokenKey(token);
|
String userkey = JwtUtils.getUserKey(token);
|
||||||
LoginUser user = redisService.getCacheObject(userKey);
|
user = redisService.getCacheObject(getTokenKey(userkey));
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
return null;
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -93,12 +121,30 @@ public class TokenService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除用户缓存信息
|
||||||
|
*/
|
||||||
public void delLoginUser(String token)
|
public void delLoginUser(String token)
|
||||||
{
|
{
|
||||||
if (StringUtils.isNotEmpty(token))
|
if (StringUtils.isNotEmpty(token))
|
||||||
{
|
{
|
||||||
String userKey = getTokenKey(token);
|
String userkey = JwtUtils.getUserKey(token);
|
||||||
redisService.deleteObject(userKey);
|
redisService.deleteObject(getTokenKey(userkey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证令牌有效期,相差不足120分钟,自动刷新缓存
|
||||||
|
*
|
||||||
|
* @param loginUser
|
||||||
|
*/
|
||||||
|
public void verifyToken(LoginUser loginUser)
|
||||||
|
{
|
||||||
|
long expireTime = loginUser.getExpireTime();
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
|
||||||
|
{
|
||||||
|
refreshToken(loginUser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,10 +156,10 @@ public class TokenService
|
||||||
public void refreshToken(LoginUser loginUser)
|
public void refreshToken(LoginUser loginUser)
|
||||||
{
|
{
|
||||||
loginUser.setLoginTime(System.currentTimeMillis());
|
loginUser.setLoginTime(System.currentTimeMillis());
|
||||||
loginUser.setExpireTime(loginUser.getLoginTime() + EXPIRE_TIME * MILLIS_SECOND);
|
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
|
||||||
// 根据uuid将loginUser缓存
|
// 根据uuid将loginUser缓存
|
||||||
String userKey = getTokenKey(loginUser.getToken());
|
String userKey = getTokenKey(loginUser.getToken());
|
||||||
redisService.setCacheObject(userKey, loginUser, EXPIRE_TIME, TimeUnit.SECONDS);
|
redisService.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getTokenKey(String token)
|
private String getTokenKey(String token)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.ruoyi.system.utils;
|
package com.ruoyi.common.security.utils;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -6,7 +6,7 @@ import com.ruoyi.common.core.constant.Constants;
|
||||||
import com.ruoyi.common.core.utils.SpringUtils;
|
import com.ruoyi.common.core.utils.SpringUtils;
|
||||||
import com.ruoyi.common.core.utils.StringUtils;
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
import com.ruoyi.common.redis.service.RedisService;
|
import com.ruoyi.common.redis.service.RedisService;
|
||||||
import com.ruoyi.system.domain.SysDictData;
|
import com.ruoyi.system.api.domain.SysDictData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字典工具类
|
* 字典工具类
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
package com.ruoyi.common.core.utils;
|
package com.ruoyi.common.security.utils;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
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;
|
import com.ruoyi.common.core.constant.TokenConstants;
|
||||||
|
import com.ruoyi.common.core.context.SecurityContextHolder;
|
||||||
|
import com.ruoyi.common.core.utils.ServletUtils;
|
||||||
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
|
import com.ruoyi.system.api.model.LoginUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 权限获取工具类
|
* 权限获取工具类
|
||||||
|
|
@ -12,21 +16,36 @@ import com.ruoyi.common.core.text.Convert;
|
||||||
*/
|
*/
|
||||||
public class SecurityUtils
|
public class SecurityUtils
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* 获取用户
|
|
||||||
*/
|
|
||||||
public static String getUsername()
|
|
||||||
{
|
|
||||||
String username = ServletUtils.getRequest().getHeader(CacheConstants.DETAILS_USERNAME);
|
|
||||||
return ServletUtils.urlDecode(username);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户ID
|
* 获取用户ID
|
||||||
*/
|
*/
|
||||||
public static Long getUserId()
|
public static Long getUserId()
|
||||||
{
|
{
|
||||||
return Convert.toLong(ServletUtils.getRequest().getHeader(CacheConstants.DETAILS_USER_ID));
|
return SecurityContextHolder.getUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户名称
|
||||||
|
*/
|
||||||
|
public static String getUsername()
|
||||||
|
{
|
||||||
|
return SecurityContextHolder.getUserName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户key
|
||||||
|
*/
|
||||||
|
public static String getUserKey()
|
||||||
|
{
|
||||||
|
return SecurityContextHolder.getUserKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取登录用户信息
|
||||||
|
*/
|
||||||
|
public static LoginUser getLoginUser()
|
||||||
|
{
|
||||||
|
return SecurityContextHolder.get(SecurityConstants.LOGIN_USER, LoginUser.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -42,10 +61,20 @@ public class SecurityUtils
|
||||||
*/
|
*/
|
||||||
public static String getToken(HttpServletRequest request)
|
public static String getToken(HttpServletRequest request)
|
||||||
{
|
{
|
||||||
String token = ServletUtils.getRequest().getHeader(CacheConstants.HEADER);
|
// 从header获取token标识
|
||||||
if (StringUtils.isNotEmpty(token) && token.startsWith(CacheConstants.TOKEN_PREFIX))
|
String token = request.getHeader(TokenConstants.AUTHENTICATION);
|
||||||
|
return replaceTokenPrefix(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 裁剪token前缀
|
||||||
|
*/
|
||||||
|
public static String replaceTokenPrefix(String token)
|
||||||
{
|
{
|
||||||
token = token.replace(CacheConstants.TOKEN_PREFIX, "");
|
// 如果前端设置了令牌前缀,则裁剪掉前缀
|
||||||
|
if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX))
|
||||||
|
{
|
||||||
|
token = token.replaceFirst(TokenConstants.PREFIX, "");
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||||
|
com.ruoyi.common.security.config.WebMvcConfig,\
|
||||||
com.ruoyi.common.security.service.TokenService,\
|
com.ruoyi.common.security.service.TokenService,\
|
||||||
com.ruoyi.common.security.aspect.PreAuthorizeAspect,\
|
com.ruoyi.common.security.aspect.PreAuthorizeAspect,\
|
||||||
|
com.ruoyi.common.security.aspect.InnerAuthAspect,\
|
||||||
com.ruoyi.common.security.handler.GlobalExceptionHandler
|
com.ruoyi.common.security.handler.GlobalExceptionHandler
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-common</artifactId>
|
<artifactId>ruoyi-common</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi</artifactId>
|
<artifactId>ruoyi</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ import org.springframework.context.annotation.Configuration;
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@RefreshScope
|
@RefreshScope
|
||||||
@ConfigurationProperties(prefix = "ignore")
|
@ConfigurationProperties(prefix = "security.ignore")
|
||||||
public class IgnoreWhiteProperties
|
public class IgnoreWhiteProperties
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,29 +1,24 @@
|
||||||
package com.ruoyi.gateway.filter;
|
package com.ruoyi.gateway.filter;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||||
import org.springframework.core.Ordered;
|
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.ServerHttpRequest;
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
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.CacheConstants;
|
||||||
import com.ruoyi.common.core.constant.Constants;
|
import com.ruoyi.common.core.constant.HttpStatus;
|
||||||
import com.ruoyi.common.core.domain.R;
|
import com.ruoyi.common.core.constant.SecurityConstants;
|
||||||
|
import com.ruoyi.common.core.constant.TokenConstants;
|
||||||
|
import com.ruoyi.common.core.utils.JwtUtils;
|
||||||
import com.ruoyi.common.core.utils.ServletUtils;
|
import com.ruoyi.common.core.utils.ServletUtils;
|
||||||
import com.ruoyi.common.core.utils.StringUtils;
|
import com.ruoyi.common.core.utils.StringUtils;
|
||||||
import com.ruoyi.common.redis.service.RedisService;
|
import com.ruoyi.common.redis.service.RedisService;
|
||||||
import com.ruoyi.gateway.config.properties.IgnoreWhiteProperties;
|
import com.ruoyi.gateway.config.properties.IgnoreWhiteProperties;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -36,69 +31,83 @@ public class AuthFilter implements GlobalFilter, Ordered
|
||||||
{
|
{
|
||||||
private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);
|
private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);
|
||||||
|
|
||||||
private final static long EXPIRE_TIME = Constants.TOKEN_EXPIRE * 60;
|
|
||||||
|
|
||||||
// 排除过滤的 uri 地址,nacos自行添加
|
// 排除过滤的 uri 地址,nacos自行添加
|
||||||
@Autowired
|
@Autowired
|
||||||
private IgnoreWhiteProperties ignoreWhite;
|
private IgnoreWhiteProperties ignoreWhite;
|
||||||
|
|
||||||
@Resource(name = "stringRedisTemplate")
|
|
||||||
private ValueOperations<String, String> sops;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RedisService redisService;
|
private RedisService redisService;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
|
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()))
|
if (StringUtils.matches(url, ignoreWhite.getWhites()))
|
||||||
{
|
{
|
||||||
return chain.filter(exchange);
|
return chain.filter(exchange);
|
||||||
}
|
}
|
||||||
String token = getToken(exchange.getRequest());
|
String token = getToken(request);
|
||||||
if (StringUtils.isBlank(token))
|
if (StringUtils.isEmpty(token))
|
||||||
{
|
{
|
||||||
return setUnauthorizedResponse(exchange, "令牌不能为空");
|
return unauthorizedResponse(exchange, "令牌不能为空");
|
||||||
}
|
}
|
||||||
String userStr = sops.get(getTokenKey(token));
|
Claims claims = JwtUtils.parseToken(token);
|
||||||
if (StringUtils.isNull(userStr))
|
if (claims == null)
|
||||||
{
|
{
|
||||||
return setUnauthorizedResponse(exchange, "登录状态已过期");
|
return unauthorizedResponse(exchange, "token已过期或验证不正确!");
|
||||||
}
|
}
|
||||||
JSONObject obj = JSONObject.parseObject(userStr);
|
String userkey = JwtUtils.getUserKey(claims);
|
||||||
String userid = obj.getString("userid");
|
boolean islogin = redisService.hasKey(getTokenKey(userkey));
|
||||||
String username = obj.getString("username");
|
if (!islogin)
|
||||||
if (StringUtils.isBlank(userid) || StringUtils.isBlank(username))
|
|
||||||
{
|
{
|
||||||
return setUnauthorizedResponse(exchange, "令牌验证失败");
|
return unauthorizedResponse(exchange, "登录状态已过期");
|
||||||
|
}
|
||||||
|
String userid = JwtUtils.getUserId(claims);
|
||||||
|
String username = JwtUtils.getUserName(claims);
|
||||||
|
if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username))
|
||||||
|
{
|
||||||
|
return unauthorizedResponse(exchange, "令牌验证失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置过期时间
|
|
||||||
redisService.expire(getTokenKey(token), EXPIRE_TIME);
|
|
||||||
// 设置用户信息到请求
|
// 设置用户信息到请求
|
||||||
ServerHttpRequest mutableReq = exchange.getRequest().mutate().header(CacheConstants.DETAILS_USER_ID, userid)
|
addHeader(mutate, SecurityConstants.USER_KEY, userkey);
|
||||||
.header(CacheConstants.DETAILS_USERNAME, ServletUtils.urlEncode(username)).build();
|
addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
|
||||||
ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
|
addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
|
||||||
|
// 内部请求来源参数清除
|
||||||
return chain.filter(mutableExchange);
|
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();
|
if (value == null)
|
||||||
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
{
|
||||||
response.setStatusCode(HttpStatus.OK);
|
return;
|
||||||
|
}
|
||||||
log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
|
String valueStr = value.toString();
|
||||||
|
String valueEncode = ServletUtils.urlEncode(valueStr);
|
||||||
return response.writeWith(Mono.fromSupplier(() -> {
|
mutate.header(name, valueEncode);
|
||||||
DataBufferFactory bufferFactory = response.bufferFactory();
|
|
||||||
return bufferFactory.wrap(JSON.toJSONBytes(R.fail(msg)));
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
private String getTokenKey(String token)
|
||||||
{
|
{
|
||||||
return CacheConstants.LOGIN_TOKEN_KEY + token;
|
return CacheConstants.LOGIN_TOKEN_KEY + token;
|
||||||
|
|
@ -109,10 +118,11 @@ public class AuthFilter implements GlobalFilter, Ordered
|
||||||
*/
|
*/
|
||||||
private String getToken(ServerHttpRequest request)
|
private String getToken(ServerHttpRequest request)
|
||||||
{
|
{
|
||||||
String token = request.getHeaders().getFirst(CacheConstants.HEADER);
|
String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION);
|
||||||
if (StringUtils.isNotEmpty(token) && token.startsWith(CacheConstants.TOKEN_PREFIX))
|
// 如果前端设置了令牌前缀,则裁剪掉前缀
|
||||||
|
if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX))
|
||||||
{
|
{
|
||||||
token = token.replace(CacheConstants.TOKEN_PREFIX, "");
|
token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY);
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,8 @@ import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.ruoyi.common.core.utils.ServletUtils;
|
||||||
import com.ruoyi.common.core.web.domain.AjaxResult;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 黑名单过滤器
|
* 黑名单过滤器
|
||||||
|
|
@ -27,10 +24,7 @@ public class BlackListUrlFilter extends AbstractGatewayFilterFactory<BlackListUr
|
||||||
String url = exchange.getRequest().getURI().getPath();
|
String url = exchange.getRequest().getURI().getPath();
|
||||||
if (config.matchBlacklist(url))
|
if (config.matchBlacklist(url))
|
||||||
{
|
{
|
||||||
ServerHttpResponse response = exchange.getResponse();
|
return ServletUtils.webFluxResponseWriter(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 chain.filter(exchange);
|
return chain.filter(exchange);
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,11 @@ import org.springframework.web.server.ServerWebExchange;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取body请求数据(解决流不能重复读取问题)
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class CacheRequestFilter extends AbstractGatewayFilterFactory<CacheRequestFilter.Config>
|
public class CacheRequestFilter extends AbstractGatewayFilterFactory<CacheRequestFilter.Config>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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.DataBuffer;
|
||||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.ruoyi.common.core.utils.ServletUtils;
|
||||||
import com.ruoyi.common.core.utils.StringUtils;
|
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 com.ruoyi.gateway.service.ValidateCodeService;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证码过滤器
|
* 验证码过滤器
|
||||||
|
|
@ -27,11 +25,14 @@ import reactor.core.publisher.Mono;
|
||||||
@Component
|
@Component
|
||||||
public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
|
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
|
@Autowired
|
||||||
private ValidateCodeService validateCodeService;
|
private ValidateCodeService validateCodeService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CaptchaProperties captchaProperties;
|
||||||
|
|
||||||
private static final String CODE = "code";
|
private static final String CODE = "code";
|
||||||
|
|
||||||
private static final String UUID = "uuid";
|
private static final String UUID = "uuid";
|
||||||
|
|
@ -42,8 +43,8 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
|
||||||
return (exchange, chain) -> {
|
return (exchange, chain) -> {
|
||||||
ServerHttpRequest request = exchange.getRequest();
|
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);
|
return chain.filter(exchange);
|
||||||
}
|
}
|
||||||
|
|
@ -56,10 +57,7 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
ServerHttpResponse response = exchange.getResponse();
|
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
|
||||||
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 chain.filter(exchange);
|
return chain.filter(exchange);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,14 +6,10 @@ import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.annotation.Order;
|
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.http.server.reactive.ServerHttpResponse;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.ruoyi.common.core.utils.ServletUtils;
|
||||||
import com.ruoyi.common.core.domain.R;
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -55,12 +51,6 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler
|
||||||
|
|
||||||
log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage());
|
log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage());
|
||||||
|
|
||||||
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
return ServletUtils.webFluxResponseWriter(response, msg);
|
||||||
response.setStatusCode(HttpStatus.OK);
|
|
||||||
|
|
||||||
return response.writeWith(Mono.fromSupplier(() -> {
|
|
||||||
DataBufferFactory bufferFactory = response.bufferFactory();
|
|
||||||
return bufferFactory.wrap(JSON.toJSONBytes(R.fail(msg)));
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
package com.ruoyi.gateway.handler;
|
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.adapter.gateway.sc.callback.GatewayCallbackManager;
|
||||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
import com.ruoyi.common.core.utils.ServletUtils;
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.WebExceptionHandler;
|
import org.springframework.web.server.WebExceptionHandler;
|
||||||
|
|
@ -19,11 +17,7 @@ public class SentinelFallbackHandler implements WebExceptionHandler
|
||||||
{
|
{
|
||||||
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange)
|
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange)
|
||||||
{
|
{
|
||||||
ServerHttpResponse serverHttpResponse = exchange.getResponse();
|
return ServletUtils.webFluxResponseWriter(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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import com.ruoyi.common.core.utils.StringUtils;
|
||||||
import com.ruoyi.common.core.utils.sign.Base64;
|
import com.ruoyi.common.core.utils.sign.Base64;
|
||||||
import com.ruoyi.common.core.web.domain.AjaxResult;
|
import com.ruoyi.common.core.web.domain.AjaxResult;
|
||||||
import com.ruoyi.common.redis.service.RedisService;
|
import com.ruoyi.common.redis.service.RedisService;
|
||||||
|
import com.ruoyi.gateway.config.properties.CaptchaProperties;
|
||||||
import com.ruoyi.gateway.service.ValidateCodeService;
|
import com.ruoyi.gateway.service.ValidateCodeService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -35,8 +36,8 @@ public class ValidateCodeServiceImpl implements ValidateCodeService
|
||||||
@Autowired
|
@Autowired
|
||||||
private RedisService redisService;
|
private RedisService redisService;
|
||||||
|
|
||||||
// 验证码类型
|
@Autowired
|
||||||
private String captchaType = "math";
|
private CaptchaProperties captchaProperties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成验证码
|
* 生成验证码
|
||||||
|
|
@ -44,6 +45,14 @@ public class ValidateCodeServiceImpl implements ValidateCodeService
|
||||||
@Override
|
@Override
|
||||||
public AjaxResult createCapcha() throws IOException, CaptchaException
|
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 uuid = IdUtils.simpleUUID();
|
||||||
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
|
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
|
||||||
|
|
@ -51,6 +60,7 @@ public class ValidateCodeServiceImpl implements ValidateCodeService
|
||||||
String capStr = null, code = null;
|
String capStr = null, code = null;
|
||||||
BufferedImage image = null;
|
BufferedImage image = null;
|
||||||
|
|
||||||
|
String captchaType = captchaProperties.getType();
|
||||||
// 生成验证码
|
// 生成验证码
|
||||||
if ("math".equals(captchaType))
|
if ("math".equals(captchaType))
|
||||||
{
|
{
|
||||||
|
|
@ -77,7 +87,6 @@ public class ValidateCodeServiceImpl implements ValidateCodeService
|
||||||
return AjaxResult.error(e.getMessage());
|
return AjaxResult.error(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
AjaxResult ajax = AjaxResult.success();
|
|
||||||
ajax.put("uuid", uuid);
|
ajax.put("uuid", uuid);
|
||||||
ajax.put("img", Base64.encode(os.toByteArray()));
|
ajax.put("img", Base64.encode(os.toByteArray()));
|
||||||
return ajax;
|
return ajax;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi</artifactId>
|
<artifactId>ruoyi</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-modules</artifactId>
|
<artifactId>ruoyi-modules</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@ import com.ruoyi.common.swagger.annotation.EnableCustomSwagger2;
|
||||||
*/
|
*/
|
||||||
@EnableCustomSwagger2
|
@EnableCustomSwagger2
|
||||||
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
|
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
|
||||||
public class RuoYFileApplication
|
public class RuoYiFileApplication
|
||||||
{
|
{
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
SpringApplication.run(RuoYFileApplication.class, args);
|
SpringApplication.run(RuoYiFileApplication.class, args);
|
||||||
System.out.println("(♥◠‿◠)ノ゙ 文件服务模块启动成功 ლ(´ڡ`ლ)゙ \n" +
|
System.out.println("(♥◠‿◠)ノ゙ 文件服务模块启动成功 ლ(´ڡ`ლ)゙ \n" +
|
||||||
" .-------. ____ __ \n" +
|
" .-------. ____ __ \n" +
|
||||||
" | _ _ \\ \\ \\ / / \n" +
|
" | _ _ \\ \\ \\ / / \n" +
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue