diff --git a/pom.xml b/pom.xml index 743bbb0c..841fcc3c 100644 --- a/pom.xml +++ b/pom.xml @@ -6,37 +6,37 @@ com.ruoyi ruoyi - 3.0.0 + 3.1.0 ruoyi http://www.ruoyi.vip 若依微服务系统 - 3.0.0 + 3.1.0 UTF-8 UTF-8 1.8 - 2.5.1 + 2.5.3 2020.0.3 2021.1 - 2.0.2 - 2.4.1 - 2.1.4 + 2.0.3 + 2.4.3 + 2.2.0 3.0.0 1.6.2 - 1.26.5 + 1.27.2 2.3.2 1.3.1 1.2.6 - 3.4.0 - 2.10.0 + 3.4.1 + 2.11.0 1.4 1.7 1.2.76 - 8.2.1 - 4.1.2 - 2.6.2 + 8.2.2 + 4.1.2 + 2.10.0 3.2.2 5.7.1 @@ -54,7 +54,7 @@ import - + com.alibaba.cloud spring-cloud-alibaba-dependencies @@ -78,28 +78,28 @@ pom import - + de.codecentric spring-boot-admin-starter-client ${spring-boot-admin.version} - + com.github.tobato fastdfs-client ${tobato.version} - + org.mybatis.spring.boot mybatis-spring-boot-starter ${spring-boot.mybatis} - + io.swagger @@ -173,7 +173,7 @@ fastjson ${fastjson.version} - + org.apache.commons @@ -188,71 +188,71 @@ - + com.ruoyi ruoyi-common-core ${ruoyi.version} - + com.ruoyi ruoyi-common-swagger ${ruoyi.version} - + com.ruoyi ruoyi-common-security ${ruoyi.version} - + com.ruoyi ruoyi-common-datascope ${ruoyi.version} - + com.ruoyi ruoyi-common-datasource ${ruoyi.version} - + com.ruoyi ruoyi-common-log ${ruoyi.version} - + com.ruoyi ruoyi-common-redis ${ruoyi.version} - + com.ruoyi ruoyi-api-system ${ruoyi.version} - + - ruoyi-auth - ruoyi-gateway - ruoyi-visual - ruoyi-modules - ruoyi-api - ruoyi-common + ruoyi-auth + ruoyi-gateway + ruoyi-visual + ruoyi-modules + ruoyi-api + ruoyi-common pom diff --git a/ruoyi-api/pom.xml b/ruoyi-api/pom.xml index 6ab71617..552d66a4 100644 --- a/ruoyi-api/pom.xml +++ b/ruoyi-api/pom.xml @@ -4,7 +4,7 @@ com.ruoyi ruoyi - 3.0.0 + 3.1.0 4.0.0 diff --git a/ruoyi-api/ruoyi-api-system/pom.xml b/ruoyi-api/ruoyi-api-system/pom.xml index 19afb305..7cf8df02 100644 --- a/ruoyi-api/ruoyi-api-system/pom.xml +++ b/ruoyi-api/ruoyi-api-system/pom.xml @@ -5,7 +5,7 @@ com.ruoyi ruoyi-api - 3.0.0 + 3.1.0 4.0.0 diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteLogService.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteLogService.java index 8a1f590f..b8df7954 100644 --- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteLogService.java +++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteLogService.java @@ -3,9 +3,11 @@ package com.ruoyi.system.api; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestHeader; +import com.ruoyi.common.core.constant.SecurityConstants; import com.ruoyi.common.core.constant.ServiceNameConstants; import com.ruoyi.common.core.domain.R; +import com.ruoyi.system.api.domain.SysLogininfor; import com.ruoyi.system.api.domain.SysOperLog; import com.ruoyi.system.api.factory.RemoteLogFallbackFactory; @@ -21,20 +23,19 @@ public interface RemoteLogService * 保存系统日志 * * @param sysOperLog 日志实体 + * @param source 请求来源 * @return 结果 */ @PostMapping("/operlog") - R saveLog(@RequestBody SysOperLog sysOperLog); + public R saveLog(@RequestBody SysOperLog sysOperLog, @RequestHeader(SecurityConstants.FROM_SOURCE) String source); /** * 保存访问记录 * - * @param username 用户名称 - * @param status 状态 - * @param message 消息 + * @param sysLogininfor 访问实体 + * @param source 请求来源 * @return 结果 */ @PostMapping("/logininfor") - R saveLogininfor(@RequestParam("username") String username, @RequestParam("status") String status, - @RequestParam("message") String message); + public R saveLogininfor(@RequestBody SysLogininfor sysLogininfor, @RequestHeader(SecurityConstants.FROM_SOURCE) String source); } diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteUserService.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteUserService.java index 5140d416..e7fe34c5 100644 --- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteUserService.java +++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/RemoteUserService.java @@ -3,8 +3,13 @@ package com.ruoyi.system.api; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import com.ruoyi.common.core.constant.SecurityConstants; import com.ruoyi.common.core.constant.ServiceNameConstants; import com.ruoyi.common.core.domain.R; +import com.ruoyi.system.api.domain.SysUser; import com.ruoyi.system.api.factory.RemoteUserFallbackFactory; import com.ruoyi.system.api.model.LoginUser; @@ -20,8 +25,19 @@ public interface RemoteUserService * 通过用户名查询用户信息 * * @param username 用户名 + * @param source 请求来源 * @return 结果 */ - @GetMapping(value = "/user/info/{username}") - public R getUserInfo(@PathVariable("username") String username); + @GetMapping("/user/info/{username}") + public R getUserInfo(@PathVariable("username") String username, @RequestHeader(SecurityConstants.FROM_SOURCE) String source); + + /** + * 注册用户信息 + * + * @param sysUser 用户信息 + * @param source 请求来源 + * @return 结果 + */ + @PostMapping("/user/register") + public R registerUserInfo(@RequestBody SysUser sysUser, @RequestHeader(SecurityConstants.FROM_SOURCE) String source); } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysLogininfor.java similarity index 92% rename from ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java rename to ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysLogininfor.java index a2713857..b97147a6 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java +++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysLogininfor.java @@ -1,4 +1,4 @@ -package com.ruoyi.system.domain; +package com.ruoyi.system.api.domain; import java.util.Date; diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java index 3e169e35..818303a5 100644 --- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java +++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java @@ -87,14 +87,7 @@ public class SysUser extends BaseEntity { @JsonProperty private String password; - /** - * 盐加密 - */ - private String salt; - - /** - * 帐号状态(0正常 1停用) - */ + /** 帐号状态(0正常 1停用) */ @Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用") private String status; diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteLogFallbackFactory.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteLogFallbackFactory.java index 1153c659..3022d1c0 100644 --- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteLogFallbackFactory.java +++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteLogFallbackFactory.java @@ -7,6 +7,7 @@ import org.springframework.cloud.openfeign.FallbackFactory; import org.springframework.stereotype.Component; import com.ruoyi.common.core.domain.R; import com.ruoyi.system.api.RemoteLogService; +import com.ruoyi.system.api.domain.SysLogininfor; import com.ruoyi.system.api.domain.SysOperLog; /** @@ -23,12 +24,12 @@ public class RemoteLogFallbackFactory implements FallbackFactory saveLog(SysOperLog sysOperLog) { + public R saveLog(SysOperLog sysOperLog, String source) { return null; } @Override - public R saveLogininfor(String username, String status, String message) { + public R saveLogininfor(SysLogininfor sysLogininfor, String source) { return null; } }; diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteUserFallbackFactory.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteUserFallbackFactory.java index e0620c91..3564ada8 100644 --- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteUserFallbackFactory.java +++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/factory/RemoteUserFallbackFactory.java @@ -7,6 +7,7 @@ import org.springframework.cloud.openfeign.FallbackFactory; import org.springframework.stereotype.Component; import com.ruoyi.common.core.domain.R; import com.ruoyi.system.api.RemoteUserService; +import com.ruoyi.system.api.domain.SysUser; import com.ruoyi.system.api.model.LoginUser; /** @@ -23,9 +24,14 @@ public class RemoteUserFallbackFactory implements FallbackFactory getUserInfo(String username) { + public R getUserInfo(String username, String source) { return R.fail("获取用户失败:" + throwable.getMessage()); } + + @Override + public R registerUserInfo(SysUser sysUser, String source) { + return R.fail("注册用户失败:" + throwable.getMessage()); + } }; } } diff --git a/ruoyi-auth/pom.xml b/ruoyi-auth/pom.xml index 638660e2..cb41993e 100644 --- a/ruoyi-auth/pom.xml +++ b/ruoyi-auth/pom.xml @@ -4,7 +4,7 @@ com.ruoyi ruoyi - 3.0.0 + 3.1.0 4.0.0 diff --git a/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java b/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java index b90c8002..3e2361d7 100644 --- a/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java +++ b/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import com.ruoyi.auth.form.LoginBody; +import com.ruoyi.auth.form.RegisterBody; import com.ruoyi.auth.service.SysLoginService; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.utils.StringUtils; @@ -63,4 +64,12 @@ public class TokenController } return R.ok(); } + + @PostMapping("register") + public R register(@RequestBody RegisterBody registerBody) + { + // 用户注册 + sysLoginService.register(registerBody.getUsername(), registerBody.getPassword()); + return R.ok(); + } } diff --git a/ruoyi-auth/src/main/java/com/ruoyi/auth/form/RegisterBody.java b/ruoyi-auth/src/main/java/com/ruoyi/auth/form/RegisterBody.java new file mode 100644 index 00000000..a8e16eed --- /dev/null +++ b/ruoyi-auth/src/main/java/com/ruoyi/auth/form/RegisterBody.java @@ -0,0 +1,11 @@ +package com.ruoyi.auth.form; + +/** + * 用户注册对象 + * + * @author ruoyi + */ +public class RegisterBody extends LoginBody +{ + +} diff --git a/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java b/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java index a8662048..a57f4a9d 100644 --- a/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java +++ b/ruoyi-auth/src/main/java/com/ruoyi/auth/service/SysLoginService.java @@ -3,14 +3,18 @@ package com.ruoyi.auth.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.ruoyi.common.core.constant.Constants; +import com.ruoyi.common.core.constant.SecurityConstants; import com.ruoyi.common.core.constant.UserConstants; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.enums.UserStatus; import com.ruoyi.common.core.exception.BaseException; import com.ruoyi.common.core.utils.SecurityUtils; +import com.ruoyi.common.core.utils.ServletUtils; import com.ruoyi.common.core.utils.StringUtils; +import com.ruoyi.common.core.utils.ip.IpUtils; import com.ruoyi.system.api.RemoteLogService; import com.ruoyi.system.api.RemoteUserService; +import com.ruoyi.system.api.domain.SysLogininfor; import com.ruoyi.system.api.domain.SysUser; import com.ruoyi.system.api.model.LoginUser; @@ -36,25 +40,25 @@ public class SysLoginService // 用户名或密码为空 错误 if (StringUtils.isAnyBlank(username, password)) { - remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写"); + recordLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写"); throw new BaseException("用户/密码必须填写"); } // 密码如果不在指定范围内 错误 if (password.length() < UserConstants.PASSWORD_MIN_LENGTH || password.length() > UserConstants.PASSWORD_MAX_LENGTH) { - remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围"); + recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围"); throw new BaseException("用户密码不在指定范围"); } // 用户名不在指定范围内 错误 if (username.length() < UserConstants.USERNAME_MIN_LENGTH || username.length() > UserConstants.USERNAME_MAX_LENGTH) { - remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围"); + recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围"); throw new BaseException("用户名不在指定范围"); } // 查询用户信息 - R userResult = remoteUserService.getUserInfo(username); + R userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER); if (R.FAIL == userResult.getCode()) { @@ -63,33 +67,93 @@ public class SysLoginService if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData())) { - remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在"); + recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在"); throw new BaseException("登录用户:" + username + " 不存在"); } LoginUser userInfo = userResult.getData(); SysUser user = userResult.getData().getSysUser(); if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) { - remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除"); - + recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除"); throw new BaseException("对不起,您的账号:" + username + " 已被删除"); } if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { - remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员"); + recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员"); throw new BaseException("对不起,您的账号:" + username + " 已停用"); } if (!SecurityUtils.matchesPassword(password, user.getPassword())) { - remoteLogService.saveLogininfor(username, Constants.LOGIN_FAIL, "用户密码错误"); + recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码错误"); throw new BaseException("用户不存在/密码错误"); } - remoteLogService.saveLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功"); + recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功"); return userInfo; } public void logout(String loginName) { - remoteLogService.saveLogininfor(loginName, Constants.LOGOUT, "退出成功"); + recordLogininfor(loginName, Constants.LOGOUT, "退出成功"); + } + + /** + * 注册 + */ + public void register(String username, String password) + { + // 用户名或密码为空 错误 + if (StringUtils.isAnyBlank(username, password)) + { + throw new BaseException("用户/密码必须填写"); + } + if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + throw new BaseException("账户长度必须在2到20个字符之间"); + } + if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + throw new BaseException("密码长度必须在5到20个字符之间"); + } + + // 注册用户信息 + SysUser sysUser = new SysUser(); + sysUser.setUserName(username); + sysUser.setNickName(username); + sysUser.setPassword(SecurityUtils.encryptPassword(password)); + R registerResult = remoteUserService.registerUserInfo(sysUser, SecurityConstants.INNER); + + if (R.FAIL == registerResult.getCode()) + { + throw new BaseException(registerResult.getMsg()); + } + recordLogininfor(username, Constants.REGISTER, "注册成功"); + } + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param status 状态 + * @param message 消息内容 + * @return + */ + public void recordLogininfor(String username, String status, String message) + { + SysLogininfor logininfor = new SysLogininfor(); + logininfor.setUserName(username); + logininfor.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest())); + logininfor.setMsg(message); + // 日志状态 + if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) + { + logininfor.setStatus("0"); + } + else if (Constants.LOGIN_FAIL.equals(status)) + { + logininfor.setStatus("1"); + } + remoteLogService.saveLogininfor(logininfor, SecurityConstants.INNER); } } \ No newline at end of file diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index 050f9b52..a91432a6 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -4,7 +4,7 @@ com.ruoyi ruoyi - 3.0.0 + 3.1.0 4.0.0 diff --git a/ruoyi-common/ruoyi-common-core/pom.xml b/ruoyi-common/ruoyi-common-core/pom.xml index 3eba3b9b..b03079c3 100644 --- a/ruoyi-common/ruoyi-common-core/pom.xml +++ b/ruoyi-common/ruoyi-common-core/pom.xml @@ -5,7 +5,7 @@ com.ruoyi ruoyi-common - 3.0.0 + 3.1.0 4.0.0 diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java index 8f666dd1..d0f30c07 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java @@ -7,33 +7,8 @@ package com.ruoyi.common.core.constant; */ public class CacheConstants { - /** - * 令牌自定义标识 - */ - public static final String HEADER = "Authorization"; - - /** - * 令牌前缀 - */ - public static final String TOKEN_PREFIX = "Bearer "; - /** * 权限缓存前缀 */ public final static String LOGIN_TOKEN_KEY = "login_tokens:"; - - /** - * 用户ID字段 - */ - public static final String DETAILS_USER_ID = "user_id"; - - /** - * 用户名字段 - */ - public static final String DETAILS_USERNAME = "username"; - - /** - * 授权信息字段 - */ - public static final String AUTHORIZATION_HEADER = "authorization"; } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/Constants.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/Constants.java index 4225eaa1..4a07f404 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/Constants.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/Constants.java @@ -17,6 +17,11 @@ public class Constants */ public static final String GBK = "GBK"; + /** + * RMI 远程方法调用 + */ + public static final String LOOKUP_RMI = "rmi://"; + /** * http请求 */ diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/SecurityConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/SecurityConstants.java new file mode 100644 index 00000000..fb8ea178 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/SecurityConstants.java @@ -0,0 +1,44 @@ +package com.ruoyi.common.core.constant; + +/** + * 权限相关通用常量 + * + * @author ruoyi + */ +public class SecurityConstants +{ + /** + * 令牌自定义标识 + */ + public static final String TOKEN_AUTHENTICATION = "Authorization"; + + /** + * 令牌前缀 + */ + public static final String TOKEN_PREFIX = "Bearer "; + + /** + * 用户ID字段 + */ + public static final String DETAILS_USER_ID = "user_id"; + + /** + * 用户名字段 + */ + public static final String DETAILS_USERNAME = "username"; + + /** + * 授权信息字段 + */ + public static final String AUTHORIZATION_HEADER = "authorization"; + + /** + * 请求来源 + */ + public static final String FROM_SOURCE = "from-source"; + + /** + * 内部请求 + */ + public static final String INNER = "inner"; +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/UserConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/UserConstants.java index cb01ba81..2d7f1e8a 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/UserConstants.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/UserConstants.java @@ -57,6 +57,9 @@ public class UserConstants /** ParentView组件标识 */ public final static String PARENT_VIEW = "ParentView"; + /** InnerLink组件标识 */ + public final static String INNER_LINK = "InnerLink"; + /** 校验返回结果码 */ public final static String UNIQUE = "0"; diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/InnerAuthException.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/InnerAuthException.java new file mode 100644 index 00000000..8a7abe9f --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/InnerAuthException.java @@ -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); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/SecurityUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/SecurityUtils.java index f2225bea..59995d90 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/SecurityUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/SecurityUtils.java @@ -2,7 +2,7 @@ package com.ruoyi.common.core.utils; import javax.servlet.http.HttpServletRequest; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import com.ruoyi.common.core.constant.CacheConstants; +import com.ruoyi.common.core.constant.SecurityConstants; import com.ruoyi.common.core.text.Convert; /** @@ -17,7 +17,7 @@ public class SecurityUtils */ public static String getUsername() { - String username = ServletUtils.getRequest().getHeader(CacheConstants.DETAILS_USERNAME); + String username = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USERNAME); return ServletUtils.urlDecode(username); } @@ -26,7 +26,7 @@ public class SecurityUtils */ public static Long getUserId() { - return Convert.toLong(ServletUtils.getRequest().getHeader(CacheConstants.DETAILS_USER_ID)); + return Convert.toLong(ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USER_ID)); } /** @@ -42,10 +42,18 @@ public class SecurityUtils */ public static String getToken(HttpServletRequest request) { - String token = ServletUtils.getRequest().getHeader(CacheConstants.HEADER); - if (StringUtils.isNotEmpty(token) && token.startsWith(CacheConstants.TOKEN_PREFIX)) + String token = request.getHeader(SecurityConstants.TOKEN_AUTHENTICATION); + return replaceTokenPrefix(token); + } + + /** + * 替换token前缀 + */ + public static String replaceTokenPrefix(String token) + { + if (StringUtils.isNotEmpty(token) && token.startsWith(SecurityConstants.TOKEN_PREFIX)) { - token = token.replace(CacheConstants.TOKEN_PREFIX, ""); + token = token.replace(SecurityConstants.TOKEN_PREFIX, ""); } return token; } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ServletUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ServletUtils.java index 6809f29f..51a6141e 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ServletUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ServletUtils.java @@ -10,11 +10,19 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import com.alibaba.fastjson.JSONObject; import com.ruoyi.common.core.constant.Constants; +import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.text.Convert; +import reactor.core.publisher.Mono; /** * 客户端工具类 @@ -213,4 +221,62 @@ public class ServletUtils return ""; } } + + /** + * 设置webflux模型响应 + * + * @param response ServerHttpResponse + * @param value 响应内容 + * @return Mono + */ + public static Mono webFluxResponseWriter(ServerHttpResponse response, Object value) + { + return webFluxResponseWriter(response, HttpStatus.OK, value, R.FAIL); + } + + /** + * 设置webflux模型响应 + * + * @param response ServerHttpResponse + * @param code 响应状态码 + * @param value 响应内容 + * @return Mono + */ + public static Mono 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 + */ + public static Mono 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 + */ + public static Mono 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)); + } } diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/StringUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/StringUtils.java index b549d604..bf86804c 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/StringUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/StringUtils.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import org.springframework.util.AntPathMatcher; +import com.ruoyi.common.core.constant.Constants; import com.ruoyi.common.core.text.StrFormatter; /** @@ -282,6 +283,17 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils return StrFormatter.format(template, params); } + /** + * 是否为http(s)://开头 + * + * @param link 链接 + * @return 结果 + */ + public static boolean ishttp(String link) + { + return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS); + } + /** * 驼峰转下划线命名 */ diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/html/EscapeUtil.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/html/EscapeUtil.java new file mode 100644 index 00000000..9ddae356 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/html/EscapeUtil.java @@ -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 = ""; + // String html = "ipt>alert(\"XSS\")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)); + } +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/html/HTMLFilter.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/html/HTMLFilter.java new file mode 100644 index 00000000..32e062e5 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/html/HTMLFilter.java @@ -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 P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>(); + private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>(); + + /** + * set of allowed html elements, along with allowed attributes for each element + **/ + private final Map> vAllowed; + /** + * counts of open tags for each (allowable) html element + **/ + private final Map vTagCounts = new HashMap<>(); + + /** + * html elements which must always be self-closing (e.g. "") + **/ + private final String[] vSelfClosingTags; + /** + * html elements which must always have separate opening and closing tags (e.g. "") + **/ + 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. "" or "") + **/ + 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. "" + * becomes " text "). If set to false, unbalanced angle brackets will be html escaped. + */ + private final boolean alwaysMakeTags; + + /** + * Default constructor. + */ + public HTMLFilter() + { + vAllowed = new HashMap<>(); + + final ArrayList a_atts = new ArrayList<>(); + a_atts.add("href"); + a_atts.add("target"); + vAllowed.put("a", a_atts); + + final ArrayList 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 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 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>) 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("")); + } + 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(""); + } + } + 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[^>]*)?>")); + } + 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 ""; + } + } + } + } + + // 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 paramNames = new ArrayList<>(); + final List 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)); + } +} \ No newline at end of file diff --git a/ruoyi-common/ruoyi-common-datascope/pom.xml b/ruoyi-common/ruoyi-common-datascope/pom.xml index d1ac37cd..e338f727 100644 --- a/ruoyi-common/ruoyi-common-datascope/pom.xml +++ b/ruoyi-common/ruoyi-common-datascope/pom.xml @@ -5,7 +5,7 @@ com.ruoyi ruoyi-common - 3.0.0 + 3.1.0 4.0.0 diff --git a/ruoyi-common/ruoyi-common-datasource/pom.xml b/ruoyi-common/ruoyi-common-datasource/pom.xml index 8fde2051..4efa4a15 100644 --- a/ruoyi-common/ruoyi-common-datasource/pom.xml +++ b/ruoyi-common/ruoyi-common-datasource/pom.xml @@ -5,7 +5,7 @@ com.ruoyi ruoyi-common - 3.0.0 + 3.1.0 4.0.0 diff --git a/ruoyi-common/ruoyi-common-log/pom.xml b/ruoyi-common/ruoyi-common-log/pom.xml index 91c14901..bf6510db 100644 --- a/ruoyi-common/ruoyi-common-log/pom.xml +++ b/ruoyi-common/ruoyi-common-log/pom.xml @@ -5,7 +5,7 @@ com.ruoyi ruoyi-common - 3.0.0 + 3.1.0 4.0.0 diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/service/AsyncLogService.java b/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/service/AsyncLogService.java index c4ea247f..4f5986d1 100644 --- a/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/service/AsyncLogService.java +++ b/ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/service/AsyncLogService.java @@ -3,6 +3,7 @@ package com.ruoyi.common.log.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import com.ruoyi.common.core.constant.SecurityConstants; import com.ruoyi.system.api.RemoteLogService; import com.ruoyi.system.api.domain.SysOperLog; @@ -23,6 +24,6 @@ public class AsyncLogService @Async public void saveSysLog(SysOperLog sysOperLog) { - remoteLogService.saveLog(sysOperLog); + remoteLogService.saveLog(sysOperLog, SecurityConstants.INNER); } } diff --git a/ruoyi-common/ruoyi-common-redis/pom.xml b/ruoyi-common/ruoyi-common-redis/pom.xml index a68652ed..ea0d2975 100644 --- a/ruoyi-common/ruoyi-common-redis/pom.xml +++ b/ruoyi-common/ruoyi-common-redis/pom.xml @@ -5,7 +5,7 @@ com.ruoyi ruoyi-common - 3.0.0 + 3.1.0 4.0.0 diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java b/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java index 4bf7f708..dd762971 100644 --- a/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/com/ruoyi/common/redis/service/RedisService.java @@ -74,6 +74,17 @@ public class RedisService return redisTemplate.expire(key, timeout, unit); } + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) + { + return redisTemplate.hasKey(key); + } + /** * 获得缓存的基本对象。 * diff --git a/ruoyi-common/ruoyi-common-security/pom.xml b/ruoyi-common/ruoyi-common-security/pom.xml index 99b7b8ab..e5d62b22 100644 --- a/ruoyi-common/ruoyi-common-security/pom.xml +++ b/ruoyi-common/ruoyi-common-security/pom.xml @@ -4,7 +4,7 @@ com.ruoyi ruoyi-common - 3.0.0 + 3.1.0 4.0.0 diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/annotation/InnerAuth.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/annotation/InnerAuth.java new file mode 100644 index 00000000..80fa59e7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/annotation/InnerAuth.java @@ -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; +} \ No newline at end of file diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/aspect/InnerAuthAspect.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/aspect/InnerAuthAspect.java new file mode 100644 index 00000000..780f65e9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/aspect/InnerAuthAspect.java @@ -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; + } +} diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/feign/FeignRequestInterceptor.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/feign/FeignRequestInterceptor.java index 045485c8..ccfa07f4 100644 --- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/feign/FeignRequestInterceptor.java +++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/feign/FeignRequestInterceptor.java @@ -2,11 +2,11 @@ package com.ruoyi.common.security.feign; import java.util.Map; import javax.servlet.http.HttpServletRequest; -import com.ruoyi.common.core.utils.ip.IpUtils; import org.springframework.stereotype.Component; -import com.ruoyi.common.core.constant.CacheConstants; +import com.ruoyi.common.core.constant.SecurityConstants; import com.ruoyi.common.core.utils.ServletUtils; import com.ruoyi.common.core.utils.StringUtils; +import com.ruoyi.common.core.utils.ip.IpUtils; import feign.RequestInterceptor; import feign.RequestTemplate; @@ -26,20 +26,20 @@ public class FeignRequestInterceptor implements RequestInterceptor { Map headers = ServletUtils.getHeaders(httpServletRequest); // 传递用户信息请求头,防止丢失 - String userId = headers.get(CacheConstants.DETAILS_USER_ID); + String userId = headers.get(SecurityConstants.DETAILS_USER_ID); if (StringUtils.isNotEmpty(userId)) { - requestTemplate.header(CacheConstants.DETAILS_USER_ID, userId); + requestTemplate.header(SecurityConstants.DETAILS_USER_ID, userId); } - String userName = headers.get(CacheConstants.DETAILS_USERNAME); + String userName = headers.get(SecurityConstants.DETAILS_USERNAME); if (StringUtils.isNotEmpty(userName)) { - requestTemplate.header(CacheConstants.DETAILS_USERNAME, userName); + requestTemplate.header(SecurityConstants.DETAILS_USERNAME, userName); } - String authentication = headers.get(CacheConstants.AUTHORIZATION_HEADER); + String authentication = headers.get(SecurityConstants.AUTHORIZATION_HEADER); if (StringUtils.isNotEmpty(authentication)) { - requestTemplate.header(CacheConstants.AUTHORIZATION_HEADER, authentication); + requestTemplate.header(SecurityConstants.AUTHORIZATION_HEADER, authentication); } // 配置客户端IP diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java index 7d8b38cc..67f6b6fe 100644 --- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java +++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java @@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import com.ruoyi.common.core.exception.BaseException; import com.ruoyi.common.core.exception.CustomException; import com.ruoyi.common.core.exception.DemoModeException; +import com.ruoyi.common.core.exception.InnerAuthException; import com.ruoyi.common.core.exception.PreAuthorizeException; import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.web.domain.AjaxResult; @@ -76,6 +77,15 @@ public class GlobalExceptionHandler { return AjaxResult.error("没有权限,请联系管理员授权"); } + /** + * 内部认证异常 + */ + @ExceptionHandler(InnerAuthException.class) + public AjaxResult InnerAuthException(InnerAuthException e) + { + return AjaxResult.error(e.getMessage()); + } + /** * 演示模式异常 */ diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java index a4e84283..95dd8a41 100644 --- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java +++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java @@ -73,6 +73,16 @@ public class TokenService { // 获取请求携带的令牌 String token = SecurityUtils.getToken(request); + return getLoginUser(token); + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(String token) + { if (StringUtils.isNotEmpty(token)) { String userKey = getTokenKey(token); diff --git a/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring.factories b/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring.factories index 1f6338d7..dce2aa1f 100644 --- a/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring.factories +++ b/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring.factories @@ -1,4 +1,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.ruoyi.common.security.service.TokenService,\ com.ruoyi.common.security.aspect.PreAuthorizeAspect,\ + com.ruoyi.common.security.aspect.InnerAuthAspect,\ com.ruoyi.common.security.handler.GlobalExceptionHandler diff --git a/ruoyi-common/ruoyi-common-swagger/pom.xml b/ruoyi-common/ruoyi-common-swagger/pom.xml index 5bc71986..d9b69912 100644 --- a/ruoyi-common/ruoyi-common-swagger/pom.xml +++ b/ruoyi-common/ruoyi-common-swagger/pom.xml @@ -5,7 +5,7 @@ com.ruoyi ruoyi-common - 3.0.0 + 3.1.0 4.0.0 diff --git a/ruoyi-gateway/pom.xml b/ruoyi-gateway/pom.xml index 5af57eac..b0b5c546 100644 --- a/ruoyi-gateway/pom.xml +++ b/ruoyi-gateway/pom.xml @@ -4,7 +4,7 @@ com.ruoyi ruoyi - 3.0.0 + 3.1.0 4.0.0 diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/properties/CaptchaProperties.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/properties/CaptchaProperties.java new file mode 100644 index 00000000..dea2e168 --- /dev/null +++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/properties/CaptchaProperties.java @@ -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; + } +} diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/properties/IgnoreWhiteProperties.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/properties/IgnoreWhiteProperties.java index 25edba94..2c53fc75 100644 --- a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/properties/IgnoreWhiteProperties.java +++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/properties/IgnoreWhiteProperties.java @@ -19,9 +19,8 @@ import org.springframework.context.annotation.Configuration; @Accessors(chain = true) @Configuration @RefreshScope -@ConfigurationProperties(prefix = "ignore") +@ConfigurationProperties(prefix = "security.ignore") public class IgnoreWhiteProperties { - /** * 放行白名单配置,网关不校验此处的白名单 */ diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/properties/XssProperties.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/properties/XssProperties.java new file mode 100644 index 00000000..834188ba --- /dev/null +++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/config/properties/XssProperties.java @@ -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 excludeUrls = new ArrayList<>(); + + public Boolean getEnabled() + { + return enabled; + } + + public void setEnabled(Boolean enabled) + { + this.enabled = enabled; + } + + public List getExcludeUrls() + { + return excludeUrls; + } + + public void setExcludeUrls(List excludeUrls) + { + this.excludeUrls = excludeUrls; + } +} diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java index 1bd39940..e0c66070 100644 --- a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java +++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/AuthFilter.java @@ -7,19 +7,16 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; -import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.data.redis.core.ValueOperations; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; -import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.ruoyi.common.core.constant.CacheConstants; import com.ruoyi.common.core.constant.Constants; -import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.core.constant.HttpStatus; +import com.ruoyi.common.core.constant.SecurityConstants; +import com.ruoyi.common.core.utils.SecurityUtils; import com.ruoyi.common.core.utils.ServletUtils; import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.redis.service.RedisService; @@ -35,7 +32,7 @@ import reactor.core.publisher.Mono; public class AuthFilter implements GlobalFilter, Ordered { private static final Logger log = LoggerFactory.getLogger(AuthFilter.class); - + private final static long EXPIRE_TIME = Constants.TOKEN_EXPIRE * 60; // 排除过滤的 uri 地址,nacos自行添加 @@ -44,61 +41,75 @@ public class AuthFilter implements GlobalFilter, Ordered @Resource(name = "stringRedisTemplate") private ValueOperations sops; - + @Autowired private RedisService redisService; @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - String url = exchange.getRequest().getURI().getPath(); + ServerHttpRequest request = exchange.getRequest(); + ServerHttpRequest.Builder mutate = request.mutate(); + + String url = request.getURI().getPath(); // 跳过不需要验证的路径 if (StringUtils.matches(url, ignoreWhite.getWhites())) { return chain.filter(exchange); } - String token = getToken(exchange.getRequest()); - if (StringUtils.isBlank(token)) + String token = getToken(request); + if (StringUtils.isEmpty(token)) { - return setUnauthorizedResponse(exchange, "令牌不能为空"); + return unauthorizedResponse(exchange, "令牌不能为空"); } String userStr = sops.get(getTokenKey(token)); - if (StringUtils.isNull(userStr)) + if (StringUtils.isEmpty(userStr)) { - return setUnauthorizedResponse(exchange, "登录状态已过期"); + return unauthorizedResponse(exchange, "登录状态已过期"); } - JSONObject obj = JSONObject.parseObject(userStr); - String userid = obj.getString("userid"); - String username = obj.getString("username"); - if (StringUtils.isBlank(userid) || StringUtils.isBlank(username)) + JSONObject cacheObj = JSONObject.parseObject(userStr); + String userid = cacheObj.getString("userid"); + String username = cacheObj.getString("username"); + if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) { - return setUnauthorizedResponse(exchange, "令牌验证失败"); + return unauthorizedResponse(exchange, "令牌验证失败"); } - + // 设置过期时间 redisService.expire(getTokenKey(token), EXPIRE_TIME); // 设置用户信息到请求 - ServerHttpRequest mutableReq = exchange.getRequest().mutate().header(CacheConstants.DETAILS_USER_ID, userid) - .header(CacheConstants.DETAILS_USERNAME, ServletUtils.urlEncode(username)).build(); - ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build(); - - return chain.filter(mutableExchange); + addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid); + addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username); + // 内部请求来源参数清除 + removeHeader(mutate, SecurityConstants.FROM_SOURCE); + return chain.filter(exchange.mutate().request(mutate.build()).build()); } - private Mono setUnauthorizedResponse(ServerWebExchange exchange, String msg) + private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) { - ServerHttpResponse response = exchange.getResponse(); - response.getHeaders().setContentType(MediaType.APPLICATION_JSON); - response.setStatusCode(HttpStatus.OK); - - log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath()); - - return response.writeWith(Mono.fromSupplier(() -> { - DataBufferFactory bufferFactory = response.bufferFactory(); - return bufferFactory.wrap(JSON.toJSONBytes(R.fail(msg))); - })); + if (value == null) + { + return; + } + String valueStr = value.toString(); + String valueEncode = ServletUtils.urlEncode(valueStr); + mutate.header(name, valueEncode); } + private void removeHeader(ServerHttpRequest.Builder mutate, String name) + { + mutate.headers(httpHeaders -> httpHeaders.remove(name)).build(); + } + + private Mono unauthorizedResponse(ServerWebExchange exchange, String msg) + { + log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath()); + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED); + } + + /** + * 获取缓存key + */ private String getTokenKey(String token) { return CacheConstants.LOGIN_TOKEN_KEY + token; @@ -109,12 +120,8 @@ public class AuthFilter implements GlobalFilter, Ordered */ private String getToken(ServerHttpRequest request) { - String token = request.getHeaders().getFirst(CacheConstants.HEADER); - if (StringUtils.isNotEmpty(token) && token.startsWith(CacheConstants.TOKEN_PREFIX)) - { - token = token.replace(CacheConstants.TOKEN_PREFIX, ""); - } - return token; + String token = request.getHeaders().getFirst(SecurityConstants.TOKEN_AUTHENTICATION); + return SecurityUtils.replaceTokenPrefix(token); } @Override diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/BlackListUrlFilter.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/BlackListUrlFilter.java index f613dea0..9deab70b 100644 --- a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/BlackListUrlFilter.java +++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/BlackListUrlFilter.java @@ -5,11 +5,8 @@ import java.util.List; import java.util.regex.Pattern; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; -import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; -import com.alibaba.fastjson.JSON; -import com.ruoyi.common.core.web.domain.AjaxResult; -import reactor.core.publisher.Mono; +import com.ruoyi.common.core.utils.ServletUtils; /** * 黑名单过滤器 @@ -27,10 +24,7 @@ public class BlackListUrlFilter extends AbstractGatewayFilterFactory { diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/ValidateCodeFilter.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/ValidateCodeFilter.java index 561111ba..b756a3a1 100644 --- a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/ValidateCodeFilter.java +++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/ValidateCodeFilter.java @@ -9,15 +9,13 @@ import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFac import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; -import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.ruoyi.common.core.utils.ServletUtils; import com.ruoyi.common.core.utils.StringUtils; -import com.ruoyi.common.core.web.domain.AjaxResult; +import com.ruoyi.gateway.config.properties.CaptchaProperties; import com.ruoyi.gateway.service.ValidateCodeService; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; /** * 验证码过滤器 @@ -27,11 +25,14 @@ import reactor.core.publisher.Mono; @Component public class ValidateCodeFilter extends AbstractGatewayFilterFactory { - private final static String AUTH_URL = "/auth/login"; + private final static String[] VALIDATE_URL = new String[] { "/auth/login", "/auth/register" }; @Autowired private ValidateCodeService validateCodeService; + @Autowired + private CaptchaProperties captchaProperties; + private static final String CODE = "code"; private static final String UUID = "uuid"; @@ -42,8 +43,8 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); - // 非登录请求,不处理 - if (!StringUtils.containsIgnoreCase(request.getURI().getPath(), AUTH_URL)) + // 非登录/注册请求或验证码关闭,不处理 + if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled()) { return chain.filter(exchange); } @@ -56,10 +57,7 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory } catch (Exception e) { - ServerHttpResponse response = exchange.getResponse(); - response.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); - return exchange.getResponse().writeWith( - Mono.just(response.bufferFactory().wrap(JSON.toJSONBytes(AjaxResult.error(e.getMessage()))))); + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage()); } return chain.filter(exchange); }; diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/XssFilter.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/XssFilter.java new file mode 100644 index 00000000..021cf436 --- /dev/null +++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/filter/XssFilter.java @@ -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 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 getBody() + { + Flux 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; + } +} diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/handler/GatewayExceptionHandler.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/handler/GatewayExceptionHandler.java index 0a8e44a3..593e7e62 100644 --- a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/handler/GatewayExceptionHandler.java +++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/handler/GatewayExceptionHandler.java @@ -6,14 +6,10 @@ import org.slf4j.LoggerFactory; import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; -import org.springframework.core.io.buffer.DataBufferFactory; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebExchange; -import com.alibaba.fastjson.JSON; -import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.core.utils.ServletUtils; import reactor.core.publisher.Mono; /** @@ -55,12 +51,6 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage()); - response.getHeaders().setContentType(MediaType.APPLICATION_JSON); - response.setStatusCode(HttpStatus.OK); - - return response.writeWith(Mono.fromSupplier(() -> { - DataBufferFactory bufferFactory = response.bufferFactory(); - return bufferFactory.wrap(JSON.toJSONBytes(R.fail(msg))); - })); + return ServletUtils.webFluxResponseWriter(response, msg); } } \ No newline at end of file diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/handler/SentinelFallbackHandler.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/handler/SentinelFallbackHandler.java index 80c64605..c770a154 100644 --- a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/handler/SentinelFallbackHandler.java +++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/handler/SentinelFallbackHandler.java @@ -1,10 +1,8 @@ package com.ruoyi.gateway.handler; -import java.nio.charset.StandardCharsets; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.slots.block.BlockException; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.http.server.reactive.ServerHttpResponse; +import com.ruoyi.common.core.utils.ServletUtils; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebExceptionHandler; @@ -19,11 +17,7 @@ public class SentinelFallbackHandler implements WebExceptionHandler { private Mono writeResponse(ServerResponse response, ServerWebExchange exchange) { - ServerHttpResponse serverHttpResponse = exchange.getResponse(); - serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); - byte[] datas = "{\"code\":429,\"msg\":\"请求超过最大数,请稍后再试\"}".getBytes(StandardCharsets.UTF_8); - DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas); - return serverHttpResponse.writeWith(Mono.just(buffer)); + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍后再试"); } @Override diff --git a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/service/impl/ValidateCodeServiceImpl.java b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/service/impl/ValidateCodeServiceImpl.java index 873ef39b..368a9fc9 100644 --- a/ruoyi-gateway/src/main/java/com/ruoyi/gateway/service/impl/ValidateCodeServiceImpl.java +++ b/ruoyi-gateway/src/main/java/com/ruoyi/gateway/service/impl/ValidateCodeServiceImpl.java @@ -16,6 +16,7 @@ import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.common.core.utils.sign.Base64; import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.redis.service.RedisService; +import com.ruoyi.gateway.config.properties.CaptchaProperties; import com.ruoyi.gateway.service.ValidateCodeService; /** @@ -35,8 +36,8 @@ public class ValidateCodeServiceImpl implements ValidateCodeService @Autowired private RedisService redisService; - // 验证码类型 - private String captchaType = "math"; + @Autowired + private CaptchaProperties captchaProperties; /** * 生成验证码 @@ -44,6 +45,14 @@ public class ValidateCodeServiceImpl implements ValidateCodeService @Override public AjaxResult createCapcha() throws IOException, CaptchaException { + AjaxResult ajax = AjaxResult.success(); + boolean captchaOnOff = captchaProperties.getEnabled(); + ajax.put("captchaOnOff", captchaOnOff); + if (!captchaOnOff) + { + return ajax; + } + // 保存验证码信息 String uuid = IdUtils.simpleUUID(); String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; @@ -51,6 +60,7 @@ public class ValidateCodeServiceImpl implements ValidateCodeService String capStr = null, code = null; BufferedImage image = null; + String captchaType = captchaProperties.getType(); // 生成验证码 if ("math".equals(captchaType)) { @@ -77,7 +87,6 @@ public class ValidateCodeServiceImpl implements ValidateCodeService return AjaxResult.error(e.getMessage()); } - AjaxResult ajax = AjaxResult.success(); ajax.put("uuid", uuid); ajax.put("img", Base64.encode(os.toByteArray())); return ajax; diff --git a/ruoyi-modules/pom.xml b/ruoyi-modules/pom.xml index 01c9fac4..047ae2d7 100644 --- a/ruoyi-modules/pom.xml +++ b/ruoyi-modules/pom.xml @@ -4,7 +4,7 @@ com.ruoyi ruoyi - 3.0.0 + 3.1.0 4.0.0 diff --git a/ruoyi-modules/ruoyi-file/pom.xml b/ruoyi-modules/ruoyi-file/pom.xml index 0c8015ef..9a6cb51b 100644 --- a/ruoyi-modules/ruoyi-file/pom.xml +++ b/ruoyi-modules/ruoyi-file/pom.xml @@ -5,7 +5,7 @@ com.ruoyi ruoyi-modules - 3.0.0 + 3.1.0 4.0.0 diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/RuoYFileApplication.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/RuoYiFileApplication.java similarity index 89% rename from ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/RuoYFileApplication.java rename to ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/RuoYiFileApplication.java index 990dc1a4..1f320da9 100644 --- a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/RuoYFileApplication.java +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/RuoYiFileApplication.java @@ -1,31 +1,31 @@ -package com.ruoyi.file; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import com.ruoyi.common.swagger.annotation.EnableCustomSwagger2; - -/** - * 文件服务 - * - * @author ruoyi - */ -@EnableCustomSwagger2 -@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class }) -public class RuoYFileApplication -{ - public static void main(String[] args) - { - SpringApplication.run(RuoYFileApplication.class, args); - System.out.println("(♥◠‿◠)ノ゙ 文件服务模块启动成功 ლ(´ڡ`ლ)゙ \n" + - " .-------. ____ __ \n" + - " | _ _ \\ \\ \\ / / \n" + - " | ( ' ) | \\ _. / ' \n" + - " |(_ o _) / _( )_ .' \n" + - " | (_,_).' __ ___(_ o _)' \n" + - " | |\\ \\ | || |(_,_)' \n" + - " | | \\ `' /| `-' / \n" + - " | | \\ / \\ / \n" + - " ''-' `'-' `-..-' "); - } -} +package com.ruoyi.file; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import com.ruoyi.common.swagger.annotation.EnableCustomSwagger2; + +/** + * 文件服务 + * + * @author ruoyi + */ +@EnableCustomSwagger2 +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class }) +public class RuoYiFileApplication +{ + public static void main(String[] args) + { + SpringApplication.run(RuoYiFileApplication.class, args); + System.out.println("(♥◠‿◠)ノ゙ 文件服务模块启动成功 ლ(´ڡ`ლ)゙ \n" + + " .-------. ____ __ \n" + + " | _ _ \\ \\ \\ / / \n" + + " | ( ' ) | \\ _. / ' \n" + + " |(_ o _) / _( )_ .' \n" + + " | (_,_).' __ ___(_ o _)' \n" + + " | |\\ \\ | || |(_,_)' \n" + + " | | \\ `' /| `-' / \n" + + " | | \\ / \\ / \n" + + " ''-' `'-' `-..-' "); + } +} diff --git a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/ResourcesConfig.java b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/ResourcesConfig.java index dd3f60b4..0fa1ddf4 100644 --- a/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/ResourcesConfig.java +++ b/ruoyi-modules/ruoyi-file/src/main/java/com/ruoyi/file/config/ResourcesConfig.java @@ -4,6 +4,7 @@ import java.io.File; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -33,4 +34,17 @@ public class ResourcesConfig implements WebMvcConfigurer { registry.addResourceHandler(localFilePrefix + "/**") .addResourceLocations("file:" + localFilePath + File.separator); } + + /** + * 开启跨域 + */ + @Override + public void addCorsMappings(CorsRegistry registry) { + // 设置允许跨域的路由 + registry.addMapping(localFilePrefix + "/**") + // 设置允许跨域请求的域名 + .allowedOrigins("*") + // 设置允许的方法 + .allowedMethods("GET"); + } } \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-gen/pom.xml b/ruoyi-modules/ruoyi-gen/pom.xml index 8e18387e..0eeb170a 100644 --- a/ruoyi-modules/ruoyi-gen/pom.xml +++ b/ruoyi-modules/ruoyi-gen/pom.xml @@ -5,7 +5,7 @@ com.ruoyi ruoyi-modules - 3.0.0 + 3.1.0 4.0.0 diff --git a/ruoyi-modules/ruoyi-gen/src/main/java/com/ruoyi/gen/util/VelocityUtils.java b/ruoyi-modules/ruoyi-gen/src/main/java/com/ruoyi/gen/util/VelocityUtils.java index 8fe36a83..02673453 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/java/com/ruoyi/gen/util/VelocityUtils.java +++ b/ruoyi-modules/ruoyi-gen/src/main/java/com/ruoyi/gen/util/VelocityUtils.java @@ -280,7 +280,8 @@ public class VelocityUtils */ public static String getParentMenuId(JSONObject paramsObj) { - if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)) + if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID) + && StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID))) { return paramsObj.getString(GenConstants.PARENT_MENU_ID); } diff --git a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/controller.java.vm b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/controller.java.vm index 600044f7..62192bd5 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/controller.java.vm +++ b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/controller.java.vm @@ -78,7 +78,7 @@ public class ${ClassName}Controller extends BaseController @GetMapping(value = "/{${pkColumn.javaField}}") public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) { - return AjaxResult.success(${className}Service.select${ClassName}ById(${pkColumn.javaField})); + return AjaxResult.success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); } /** @@ -111,6 +111,6 @@ public class ${ClassName}Controller extends BaseController @DeleteMapping("/{${pkColumn.javaField}s}") public AjaxResult remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) { - return toAjax(${className}Service.delete${ClassName}ByIds(${pkColumn.javaField}s)); + return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s)); } } diff --git a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/mapper.java.vm b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/mapper.java.vm index 359ee501..f7446402 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/mapper.java.vm +++ b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/mapper.java.vm @@ -17,10 +17,10 @@ public interface ${ClassName}Mapper /** * 查询${functionName} * - * @param ${pkColumn.javaField} ${functionName}ID + * @param ${pkColumn.javaField} ${functionName}主键 * @return ${functionName} */ - public ${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}); + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); /** * 查询${functionName}列表 @@ -49,27 +49,27 @@ public interface ${ClassName}Mapper /** * 删除${functionName} * - * @param ${pkColumn.javaField} ${functionName}ID + * @param ${pkColumn.javaField} ${functionName}主键 * @return 结果 */ - public int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}); + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); /** * 批量删除${functionName} * - * @param ${pkColumn.javaField}s 需要删除的数据ID + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 * @return 结果 */ - public int delete${ClassName}ByIds(${pkColumn.javaType}[] ${pkColumn.javaField}s); + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); #if($table.sub) /** * 批量删除${subTable.functionName} * - * @param customerIds 需要删除的数据ID + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 * @return 结果 */ - public int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + public int delete${subClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); /** * 批量新增${subTable.functionName} @@ -81,7 +81,7 @@ public interface ${ClassName}Mapper /** - * 通过${functionName}ID删除${subTable.functionName}信息 + * 通过${functionName}主键删除${subTable.functionName}信息 * * @param ${pkColumn.javaField} ${functionName}ID * @return 结果 diff --git a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/service.java.vm b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/service.java.vm index af422f34..250f68c8 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/service.java.vm +++ b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/service.java.vm @@ -14,10 +14,10 @@ public interface I${ClassName}Service /** * 查询${functionName} * - * @param ${pkColumn.javaField} ${functionName}ID + * @param ${pkColumn.javaField} ${functionName}主键 * @return ${functionName} */ - public ${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}); + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); /** * 查询${functionName}列表 @@ -46,16 +46,16 @@ public interface I${ClassName}Service /** * 批量删除${functionName} * - * @param ${pkColumn.javaField}s 需要删除的${functionName}ID + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合 * @return 结果 */ - public int delete${ClassName}ByIds(${pkColumn.javaType}[] ${pkColumn.javaField}s); + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); /** * 删除${functionName}信息 * - * @param ${pkColumn.javaField} ${functionName}ID + * @param ${pkColumn.javaField} ${functionName}主键 * @return 结果 */ - public int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}); + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); } diff --git a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/serviceImpl.java.vm b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/serviceImpl.java.vm index 2e928e03..784d3929 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/serviceImpl.java.vm +++ b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/serviceImpl.java.vm @@ -34,13 +34,13 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service /** * 查询${functionName} * - * @param ${pkColumn.javaField} ${functionName}ID + * @param ${pkColumn.javaField} ${functionName}主键 * @return ${functionName} */ @Override - public ${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}) + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) { - return ${className}Mapper.select${ClassName}ById(${pkColumn.javaField}); + return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); } /** @@ -108,34 +108,34 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service /** * 批量删除${functionName} * - * @param ${pkColumn.javaField}s 需要删除的${functionName}ID + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键 * @return 结果 */ #if($table.sub) @Transactional #end @Override - public int delete${ClassName}ByIds(${pkColumn.javaType}[] ${pkColumn.javaField}s) + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s) { #if($table.sub) ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s); #end - return ${className}Mapper.delete${ClassName}ByIds(${pkColumn.javaField}s); + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s); } /** * 删除${functionName}信息 * - * @param ${pkColumn.javaField} ${functionName}ID + * @param ${pkColumn.javaField} ${functionName}主键 * @return 结果 */ @Override - public int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}) + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) { #if($table.sub) ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); #end - return ${className}Mapper.delete${ClassName}ById(${pkColumn.javaField}); + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); } #if($table.sub) @@ -147,7 +147,7 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service public void insert${subClassName}(${ClassName} ${className}) { List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); - Long ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); + ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); if (StringUtils.isNotNull(${subclassName}List)) { List<${subClassName}> list = new ArrayList<${subClassName}>(); diff --git a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index-tree.vue.vm b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index-tree.vue.vm index ab870681..f7105cba 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index-tree.vue.vm +++ b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index-tree.vue.vm @@ -258,46 +258,10 @@ import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName}, export${BusinessName} } from "@/api/${moduleName}/${businessName}"; import Treeselect from "@riophae/vue-treeselect"; import "@riophae/vue-treeselect/dist/vue-treeselect.css"; -#foreach($column in $columns) -#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "imageUpload") -import ImageUpload from '@/components/ImageUpload'; -#break -#end -#end -#foreach($column in $columns) -#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "fileUpload") -import FileUpload from '@/components/FileUpload'; -#break -#end -#end -#foreach($column in $columns) -#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "editor") -import Editor from '@/components/Editor'; -#break -#end -#end export default { name: "${BusinessName}", components: { -#foreach($column in $columns) -#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "imageUpload") - ImageUpload, -#break -#end -#end -#foreach($column in $columns) -#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "fileUpload") - FileUpload, -#break -#end -#end -#foreach($column in $columns) -#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "editor") - Editor, -#break -#end -#end Treeselect }, data() { diff --git a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index.vue.vm b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index.vue.vm index 57da66b1..1e35e567 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index.vue.vm +++ b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/vue/index.vue.vm @@ -309,47 +309,9 @@ diff --git a/ruoyi-ui/src/components/HeaderSearch/index.vue b/ruoyi-ui/src/components/HeaderSearch/index.vue index ae952a98..09311842 100644 --- a/ruoyi-ui/src/components/HeaderSearch/index.vue +++ b/ruoyi-ui/src/components/HeaderSearch/index.vue @@ -70,9 +70,11 @@ export default { this.show = false }, change(val) { + const path = val.path; if(this.ishttp(val.path)) { // http(s):// 路径新窗口打开 - window.open(val.path, "_blank"); + const pindex = path.indexOf("http"); + window.open(path.substr(pindex, path.length), "_blank"); } else { this.$router.push(val.path) } diff --git a/ruoyi-ui/src/components/ImageUpload/index.vue b/ruoyi-ui/src/components/ImageUpload/index.vue index cf772eed..0bf478ed 100644 --- a/ruoyi-ui/src/components/ImageUpload/index.vue +++ b/ruoyi-ui/src/components/ImageUpload/index.vue @@ -5,33 +5,38 @@ list-type="picture-card" :on-success="handleUploadSuccess" :before-upload="handleBeforeUpload" + :limit="limit" :on-error="handleUploadError" + :on-exceed="handleExceed" name="file" - :show-file-list="false" + :on-remove="handleRemove" + :show-file-list="true" :headers="headers" - style="display: inline-block; vertical-align: top" + :file-list="fileList" + :on-preview="handlePictureCardPreview" + :class="{hide: this.fileList.length >= this.limit}" > - -
- -
-
-
- -
-
- - - - - - -
-
-
+ - - + + +
+ 请上传 + + + 的文件 +
+ + + @@ -40,36 +45,123 @@ import { getToken } from "@/utils/auth"; export default { + props: { + value: [String, Object, Array], + // 图片数量限制 + limit: { + type: Number, + default: 5, + }, + // 大小限制(MB) + fileSize: { + type: Number, + default: 5, + }, + // 文件类型, 例如['png', 'jpg', 'jpeg'] + fileType: { + type: Array, + default: () => ["png", "jpg", "jpeg"], + }, + // 是否显示提示 + isShowTip: { + type: Boolean, + default: true + } + }, data() { return { + dialogImageUrl: "", dialogVisible: false, + hideUpload: false, uploadImgUrl: process.env.VUE_APP_BASE_API + "/file/upload", // 上传的图片服务器地址 headers: { Authorization: "Bearer " + getToken(), }, + fileList: [] }; }, - props: { + watch: { value: { - type: String, - default: "", + handler(val) { + if (val) { + // 首先将值转为数组 + const list = Array.isArray(val) ? val : this.value.split(','); + // 然后将数组转为对象数组 + this.fileList = list.map(item => { + if (typeof item === "string") { + item = { name: item, url: item }; + } + return item; + }); + } else { + this.fileList = []; + return []; + } + }, + deep: true, + immediate: true + } + }, + computed: { + // 是否显示提示 + showTip() { + return this.isShowTip && (this.fileType || this.fileSize); }, }, methods: { - removeImage() { - this.$emit("input", ""); + // 删除图片 + handleRemove(file, fileList) { + const findex = this.fileList.map(f => f.name).indexOf(file.name); + this.fileList.splice(findex, 1); + this.$emit("input", this.listToString(this.fileList)); }, + // 上传成功回调 handleUploadSuccess(res) { - this.$emit("input", res.data.url); + this.fileList.push({ name: res.data.url, url: res.data.url }); + this.$emit("input", this.listToString(this.fileList)); this.loading.close(); }, - handleBeforeUpload() { + // 上传前loading加载 + handleBeforeUpload(file) { + let isImg = false; + if (this.fileType.length) { + let fileExtension = ""; + if (file.name.lastIndexOf(".") > -1) { + fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1); + } + isImg = this.fileType.some(type => { + if (file.type.indexOf(type) > -1) return true; + if (fileExtension && fileExtension.indexOf(type) > -1) return true; + return false; + }); + } else { + isImg = file.type.indexOf("image") > -1; + } + + if (!isImg) { + this.$message.error( + `文件格式不正确, 请上传${this.fileType.join("/")}图片格式文件!` + ); + return false; + } + if (this.fileSize) { + const isLt = file.size / 1024 / 1024 < this.fileSize; + if (!isLt) { + this.$message.error(`上传头像图片大小不能超过 ${this.fileSize} MB!`); + return false; + } + } this.loading = this.$loading({ lock: true, text: "上传中", background: "rgba(0, 0, 0, 0.7)", }); }, + // 文件个数超出 + handleExceed() { + this.$message.error(`上传文件数量不能超过 ${this.limit} 个!`); + }, + // 上传失败 handleUploadError() { this.$message({ type: "error", @@ -77,24 +169,37 @@ export default { }); this.loading.close(); }, - }, - watch: {}, + // 预览 + handlePictureCardPreview(file) { + this.dialogImageUrl = file.url; + this.dialogVisible = true; + }, + // 对象转成指定字符串分隔 + listToString(list, separator) { + let strs = ""; + separator = separator || ","; + for (let i in list) { + strs += list[i].url + separator; + } + return strs != '' ? strs.substr(0, strs.length - 1) : ''; + } + } }; - \ No newline at end of file +// 去掉动画效果 +::v-deep .el-list-enter-active, +::v-deep .el-list-leave-active { + transition: all 0s; +} + +::v-deep .el-list-enter, .el-list-leave-active { + opacity: 0; + transform: translateY(0); +} + + diff --git a/ruoyi-ui/src/components/TopNav/index.vue b/ruoyi-ui/src/components/TopNav/index.vue index d89930a8..1b7c4d9e 100644 --- a/ruoyi-ui/src/components/TopNav/index.vue +++ b/ruoyi-ui/src/components/TopNav/index.vue @@ -12,7 +12,7 @@ - + @@ -382,6 +395,22 @@ export default { this.single = selection.length != 1; this.multiple = !selection.length; }, + // 更多操作触发 + handleCommand(command, row) { + switch (command) { + case "handleRun": + this.handleRun(row); + break; + case "handleView": + this.handleView(row); + break; + case "handleJobLog": + this.handleJobLog(row); + break; + default: + break; + } + }, // 任务状态修改 handleStatusChange(row) { let text = row.status === "0" ? "启用" : "停用"; @@ -417,8 +446,9 @@ export default { }); }, /** 任务日志列表查询 */ - handleJobLog() { - this.$router.push("/job/log"); + handleJobLog(row) { + const jobId = row.jobId || 0; + this.$router.push({ path: '/monitor/job-log/index', query: { jobId: jobId } }) }, /** 新增按钮操作 */ handleAdd() { @@ -468,7 +498,7 @@ export default { }).then(() => { this.getList(); this.msgSuccess("删除成功"); - }) + }).catch(() => {}); }, /** 导出按钮操作 */ handleExport() { diff --git a/ruoyi-ui/src/views/monitor/job/log.vue b/ruoyi-ui/src/views/monitor/job/log.vue index 7c582eb6..5aa102d6 100644 --- a/ruoyi-ui/src/views/monitor/job/log.vue +++ b/ruoyi-ui/src/views/monitor/job/log.vue @@ -93,6 +93,15 @@ v-hasPermi="['monitor:job:export']" >导出 + + 关闭 + @@ -167,6 +176,7 @@ + + diff --git a/ruoyi-ui/src/views/system/dict/index.vue b/ruoyi-ui/src/views/system/dict/index.vue index 899e5262..2b2391f7 100644 --- a/ruoyi-ui/src/views/system/dict/index.vue +++ b/ruoyi-ui/src/views/system/dict/index.vue @@ -117,7 +117,7 @@ diff --git a/ruoyi-ui/src/views/system/menu/index.vue b/ruoyi-ui/src/views/system/menu/index.vue index 507ae509..059f8d85 100644 --- a/ruoyi-ui/src/views/system/menu/index.vue +++ b/ruoyi-ui/src/views/system/menu/index.vue @@ -89,7 +89,7 @@ - + @@ -144,7 +144,13 @@ - + + + + + + 是否外链 + @@ -152,22 +158,46 @@ - + + + + + + 路由地址 + - + + + + + + 组件路径 + - + + + + + + 权限字符 + - + + + + + + 显示状态 + - + + + + + + 菜单状态 + - + + + + + + 是否缓存 + 缓存 不缓存 diff --git a/ruoyi-ui/src/views/system/notice/index.vue b/ruoyi-ui/src/views/system/notice/index.vue index f3931a46..cfbe761e 100644 --- a/ruoyi-ui/src/views/system/notice/index.vue +++ b/ruoyi-ui/src/views/system/notice/index.vue @@ -176,14 +176,10 @@