diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..fbcab775d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: http://doc.ruoyi.vip/ruoyi-vue/other/donate.html diff --git a/README.md b/README.md index a8dbc22ef..5a34add02 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,16 @@ ## 平台的简介 +

+ logo +

+

RuoYi v3.8.1

+

基于SpringBoot+Vue前后端分离的Java快速开发框架

+

+ + + +

+ +## 平台简介 若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 @@ -7,6 +19,7 @@ * 权限认证使用Jwt,支持多终端认证系统。 * 支持加载动态权限菜单,多方式轻松权限控制。 * 高效率开发,使用代码生成器可以一键生成前后端代码。 +* 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3),保持同步更新。 * 提供了单应用版本[RuoYi-Vue-fast](https://github.com/yangzongzhuan/RuoYi-Vue-fast),Oracle版本[RuoYi-Vue-Oracle](https://github.com/yangzongzhuan/RuoYi-Vue-Oracle),保持同步更新。 * 不分离版本,请移步[RuoYi](https://gitee.com/y_project/RuoYi),微服务版本,请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud) * 特别鸣谢:[element](https://github.com/ElemeFE/element),[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin),[eladmin-web](https://github.com/elunez/eladmin-web)。 @@ -82,4 +95,4 @@ ## 若依前后端分离交流群 -QQ群: [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) 点击按钮入群。 \ No newline at end of file +QQ群: [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/已满-101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [![加入QQ群](https://img.shields.io/badge/264312783-blue.svg)](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) 点击按钮入群。 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 6feb0ee79..c024d33d7 100644 --- a/pom.xml +++ b/pom.xml @@ -6,14 +6,14 @@ com.ruoyi ruoyi - 3.7.0 + 3.8.1 ruoyi http://www.ruoyi.vip 若依管理系统 - 3.7.0 + 3.8.1 UTF-8 UTF-8 1.8 @@ -24,14 +24,15 @@ 2.3.2 2.2.0 1.4.0 - 1.2.78 - 5.8.2 - 5.9.0 + 1.2.79 + 5.8.6 + 5.10.0 2.11.0 1.4 3.2.2 4.1.2 - 1.7 + 2.3 + 2.17.1 0.9.1 @@ -43,7 +44,7 @@ org.springframework.boot spring-boot-dependencies - 2.5.5 + 2.5.8 pom import @@ -132,14 +133,8 @@ org.apache.velocity - velocity + velocity-engine-core ${velocity.version} - - - commons-collections - commons-collections - - @@ -156,6 +151,19 @@ ${fastjson.version} + + + org.apache.logging.log4j + log4j-api + ${log4j2.version} + + + + org.apache.logging.log4j + log4j-to-slf4j + ${log4j2.version} + + io.jsonwebtoken diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 1d1d687b4..14769e515 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -5,7 +5,7 @@ ruoyi com.ruoyi - 3.7.0 + 3.8.1 4.0.0 jar diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java index 649fd115e..955c78436 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java @@ -6,8 +6,8 @@ import java.util.concurrent.TimeUnit; import javax.annotation.Resource; import javax.imageio.ImageIO; import javax.servlet.http.HttpServletResponse; +import com.ruoyi.common.config.RuoYiConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.util.FastByteArrayOutputStream; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -36,10 +36,6 @@ public class CaptchaController @Autowired private RedisCache redisCache; - // 验证码类型 - @Value("${ruoyi.captchaType}") - private String captchaType; - @Autowired private ISysConfigService configService; /** @@ -64,6 +60,7 @@ public class CaptchaController BufferedImage image = null; // 生成验证码 + String captchaType = RuoYiConfig.getCaptchaType(); if ("math".equals(captchaType)) { String capText = captchaProducerMath.createText(); diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java index 14f57fdaf..5bd40baed 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java @@ -1,11 +1,13 @@ package com.ruoyi.web.controller.monitor; import java.util.List; +import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.annotation.Log; @@ -40,12 +42,12 @@ public class SysLogininforController extends BaseController @Log(title = "登录日志", businessType = BusinessType.EXPORT) @PreAuthorize("@ss.hasPermi('monitor:logininfor:export')") - @GetMapping("/export") - public AjaxResult export(SysLogininfor logininfor) + @PostMapping("/export") + public void export(HttpServletResponse response, SysLogininfor logininfor) { List list = logininforService.selectLogininforList(logininfor); ExcelUtil util = new ExcelUtil(SysLogininfor.class); - return util.exportExcel(list, "登录日志"); + util.exportExcel(response, list, "登录日志"); } @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java index 75bf12682..4ce126a54 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java @@ -1,11 +1,13 @@ package com.ruoyi.web.controller.monitor; import java.util.List; +import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.annotation.Log; @@ -40,12 +42,12 @@ public class SysOperlogController extends BaseController @Log(title = "操作日志", businessType = BusinessType.EXPORT) @PreAuthorize("@ss.hasPermi('monitor:operlog:export')") - @GetMapping("/export") - public AjaxResult export(SysOperLog operLog) + @PostMapping("/export") + public void export(HttpServletResponse response, SysOperLog operLog) { List list = operLogService.selectOperLogList(operLog); ExcelUtil util = new ExcelUtil(SysOperLog.class); - return util.exportExcel(list, "操作日志"); + util.exportExcel(response, list, "操作日志"); } @Log(title = "操作日志", businessType = BusinessType.DELETE) diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java index dc2532fbc..04f71629a 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java @@ -1,6 +1,7 @@ package com.ruoyi.web.controller.system; import java.util.List; +import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; @@ -13,7 +14,6 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.annotation.Log; -import com.ruoyi.common.annotation.RepeatSubmit; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; @@ -49,12 +49,12 @@ public class SysConfigController extends BaseController @Log(title = "参数管理", businessType = BusinessType.EXPORT) @PreAuthorize("@ss.hasPermi('system:config:export')") - @GetMapping("/export") - public AjaxResult export(SysConfig config) + @PostMapping("/export") + public void export(HttpServletResponse response, SysConfig config) { List list = configService.selectConfigList(config); ExcelUtil util = new ExcelUtil(SysConfig.class); - return util.exportExcel(list, "参数数据"); + util.exportExcel(response, list, "参数数据"); } /** @@ -82,7 +82,6 @@ public class SysConfigController extends BaseController @PreAuthorize("@ss.hasPermi('system:config:add')") @Log(title = "参数管理", businessType = BusinessType.INSERT) @PostMapping - @RepeatSubmit public AjaxResult add(@Validated @RequestBody SysConfig config) { if (UserConstants.NOT_UNIQUE.equals(configService.checkConfigKeyUnique(config))) diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java index b9e3dc3b1..eea89fd0d 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java @@ -2,6 +2,7 @@ package com.ruoyi.web.controller.system; import java.util.ArrayList; import java.util.List; +import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; @@ -50,12 +51,12 @@ public class SysDictDataController extends BaseController @Log(title = "字典数据", businessType = BusinessType.EXPORT) @PreAuthorize("@ss.hasPermi('system:dict:export')") - @GetMapping("/export") - public AjaxResult export(SysDictData dictData) + @PostMapping("/export") + public void export(HttpServletResponse response, SysDictData dictData) { List list = dictDataService.selectDictDataList(dictData); ExcelUtil util = new ExcelUtil(SysDictData.class); - return util.exportExcel(list, "字典数据"); + util.exportExcel(response, list, "字典数据"); } /** diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java index acad00e26..0dd3474cf 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java @@ -1,6 +1,7 @@ package com.ruoyi.web.controller.system; import java.util.List; +import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; @@ -45,12 +46,12 @@ public class SysDictTypeController extends BaseController @Log(title = "字典类型", businessType = BusinessType.EXPORT) @PreAuthorize("@ss.hasPermi('system:dict:export')") - @GetMapping("/export") - public AjaxResult export(SysDictType dictType) + @PostMapping("/export") + public void export(HttpServletResponse response, SysDictType dictType) { List list = dictTypeService.selectDictTypeList(dictType); ExcelUtil util = new ExcelUtil(SysDictType.class); - return util.exportExcel(list, "字典类型"); + util.exportExcel(response, list, "字典类型"); } /** diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java index 30a87616c..1a2b408ed 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java @@ -1,6 +1,7 @@ package com.ruoyi.web.controller.system; import java.util.List; +import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; @@ -48,12 +49,12 @@ public class SysPostController extends BaseController @Log(title = "岗位管理", businessType = BusinessType.EXPORT) @PreAuthorize("@ss.hasPermi('system:post:export')") - @GetMapping("/export") - public AjaxResult export(SysPost post) + @PostMapping("/export") + public void export(HttpServletResponse response, SysPost post) { List list = postService.selectPostList(post); ExcelUtil util = new ExcelUtil(SysPost.class); - return util.exportExcel(list, "岗位数据"); + util.exportExcel(response, list, "岗位数据"); } /** diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java index 227ac872d..be515fd61 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java @@ -60,6 +60,9 @@ public class SysProfileController extends BaseController @PutMapping public AjaxResult updateProfile(@RequestBody SysUser user) { + LoginUser loginUser = getLoginUser(); + SysUser sysUser = loginUser.getUser(); + user.setUserName(sysUser.getUserName()); if (StringUtils.isNotEmpty(user.getPhonenumber()) && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) { @@ -70,8 +73,6 @@ public class SysProfileController extends BaseController { return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); } - LoginUser loginUser = getLoginUser(); - SysUser sysUser = loginUser.getUser(); user.setUserId(sysUser.getUserId()); user.setPassword(null); if (userService.updateUserProfile(user) > 0) diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java index d73749086..d70fa8158 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java @@ -1,6 +1,7 @@ package com.ruoyi.web.controller.system; import java.util.List; +import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; @@ -61,12 +62,12 @@ public class SysRoleController extends BaseController @Log(title = "角色管理", businessType = BusinessType.EXPORT) @PreAuthorize("@ss.hasPermi('system:role:export')") - @GetMapping("/export") - public AjaxResult export(SysRole role) + @PostMapping("/export") + public void export(HttpServletResponse response, SysRole role) { List list = roleService.selectRoleList(role); ExcelUtil util = new ExcelUtil(SysRole.class); - return util.exportExcel(list, "角色数据"); + util.exportExcel(response, list, "角色数据"); } /** diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java index 6cfbfb8d7..e9be25e04 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java @@ -2,6 +2,7 @@ package com.ruoyi.web.controller.system; import java.util.List; import java.util.stream.Collectors; +import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; @@ -62,12 +63,12 @@ public class SysUserController extends BaseController @Log(title = "用户管理", businessType = BusinessType.EXPORT) @PreAuthorize("@ss.hasPermi('system:user:export')") - @GetMapping("/export") - public AjaxResult export(SysUser user) + @PostMapping("/export") + public void export(HttpServletResponse response, SysUser user) { List list = userService.selectUserList(user); ExcelUtil util = new ExcelUtil(SysUser.class); - return util.exportExcel(list, "用户数据"); + util.exportExcel(response, list, "用户数据"); } @Log(title = "用户管理", businessType = BusinessType.IMPORT) @@ -82,11 +83,11 @@ public class SysUserController extends BaseController return AjaxResult.success(message); } - @GetMapping("/importTemplate") - public AjaxResult importTemplate() + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) { ExcelUtil util = new ExcelUtil(SysUser.class); - return util.importTemplateExcel("用户数据"); + util.importTemplateExcel(response, "用户数据"); } /** @@ -103,9 +104,10 @@ public class SysUserController extends BaseController ajax.put("posts", postService.selectPostAll()); if (StringUtils.isNotNull(userId)) { - ajax.put(AjaxResult.DATA_TAG, userService.selectUserById(userId)); + SysUser sysUser = userService.selectUserById(userId); + ajax.put(AjaxResult.DATA_TAG, sysUser); ajax.put("postIds", postService.selectPostListByUserId(userId)); - ajax.put("roleIds", roleService.selectRoleListByUserId(userId)); + ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList())); } return ajax; } diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index ae0ffbc9e..ccef2502d 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -3,9 +3,9 @@ ruoyi: # 名称 name: RuoYi # 版本 - version: 3.7.0 + version: 3.8.1 # 版权年份 - copyrightYear: 2021 + copyrightYear: 2022 # 实例演示开关 demoEnabled: true # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) @@ -25,10 +25,13 @@ server: tomcat: # tomcat的URI编码 uri-encoding: UTF-8 - # tomcat最大线程数,默认为200 - max-threads: 800 - # Tomcat启动初始化的线程数,默认值25 - min-spare-threads: 30 + # 连接数满后的排队数,默认为100 + accept-count: 1000 + threads: + # tomcat最大线程数,默认为200 + max: 800 + # Tomcat启动初始化的线程数,默认值10 + min-spare: 100 # 日志配置 logging: diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index 486d39f8c..e18104ca1 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -5,7 +5,7 @@ ruoyi com.ruoyi - 3.7.0 + 3.8.1 4.0.0 @@ -43,8 +43,8 @@ - javax.validation - validation-api + org.springframework.boot + spring-boot-starter-validation @@ -89,12 +89,18 @@ snakeyaml - + io.jsonwebtoken jjwt + + + javax.xml.bind + jaxb-api + + org.springframework.boot diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java index 408300525..1e2774350 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java @@ -27,5 +27,5 @@ public @interface RepeatSubmit /** * 提示消息 */ - public String message() default "不允许重复提交,请稍后再试"; + public String message() default "不允许重复提交,请稍候再试"; } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java index 843b91912..84c0029db 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java @@ -30,6 +30,9 @@ public class RuoYiConfig /** 获取地址开关 */ private static boolean addressEnabled; + /** 验证码类型 */ + private static String captchaType; + public String getName() { return name; @@ -90,6 +93,14 @@ public class RuoYiConfig RuoYiConfig.addressEnabled = addressEnabled; } + public static String getCaptchaType() { + return captchaType; + } + + public void setCaptchaType(String captchaType) { + RuoYiConfig.captchaType = captchaType; + } + /** * 获取导入上传路径 */ diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java index 9f55771bf..6deff23bb 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java @@ -142,10 +142,26 @@ public class Constants /** * RMI 远程方法调用 */ - public static final String LOOKUP_RMI = "rmi://"; + public static final String LOOKUP_RMI = "rmi:"; /** * LDAP 远程方法调用 */ - public static final String LOOKUP_LDAP = "ldap://"; + public static final String LOOKUP_LDAP = "ldap:"; + + /** + * LDAPS 远程方法调用 + */ + public static final String LOOKUP_LDAPS = "ldaps:"; + + /** + * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) + */ + public static final String[] JOB_WHITELIST_STR = { "com.ruoyi" }; + + /** + * 定时任务违规的字符 + */ + public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", + "org.springframework", "org.apache", "com.ruoyi.common.utils.file" }; } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java index 5b450cb87..2aeaf9bae 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java @@ -16,6 +16,7 @@ import com.ruoyi.common.core.page.PageDomain; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableSupport; import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.PageUtils; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.sql.SqlUtil; @@ -51,15 +52,7 @@ public class BaseController */ protected void startPage() { - PageDomain pageDomain = TableSupport.buildPageRequest(); - Integer pageNum = pageDomain.getPageNum(); - Integer pageSize = pageDomain.getPageSize(); - if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize)) - { - String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); - Boolean reasonable = pageDomain.getReasonable(); - PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable); - } + PageUtils.startPage(); } /** diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java index b26e066ab..8ca1b9b11 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java @@ -145,4 +145,18 @@ public class AjaxResult extends HashMap { return new AjaxResult(code, msg, null); } + + /** + * 方便链式调用 + * + * @param key 键 + * @param value 值 + * @return 数据对象 + */ + @Override + public AjaxResult put(String key, Object value) + { + super.put(key, value); + return this; + } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java index 8c7c494bc..229f41803 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java @@ -3,6 +3,7 @@ package com.ruoyi.common.core.domain.entity; import java.util.ArrayList; import java.util.List; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -30,7 +31,7 @@ public class SysMenu extends BaseEntity private Long parentId; /** 显示顺序 */ - private String orderNum; + private Integer orderNum; /** 路由地址 */ private String path; @@ -107,13 +108,13 @@ public class SysMenu extends BaseEntity this.parentId = parentId; } - @NotBlank(message = "显示顺序不能为空") - public String getOrderNum() + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() { return orderNum; } - public void setOrderNum(String orderNum) + public void setOrderNum(Integer orderNum) { this.orderNum = orderNum; } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java index 08cf15142..4aa1d2b2d 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java @@ -2,9 +2,7 @@ package com.ruoyi.common.core.domain.entity; import java.util.Date; import java.util.List; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Size; +import javax.validation.constraints.*; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -14,6 +12,7 @@ import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.annotation.Excel.Type; import com.ruoyi.common.annotation.Excels; import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.xss.Xss; /** * 用户对象 sys_user @@ -135,6 +134,7 @@ public class SysUser extends BaseEntity this.deptId = deptId; } + @Xss(message = "用户昵称不能包含脚本字符") @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符") public String getNickName() { @@ -146,6 +146,7 @@ public class SysUser extends BaseEntity this.nickName = nickName; } + @Xss(message = "用户账号不能包含脚本字符") @NotBlank(message = "用户账号不能为空") @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符") public String getUserName() diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java index 238753359..3246b7731 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java @@ -209,6 +209,18 @@ public class RedisCache return opsForHash.get(key, hKey); } + /** + * 删除Hash中的数据 + * + * @param key + * @param mapkey + */ + public void delCacheMapValue(final String key, final String hkey) + { + HashOperations hashOperations = redisTemplate.opsForHash(); + hashOperations.delete(key, hkey); + } + /** * 获取多个Hash中的数据 * diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java new file mode 100644 index 000000000..7db37a2fe --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java @@ -0,0 +1,30 @@ +package com.ruoyi.common.utils; + +import com.github.pagehelper.PageHelper; +import com.ruoyi.common.core.page.PageDomain; +import com.ruoyi.common.core.page.TableSupport; +import com.ruoyi.common.utils.sql.SqlUtil; + +/** + * 分页工具类 + * + * @author ruoyi + */ +public class PageUtils extends PageHelper +{ + /** + * 设置请求分页数据 + */ + public static void startPage() + { + PageDomain pageDomain = TableSupport.buildPageRequest(); + Integer pageNum = pageDomain.getPageNum(); + Integer pageSize = pageDomain.getPageSize(); + if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize)) + { + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + Boolean reasonable = pageDomain.getReasonable(); + PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java index 395920a24..f34935284 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java @@ -36,7 +36,7 @@ public class Threads * 停止线程池 * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数. - * 如果仍人超時,則強制退出. + * 如果仍然超時,則強制退出. * 另对在shutdown时线程本身被调用中断做了处理. */ public static void shutdownAndAwaitTermination(ExecutorService pool) diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java new file mode 100644 index 000000000..d9821e0ec --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.utils.bean; + +import java.util.Set; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; + +/** + * bean对象属性验证 + * + * @author ruoyi + */ +public class BeanValidators +{ + public static void validateWithException(Validator validator, Object object, Class... groups) + throws ConstraintViolationException + { + Set> constraintViolations = validator.validate(object, groups); + if (!constraintViolations.isEmpty()) + { + throw new ConstraintViolationException(constraintViolations); + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java index 8d8f5a6a3..dd1bcc46f 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java @@ -210,6 +210,8 @@ public class FileUtils .append("utf-8''") .append(percentEncodedFileName); + response.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); response.setHeader("Content-disposition", contentDispositionValue.toString()); response.setHeader("download-filename", percentEncodedFileName); } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java index 65fd7920f..dda96c32f 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java @@ -69,26 +69,37 @@ public class EscapeUtil */ private static String encode(String text) { - int len; - if ((text == null) || ((len = text.length()) == 0)) + if (StringUtils.isEmpty(text)) { return StringUtils.EMPTY; } - StringBuilder buffer = new StringBuilder(len + (len >> 2)); + + final StringBuilder tmp = new StringBuilder(text.length() * 6); char c; - for (int i = 0; i < len; i++) + for (int i = 0; i < text.length(); i++) { c = text.charAt(i); - if (c < 64) + if (c < 256) { - buffer.append(TEXT[c]); + tmp.append("%"); + if (c < 16) + { + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); } else { - buffer.append(c); + tmp.append("%u"); + if (c <= 0xfff) + { + // issue#I49JU8@Gitee + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); } } - return buffer.toString(); + return tmp.toString(); } /** @@ -145,11 +156,12 @@ public class EscapeUtil public static void main(String[] args) { String html = ""; + String escape = EscapeUtil.escape(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)); + System.out.println("clean: " + EscapeUtil.clean(html)); + System.out.println("escape: " + escape); + System.out.println("unescape: " + EscapeUtil.unescape(escape)); } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java index 415acbab2..d3b211a48 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java @@ -332,7 +332,7 @@ public final class HTMLFilter final String name = m.group(1).toLowerCase(); if (allowed(name)) { - if (false == inArray(name, vSelfClosingTags)) + if (!inArray(name, vSelfClosingTags)) { if (vTagCounts.containsKey(name)) { diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java index c920f5b12..f57baf0fc 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java @@ -30,6 +30,17 @@ public class HttpUtils { private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url) + { + return sendGet(url, StringUtils.EMPTY); + } + /** * 向指定 URL 发送GET方法的请求 * diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java index 4169360a1..93a19e870 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java @@ -431,7 +431,7 @@ public class ExcelUtil * @return 结果 * @throws IOException */ - public void exportExcel(HttpServletResponse response, List list, String sheetName)throws IOException + public void exportExcel(HttpServletResponse response, List list, String sheetName) { exportExcel(response, list, sheetName, StringUtils.EMPTY); } @@ -446,12 +446,12 @@ public class ExcelUtil * @return 结果 * @throws IOException */ - public void exportExcel(HttpServletResponse response, List list, String sheetName, String title) throws IOException + public void exportExcel(HttpServletResponse response, List list, String sheetName, String title) { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); this.init(list, sheetName, title, Type.EXPORT); - exportExcel(response.getOutputStream()); + exportExcel(response); } /** @@ -484,7 +484,7 @@ public class ExcelUtil * @param sheetName 工作表的名称 * @return 结果 */ - public void importTemplateExcel(HttpServletResponse response, String sheetName) throws IOException + public void importTemplateExcel(HttpServletResponse response, String sheetName) { importTemplateExcel(response, sheetName, StringUtils.EMPTY); } @@ -496,12 +496,12 @@ public class ExcelUtil * @param title 标题 * @return 结果 */ - public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) throws IOException + public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); this.init(null, sheetName, title, Type.IMPORT); - exportExcel(response.getOutputStream()); + exportExcel(response); } /** @@ -509,12 +509,12 @@ public class ExcelUtil * * @return 结果 */ - public void exportExcel(OutputStream out) + public void exportExcel(HttpServletResponse response) { try { writeSheet(); - wb.write(out); + wb.write(response.getOutputStream()); } catch (Exception e) { @@ -523,7 +523,6 @@ public class ExcelUtil finally { IOUtils.closeQuietly(wb); - IOUtils.closeQuietly(out); } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java index ceff84132..71a7ae10f 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java @@ -10,6 +10,11 @@ import com.ruoyi.common.utils.StringUtils; */ public class SqlUtil { + /** + * 定义常用的 sql关键字 + */ + public static String SQL_REGEX = "select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare "; + /** * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) */ @@ -34,4 +39,23 @@ public class SqlUtil { return value.matches(SQL_PATTERN); } + + /** + * SQL关键字检查 + */ + public static void filterKeyword(String value) + { + if (StringUtils.isEmpty(value)) + { + return; + } + String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); + for (int i = 0; i < sqlKeywords.length; i++) + { + if (StringUtils.indexOfIgnoreCase(value, sqlKeywords[i]) > -1) + { + throw new UtilException("参数存在SQL注入风险"); + } + } + } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java index eef72ee0b..c0c18af01 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java @@ -343,25 +343,25 @@ public final class UUID implements java.io.Serializable, Comparable final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36); // time_low builder.append(digits(mostSigBits >> 32, 8)); - if (false == isSimple) + if (!isSimple) { builder.append('-'); } // time_mid builder.append(digits(mostSigBits >> 16, 4)); - if (false == isSimple) + if (!isSimple) { builder.append('-'); } // time_high_and_version builder.append(digits(mostSigBits, 4)); - if (false == isSimple) + if (!isSimple) { builder.append('-'); } // variant_and_sequence builder.append(digits(leastSigBits >> 48, 4)); - if (false == isSimple) + if (!isSimple) { builder.append('-'); } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java b/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java new file mode 100644 index 000000000..14e43dc55 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java @@ -0,0 +1,27 @@ +package com.ruoyi.common.xss; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义xss校验注解 + * + * @author ruoyi + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) +@Constraint(validatedBy = { XssValidator.class }) +public @interface Xss +{ + String message() + + default "不允许任何脚本运行"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java new file mode 100644 index 000000000..43163721d --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java @@ -0,0 +1,29 @@ +package com.ruoyi.common.xss; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 自定义xss校验注解实现 + * + * @author ruoyi + */ +public class XssValidator implements ConstraintValidator +{ + private final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />"; + + @Override + public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) + { + return !containsHtml(value); + } + + public boolean containsHtml(String value) + { + Pattern pattern = Pattern.compile(HTML_PATTERN); + Matcher matcher = pattern.matcher(value); + return matcher.matches(); + } +} \ No newline at end of file diff --git a/ruoyi-framework/pom.xml b/ruoyi-framework/pom.xml index 0eea3ce11..f1b68c7c5 100644 --- a/ruoyi-framework/pom.xml +++ b/ruoyi-framework/pom.xml @@ -5,7 +5,7 @@ ruoyi com.ruoyi - 3.7.0 + 3.8.1 4.0.0 diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java index 5020d000b..7b8ccf1d3 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java @@ -61,7 +61,7 @@ public class RateLimiterAspect Long number = redisTemplate.execute(limitScript, keys, count, time); if (StringUtils.isNull(number) || number.intValue() > count) { - throw new ServiceException("访问过于频繁,请稍后再试"); + throw new ServiceException("访问过于频繁,请稍候再试"); } log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key); } @@ -71,7 +71,7 @@ public class RateLimiterAspect } catch (Exception e) { - throw new RuntimeException("服务器限流异常,请稍后再试"); + throw new RuntimeException("服务器限流异常,请稍候再试"); } } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java index ab12e4164..610807a86 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java @@ -18,7 +18,6 @@ import com.ruoyi.common.utils.StringUtils; * @author ruoyi */ @Configuration -@ConditionalOnProperty(value = "xss.enabled", havingValue = "true") public class FilterConfig { @Value("${xss.excludes}") @@ -29,6 +28,7 @@ public class FilterConfig @SuppressWarnings({ "rawtypes", "unchecked" }) @Bean + @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") public FilterRegistrationBean xssFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java index 618a925dc..63739f9c0 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java @@ -1,13 +1,13 @@ package com.ruoyi.framework.config; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadPoolExecutor; +import com.ruoyi.common.utils.Threads; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import com.ruoyi.common.utils.Threads; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; /** * 线程池配置 @@ -49,7 +49,8 @@ public class ThreadPoolConfig protected ScheduledExecutorService scheduledExecutorService() { return new ScheduledThreadPoolExecutor(corePoolSize, - new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build()) + new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), + new ThreadPoolExecutor.CallerRunsPolicy()) { @Override protected void afterExecute(Runnable r, Throwable t) diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java index 33c9c876b..b956b3f25 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java @@ -60,14 +60,10 @@ public class SameUrlDataInterceptor extends RepeatSubmitInterceptor String url = request.getRequestURI(); // 唯一值(没有消息头则使用请求地址) - String submitKey = request.getHeader(header); - if (StringUtils.isEmpty(submitKey)) - { - submitKey = url; - } + String submitKey = StringUtils.trimToEmpty(request.getHeader(header)); - // 唯一标识(指定key + 消息头) - String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + submitKey; + // 唯一标识(指定key + url + 消息头) + String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + url + submitKey; Object sessionObj = redisCache.getCacheObject(cacheRepeatKey); if (sessionObj != null) diff --git a/ruoyi-generator/pom.xml b/ruoyi-generator/pom.xml index f68bbe984..b80b938cf 100644 --- a/ruoyi-generator/pom.xml +++ b/ruoyi-generator/pom.xml @@ -5,7 +5,7 @@ ruoyi com.ruoyi - 3.7.0 + 3.8.1 4.0.0 @@ -20,7 +20,7 @@ org.apache.velocity - velocity + velocity-engine-core diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java index f2ffbe951..68bba7bed 100644 --- a/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java @@ -7,6 +7,7 @@ import java.io.StringWriter; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -286,7 +287,7 @@ public class GenTableServiceImpl implements IGenTableService { GenTable table = genTableMapper.selectGenTableByName(tableName); List tableColumns = table.getColumns(); - List tableColumnNames = tableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList()); + Map tableColumnMap = tableColumns.stream().collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity())); List dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); if (StringUtils.isEmpty(dbTableColumns)) @@ -296,9 +297,20 @@ public class GenTableServiceImpl implements IGenTableService List dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList()); dbTableColumns.forEach(column -> { - if (!tableColumnNames.contains(column.getColumnName())) + GenUtils.initColumnField(column, table); + if (tableColumnMap.containsKey(column.getColumnName())) + { + GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName()); + column.setColumnId(prevColumn.getColumnId()); + if (column.isList()) + { + // 如果是列表,继续保留字典类型 + column.setDictType(prevColumn.getDictType()); + } + genTableColumnMapper.updateGenTableColumn(column); + } + else { - GenUtils.initColumnField(column, table); genTableColumnMapper.insertGenTableColumn(column); } }); @@ -359,7 +371,7 @@ public class GenTableServiceImpl implements IGenTableService zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table))); IOUtils.write(sw.toString(), zip, Constants.UTF8); IOUtils.closeQuietly(sw); - zip.flush(); + zip.flush(); zip.closeEntry(); } catch (IOException e) @@ -472,7 +484,7 @@ public class GenTableServiceImpl implements IGenTableService String treeName = paramsObj.getString(GenConstants.TREE_NAME); String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID); String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME); - + genTable.setTreeCode(treeCode); genTable.setTreeParentCode(treeParentCode); genTable.setTreeName(treeName); diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java index 4be1eecbb..26456335e 100644 --- a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java @@ -20,10 +20,9 @@ public class VelocityInitializer try { // 加载classpath目录下的vm文件 - p.setProperty("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); // 定义字符集 p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8); - p.setProperty(Velocity.OUTPUT_ENCODING, Constants.UTF8); // 初始化Velocity引擎,指定配置Properties Velocity.init(p); } diff --git a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java index 2683ec8fd..2ae6802e3 100644 --- a/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java +++ b/ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java @@ -3,6 +3,7 @@ package com.ruoyi.generator.util; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Set; import org.apache.velocity.VelocityContext; import com.alibaba.fastjson.JSONObject; import com.ruoyi.common.constant.GenConstants; @@ -270,11 +271,12 @@ public class VelocityUtils public static String getDicts(GenTable genTable) { List columns = genTable.getColumns(); - List dicts = new ArrayList(); + Set dicts = new HashSet(); for (GenTableColumn column : columns) { if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny( - column.getHtmlType(), new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO })) + column.getHtmlType(), + new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX })) { dicts.add("'" + column.getDictType() + "'"); } diff --git a/ruoyi-generator/src/main/resources/vm/java/controller.java.vm b/ruoyi-generator/src/main/resources/vm/java/controller.java.vm index 56ff5e66a..ab19cf51f 100644 --- a/ruoyi-generator/src/main/resources/vm/java/controller.java.vm +++ b/ruoyi-generator/src/main/resources/vm/java/controller.java.vm @@ -1,6 +1,7 @@ package ${packageName}.controller; import java.util.List; +import javax.servlet.http.HttpServletResponse; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -61,12 +62,12 @@ public class ${ClassName}Controller extends BaseController */ @PreAuthorize("@ss.hasPermi('${permissionPrefix}:export')") @Log(title = "${functionName}", businessType = BusinessType.EXPORT) - @GetMapping("/export") - public AjaxResult export(${ClassName} ${className}) + @PostMapping("/export") + public void export(HttpServletResponse response, ${ClassName} ${className}) { List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); - return util.exportExcel(list, "${functionName}数据"); + util.exportExcel(response, list, "${functionName}数据"); } /** diff --git a/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm b/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm index 2fc79492e..116f6c544 100644 --- a/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm +++ b/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm @@ -129,6 +129,9 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service * @param ${pkColumn.javaField} ${functionName}主键 * @return 结果 */ +#if($table.sub) + @Transactional +#end @Override public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) { diff --git a/ruoyi-generator/src/main/resources/vm/js/api.js.vm b/ruoyi-generator/src/main/resources/vm/js/api.js.vm index cd2403cc6..d78cd2f64 100644 --- a/ruoyi-generator/src/main/resources/vm/js/api.js.vm +++ b/ruoyi-generator/src/main/resources/vm/js/api.js.vm @@ -42,12 +42,3 @@ export function del${BusinessName}(${pkColumn.javaField}) { method: 'delete' }) } - -// 导出${functionName} -export function export${BusinessName}(query) { - return request({ - url: '/${moduleName}/${businessName}/export', - method: 'get', - params: query - }) -} \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm index 19802cee8..adba807b4 100644 --- a/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm +++ b/ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm @@ -105,10 +105,20 @@ {{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }} +#elseif($column.list && $column.htmlType == "imageUpload") + + + #elseif($column.list && "" != $column.dictType) #elseif($column.list && "" != $javaField) @@ -170,11 +180,11 @@ #elseif($column.htmlType == "imageUpload") - + #elseif($column.htmlType == "fileUpload") - + #elseif($column.htmlType == "editor") @@ -259,7 +269,7 @@ diff --git a/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm new file mode 100644 index 000000000..08bb07110 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm @@ -0,0 +1,564 @@ + + + diff --git a/ruoyi-generator/src/main/resources/vm/vue/v3/readme.txt b/ruoyi-generator/src/main/resources/vm/vue/v3/readme.txt new file mode 100644 index 000000000..99239bb53 --- /dev/null +++ b/ruoyi-generator/src/main/resources/vm/vue/v3/readme.txt @@ -0,0 +1 @@ +ʹõRuoYi-Vue3ǰˣôҪһ´Ŀ¼ģindex.vue.vmindex-tree.vue.vmļϼvueĿ¼ \ No newline at end of file diff --git a/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm b/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm index 0c681d9cd..5b704e737 100644 --- a/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm +++ b/ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm @@ -23,7 +23,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #end - select#foreach($column in $columns) $column.columnName#if($velocityCount != $columns.size()),#end#end from ${tableName} + select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end from ${tableName} - select d.dept_id from sys_dept d left join sys_role_dept rd on d.dept_id = rd.dept_id diff --git a/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml index 9f9fcb66a..144adb1b9 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml @@ -84,7 +84,7 @@ order by m.parent_id, m.order_num - select m.menu_id from sys_menu m left join sys_role_menu rm on m.menu_id = rm.menu_id diff --git a/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml index 6ac8e7c36..2425ae058 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml @@ -46,7 +46,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where post_id = #{postId} - select p.post_id from sys_post p left join sys_user_post up on up.post_id = p.post_id diff --git a/ruoyi-ui/package.json b/ruoyi-ui/package.json index 034e0642d..fc15b2e15 100644 --- a/ruoyi-ui/package.json +++ b/ruoyi-ui/package.json @@ -1,6 +1,6 @@ { "name": "ruoyi", - "version": "3.7.0", + "version": "3.8.1", "description": "若依管理系统", "author": "若依", "license": "MIT", @@ -37,17 +37,17 @@ }, "dependencies": { "@riophae/vue-treeselect": "0.4.0", - "axios": "0.21.0", - "clipboard": "2.0.6", - "core-js": "3.8.1", + "axios": "0.24.0", + "clipboard": "2.0.8", + "core-js": "3.19.1", "echarts": "4.9.0", "element-ui": "2.15.6", "file-saver": "2.0.5", "fuse.js": "6.4.3", "highlight.js": "9.18.5", "js-beautify": "1.13.0", - "js-cookie": "2.2.1", - "jsencrypt": "3.0.0-rc.1", + "js-cookie": "3.0.1", + "jsencrypt": "3.2.1", "nprogress": "0.2.0", "quill": "1.3.7", "screenfull": "5.0.2", @@ -55,7 +55,7 @@ "vue": "2.6.12", "vue-count-to": "1.0.13", "vue-cropper": "0.5.5", - "vue-meta": "^2.4.0", + "vue-meta": "2.4.0", "vue-router": "3.4.9", "vuedraggable": "2.24.3", "vuex": "3.6.0" @@ -65,7 +65,9 @@ "@vue/cli-plugin-eslint": "4.4.6", "@vue/cli-service": "4.4.6", "babel-eslint": "10.1.0", + "babel-plugin-dynamic-import-node": "2.3.3", "chalk": "4.1.0", + "compression-webpack-plugin": "5.0.2", "connect": "3.6.6", "eslint": "7.15.0", "eslint-plugin-vue": "7.2.0", diff --git a/ruoyi-ui/src/api/login.js b/ruoyi-ui/src/api/login.js index 224561606..26742e79c 100644 --- a/ruoyi-ui/src/api/login.js +++ b/ruoyi-ui/src/api/login.js @@ -10,6 +10,9 @@ export function login(username, password, code, uuid) { } return request({ url: '/login', + headers: { + isToken: false + }, method: 'post', data: data }) @@ -47,6 +50,9 @@ export function logout() { export function getCodeImg() { return request({ url: '/captchaImage', + headers: { + isToken: false + }, method: 'get', timeout: 20000 }) diff --git a/ruoyi-ui/src/api/monitor/job.js b/ruoyi-ui/src/api/monitor/job.js index 58c43434a..38155693a 100644 --- a/ruoyi-ui/src/api/monitor/job.js +++ b/ruoyi-ui/src/api/monitor/job.js @@ -43,15 +43,6 @@ export function delJob(jobId) { }) } -// 导出定时任务调度 -export function exportJob(query) { - return request({ - url: '/monitor/job/export', - method: 'get', - params: query - }) -} - // 任务状态修改 export function changeJobStatus(jobId, status) { const data = { diff --git a/ruoyi-ui/src/api/monitor/jobLog.js b/ruoyi-ui/src/api/monitor/jobLog.js index be1fffdfa..6e0be6166 100644 --- a/ruoyi-ui/src/api/monitor/jobLog.js +++ b/ruoyi-ui/src/api/monitor/jobLog.js @@ -24,12 +24,3 @@ export function cleanJobLog() { method: 'delete' }) } - -// 导出调度日志 -export function exportJobLog(query) { - return request({ - url: '/monitor/jobLog/export', - method: 'get', - params: query - }) -} \ No newline at end of file diff --git a/ruoyi-ui/src/api/monitor/logininfor.js b/ruoyi-ui/src/api/monitor/logininfor.js index 0b89cdc6e..cd7815172 100644 --- a/ruoyi-ui/src/api/monitor/logininfor.js +++ b/ruoyi-ui/src/api/monitor/logininfor.js @@ -24,12 +24,3 @@ export function cleanLogininfor() { method: 'delete' }) } - -// 导出登录日志 -export function exportLogininfor(query) { - return request({ - url: '/monitor/logininfor/export', - method: 'get', - params: query - }) -} \ No newline at end of file diff --git a/ruoyi-ui/src/api/monitor/operlog.js b/ruoyi-ui/src/api/monitor/operlog.js index c519355ef..6e881dfe8 100644 --- a/ruoyi-ui/src/api/monitor/operlog.js +++ b/ruoyi-ui/src/api/monitor/operlog.js @@ -24,12 +24,3 @@ export function cleanOperlog() { method: 'delete' }) } - -// 导出操作日志 -export function exportOperlog(query) { - return request({ - url: '/monitor/operlog/export', - method: 'get', - params: query - }) -} \ No newline at end of file diff --git a/ruoyi-ui/src/api/monitor/server.js b/ruoyi-ui/src/api/monitor/server.js index 4991a4416..cac77916e 100644 --- a/ruoyi-ui/src/api/monitor/server.js +++ b/ruoyi-ui/src/api/monitor/server.js @@ -1,6 +1,6 @@ import request from '@/utils/request' -// 查询服务器详细 +// 获取服务信息 export function getServer() { return request({ url: '/monitor/server', diff --git a/ruoyi-ui/src/api/system/config.js b/ruoyi-ui/src/api/system/config.js index f1f872757..7858c6928 100644 --- a/ruoyi-ui/src/api/system/config.js +++ b/ruoyi-ui/src/api/system/config.js @@ -58,12 +58,3 @@ export function refreshCache() { method: 'delete' }) } - -// 导出参数 -export function exportConfig(query) { - return request({ - url: '/system/config/export', - method: 'get', - params: query - }) -} \ No newline at end of file diff --git a/ruoyi-ui/src/api/system/dict/data.js b/ruoyi-ui/src/api/system/dict/data.js index d3f8c2f20..2a6e48112 100644 --- a/ruoyi-ui/src/api/system/dict/data.js +++ b/ruoyi-ui/src/api/system/dict/data.js @@ -50,12 +50,3 @@ export function delData(dictCode) { method: 'delete' }) } - -// 导出字典数据 -export function exportData(query) { - return request({ - url: '/system/dict/data/export', - method: 'get', - params: query - }) -} \ No newline at end of file diff --git a/ruoyi-ui/src/api/system/dict/type.js b/ruoyi-ui/src/api/system/dict/type.js index 2c66bd5fb..526977d36 100644 --- a/ruoyi-ui/src/api/system/dict/type.js +++ b/ruoyi-ui/src/api/system/dict/type.js @@ -51,15 +51,6 @@ export function refreshCache() { }) } -// 导出字典类型 -export function exportType(query) { - return request({ - url: '/system/dict/type/export', - method: 'get', - params: query - }) -} - // 获取字典选择框列表 export function optionselect() { return request({ diff --git a/ruoyi-ui/src/api/system/post.js b/ruoyi-ui/src/api/system/post.js index fb124d961..8faa2669f 100644 --- a/ruoyi-ui/src/api/system/post.js +++ b/ruoyi-ui/src/api/system/post.js @@ -42,12 +42,3 @@ export function delPost(postId) { method: 'delete' }) } - -// 导出岗位 -export function exportPost(query) { - return request({ - url: '/system/post/export', - method: 'get', - params: query - }) -} \ No newline at end of file diff --git a/ruoyi-ui/src/api/system/role.js b/ruoyi-ui/src/api/system/role.js index aa426df76..b5ebdf6c0 100644 --- a/ruoyi-ui/src/api/system/role.js +++ b/ruoyi-ui/src/api/system/role.js @@ -65,15 +65,6 @@ export function delRole(roleId) { }) } -// 导出角色 -export function exportRole(query) { - return request({ - url: '/system/role/export', - method: 'get', - params: query - }) -} - // 查询角色已授权用户列表 export function allocatedUserList(query) { return request({ diff --git a/ruoyi-ui/src/api/system/user.js b/ruoyi-ui/src/api/system/user.js index 85bdc19cf..ad227f97b 100644 --- a/ruoyi-ui/src/api/system/user.js +++ b/ruoyi-ui/src/api/system/user.js @@ -1,5 +1,5 @@ import request from '@/utils/request' -import { praseStrEmpty } from "@/utils/ruoyi"; +import { parseStrEmpty } from "@/utils/ruoyi"; // 查询用户列表 export function listUser(query) { @@ -13,7 +13,7 @@ export function listUser(query) { // 查询用户详细 export function getUser(userId) { return request({ - url: '/system/user/' + praseStrEmpty(userId), + url: '/system/user/' + parseStrEmpty(userId), method: 'get' }) } @@ -44,15 +44,6 @@ export function delUser(userId) { }) } -// 导出用户 -export function exportUser(query) { - return request({ - url: '/system/user/export', - method: 'get', - params: query - }) -} - // 用户密码重置 export function resetUserPwd(userId, password) { const data = { @@ -118,14 +109,6 @@ export function uploadAvatar(data) { }) } -// 下载用户导入模板 -export function importTemplate() { - return request({ - url: '/system/user/importTemplate', - method: 'get' - }) -} - // 查询授权角色 export function getAuthRole(userId) { return request({ diff --git a/ruoyi-ui/src/components/Breadcrumb/index.vue b/ruoyi-ui/src/components/Breadcrumb/index.vue index 1fbae5f3c..1696f5471 100644 --- a/ruoyi-ui/src/components/Breadcrumb/index.vue +++ b/ruoyi-ui/src/components/Breadcrumb/index.vue @@ -2,7 +2,7 @@ - {{ item.meta.title }} + {{ item.meta.title }} {{ item.meta.title }} diff --git a/ruoyi-ui/src/components/Crontab/day.vue b/ruoyi-ui/src/components/Crontab/day.vue index bf9f5664e..fe3eaf0c4 100644 --- a/ruoyi-ui/src/components/Crontab/day.vue +++ b/ruoyi-ui/src/components/Crontab/day.vue @@ -2,7 +2,7 @@ - 日,允许的通配符[, - * / L M] + 日,允许的通配符[, - * ? / L W] @@ -15,23 +15,23 @@ 周期从 - - - 日 + - + 从 - 号开始,每 - 日执行一次 + 号开始,每 + 日执行一次 每月 - 号最近的那个工作日 + 号最近的那个工作日 @@ -72,31 +72,22 @@ export default { // 单选按钮值变化时 radioChange() { ('day rachange'); - if (this.radioValue === 1) { - this.$emit('update', 'day', '*', 'day'); - this.$emit('update', 'week', '?', 'day'); - this.$emit('update', 'month', '*', 'day'); - } else { - if (this.cron.hour === '*') { - this.$emit('update', 'hour', '0', 'day'); - } - if (this.cron.min === '*') { - this.$emit('update', 'min', '0', 'day'); - } - if (this.cron.second === '*') { - this.$emit('update', 'second', '0', 'day'); - } + if (this.radioValue !== 2 && this.cron.week !== '?') { + this.$emit('update', 'week', '?', 'day') } switch (this.radioValue) { + case 1: + this.$emit('update', 'day', '*'); + break; case 2: this.$emit('update', 'day', '?'); break; case 3: - this.$emit('update', 'day', this.cycle01 + '-' + this.cycle02); + this.$emit('update', 'day', this.cycleTotal); break; case 4: - this.$emit('update', 'day', this.average01 + '/' + this.average02); + this.$emit('update', 'day', this.averageTotal); break; case 5: this.$emit('update', 'day', this.workday + 'W'); @@ -125,7 +116,7 @@ export default { // 最近工作日值变化时 workdayChange() { if (this.radioValue == '5') { - this.$emit('update', 'day', this.workday + 'W'); + this.$emit('update', 'day', this.workdayCheck + 'W'); } }, // checkbox值变化时 @@ -133,19 +124,10 @@ export default { if (this.radioValue == '7') { this.$emit('update', 'day', this.checkboxString); } - }, - // 父组件传递的week发生变化触发 - weekChange() { - //判断week值与day不能同时为“?” - if (this.cron.week == '?' && this.radioValue == '2') { - this.radioValue = '1'; - } else if (this.cron.week !== '?' && this.radioValue != '2') { - this.radioValue = '2'; - } - }, + } }, watch: { - "radioValue": "radioChange", + 'radioValue': 'radioChange', 'cycleTotal': 'cycleChange', 'averageTotal': 'averageChange', 'workdayCheck': 'workdayChange', @@ -154,20 +136,20 @@ export default { computed: { // 计算两个周期值 cycleTotal: function () { - this.cycle01 = this.checkNum(this.cycle01, 1, 31) - this.cycle02 = this.checkNum(this.cycle02, 1, 31) - return this.cycle01 + '-' + this.cycle02; + const cycle01 = this.checkNum(this.cycle01, 1, 30) + const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 2, 31, 31) + return cycle01 + '-' + cycle02; }, // 计算平均用到的值 averageTotal: function () { - this.average01 = this.checkNum(this.average01, 1, 31) - this.average02 = this.checkNum(this.average02, 1, 31) - return this.average01 + '/' + this.average02; + const average01 = this.checkNum(this.average01, 1, 30) + const average02 = this.checkNum(this.average02, 1, 31 - average01 || 0) + return average01 + '/' + average02; }, // 计算工作日格式 workdayCheck: function () { - this.workday = this.checkNum(this.workday, 1, 31) - return this.workday; + const workday = this.checkNum(this.workday, 1, 31) + return workday; }, // 计算勾选的checkbox值合集 checkboxString: function () { diff --git a/ruoyi-ui/src/components/Crontab/hour.vue b/ruoyi-ui/src/components/Crontab/hour.vue index 5a1e14678..4b1f1fcdb 100644 --- a/ruoyi-ui/src/components/Crontab/hour.vue +++ b/ruoyi-ui/src/components/Crontab/hour.vue @@ -1,122 +1,114 @@ - - - \ No newline at end of file + + + diff --git a/ruoyi-ui/src/components/Crontab/index.vue b/ruoyi-ui/src/components/Crontab/index.vue index 27b4ab36c..3963df28e 100644 --- a/ruoyi-ui/src/components/Crontab/index.vue +++ b/ruoyi-ui/src/components/Crontab/index.vue @@ -2,7 +2,12 @@
- + @@ -268,7 +273,7 @@ export default { insValue = 5; } else { this.$refs[refName].checkboxList = value.split(","); - insValue = 7; + insValue = 6; } } else if (name == "year") { if (value == "") { diff --git a/ruoyi-ui/src/components/Crontab/min.vue b/ruoyi-ui/src/components/Crontab/min.vue index 980c4e768..0a106ce01 100644 --- a/ruoyi-ui/src/components/Crontab/min.vue +++ b/ruoyi-ui/src/components/Crontab/min.vue @@ -9,16 +9,16 @@ 周期从 - - - 分钟 + - + 分钟 从 - 分钟开始,每 - 分钟执行一次 + 分钟开始,每 + 分钟执行一次 @@ -52,19 +52,15 @@ export default { methods: { // 单选按钮值变化时 radioChange() { - if (this.radioValue !== 1 && this.cron.second === '*') { - this.$emit('update', 'second', '0', 'min'); - } switch (this.radioValue) { case 1: this.$emit('update', 'min', '*', 'min'); - this.$emit('update', 'hour', '*', 'min'); break; case 2: - this.$emit('update', 'min', this.cycle01 + '-' + this.cycle02, 'min'); + this.$emit('update', 'min', this.cycleTotal, 'min'); break; case 3: - this.$emit('update', 'min', this.average01 + '/' + this.average02, 'min'); + this.$emit('update', 'min', this.averageTotal, 'min'); break; case 4: this.$emit('update', 'min', this.checkboxString, 'min'); @@ -92,7 +88,7 @@ export default { }, watch: { - "radioValue": "radioChange", + 'radioValue': 'radioChange', 'cycleTotal': 'cycleChange', 'averageTotal': 'averageChange', 'checkboxString': 'checkboxChange', @@ -100,15 +96,15 @@ export default { computed: { // 计算两个周期值 cycleTotal: function () { - this.cycle01 = this.checkNum(this.cycle01, 0, 59) - this.cycle02 = this.checkNum(this.cycle02, 0, 59) - return this.cycle01 + '-' + this.cycle02; + const cycle01 = this.checkNum(this.cycle01, 0, 58) + const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59) + return cycle01 + '-' + cycle02; }, // 计算平均用到的值 averageTotal: function () { - this.average01 = this.checkNum(this.average01, 0, 59) - this.average02 = this.checkNum(this.average02, 1, 59) - return this.average01 + '/' + this.average02; + const average01 = this.checkNum(this.average01, 0, 58) + const average02 = this.checkNum(this.average02, 1, 59 - average01 || 0) + return average01 + '/' + average02; }, // 计算勾选的checkbox值合集 checkboxString: function () { diff --git a/ruoyi-ui/src/components/Crontab/month.vue b/ruoyi-ui/src/components/Crontab/month.vue index 619d1e791..fd0ac384f 100644 --- a/ruoyi-ui/src/components/Crontab/month.vue +++ b/ruoyi-ui/src/components/Crontab/month.vue @@ -9,16 +9,16 @@ 周期从 - - - 月 + - + 从 - 月开始,每 - 月月执行一次 + 月开始,每 + 月月执行一次 @@ -51,29 +51,15 @@ export default { methods: { // 单选按钮值变化时 radioChange() { - if (this.radioValue === 1) { - this.$emit('update', 'month', '*'); - this.$emit('update', 'year', '*'); - } else { - if (this.cron.day === '*') { - this.$emit('update', 'day', '0', 'month'); - } - if (this.cron.hour === '*') { - this.$emit('update', 'hour', '0', 'month'); - } - if (this.cron.min === '*') { - this.$emit('update', 'min', '0', 'month'); - } - if (this.cron.second === '*') { - this.$emit('update', 'second', '0', 'month'); - } - } switch (this.radioValue) { + case 1: + this.$emit('update', 'month', '*'); + break; case 2: - this.$emit('update', 'month', this.cycle01 + '-' + this.cycle02); + this.$emit('update', 'month', this.cycleTotal); break; case 3: - this.$emit('update', 'month', this.average01 + '/' + this.average02); + this.$emit('update', 'month', this.averageTotal); break; case 4: this.$emit('update', 'month', this.checkboxString); @@ -100,7 +86,7 @@ export default { } }, watch: { - "radioValue": "radioChange", + 'radioValue': 'radioChange', 'cycleTotal': 'cycleChange', 'averageTotal': 'averageChange', 'checkboxString': 'checkboxChange' @@ -108,15 +94,15 @@ export default { computed: { // 计算两个周期值 cycleTotal: function () { - this.cycle01 = this.checkNum(this.cycle01, 1, 12) - this.cycle02 = this.checkNum(this.cycle02, 1, 12) - return this.cycle01 + '-' + this.cycle02; + const cycle01 = this.checkNum(this.cycle01, 1, 11) + const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 2, 12) + return cycle01 + '-' + cycle02; }, // 计算平均用到的值 averageTotal: function () { - this.average01 = this.checkNum(this.average01, 1, 12) - this.average02 = this.checkNum(this.average02, 1, 12) - return this.average01 + '/' + this.average02; + const average01 = this.checkNum(this.average01, 1, 11) + const average02 = this.checkNum(this.average02, 1, 12 - average01 || 0) + return average01 + '/' + average02; }, // 计算勾选的checkbox值合集 checkboxString: function () { diff --git a/ruoyi-ui/src/components/Crontab/result.vue b/ruoyi-ui/src/components/Crontab/result.vue index 07b963b79..aea6e0e46 100644 --- a/ruoyi-ui/src/components/Crontab/result.vue +++ b/ruoyi-ui/src/components/Crontab/result.vue @@ -179,7 +179,7 @@ export default { // 获取达到条件的日期是星期X let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week'); // 当星期日时 - if (thisWeek == 0) { + if (thisWeek == 1) { // 先找下一个日,并判断是否为月底 DD++; thisDD = DD < 10 ? '0' + DD : DD; @@ -187,7 +187,7 @@ export default { if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) { DD -= 3; } - } else if (thisWeek == 6) { + } else if (thisWeek == 7) { // 当星期6时只需判断不是1号就可进行操作 if (this.dayRuleSup !== 1) { DD--; @@ -200,7 +200,7 @@ export default { // 获取当前日期是属于星期几 let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week'); // 校验当前星期是否在星期池(dayRuleSup)中 - if (Array.indexOf(this.dayRuleSup, thisWeek) < 0) { + if (this.dayRuleSup.indexOf(thisWeek) < 0) { // 如果到达最大值时 if (Di == DDate.length - 1) { resetDay(); @@ -385,7 +385,7 @@ export default { } else if (rule.indexOf('#') >= 0) { this.dayRule = 'assWeek'; let matchRule = rule.match(/[0-9]{1}/g); - this.dayRuleSup = [Number(matchRule[0]), Number(matchRule[1])]; + this.dayRuleSup = [Number(matchRule[1]), Number(matchRule[0])]; this.dateArr[3] = [1]; if (this.dayRuleSup[1] == 7) { this.dayRuleSup[1] = 0; @@ -401,14 +401,6 @@ export default { this.dayRule = 'weekDay'; this.dayRuleSup = this.getAssignArr(rule) } - // 如果weekDay时将7调整为0【week值0即是星期日】 - if (this.dayRule == 'weekDay') { - for (let i = 0; i < this.dayRuleSup.length; i++) { - if (this.dayRuleSup[i] == 7) { - this.dayRuleSup[i] = 0; - } - } - } } }, // 获取"日"数组-少量为日期规则 @@ -543,14 +535,15 @@ export default { if (type == undefined) { return Y + '-' + (M < 10 ? '0' + M : M) + '-' + (D < 10 ? '0' + D : D) + ' ' + (h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s); } else if (type == 'week') { - return week; + // 在quartz中 1为星期日 + return week + 1; } }, // 检查日期是否存在 checkDate(value) { let time = new Date(value); let format = this.formatDate(time) - return value == format ? true : false; + return value === format; } }, watch: { diff --git a/ruoyi-ui/src/components/Crontab/second.vue b/ruoyi-ui/src/components/Crontab/second.vue index 0fdf3386d..e7b776171 100644 --- a/ruoyi-ui/src/components/Crontab/second.vue +++ b/ruoyi-ui/src/components/Crontab/second.vue @@ -9,16 +9,16 @@ 周期从 - - - 秒 + - + 从 - 秒开始,每 - 秒执行一次 + 秒开始,每 + 秒执行一次 @@ -54,13 +54,12 @@ export default { switch (this.radioValue) { case 1: this.$emit('update', 'second', '*', 'second'); - this.$emit('update', 'min', '*', 'second'); break; case 2: - this.$emit('update', 'second', this.cycle01 + '-' + this.cycle02); + this.$emit('update', 'second', this.cycleTotal); break; case 3: - this.$emit('update', 'second', this.average01 + '/' + this.average02); + this.$emit('update', 'second', this.averageTotal); break; case 4: this.$emit('update', 'second', this.checkboxString); @@ -84,25 +83,10 @@ export default { if (this.radioValue == '4') { this.$emit('update', 'second', this.checkboxString); } - }, - othChange() { - // 反解析 - let ins = this.cron.second - ('反解析 second', ins); - if (ins === '*') { - this.radioValue = 1; - } else if (ins.indexOf('-') > -1) { - this.radioValue = 2 - } else if (ins.indexOf('/') > -1) { - this.radioValue = 3 - } else { - this.radioValue = 4 - this.checkboxList = ins.split(',') - } } }, watch: { - "radioValue": "radioChange", + 'radioValue': 'radioChange', 'cycleTotal': 'cycleChange', 'averageTotal': 'averageChange', 'checkboxString': 'checkboxChange', @@ -113,15 +97,15 @@ export default { computed: { // 计算两个周期值 cycleTotal: function () { - this.cycle01 = this.checkNum(this.cycle01, 0, 59) - this.cycle02 = this.checkNum(this.cycle02, 0, 59) - return this.cycle01 + '-' + this.cycle02; + const cycle01 = this.checkNum(this.cycle01, 0, 58) + const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59) + return cycle01 + '-' + cycle02; }, // 计算平均用到的值 averageTotal: function () { - this.average01 = this.checkNum(this.average01, 0, 59) - this.average02 = this.checkNum(this.average02, 1, 59) - return this.average01 + '/' + this.average02; + const average01 = this.checkNum(this.average01, 0, 58) + const average02 = this.checkNum(this.average02, 1, 59 - average01 || 0) + return average01 + '/' + average02; }, // 计算勾选的checkbox值合集 checkboxString: function () { diff --git a/ruoyi-ui/src/components/Crontab/week.vue b/ruoyi-ui/src/components/Crontab/week.vue index 5ad949d6b..1cec700e8 100644 --- a/ruoyi-ui/src/components/Crontab/week.vue +++ b/ruoyi-ui/src/components/Crontab/week.vue @@ -2,7 +2,7 @@ - 周,允许的通配符[, - * / L #] + 周,允许的通配符[, - * ? / L #] @@ -15,8 +15,25 @@ 周期从星期 - - - + + {{item.value}} + + - + + {{item.value}} + @@ -24,14 +41,18 @@ 周的星期 - + + {{item.value}} + 本月最后一个星期 - + + {{item.value}} + @@ -39,7 +60,7 @@ 指定 - {{item}} + {{item.value}} @@ -52,13 +73,42 @@ export default { data() { return { radioValue: 2, - weekday: 1, - cycle01: 1, - cycle02: 2, + weekday: 2, + cycle01: 2, + cycle02: 3, average01: 1, - average02: 1, + average02: 2, checkboxList: [], - weekList: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], + weekList: [ + { + key: 2, + value: '星期一' + }, + { + key: 3, + value: '星期二' + }, + { + key: 4, + value: '星期三' + }, + { + key: 5, + value: '星期四' + }, + { + key: 6, + value: '星期五' + }, + { + key: 7, + value: '星期六' + }, + { + key: 1, + value: '星期日' + } + ], checkNum: this.$options.propsData.check } }, @@ -67,45 +117,30 @@ export default { methods: { // 单选按钮值变化时 radioChange() { - if (this.radioValue === 1) { - this.$emit('update', 'week', '*'); - this.$emit('update', 'year', '*'); - } else { - if (this.cron.month === '*') { - this.$emit('update', 'month', '0', 'week'); - } - if (this.cron.day === '*') { - this.$emit('update', 'day', '0', 'week'); - } - if (this.cron.hour === '*') { - this.$emit('update', 'hour', '0', 'week'); - } - if (this.cron.min === '*') { - this.$emit('update', 'min', '0', 'week'); - } - if (this.cron.second === '*') { - this.$emit('update', 'second', '0', 'week'); - } + if (this.radioValue !== 2 && this.cron.day !== '?') { + this.$emit('update', 'day', '?', 'week'); } switch (this.radioValue) { + case 1: + this.$emit('update', 'week', '*'); + break; case 2: this.$emit('update', 'week', '?'); break; case 3: - this.$emit('update', 'week', this.cycle01 + '-' + this.cycle02); + this.$emit('update', 'week', this.cycleTotal); break; case 4: - this.$emit('update', 'week', this.average01 + '#' + this.average02); + this.$emit('update', 'week', this.averageTotal); break; case 5: - this.$emit('update', 'week', this.weekday + 'L'); + this.$emit('update', 'week', this.weekdayCheck + 'L'); break; case 6: this.$emit('update', 'week', this.checkboxString); break; } }, - // 根据互斥事件,更改radio的值 // 周期两个值变化时 cycleChange() { @@ -133,7 +168,7 @@ export default { }, }, watch: { - "radioValue": "radioChange", + 'radioValue': 'radioChange', 'cycleTotal': 'cycleChange', 'averageTotal': 'averageChange', 'weekdayCheck': 'weekdayChange', @@ -150,7 +185,7 @@ export default { averageTotal: function () { this.average01 = this.checkNum(this.average01, 1, 4) this.average02 = this.checkNum(this.average02, 1, 7) - return this.average01 + '#' + this.average02; + return this.average02 + '#' + this.average01; }, // 最近的工作日(格式) weekdayCheck: function () { diff --git a/ruoyi-ui/src/components/Crontab/year.vue b/ruoyi-ui/src/components/Crontab/year.vue index 800dfa522..5487a6c7f 100644 --- a/ruoyi-ui/src/components/Crontab/year.vue +++ b/ruoyi-ui/src/components/Crontab/year.vue @@ -15,16 +15,16 @@ 周期从 - - - + - + 从 - 年开始,每 - 年执行一次 + 年开始,每 + 年执行一次 @@ -59,21 +59,6 @@ export default { methods: { // 单选按钮值变化时 radioChange() { - if (this.cron.month === '*') { - this.$emit('update', 'month', '0', 'year'); - } - if (this.cron.day === '*') { - this.$emit('update', 'day', '0', 'year'); - } - if (this.cron.hour === '*') { - this.$emit('update', 'hour', '0', 'year'); - } - if (this.cron.min === '*') { - this.$emit('update', 'min', '0', 'year'); - } - if (this.cron.second === '*') { - this.$emit('update', 'second', '0', 'year'); - } switch (this.radioValue) { case 1: this.$emit('update', 'year', ''); @@ -82,10 +67,10 @@ export default { this.$emit('update', 'year', '*'); break; case 3: - this.$emit('update', 'year', this.cycle01 + '-' + this.cycle02); + this.$emit('update', 'year', this.cycleTotal); break; case 4: - this.$emit('update', 'year', this.average01 + '/' + this.average02); + this.$emit('update', 'year', this.averageTotal); break; case 5: this.$emit('update', 'year', this.checkboxString); @@ -112,7 +97,7 @@ export default { } }, watch: { - "radioValue": "radioChange", + 'radioValue': 'radioChange', 'cycleTotal': 'cycleChange', 'averageTotal': 'averageChange', 'checkboxString': 'checkboxChange' @@ -120,15 +105,15 @@ export default { computed: { // 计算两个周期值 cycleTotal: function () { - this.cycle01 = this.checkNum(this.cycle01, this.fullYear, this.fullYear + 100) - this.cycle02 = this.checkNum(this.cycle02, this.fullYear + 1, this.fullYear + 101) - return this.cycle01 + '-' + this.cycle02; + const cycle01 = this.checkNum(this.cycle01, this.fullYear, 2098) + const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : this.fullYear + 1, 2099) + return cycle01 + '-' + cycle02; }, // 计算平均用到的值 averageTotal: function () { - this.average01 = this.checkNum(this.average01, this.fullYear, this.fullYear + 100) - this.average02 = this.checkNum(this.average02, 1, 10) - return this.average01 + '/' + this.average02; + const average01 = this.checkNum(this.average01, this.fullYear, 2098) + const average02 = this.checkNum(this.average02, 1, 2099 - average01 || this.fullYear) + return average01 + '/' + average02; }, // 计算勾选的checkbox值合集 checkboxString: function () { @@ -139,6 +124,8 @@ export default { mounted: function () { // 仅获取当前年份 this.fullYear = Number(new Date().getFullYear()); + this.cycle01 = this.fullYear + this.average01 = this.fullYear } } diff --git a/ruoyi-ui/src/components/ImagePreview/index.vue b/ruoyi-ui/src/components/ImagePreview/index.vue new file mode 100644 index 000000000..743d8d51d --- /dev/null +++ b/ruoyi-ui/src/components/ImagePreview/index.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/ruoyi-ui/src/components/RightToolbar/index.vue b/ruoyi-ui/src/components/RightToolbar/index.vue index c7ab139d7..976974e4a 100644 --- a/ruoyi-ui/src/components/RightToolbar/index.vue +++ b/ruoyi-ui/src/components/RightToolbar/index.vue @@ -62,7 +62,7 @@ export default { }, // 右侧列表元素变化 dataChange(data) { - for (var item in this.columns) { + for (let item in this.columns) { const key = this.columns[item].key; this.columns[item].visible = !data.includes(key); } diff --git a/ruoyi-ui/src/components/RuoYi/Doc/index.vue b/ruoyi-ui/src/components/RuoYi/Doc/index.vue index 3915c2965..cc829b243 100644 --- a/ruoyi-ui/src/components/RuoYi/Doc/index.vue +++ b/ruoyi-ui/src/components/RuoYi/Doc/index.vue @@ -1,6 +1,6 @@ diff --git a/ruoyi-ui/src/components/RuoYi/Git/index.vue b/ruoyi-ui/src/components/RuoYi/Git/index.vue index 2aab63c15..517c44865 100644 --- a/ruoyi-ui/src/components/RuoYi/Git/index.vue +++ b/ruoyi-ui/src/components/RuoYi/Git/index.vue @@ -1,6 +1,6 @@ diff --git a/ruoyi-ui/src/components/SizeSelect/index.vue b/ruoyi-ui/src/components/SizeSelect/index.vue index 5503b9712..00496f71d 100644 --- a/ruoyi-ui/src/components/SizeSelect/index.vue +++ b/ruoyi-ui/src/components/SizeSelect/index.vue @@ -5,8 +5,7 @@
- {{ - item.label }} + {{ item.label }} diff --git a/ruoyi-ui/src/directive/index.js b/ruoyi-ui/src/directive/index.js index 030fd4fe2..b2901d7e2 100644 --- a/ruoyi-ui/src/directive/index.js +++ b/ruoyi-ui/src/directive/index.js @@ -3,10 +3,12 @@ import hasPermi from './permission/hasPermi' import dialogDrag from './dialog/drag' import dialogDragWidth from './dialog/dragWidth' import dialogDragHeight from './dialog/dragHeight' +import clipboard from './module/clipboard' const install = function(Vue) { Vue.directive('hasRole', hasRole) Vue.directive('hasPermi', hasPermi) + Vue.directive('clipboard', clipboard) Vue.directive('dialogDrag', dialogDrag) Vue.directive('dialogDragWidth', dialogDragWidth) Vue.directive('dialogDragHeight', dialogDragHeight) diff --git a/ruoyi-ui/src/directive/module/clipboard.js b/ruoyi-ui/src/directive/module/clipboard.js new file mode 100644 index 000000000..635315a84 --- /dev/null +++ b/ruoyi-ui/src/directive/module/clipboard.js @@ -0,0 +1,54 @@ +/** +* v-clipboard 文字复制剪贴 +* Copyright (c) 2021 ruoyi +*/ + +import Clipboard from 'clipboard' +export default { + bind(el, binding, vnode) { + switch (binding.arg) { + case 'success': + el._vClipBoard_success = binding.value; + break; + case 'error': + el._vClipBoard_error = binding.value; + break; + default: { + const clipboard = new Clipboard(el, { + text: () => binding.value, + action: () => binding.arg === 'cut' ? 'cut' : 'copy' + }); + clipboard.on('success', e => { + const callback = el._vClipBoard_success; + callback && callback(e); + }); + clipboard.on('error', e => { + const callback = el._vClipBoard_error; + callback && callback(e); + }); + el._vClipBoard = clipboard; + } + } + }, + update(el, binding) { + if (binding.arg === 'success') { + el._vClipBoard_success = binding.value; + } else if (binding.arg === 'error') { + el._vClipBoard_error = binding.value; + } else { + el._vClipBoard.text = function () { return binding.value; }; + el._vClipBoard.action = () => binding.arg === 'cut' ? 'cut' : 'copy'; + } + }, + unbind(el, binding) { + if (!el._vClipboard) return + if (binding.arg === 'success') { + delete el._vClipBoard_success; + } else if (binding.arg === 'error') { + delete el._vClipBoard_error; + } else { + el._vClipBoard.destroy(); + delete el._vClipBoard; + } + } +} diff --git a/ruoyi-ui/src/layout/components/Settings/index.vue b/ruoyi-ui/src/layout/components/Settings/index.vue index 4dff1d0c5..bd2f553cf 100644 --- a/ruoyi-ui/src/layout/components/Settings/index.vue +++ b/ruoyi-ui/src/layout/components/Settings/index.vue @@ -162,7 +162,7 @@ export default { this.sideTheme = val; }, saveSetting() { - this.$modal.loading("正在保存到本地,请稍后..."); + this.$modal.loading("正在保存到本地,请稍候..."); this.$cache.local.set( "layout-setting", `{ @@ -178,7 +178,7 @@ export default { setTimeout(this.$modal.closeLoading(), 1000) }, resetSetting() { - this.$modal.loading("正在清除设置缓存并刷新,请稍后..."); + this.$modal.loading("正在清除设置缓存并刷新,请稍候..."); this.$cache.local.remove("layout-setting") setTimeout("window.location.reload()", 1000) } diff --git a/ruoyi-ui/src/layout/components/Sidebar/Logo.vue b/ruoyi-ui/src/layout/components/Sidebar/Logo.vue index 82f3d581b..c8401c519 100644 --- a/ruoyi-ui/src/layout/components/Sidebar/Logo.vue +++ b/ruoyi-ui/src/layout/components/Sidebar/Logo.vue @@ -29,7 +29,7 @@ export default { variables() { return variables; }, - sideTheme() { + sideTheme() { return this.$store.state.settings.sideTheme } }, diff --git a/ruoyi-ui/src/layout/components/TagsView/index.vue b/ruoyi-ui/src/layout/components/TagsView/index.vue index e43aa2e4a..2381d2e09 100644 --- a/ruoyi-ui/src/layout/components/TagsView/index.vue +++ b/ruoyi-ui/src/layout/components/TagsView/index.vue @@ -152,31 +152,24 @@ export default { }) }, refreshSelectedTag(view) { - this.$store.dispatch('tagsView/delCachedView', view).then(() => { - const { fullPath } = view - this.$nextTick(() => { - this.$router.replace({ - path: '/redirect' + fullPath - }) - }) - }) + this.$tab.refreshPage(view); }, closeSelectedTag(view) { - this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => { + this.$tab.closePage(view).then(({ visitedViews }) => { if (this.isActive(view)) { this.toLastView(visitedViews, view) } }) }, closeRightTags() { - this.$store.dispatch('tagsView/delRightTags', this.selectedTag).then(visitedViews => { + this.$tab.closeRightPage(this.selectedTag).then(visitedViews => { if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) { this.toLastView(visitedViews) } }) }, closeLeftTags() { - this.$store.dispatch('tagsView/delLeftTags', this.selectedTag).then(visitedViews => { + this.$tab.closeLeftPage(this.selectedTag).then(visitedViews => { if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) { this.toLastView(visitedViews) } @@ -184,12 +177,12 @@ export default { }, closeOthersTags() { this.$router.push(this.selectedTag).catch(()=>{}); - this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => { + this.$tab.closeOtherPage(this.selectedTag).then(() => { this.moveToCurrentTag() }) }, closeAllTags(view) { - this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => { + this.$tab.closeAllPage().then(({ visitedViews }) => { if (this.affixTags.some(tag => tag.path === this.$route.path)) { return } diff --git a/ruoyi-ui/src/layout/index.vue b/ruoyi-ui/src/layout/index.vue index 718723ea4..4d6fe0244 100644 --- a/ruoyi-ui/src/layout/index.vue +++ b/ruoyi-ui/src/layout/index.vue @@ -98,7 +98,7 @@ export default { } .hideSidebar .fixed-header { - width: calc(100% - 54px) + width: calc(100% - 54px); } .mobile .fixed-header { diff --git a/ruoyi-ui/src/main.js b/ruoyi-ui/src/main.js index 489600aa4..ebd94b9d8 100644 --- a/ruoyi-ui/src/main.js +++ b/ruoyi-ui/src/main.js @@ -10,8 +10,9 @@ import '@/assets/styles/ruoyi.scss' // ruoyi css import App from './App' import store from './store' import router from './router' -import directive from './directive' //directive +import directive from './directive' // directive import plugins from './plugins' // plugins +import { download } from '@/utils/request' import './assets/icons' // icon import './permission' // permission control @@ -28,6 +29,8 @@ import Editor from "@/components/Editor" import FileUpload from "@/components/FileUpload" // 图片上传组件 import ImageUpload from "@/components/ImageUpload" +// 图片预览组件 +import ImagePreview from "@/components/ImagePreview" // 字典标签组件 import DictTag from '@/components/DictTag' // 头部标签组件 @@ -43,6 +46,7 @@ Vue.prototype.resetForm = resetForm Vue.prototype.addDateRange = addDateRange Vue.prototype.selectDictLabel = selectDictLabel Vue.prototype.selectDictLabels = selectDictLabels +Vue.prototype.download = download Vue.prototype.handleTree = handleTree // 全局组件挂载 @@ -52,6 +56,7 @@ Vue.component('RightToolbar', RightToolbar) Vue.component('Editor', Editor) Vue.component('FileUpload', FileUpload) Vue.component('ImageUpload', ImageUpload) +Vue.component('ImagePreview', ImagePreview) Vue.use(directive) Vue.use(plugins) diff --git a/ruoyi-ui/src/plugins/auth.js b/ruoyi-ui/src/plugins/auth.js new file mode 100644 index 000000000..af740aaef --- /dev/null +++ b/ruoyi-ui/src/plugins/auth.js @@ -0,0 +1,60 @@ +import store from '@/store' + +function authPermission(permission) { + const all_permission = "*:*:*"; + const permissions = store.getters && store.getters.permissions + if (permission && permission.length > 0) { + return permissions.some(v => { + return all_permission === v || v === permission + }) + } else { + return false + } +} + +function authRole(role) { + const super_admin = "admin"; + const roles = store.getters && store.getters.roles + if (role && role.length > 0) { + return roles.some(v => { + return super_admin === v || v === role + }) + } else { + return false + } +} + +export default { + // 验证用户是否具备某权限 + hasPermi(permission) { + return authPermission(permission); + }, + // 验证用户是否含有指定权限,只需包含其中一个 + hasPermiOr(permissions) { + return permissions.some(item => { + return authPermission(item) + }) + }, + // 验证用户是否含有指定权限,必须全部拥有 + hasPermiAnd(permissions) { + return permissions.every(item => { + return authPermission(item) + }) + }, + // 验证用户是否具备某角色 + hasRole(role) { + return authRole(role); + }, + // 验证用户是否含有指定角色,只需包含其中一个 + hasRoleOr(roles) { + return roles.some(item => { + return authRole(item) + }) + }, + // 验证用户是否含有指定角色,必须全部拥有 + hasRoleAnd(roles) { + return roles.every(item => { + return authRole(item) + }) + } +} diff --git a/ruoyi-ui/src/plugins/download.js b/ruoyi-ui/src/plugins/download.js index cb10ab0e4..86e20311e 100644 --- a/ruoyi-ui/src/plugins/download.js +++ b/ruoyi-ui/src/plugins/download.js @@ -1,6 +1,9 @@ -import { saveAs } from 'file-saver' import axios from 'axios' +import { Message } from 'element-ui' +import { saveAs } from 'file-saver' import { getToken } from '@/utils/auth' +import errorCode from '@/utils/errorCode' +import { blobValidate } from "@/utils/ruoyi"; const baseURL = process.env.VUE_APP_BASE_API @@ -12,9 +15,14 @@ export default { url: url, responseType: 'blob', headers: { 'Authorization': 'Bearer ' + getToken() } - }).then(res => { - const blob = new Blob([res.data]) - this.saveAs(blob, decodeURI(res.headers['download-filename'])) + }).then(async (res) => { + const isLogin = await blobValidate(res.data); + if (isLogin) { + const blob = new Blob([res.data]) + this.saveAs(blob, decodeURI(res.headers['download-filename'])) + } else { + this.printErrMsg(res.data); + } }) }, resource(resource) { @@ -24,9 +32,14 @@ export default { url: url, responseType: 'blob', headers: { 'Authorization': 'Bearer ' + getToken() } - }).then(res => { - const blob = new Blob([res.data]) - this.saveAs(blob, decodeURI(res.headers['download-filename'])) + }).then(async (res) => { + const isLogin = await blobValidate(res.data); + if (isLogin) { + const blob = new Blob([res.data]) + this.saveAs(blob, decodeURI(res.headers['download-filename'])) + } else { + this.printErrMsg(res.data); + } }) }, zip(url, name) { @@ -36,13 +49,24 @@ export default { url: url, responseType: 'blob', headers: { 'Authorization': 'Bearer ' + getToken() } - }).then(res => { - const blob = new Blob([res.data], { type: 'application/zip' }) - this.saveAs(blob, name) + }).then(async (res) => { + const isLogin = await blobValidate(res.data); + if (isLogin) { + const blob = new Blob([res.data], { type: 'application/zip' }) + this.saveAs(blob, name) + } else { + this.printErrMsg(res.data); + } }) }, saveAs(text, name, opts) { saveAs(text, name, opts); + }, + async printErrMsg(data) { + const resText = await data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + Message.error(errMsg); } } diff --git a/ruoyi-ui/src/plugins/index.js b/ruoyi-ui/src/plugins/index.js index a138e6d6f..9bc6eacd3 100644 --- a/ruoyi-ui/src/plugins/index.js +++ b/ruoyi-ui/src/plugins/index.js @@ -1,9 +1,15 @@ +import tab from './tab' +import auth from './auth' import cache from './cache' import modal from './modal' import download from './download' export default { install(Vue) { + // 页签操作 + Vue.prototype.$tab = tab + // 认证对象 + Vue.prototype.$auth = auth // 缓存对象 Vue.prototype.$cache = cache // 模态框对象 diff --git a/ruoyi-ui/src/plugins/modal.js b/ruoyi-ui/src/plugins/modal.js index 7df61a890..503a16f46 100644 --- a/ruoyi-ui/src/plugins/modal.js +++ b/ruoyi-ui/src/plugins/modal.js @@ -59,6 +59,14 @@ export default { type: "warning", }) }, + // 提交内容 + prompt(content) { + return MessageBox.prompt(content, "系统提示", { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: "warning", + }) + }, // 打开遮罩层 loading(content) { loadingInstance = Loading.service({ diff --git a/ruoyi-ui/src/plugins/tab.js b/ruoyi-ui/src/plugins/tab.js new file mode 100644 index 000000000..c8058a948 --- /dev/null +++ b/ruoyi-ui/src/plugins/tab.js @@ -0,0 +1,67 @@ +import store from '@/store' +import router from '@/router'; + +export default { + // 刷新当前tab页签 + refreshPage(obj) { + const { path, query, matched } = router.currentRoute; + if (obj === undefined) { + matched.forEach((m) => { + if (m.components && m.components.default && m.components.default.name) { + if (!['Layout', 'ParentView'].includes(m.components.default.name)) { + obj = { name: m.components.default.name, path: path, query: query }; + } + } + }); + } + return store.dispatch('tagsView/delCachedView', obj).then(() => { + const { path, query } = obj + router.replace({ + path: '/redirect' + path, + query: query + }) + }) + }, + // 关闭当前tab页签,打开新页签 + closeOpenPage(obj) { + store.dispatch("tagsView/delView", router.currentRoute); + if (obj !== undefined) { + return router.push(obj); + } + }, + // 关闭指定tab页签 + closePage(obj) { + if (obj === undefined) { + return store.dispatch('tagsView/delView', router.currentRoute).then(({ lastPath }) => { + return router.push(lastPath || '/'); + }); + } + return store.dispatch('tagsView/delView', obj); + }, + // 关闭所有tab页签 + closeAllPage() { + return store.dispatch('tagsView/delAllViews'); + }, + // 关闭左侧tab页签 + closeLeftPage(obj) { + return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute); + }, + // 关闭右侧tab页签 + closeRightPage(obj) { + return store.dispatch('tagsView/delRightTags', obj || router.currentRoute); + }, + // 关闭其他tab页签 + closeOtherPage(obj) { + return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute); + }, + // 添加tab页签 + openPage(title, url) { + var obj = { path: url, meta: { title: title } } + store.dispatch('tagsView/addView', obj); + return router.push(url); + }, + // 修改tab页签 + updatePage(obj) { + return store.dispatch('tagsView/updateVisitedView', obj); + } +} diff --git a/ruoyi-ui/src/router/index.js b/ruoyi-ui/src/router/index.js index a826980f2..e4ccf19a6 100644 --- a/ruoyi-ui/src/router/index.js +++ b/ruoyi-ui/src/router/index.js @@ -17,6 +17,8 @@ import Layout from '@/layout' * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 * name:'router-name' // 设定路由的名字,一定要填写不然使用时会出现各种问题 * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数 + * roles: ['admin', 'common'] // 访问路由的角色权限 + * permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限 * meta : { noCache: true // 如果设置为true,则不会被 缓存(默认 false) title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字 @@ -35,28 +37,28 @@ export const constantRoutes = [ children: [ { path: '/redirect/:path(.*)', - component: (resolve) => require(['@/views/redirect'], resolve) + component: () => import('@/views/redirect') } ] }, { path: '/login', - component: (resolve) => require(['@/views/login'], resolve), + component: () => import('@/views/login'), hidden: true }, { path: '/register', - component: (resolve) => require(['@/views/register'], resolve), + component: () => import('@/views/register'), hidden: true }, { path: '/404', - component: (resolve) => require(['@/views/error/404'], resolve), + component: () => import('@/views/error/404'), hidden: true }, { path: '/401', - component: (resolve) => require(['@/views/error/401'], resolve), + component: () => import('@/views/error/401'), hidden: true }, { @@ -66,7 +68,7 @@ export const constantRoutes = [ children: [ { path: 'index', - component: (resolve) => require(['@/views/index'], resolve), + component: () => import('@/views/index'), name: 'Index', meta: { title: '首页', icon: 'dashboard', affix: true } } @@ -80,22 +82,27 @@ export const constantRoutes = [ children: [ { path: 'profile', - component: (resolve) => require(['@/views/system/user/profile/index'], resolve), + component: () => import('@/views/system/user/profile/index'), name: 'Profile', meta: { title: '个人中心', icon: 'user' } } ] - }, + } +] + +// 动态路由,基于用户权限动态去加载 +export const dynamicRoutes = [ { path: '/system/user-auth', component: Layout, hidden: true, + permissions: ['system:user:edit'], children: [ { path: 'role/:userId(\\d+)', - component: (resolve) => require(['@/views/system/user/authRole'], resolve), + component: () => import('@/views/system/user/authRole'), name: 'AuthRole', - meta: { title: '分配角色', activeMenu: '/system/user'} + meta: { title: '分配角色', activeMenu: '/system/user' } } ] }, @@ -103,12 +110,13 @@ export const constantRoutes = [ path: '/system/role-auth', component: Layout, hidden: true, + permissions: ['system:role:edit'], children: [ { path: 'user/:roleId(\\d+)', - component: (resolve) => require(['@/views/system/role/authUser'], resolve), + component: () => import('@/views/system/role/authUser'), name: 'AuthUser', - meta: { title: '分配用户', activeMenu: '/system/role'} + meta: { title: '分配用户', activeMenu: '/system/role' } } ] }, @@ -116,12 +124,13 @@ export const constantRoutes = [ path: '/system/dict-data', component: Layout, hidden: true, + permissions: ['system:dict:list'], children: [ { path: 'index/:dictId(\\d+)', - component: (resolve) => require(['@/views/system/dict/data'], resolve), + component: () => import('@/views/system/dict/data'), name: 'Data', - meta: { title: '字典数据', activeMenu: '/system/dict'} + meta: { title: '字典数据', activeMenu: '/system/dict' } } ] }, @@ -129,12 +138,13 @@ export const constantRoutes = [ path: '/monitor/job-log', component: Layout, hidden: true, + permissions: ['monitor:job:list'], children: [ { path: 'index', - component: (resolve) => require(['@/views/monitor/job/log'], resolve), + component: () => import('@/views/monitor/job/log'), name: 'JobLog', - meta: { title: '调度日志', activeMenu: '/monitor/job'} + meta: { title: '调度日志', activeMenu: '/monitor/job' } } ] }, @@ -142,12 +152,13 @@ export const constantRoutes = [ path: '/tool/gen-edit', component: Layout, hidden: true, + permissions: ['tool:gen:edit'], children: [ { path: 'index', - component: (resolve) => require(['@/views/tool/gen/editTable'], resolve), + component: () => import('@/views/tool/gen/editTable'), name: 'GenEdit', - meta: { title: '修改生成配置', activeMenu: '/tool/gen'} + meta: { title: '修改生成配置', activeMenu: '/tool/gen' } } ] } diff --git a/ruoyi-ui/src/store/modules/permission.js b/ruoyi-ui/src/store/modules/permission.js index 4c8ed023b..8c3c33909 100644 --- a/ruoyi-ui/src/store/modules/permission.js +++ b/ruoyi-ui/src/store/modules/permission.js @@ -1,7 +1,8 @@ -import { constantRoutes } from '@/router' +import auth from '@/plugins/auth' +import router, { constantRoutes, dynamicRoutes } from '@/router' import { getRouters } from '@/api/menu' import Layout from '@/layout/index' -import ParentView from '@/components/ParentView'; +import ParentView from '@/components/ParentView' import InnerLink from '@/layout/components/InnerLink' const permission = { @@ -24,7 +25,7 @@ const permission = { // 顶部导航菜单默认添加统计报表栏指向首页 const index = [{ path: 'index', - meta: { title: '统计报表', icon: 'dashboard'} + meta: { title: '统计报表', icon: 'dashboard' } }] state.topbarRouters = routes.concat(index); }, @@ -42,7 +43,9 @@ const permission = { const rdata = JSON.parse(JSON.stringify(res.data)) const sidebarRoutes = filterAsyncRouter(sdata) const rewriteRoutes = filterAsyncRouter(rdata, false, true) + const asyncRoutes = filterDynamicRoutes(dynamicRoutes); rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true }) + router.addRoutes(asyncRoutes); commit('SET_ROUTES', rewriteRoutes) commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes)) commit('SET_DEFAULT_ROUTES', sidebarRoutes) @@ -106,6 +109,23 @@ function filterChildren(childrenMap, lastRouter = false) { return children } +// 动态路由遍历,验证是否具备权限 +export function filterDynamicRoutes(routes) { + const res = [] + routes.forEach(route => { + if (route.permissions) { + if (auth.hasPermiOr(route.permissions)) { + res.push(route) + } + } else if (route.roles) { + if (auth.hasRoleOr(route.roles)) { + res.push(route) + } + } + }) + return res +} + export const loadView = (view) => { if (process.env.NODE_ENV === 'development') { return (resolve) => require([`@/views/${view}`], resolve) diff --git a/ruoyi-ui/src/store/modules/settings.js b/ruoyi-ui/src/store/modules/settings.js index 3277a60d9..d08d8c2cc 100644 --- a/ruoyi-ui/src/store/modules/settings.js +++ b/ruoyi-ui/src/store/modules/settings.js @@ -8,7 +8,7 @@ const state = { theme: storageSetting.theme || '#409EFF', sideTheme: storageSetting.sideTheme || sideTheme, showSettings: showSettings, - topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav, + topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav, tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView, fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader, sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo, diff --git a/ruoyi-ui/src/store/modules/tagsView.js b/ruoyi-ui/src/store/modules/tagsView.js index 93d44040d..9acf5dc54 100644 --- a/ruoyi-ui/src/store/modules/tagsView.js +++ b/ruoyi-ui/src/store/modules/tagsView.js @@ -14,7 +14,7 @@ const mutations = { }, ADD_CACHED_VIEW: (state, view) => { if (state.cachedViews.includes(view.name)) return - if (!view.meta.noCache) { + if (view.meta && !view.meta.noCache) { state.cachedViews.push(view.name) } }, diff --git a/ruoyi-ui/src/store/modules/user.js b/ruoyi-ui/src/store/modules/user.js index 92bd8f76a..fce1a86b8 100644 --- a/ruoyi-ui/src/store/modules/user.js +++ b/ruoyi-ui/src/store/modules/user.js @@ -66,7 +66,7 @@ const user = { }) }) }, - + // 退出系统 LogOut({ commit, state }) { return new Promise((resolve, reject) => { diff --git a/ruoyi-ui/src/utils/dict/index.js b/ruoyi-ui/src/utils/dict/index.js index 66ddfef9e..d6fdb802c 100644 --- a/ruoyi-ui/src/utils/dict/index.js +++ b/ruoyi-ui/src/utils/dict/index.js @@ -5,7 +5,7 @@ export default function(Vue, options) { mergeOptions(options) Vue.mixin({ data() { - if (this.$options.dicts === undefined || this.$options.dicts === null) { + if (this.$options === undefined || this.$options.dicts === undefined || this.$options.dicts === null) { return {} } const dict = new Dict() diff --git a/ruoyi-ui/src/utils/index.js b/ruoyi-ui/src/utils/index.js index 2893bc83a..9679e757a 100644 --- a/ruoyi-ui/src/utils/index.js +++ b/ruoyi-ui/src/utils/index.js @@ -381,7 +381,7 @@ export function titleCase(str) { // 下划转驼峰 export function camelCase(str) { - return str.replace(/-[a-z]/g, str1 => str1.substr(-1).toUpperCase()) + return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase()) } export function isNumberStr(str) { diff --git a/ruoyi-ui/src/utils/request.js b/ruoyi-ui/src/utils/request.js index 9b40b4acb..107a28d67 100644 --- a/ruoyi-ui/src/utils/request.js +++ b/ruoyi-ui/src/utils/request.js @@ -1,8 +1,15 @@ import axios from 'axios' -import { Notification, MessageBox, Message } from 'element-ui' +import { Notification, MessageBox, Message, Loading } from 'element-ui' import store from '@/store' import { getToken } from '@/utils/auth' import errorCode from '@/utils/errorCode' +import { tansParams, blobValidate } from "@/utils/ruoyi"; +import cache from '@/plugins/cache' +import { saveAs } from 'file-saver' + +let downloadLoadingInstance; +// 是否显示重新登录 +let isReloginShow; axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' // 创建axios实例 @@ -12,37 +19,46 @@ const service = axios.create({ // 超时 timeout: 10000 }) + // request拦截器 service.interceptors.request.use(config => { // 是否需要设置 token const isToken = (config.headers || {}).isToken === false + // 是否需要防止数据重复提交 + const isRepeatSubmit = (config.headers || {}).repeatSubmit === false if (getToken() && !isToken) { config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 } // get请求映射params参数 if (config.method === 'get' && config.params) { - let url = config.url + '?'; - for (const propName of Object.keys(config.params)) { - const value = config.params[propName]; - var part = encodeURIComponent(propName) + "="; - if (value !== null && typeof(value) !== "undefined") { - if (typeof value === 'object') { - for (const key of Object.keys(value)) { - if (value[key] !== null && typeof (value[key]) !== 'undefined') { - let params = propName + '[' + key + ']'; - let subPart = encodeURIComponent(params) + '='; - url += subPart + encodeURIComponent(value[key]) + '&'; - } - } - } else { - url += part + encodeURIComponent(value) + "&"; - } - } - } + let url = config.url + '?' + tansParams(config.params); url = url.slice(0, -1); config.params = {}; config.url = url; } + if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { + const requestObj = { + url: config.url, + data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, + time: new Date().getTime() + } + const sessionObj = cache.session.getJSON('sessionObj') + if (sessionObj === undefined || sessionObj === null || sessionObj === '') { + cache.session.setJSON('sessionObj', requestObj) + } else { + const s_url = sessionObj.url; // 请求地址 + const s_data = sessionObj.data; // 请求数据 + const s_time = sessionObj.time; // 请求时间 + const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交 + if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { + const message = '数据正在处理,请勿重复提交'; + console.warn(`[${s_url}]: ` + message) + return Promise.reject(new Error(message)) + } else { + cache.session.setJSON('sessionObj', requestObj) + } + } + } return config }, error => { console.log(error) @@ -55,17 +71,30 @@ service.interceptors.response.use(res => { const code = res.data.code || 200; // 获取错误信息 const msg = errorCode[code] || res.data.msg || errorCode['default'] + // 二进制数据则直接返回 + if(res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer'){ + return res.data + } if (code === 401) { - MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { + if (!isReloginShow) { + isReloginShow = true; + MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' } ).then(() => { + isReloginShow = false; store.dispatch('LogOut').then(() => { - location.href = '/index'; + // 如果是登录页面不需要重新加载 + if (window.location.hash.indexOf("#/login") != 0) { + location.href = '/index'; + } }) - }).catch(() => {}); + }).catch(() => { + isReloginShow = false; + }); + } return Promise.reject('无效的会话,或者会话已过期,请重新登录。') } else if (code === 500) { Message({ @@ -103,4 +132,30 @@ service.interceptors.response.use(res => { } ) +// 通用下载方法 +export function download(url, params, filename) { + downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }) + return service.post(url, params, { + transformRequest: [(params) => { return tansParams(params) }], + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + responseType: 'blob' + }).then(async (data) => { + const isLogin = await blobValidate(data); + if (isLogin) { + const blob = new Blob([data]) + saveAs(blob, filename) + } else { + const resText = await data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + Message.error(errMsg); + } + downloadLoadingInstance.close(); + }).catch((r) => { + console.error(r) + Message.error('下载文件出现错误,请联系管理员!') + downloadLoadingInstance.close(); + }) +} + export default service diff --git a/ruoyi-ui/src/utils/ruoyi.js b/ruoyi-ui/src/utils/ruoyi.js index 63bd379b7..8e3cb0cea 100644 --- a/ruoyi-ui/src/utils/ruoyi.js +++ b/ruoyi-ui/src/utils/ruoyi.js @@ -1,3 +1,5 @@ + + /** * 通用js方法封装处理 * Copyright (c) 2019 ruoyi @@ -5,130 +7,133 @@ // 日期格式化 export function parseTime(time, pattern) { - if (arguments.length === 0 || !time) { - return null - } - const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' - let date - if (typeof time === 'object') { - date = time - } else { - if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { - time = parseInt(time) - } else if (typeof time === 'string') { - time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm),''); - } - if ((typeof time === 'number') && (time.toString().length === 10)) { - time = time * 1000 - } - date = new Date(time) - } - const formatObj = { - y: date.getFullYear(), - m: date.getMonth() + 1, - d: date.getDate(), - h: date.getHours(), - i: date.getMinutes(), - s: date.getSeconds(), - a: date.getDay() - } - const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { - let value = formatObj[key] - // Note: getDay() returns 0 on Sunday - if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } - if (result.length > 0 && value < 10) { - value = '0' + value - } - return value || 0 - }) - return time_str + if (arguments.length === 0 || !time) { + return null + } + const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), ''); + } + if ((typeof time === 'number') && (time.toString().length === 10)) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str } // 表单重置 export function resetForm(refName) { - if (this.$refs[refName]) { - this.$refs[refName].resetFields(); - } + if (this.$refs[refName]) { + this.$refs[refName].resetFields(); + } } // 添加日期范围 export function addDateRange(params, dateRange, propName) { - let search = params; - search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}; - dateRange = Array.isArray(dateRange) ? dateRange : []; - if (typeof (propName) === 'undefined') { - search.params['beginTime'] = dateRange[0]; - search.params['endTime'] = dateRange[1]; - } else { - search.params['begin' + propName] = dateRange[0]; - search.params['end' + propName] = dateRange[1]; - } - return search; + let search = params; + search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}; + dateRange = Array.isArray(dateRange) ? dateRange : []; + if (typeof (propName) === 'undefined') { + search.params['beginTime'] = dateRange[0]; + search.params['endTime'] = dateRange[1]; + } else { + search.params['begin' + propName] = dateRange[0]; + search.params['end' + propName] = dateRange[1]; + } + return search; } -// 回显数据字典 +// 回显数据字典 export function selectDictLabel(datas, value) { - var actions = []; - Object.keys(datas).some((key) => { - if (datas[key].value == ('' + value)) { - actions.push(datas[key].label); - return true; - } - }) - return actions.join(''); + var actions = []; + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + value)) { + actions.push(datas[key].label); + return true; + } + }) + return actions.join(''); } // 回显数据字典(字符串数组) export function selectDictLabels(datas, value, separator) { - var actions = []; - var currentSeparator = undefined === separator ? "," : separator; - var temp = value.split(currentSeparator); - Object.keys(value.split(currentSeparator)).some((val) => { - Object.keys(datas).some((key) => { - if (datas[key].dictValue == ('' + temp[val])) { - actions.push(datas[key].dictLabel + currentSeparator); - } - }) - }) - return actions.join('').substring(0, actions.join('').length - 1); + if(value === undefined) { + return ""; + } + var actions = []; + var currentSeparator = undefined === separator ? "," : separator; + var temp = value.split(currentSeparator); + Object.keys(value.split(currentSeparator)).some((val) => { + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + temp[val])) { + actions.push(datas[key].label + currentSeparator); + } + }) + }) + return actions.join('').substring(0, actions.join('').length - 1); } // 字符串格式化(%s ) export function sprintf(str) { - var args = arguments, flag = true, i = 1; - str = str.replace(/%s/g, function () { - var arg = args[i++]; - if (typeof arg === 'undefined') { - flag = false; - return ''; - } - return arg; - }); - return flag ? str : ''; + var args = arguments, flag = true, i = 1; + str = str.replace(/%s/g, function () { + var arg = args[i++]; + if (typeof arg === 'undefined') { + flag = false; + return ''; + } + return arg; + }); + return flag ? str : ''; } // 转换字符串,undefined,null等转化为"" -export function praseStrEmpty(str) { - if (!str || str == "undefined" || str == "null") { - return ""; - } - return str; +export function parseStrEmpty(str) { + if (!str || str == "undefined" || str == "null") { + return ""; + } + return str; } // 数据合并 export function mergeRecursive(source, target) { - for (var p in target) { - try { - if (target[p].constructor == Object) { - source[p] = mergeRecursive(source[p], target[p]); - } else { - source[p] = target[p]; - } - } catch(e) { - source[p] = target[p]; - } + for (var p in target) { + try { + if (target[p].constructor == Object) { + source[p] = mergeRecursive(source[p], target[p]); + } else { + source[p] = target[p]; + } + } catch (e) { + source[p] = target[p]; } - return source; + } + return source; }; /** @@ -139,45 +144,82 @@ export function mergeRecursive(source, target) { * @param {*} children 孩子节点字段 默认 'children' */ export function handleTree(data, id, parentId, children) { - let config = { - id: id || 'id', - parentId: parentId || 'parentId', - childrenList: children || 'children' - }; + let config = { + id: id || 'id', + parentId: parentId || 'parentId', + childrenList: children || 'children' + }; - var childrenListMap = {}; - var nodeIds = {}; - var tree = []; + var childrenListMap = {}; + var nodeIds = {}; + var tree = []; - for (let d of data) { - let parentId = d[config.parentId]; - if (childrenListMap[parentId] == null) { - childrenListMap[parentId] = []; - } - nodeIds[d[config.id]] = d; - childrenListMap[parentId].push(d); - } + for (let d of data) { + let parentId = d[config.parentId]; + if (childrenListMap[parentId] == null) { + childrenListMap[parentId] = []; + } + nodeIds[d[config.id]] = d; + childrenListMap[parentId].push(d); + } - for (let d of data) { - let parentId = d[config.parentId]; - if (nodeIds[parentId] == null) { - tree.push(d); - } - } + for (let d of data) { + let parentId = d[config.parentId]; + if (nodeIds[parentId] == null) { + tree.push(d); + } + } - for (let t of tree) { - adaptToChildrenList(t); - } + for (let t of tree) { + adaptToChildrenList(t); + } - function adaptToChildrenList(o) { - if (childrenListMap[o[config.id]] !== null) { - o[config.childrenList] = childrenListMap[o[config.id]]; - } - if (o[config.childrenList]) { - for (let c of o[config.childrenList]) { - adaptToChildrenList(c); - } - } - } - return tree; + function adaptToChildrenList(o) { + if (childrenListMap[o[config.id]] !== null) { + o[config.childrenList] = childrenListMap[o[config.id]]; + } + if (o[config.childrenList]) { + for (let c of o[config.childrenList]) { + adaptToChildrenList(c); + } + } + } + return tree; } + +/** +* 参数处理 +* @param {*} params 参数 +*/ +export function tansParams(params) { + let result = '' + for (const propName of Object.keys(params)) { + const value = params[propName]; + var part = encodeURIComponent(propName) + "="; + if (value !== null && typeof (value) !== "undefined") { + if (typeof value === 'object') { + for (const key of Object.keys(value)) { + if (value[key] !== null && typeof (value[key]) !== 'undefined') { + let params = propName + '[' + key + ']'; + var subPart = encodeURIComponent(params) + "="; + result += subPart + encodeURIComponent(value[key]) + "&"; + } + } + } else { + result += part + encodeURIComponent(value) + "&"; + } + } + } + return result +} + +// 验证是否为blob格式 +export async function blobValidate(data) { + try { + const text = await data.text(); + JSON.parse(text); + return false; + } catch (error) { + return true; + } +} \ No newline at end of file diff --git a/ruoyi-ui/src/views/index.vue b/ruoyi-ui/src/views/index.vue index 00379e5aa..1cf7954be 100644 --- a/ruoyi-ui/src/views/index.vue +++ b/ruoyi-ui/src/views/index.vue @@ -120,9 +120,9 @@

QQ群:满937441 满887144332 满180251782 满104180207 - 满186866453 满201396349 - - 101456076满186866453 满201396349 满101456076 满101539465 + + 264312783

@@ -147,6 +147,92 @@ 更新日志 + +

    +
  1. 新增Vue3前端代码生成模板
  2. +
  3. 新增图片预览组件
  4. +
  5. 新增压缩插件实现打包Gzip
  6. +
  7. 自定义xss校验注解实现
  8. +
  9. 自定义文字复制剪贴指令
  10. +
  11. 代码生成预览支持复制内容
  12. +
  13. 路由支持单独配置菜单或角色权限
  14. +
  15. 用户管理部门查询选择节点后分页参数初始
  16. +
  17. 修复用户分配角色属性错误
  18. +
  19. 修复打包后字体图标偶现的乱码问题
  20. +
  21. 修复菜单管理重置表单出现的错误
  22. +
  23. 修复版本差异导致的懒加载报错问题
  24. +
  25. 修复Cron组件中周回显问题
  26. +
  27. 修复定时任务多参数逗号分隔的问题
  28. +
  29. 修复根据ID查询列表可能出现的主键溢出问题
  30. +
  31. 修复tomcat配置参数已过期问题
  32. +
  33. 升级clipboard到最新版本2.0.8
  34. +
  35. 升级oshi到最新版本v5.8.6
  36. +
  37. 升级fastjson到最新版1.2.79
  38. +
  39. 升级spring-boot到最新版本2.5.8
  40. +
  41. 升级log4j2到2.17.1,防止漏洞风险
  42. +
  43. 优化下载解析blob异常提示
  44. +
  45. 优化代码生成字典组重复问题
  46. +
  47. 优化查询用户的角色组&岗位组代码
  48. +
  49. 优化定时任务cron表达式小时设置24
  50. +
  51. 优化用户导入提示溢出则显示滚动条
  52. +
  53. 优化防重复提交标识组合为(key+url+header)
  54. +
  55. 优化分页方法设置成通用方便灵活调用
  56. +
  57. 其他细节优化
  58. +
+ + +
    +
  1. 新增配套并同步的Vue3前端版本
  2. +
  3. 新增通用方法简化模态/缓存/下载/权限/页签使用
  4. +
  5. 优化导出数据/使用通用下载方法
  6. +
  7. Excel注解支持自定义数据处理器
  8. +
  9. Excel注解支持导入导出标题信息
  10. +
  11. Excel导入支持@Excels注解
  12. +
  13. 新增组件data-dict,简化数据字典使用
  14. +
  15. 新增Jaxb依赖,防止jdk8以上出现的兼容错误
  16. +
  17. 生产环境使用路由懒加载提升页面响应速度
  18. +
  19. 修复五级以上菜单出现的404问题
  20. +
  21. 防重提交注解支持配置间隔时间/提示消息
  22. +
  23. 日志注解新增是否保存响应参数
  24. +
  25. 任务屏蔽违规字符&参数忽略双引号中的逗号
  26. +
  27. 升级SpringBoot到最新版本2.5.6
  28. +
  29. 升级pagehelper到最新版1.4.0
  30. +
  31. 升级spring-boot-mybatis到最新版2.2.0
  32. +
  33. 升级oshi到最新版本v5.8.2
  34. +
  35. 升级druid到最新版1.2.8
  36. +
  37. 升级velocity到最新版本2.3
  38. +
  39. 升级fastjson到最新版1.2.78
  40. +
  41. 升级axios到最新版本0.24.0
  42. +
  43. 升级dart-sass到版本1.32.13
  44. +
  45. 升级core-js到最新版本3.19.1
  46. +
  47. 升级jsencrypt到最新版本3.2.1
  48. +
  49. 升级js-cookie到最新版本3.0.1
  50. +
  51. 升级file-saver到最新版本2.0.5
  52. +
  53. 升级sass-loader到最新版本10.1.1
  54. +
  55. 升级element-ui到最新版本2.15.6
  56. +
  57. 新增sendGet无参请求方法
  58. +
  59. 禁用el-tag组件的渐变动画
  60. +
  61. 代码生成点击预览重置激活tab
  62. +
  63. AjaxResult重写put方法,以方便链式调用
  64. +
  65. 优化登录/验证码请求headers不设置token
  66. +
  67. 优化用户个人信息接口防止修改用户名
  68. +
  69. 优化Cron表达式生成器关闭时销毁避免缓存
  70. +
  71. 优化注册成功提示消息类型success
  72. +
  73. 优化aop语法,使用spring自动注入注解
  74. +
  75. 优化记录登录信息,移除不必要的修改
  76. +
  77. 优化mybatis全局默认的执行器
  78. +
  79. 优化Excel导入图片可能出现的异常
  80. +
  81. 修复代码生成模板主子表删除缺少事务
  82. +
  83. 修复日志记录可能出现的转换异常
  84. +
  85. 修复代码生成复选框字典遗漏问题
  86. +
  87. 修复关闭xss功能导致可重复读RepeatableFilter失效
  88. +
  89. 修复字符串无法被反转义问题
  90. +
  91. 修复后端主子表代码模板方法名生成错误问题
  92. +
  93. 修复xss过滤后格式出现的异常
  94. +
  95. 修复swagger没有指定dataTypeClass导致启动出现warn日志
  96. +
  97. 其他细节优化
  98. +
+
  1. 参数管理支持配置验证码开关
  2. @@ -590,7 +676,7 @@
  3. 修复表格时间为空出现的异常
  4. 添加Jackson日期反序列化时区配置
  5. 调整根据用户权限加载菜单数据树形结构
  6. -
  7. 调整成功登陆不恢复按钮,防止多次点击
  8. +
  9. 调整成功登录不恢复按钮,防止多次点击
  10. 修改用户个人资料同步缓存信息
  11. 修复页面同时出现el-upload和Editor不显示处理
  12. 修复在角色管理页修改菜单权限偶尔未选中问题
  13. @@ -664,7 +750,7 @@ export default { data() { return { // 版本号 - version: "3.7.0", + version: "3.8.1", }; }, methods: { diff --git a/ruoyi-ui/src/views/login.vue b/ruoyi-ui/src/views/login.vue index 255eafca0..6e240dd24 100644 --- a/ruoyi-ui/src/views/login.vue +++ b/ruoyi-ui/src/views/login.vue @@ -3,7 +3,12 @@ @@ -66,7 +71,6 @@ export default { data() { return { codeUrl: "", - cookiePassword: "", loginForm: { username: "admin", password: "admin123", diff --git a/ruoyi-ui/src/views/monitor/cache/index.vue b/ruoyi-ui/src/views/monitor/cache/index.vue index 22501dc34..cafa28cfb 100644 --- a/ruoyi-ui/src/views/monitor/cache/index.vue +++ b/ruoyi-ui/src/views/monitor/cache/index.vue @@ -139,7 +139,7 @@ export default { }, // 打开加载层 openLoading() { - this.$modal.loading("正在加载缓存监控数据,请稍后!"); + this.$modal.loading("正在加载缓存监控数据,请稍候!"); }, }, }; diff --git a/ruoyi-ui/src/views/monitor/job/index.vue b/ruoyi-ui/src/views/monitor/job/index.vue index 06b939d85..84ea6af69 100644 --- a/ruoyi-ui/src/views/monitor/job/index.vue +++ b/ruoyi-ui/src/views/monitor/job/index.vue @@ -75,7 +75,6 @@ plain icon="el-icon-download" size="mini" - :loading="exportLoading" @click="handleExport" v-hasPermi="['monitor:job:export']" >导出 @@ -205,7 +204,7 @@ - + 立即执行 执行一次 @@ -295,7 +294,7 @@