diff --git a/README.md b/README.md index c8fbb7cac..34ffad8c7 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@

logo

-

RuoYi v3.9.0

+

RuoYi v3.9.1

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

- +

diff --git a/pom.xml b/pom.xml index 200de75c0..5f33e2f19 100644 --- a/pom.xml +++ b/pom.xml @@ -5,28 +5,28 @@ 4.0.0 com.ruoyi - hair-link - 3.9.0 + ruoyi + 3.9.1 hair-link http://www.example.com Hair-Link 美发预约管理系统 - 3.9.0 + 3.9.1 UTF-8 UTF-8 1.8 3.1.1 2.5.15 - 1.2.23 - 1.21 + 1.2.27 + 7.32.0 3.0.0 2.3.3 1.4.7 - 2.0.58 - 6.8.3 - 2.19.0 + 2.0.60 + 6.9.1 + 2.21.0 4.1.2 2.3 0.9.1 @@ -34,7 +34,7 @@ 2.14.3 4.6 - 9.0.108 + 9.0.112 1.2.13 5.7.14 5.3.39 @@ -112,9 +112,9 @@ - eu.bitwalker - UserAgentUtils - ${bitwalker.version} + nl.basjes.parse.useragent + yauaa + ${yauaa.version} diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 4e1863e77..a6bf74197 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -5,7 +5,7 @@ hair-link com.ruoyi - 3.9.0 + 3.9.1 4.0.0 jar diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java index e2b44f662..cda80bd85 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java @@ -34,7 +34,7 @@ public class CommonController @Autowired private ServerConfig serverConfig; - private static final String FILE_DELIMETER = ","; + private static final String FILE_DELIMITER = ","; /** * 通用下载请求 @@ -119,10 +119,10 @@ public class CommonController originalFilenames.add(file.getOriginalFilename()); } AjaxResult ajax = AjaxResult.success(); - ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER)); - ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER)); - ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER)); - ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER)); + ajax.put("urls", StringUtils.join(urls, FILE_DELIMITER)); + ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMITER)); + ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMITER)); + ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMITER)); return ajax; } catch (Exception e) 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 5b02a70ab..b32504567 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 @@ -96,7 +96,8 @@ public class SysProfileController extends BaseController String newPassword = params.get("newPassword"); LoginUser loginUser = getLoginUser(); Long userId = loginUser.getUserId(); - String password = loginUser.getPassword(); + SysUser user = userService.selectUserById(userId); + String password = user.getPassword(); if (!SecurityUtils.matchesPassword(oldPassword, password)) { return error("修改密码失败,旧密码错误"); diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 79c54a9e3..7e6bc848f 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -3,7 +3,7 @@ hairlink: # 名称 name: Hair-Link # 版本 - version: 3.9.0 + version: 3.9.1 # 版权年份 copyrightYear: 2025 # 文件路径 示例( Windows配置D:/hairlink/uploadPath,Linux配置 /home/hairlink/uploadPath) diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index af6506b9d..fb3471b58 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -5,7 +5,7 @@ hair-link com.ruoyi - 3.9.0 + 3.9.1 4.0.0 @@ -77,12 +77,6 @@ poi-ooxml - - - org.yaml - snakeyaml - - io.jsonwebtoken @@ -109,8 +103,8 @@ - eu.bitwalker - UserAgentUtils + nl.basjes.parse.useragent + yauaa 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 44ec99c63..1997a0497 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 @@ -83,12 +83,12 @@ public class Constants /** * 角色权限分隔符 */ - public static final String ROLE_DELIMETER = ","; + public static final String ROLE_DELIMITER = ","; /** * 权限标识分隔符 */ - public static final String PERMISSION_DELIMETER = ","; + public static final String PERMISSION_DELIMITER = ","; /** * 验证码有效期(分钟) 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 06a10d741..559575c5b 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 @@ -5,6 +5,7 @@ import java.util.List; import javax.validation.constraints.*; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import com.fasterxml.jackson.annotation.JsonProperty; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.annotation.Excel.Type; @@ -203,6 +204,7 @@ public class SysUser extends BaseEntity this.avatar = avatar; } + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) public String getPassword() { return password; diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java index 854566839..2be0eadd1 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java @@ -1,7 +1,9 @@ package com.ruoyi.common.utils; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import com.alibaba.fastjson2.JSONArray; import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.core.domain.entity.SysDictData; @@ -90,37 +92,25 @@ public class DictUtils */ public static String getDictLabel(String dictType, String dictValue, String separator) { - StringBuilder propertyString = new StringBuilder(); List datas = getDictCache(dictType); - if (StringUtils.isNull(datas)) + if (StringUtils.isNull(datas) || StringUtils.isEmpty(dictValue)) { return StringUtils.EMPTY; } - if (StringUtils.containsAny(separator, dictValue)) + Map dictMap = datas.stream().collect(HashMap::new, (map, dict) -> map.put(dict.getDictValue(), dict.getDictLabel()), Map::putAll); + if (!StringUtils.contains(dictValue, separator)) { - for (SysDictData dict : datas) + return dictMap.getOrDefault(dictValue, StringUtils.EMPTY); + } + StringBuilder labelBuilder = new StringBuilder(); + for (String seperatedValue : dictValue.split(separator)) + { + if (dictMap.containsKey(seperatedValue)) { - for (String value : dictValue.split(separator)) - { - if (value.equals(dict.getDictValue())) - { - propertyString.append(dict.getDictLabel()).append(separator); - break; - } - } + labelBuilder.append(dictMap.get(seperatedValue)).append(separator); } } - else - { - for (SysDictData dict : datas) - { - if (dictValue.equals(dict.getDictValue())) - { - return dict.getDictLabel(); - } - } - } - return StringUtils.stripEnd(propertyString.toString(), separator); + return StringUtils.removeEnd(labelBuilder.toString(), separator); } /** @@ -133,37 +123,25 @@ public class DictUtils */ public static String getDictValue(String dictType, String dictLabel, String separator) { - StringBuilder propertyString = new StringBuilder(); List datas = getDictCache(dictType); - if (StringUtils.isNull(datas)) + if (StringUtils.isNull(datas) || StringUtils.isEmpty(dictLabel)) { return StringUtils.EMPTY; } - if (StringUtils.containsAny(separator, dictLabel)) + Map dictMap = datas.stream().collect(HashMap::new, (map, dict) -> map.put(dict.getDictLabel(), dict.getDictValue()), Map::putAll); + if (!StringUtils.contains(dictLabel, separator)) { - for (SysDictData dict : datas) + return dictMap.getOrDefault(dictLabel, StringUtils.EMPTY); + } + StringBuilder valueBuilder = new StringBuilder(); + for (String seperatedValue : dictLabel.split(separator)) + { + if (dictMap.containsKey(seperatedValue)) { - for (String label : dictLabel.split(separator)) - { - if (label.equals(dict.getDictLabel())) - { - propertyString.append(dict.getDictValue()).append(separator); - break; - } - } + valueBuilder.append(dictMap.get(seperatedValue)).append(separator); } } - else - { - for (SysDictData dict : datas) - { - if (dictLabel.equals(dict.getDictLabel())) - { - return dict.getDictValue(); - } - } - } - return StringUtils.stripEnd(propertyString.toString(), separator); + return StringUtils.removeEnd(valueBuilder.toString(), separator); } /** diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/UserAgentUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/UserAgentUtils.java new file mode 100644 index 000000000..a133be202 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/http/UserAgentUtils.java @@ -0,0 +1,254 @@ +package com.ruoyi.common.utils.http; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import com.ruoyi.common.utils.StringUtils; +import nl.basjes.parse.useragent.UserAgent; +import nl.basjes.parse.useragent.UserAgentAnalyzer; + +/** + * UserAgent解析工具类 + * + * @author ruoyi + */ +public class UserAgentUtils +{ + public static final String UNKNOWN = ""; + + // 浏览器正则表达式模式 + private static final Pattern CHROME_PATTERN = Pattern.compile("Chrome/(\\d+)(?:\\.\\d+)*"); + private static final Pattern FIREFOX_PATTERN = Pattern.compile("Firefox/(\\d+)(?:\\.\\d+)*"); + private static final Pattern EDGE_PATTERN = Pattern.compile("Edg(?:e)?/(\\d+)(?:\\.\\d+)*"); + private static final Pattern SAFARI_PATTERN = Pattern.compile("Version/(\\d+)(?:\\.\\d+)*"); + private static final Pattern OPERA_PATTERN = Pattern.compile("Opera/(\\d+)(?:\\.\\d+)*"); + private static final Pattern IE_PATTERN = Pattern.compile("(?:MSIE |Trident/.*rv:)(\\d+)(?:\\.\\d+)*"); + private static final Pattern SAMSUNG_PATTERN = Pattern.compile("SamsungBrowser/(\\d+)(?:\\.\\d+)*"); + private static final Pattern UC_PATTERN = Pattern.compile("UCBrowser/(\\d+)(?:\\.\\d+)*"); + private static final Pattern QQ_PATTERN = Pattern.compile("QQBrowser/(\\d+)(?:\\.\\d+)*"); + private static final Pattern WECHAT_PATTERN = Pattern.compile("MicroMessenger/(\\d+)(?:\\.\\d+)*"); + private static final Pattern BAIDU_PATTERN = Pattern.compile("baidubrowser/(\\d+)(?:\\.\\d+)*"); + + // 操作系统正则表达式模式 + private static final Pattern WINDOWS_PATTERN = Pattern.compile("Windows NT (\\d+\\.\\d+)"); + private static final Pattern MACOS_PATTERN = Pattern.compile("Mac OS X (\\d+[_\\d]*)"); + private static final Pattern ANDROID_PATTERN = Pattern.compile("Android (\\d+)(?:\\.\\d+)*"); + private static final Pattern IOS_PATTERN = Pattern.compile("OS[\\s_](\\d+)(?:_\\d+)*"); + private static final Pattern LINUX_PATTERN = Pattern.compile("Linux"); + private static final Pattern CHROMEOS_PATTERN = Pattern.compile("CrOS"); + + private static final UserAgentAnalyzer userAgentAnalyzer = UserAgentAnalyzer + .newBuilder().hideMatcherLoadStats() + .withCache(5000) + .showMinimalVersion() + .withField(UserAgent.AGENT_NAME_VERSION) + .withField(UserAgent.OPERATING_SYSTEM_NAME_VERSION) + .build(); + + /** + * 获取客户端浏览器 + */ + public static String getBrowser(String userAgent) + { + UserAgent.ImmutableUserAgent iua = userAgentAnalyzer.parse(userAgent); + String agentNameVersion = iua.get(UserAgent.AGENT_NAME_VERSION).getValue(); + if (StringUtils.isBlank(agentNameVersion) || agentNameVersion.contains("??")) + { + return formatBrowser(userAgent); + } + return agentNameVersion; + } + + /** + * 获取客户端操作系统 + */ + public static String getOperatingSystem(String userAgent) + { + UserAgent.ImmutableUserAgent iua = userAgentAnalyzer.parse(userAgent); + String operatingSystemNameVersion = iua.get(UserAgent.OPERATING_SYSTEM_NAME_VERSION).getValue(); + if (StringUtils.isBlank(operatingSystemNameVersion) || operatingSystemNameVersion.contains("??")) + { + return formatOperatingSystem(userAgent); + } + return operatingSystemNameVersion; + } + + /** + * 全面浏览器检测 + */ + private static String formatBrowser(String browser) + { + // Chrome系列浏览器 + Matcher chromeMatcher = CHROME_PATTERN.matcher(browser); + if (chromeMatcher.find() && (browser.contains("Chrome") || browser.contains("CriOS"))) + { + return "Chrome" + chromeMatcher.group(1); + } + // Firefox + Matcher firefoxMatcher = FIREFOX_PATTERN.matcher(browser); + if (firefoxMatcher.find()) + { + return "Firefox" + firefoxMatcher.group(1); + } + // Edge浏览器 + Matcher edgeMatcher = EDGE_PATTERN.matcher(browser); + if (edgeMatcher.find()) + { + return "Edge" + edgeMatcher.group(1); + } + // Safari浏览器(需排除Chrome) + Matcher safariMatcher = SAFARI_PATTERN.matcher(browser); + if (safariMatcher.find() && !browser.contains("Chrome")) + { + return "Safari" + safariMatcher.group(1); + } + // 微信内置浏览器 + Matcher wechatMatcher = WECHAT_PATTERN.matcher(browser); + if (wechatMatcher.find()) + { + return "WeChat" + wechatMatcher.group(1); + } + // UC浏览器 + Matcher ucMatcher = UC_PATTERN.matcher(browser); + if (ucMatcher.find()) + { + return "UC Browser" + ucMatcher.group(1); + } + // QQ浏览器 + Matcher qqMatcher = QQ_PATTERN.matcher(browser); + if (qqMatcher.find()) + { + return "QQ Browser" + qqMatcher.group(1); + } + // 百度浏览器 + Matcher baiduMatcher = BAIDU_PATTERN.matcher(browser); + if (baiduMatcher.find()) + { + return "Baidu Browser" + baiduMatcher.group(1); + } + // Samsung浏览器 + Matcher samsungMatcher = SAMSUNG_PATTERN.matcher(browser); + if (samsungMatcher.find()) + { + return "Samsung Browser" + samsungMatcher.group(1); + } + // Opera浏览器 + Matcher operaMatcher = OPERA_PATTERN.matcher(browser); + if (operaMatcher.find()) + { + return "Opera" + operaMatcher.group(1); + } + // IE浏览器 + Matcher ieMatcher = IE_PATTERN.matcher(browser); + if (ieMatcher.find()) + { + return "Internet Explorer" + ieMatcher.group(1); + } + return UNKNOWN; + } + + /** + * 检测操作系统 + */ + private static String formatOperatingSystem(String operatingSystem) + { + // Windows系统 + Matcher windowsMatcher = WINDOWS_PATTERN.matcher(operatingSystem); + if (windowsMatcher.find()) + { + return "Windows" + getWindowsVersionDisplay(windowsMatcher.group(1)); + } + // macOS系统 + Matcher macMatcher = MACOS_PATTERN.matcher(operatingSystem); + if (macMatcher.find()) + { + String version = macMatcher.group(1).replace("_", "."); + return "macOS" + extractMajorVersion(version); + } + // Android系统 + Matcher androidMatcher = ANDROID_PATTERN.matcher(operatingSystem); + if (androidMatcher.find()) + { + return "Android" + extractMajorVersion(androidMatcher.group(1)); + } + // iOS系统 + Matcher iosMatcher = IOS_PATTERN.matcher(operatingSystem); + if (iosMatcher.find() && (operatingSystem.contains("iPhone") || operatingSystem.contains("iPad"))) + { + return "iOS" + extractMajorVersion(iosMatcher.group(1)); + } + // Linux系统 + if (LINUX_PATTERN.matcher(operatingSystem).find() && !operatingSystem.contains("Android")) + { + return "Linux"; + } + // Chrome OS + if (CHROMEOS_PATTERN.matcher(operatingSystem).find()) + { + return "Chrome OS"; + } + return UNKNOWN; + } + + /** + * 提取优化的主版本号 + */ + private static String extractMajorVersion(String fullVersion) + { + if (StringUtils.isEmpty(fullVersion)) + { + return StringUtils.EMPTY; + } + try + { + // 清理版本号中的非数字字符 + String cleanVersion = fullVersion.replaceAll("[^0-9.]", ""); + String[] parts = cleanVersion.split("\\."); + if (parts.length > 0) + { + String firstPart = parts[0]; + if (firstPart.matches("\\d+")) + { + int version = Integer.parseInt(firstPart); + + // 处理三位数版本号(如142 -> 14) + if (version >= 100) + { + return String.valueOf(version / 10); + } + return firstPart; + } + } + } + catch (NumberFormatException e) + { + // 版本号解析失败,返回原始值 + } + return fullVersion; + } + + /** + * Windows版本号显示优化 + */ + private static String getWindowsVersionDisplay(String version) + { + switch (version) + { + case "10.0": + return "10"; + case "6.3": + return "8.1"; + case "6.2": + return "8"; + case "6.1": + return "7"; + case "6.0": + return "Vista"; + case "5.1": + return "XP"; + case "5.0": + return "2000"; + default: + return extractMajorVersion(version); + } + } +} 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 5131908f6..6b0dee99b 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 @@ -174,12 +174,12 @@ public class ExcelUtil /** * 对象的子列表方法 */ - private Method subMethod; + private Map subMethods; /** * 对象的子列表属性 */ - private List subFields; + private Map> subFieldsMap; /** * 统计列表 @@ -252,7 +252,10 @@ public class ExcelUtil int titleLastCol = this.fields.size() - 1; if (isSubList()) { - titleLastCol = titleLastCol + subFields.size() - 1; + for (List currentSubFields : subFieldsMap.values()) + { + titleLastCol = titleLastCol + currentSubFields.size() - 1; + } } Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0); titleRow.setHeightInPoints(30); @@ -272,16 +275,17 @@ public class ExcelUtil { Row subRow = sheet.createRow(rownum); int column = 0; - int subFieldSize = subFields != null ? subFields.size() : 0; for (Object[] objects : fields) { Field field = (Field) objects[0]; Excel attr = (Excel) objects[1]; + CellStyle cellStyle = styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())); if (Collection.class.isAssignableFrom(field.getType())) { Cell cell = subRow.createCell(column); cell.setCellValue(attr.name()); - cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + cell.setCellStyle(cellStyle); + int subFieldSize = subFieldsMap != null ? subFieldsMap.get(field.getName()).size() : 0; if (subFieldSize > 1) { CellRangeAddress cellAddress = new CellRangeAddress(rownum, rownum, column, column + subFieldSize - 1); @@ -293,7 +297,7 @@ public class ExcelUtil { Cell cell = subRow.createCell(column++); cell.setCellValue(attr.name()); - cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + cell.setCellStyle(cellStyle); } } rownum++; @@ -374,7 +378,11 @@ public class ExcelUtil Map cellMap = new HashMap(); // 获取表头 Row heard = sheet.getRow(titleNum); - for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) + if (heard == null) + { + throw new UtilException("文件标题行为空,请检查Excel文件格式"); + } + for (int i = 0; i < heard.getLastCellNum(); i++) { Cell cell = heard.getCell(i); if (StringUtils.isNotNull(cell)) @@ -382,10 +390,6 @@ public class ExcelUtil String value = this.getCellValue(heard, i).toString(); cellMap.put(value, i); } - else - { - cellMap.put(null, i); - } } // 有数据时才处理 得到类的所有field. List fields = this.getFields(); @@ -697,7 +701,8 @@ public class ExcelUtil Excel excel = (Excel) os[1]; if (Collection.class.isAssignableFrom(field.getType())) { - for (Field subField : subFields) + List currentSubFields = subFieldsMap.get(field.getName()); + for (Field subField : currentSubFields) { Excel subExcel = subField.getAnnotation(Excel.class); this.createHeadCell(subExcel, row, column++); @@ -710,7 +715,7 @@ public class ExcelUtil } if (Type.EXPORT.equals(type)) { - fillExcelData(index, row); + fillExcelData(index); addStatisticsRow(); } } @@ -720,10 +725,9 @@ public class ExcelUtil * 填充excel数据 * * @param index 序号 - * @param row 单元格行 */ @SuppressWarnings("unchecked") - public void fillExcelData(int index, Row row) + public void fillExcelData(int index) { int startNo = index * sheetSize; int endNo = Math.min(startNo + sheetSize, list.size()); @@ -731,7 +735,7 @@ public class ExcelUtil for (int i = startNo; i < endNo; i++) { - row = sheet.createRow(currentRowNum); + Row row = sheet.createRow(currentRowNum); T vo = (T) list.get(i); int column = 0; int maxSubListSize = getCurrentMaxSubListSize(vo); @@ -744,6 +748,7 @@ public class ExcelUtil try { Collection subList = (Collection) getTargetValue(vo, field, excel); + List currentSubFields = subFieldsMap.get(field.getName()); if (subList != null && !subList.isEmpty()) { int subIndex = 0; @@ -756,15 +761,15 @@ public class ExcelUtil } int subColumn = column; - for (Field subField : subFields) + for (Field subField : currentSubFields) { Excel subExcel = subField.getAnnotation(Excel.class); addCell(subExcel, subRow, (T) subVo, subField, subColumn++); } subIndex++; } - column += subFields.size(); } + column += currentSubFields.size(); } catch (Exception e) { @@ -1131,7 +1136,7 @@ public class ExcelUtil { // 创建cell cell = row.createCell(column); - if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()) + if (isSubListValue(vo) && getListCellValue(vo) > 1 && attr.needMerge()) { if (subMergedLastRowNum >= subMergedFirstRowNum) { @@ -1238,18 +1243,36 @@ public class ExcelUtil public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol) { String hideSheetName = "combo_" + firstCol + "_" + endCol; - Sheet hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据 - for (int i = 0; i < textlist.length; i++) + Sheet hideSheet = null; + String hideSheetDataName = hideSheetName + "_data"; + Name name = wb.getName(hideSheetDataName); + if (name != null) { - hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]); + // 名称已存在,尝试从名称的引用中找到sheet名称 + String refersToFormula = name.getRefersToFormula(); + if (StringUtils.isNotEmpty(refersToFormula) && refersToFormula.contains("!")) + { + String sheetNameFromFormula = refersToFormula.substring(0, refersToFormula.indexOf("!")); + hideSheet = wb.getSheet(sheetNameFromFormula); + } } - // 创建名称,可被其他单元格引用 - Name name = wb.createName(); - name.setNameName(hideSheetName + "_data"); - name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length); + + if (hideSheet == null) + { + hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据 + for (int i = 0; i < textlist.length; i++) + { + hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]); + } + // 创建名称,可被其他单元格引用 + name = wb.createName(); + name.setNameName(hideSheetDataName); + name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length); + } + DataValidationHelper helper = sheet.getDataValidationHelper(); // 加载下拉列表内容 - DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetName + "_data"); + DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetDataName); // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); // 数据有效性对象 @@ -1537,6 +1560,8 @@ public class ExcelUtil { List fields = new ArrayList(); List tempFields = new ArrayList<>(); + subFieldsMap = new HashMap<>(); + subMethods = new HashMap<>(); tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); if (StringUtils.isNotEmpty(includeFields)) @@ -1584,10 +1609,11 @@ public class ExcelUtil } if (Collection.class.isAssignableFrom(field.getType())) { - subMethod = getSubMethod(field.getName(), clazz); + String fieldName = field.getName(); + subMethods.put(fieldName, getSubMethod(fieldName, clazz)); ParameterizedType pt = (ParameterizedType) field.getGenericType(); Class subClass = (Class) pt.getActualTypeArguments()[0]; - this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); + subFieldsMap.put(fieldName, FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class)); } } @@ -1656,7 +1682,8 @@ public class ExcelUtil { this.sheet = wb.createSheet(); this.createTitle(); - wb.setSheetName(index, sheetName + index); + int actualIndex = wb.getSheetIndex(this.sheet); + wb.setSheetName(actualIndex, sheetName + index); } } @@ -1839,7 +1866,7 @@ public class ExcelUtil */ public boolean isSubList() { - return StringUtils.isNotNull(subFields) && subFields.size() > 0; + return !StringUtils.isEmpty(subFieldsMap); } /** @@ -1847,24 +1874,32 @@ public class ExcelUtil */ public boolean isSubListValue(T vo) { - return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0; + return !StringUtils.isEmpty(subFieldsMap) && getListCellValue(vo) > 0; } /** * 获取集合的值 */ - public Collection getListCellValue(Object obj) + public int getListCellValue(Object obj) { - Object value; + Collection value; + int max = 0; try { - value = subMethod.invoke(obj, new Object[] {}); + for (String s : subMethods.keySet()) + { + value = (Collection) subMethods.get(s).invoke(obj); + if (value.size() > max) + { + max = value.size(); + } + } } catch (Exception e) { - return new ArrayList(); + return 0; } - return (Collection) value; + return max; } /** 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 2c4ed876a..e7b1c59f4 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 @@ -13,7 +13,7 @@ public class SqlUtil /** * 定义常用的 sql关键字 */ - public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()"; + public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|information_schema|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()"; /** * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) diff --git a/ruoyi-framework/pom.xml b/ruoyi-framework/pom.xml index 855729583..350cec924 100644 --- a/ruoyi-framework/pom.xml +++ b/ruoyi-framework/pom.xml @@ -5,7 +5,7 @@ hair-link com.ruoyi - 3.9.0 + 3.9.1 4.0.0 diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java index 719c3711f..6eeaa940a 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java @@ -94,7 +94,7 @@ public class DataScopeAspect List conditions = new ArrayList(); List scopeCustomIds = new ArrayList(); user.getRoles().forEach(role -> { - if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) + if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && (StringUtils.isEmpty(permission) || StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))) { scopeCustomIds.add(Convert.toStr(role.getRoleId())); } @@ -107,7 +107,7 @@ public class DataScopeAspect { continue; } - if (!StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) + if (StringUtils.isNotEmpty(permission) && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) { continue; } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java index 2da41fec7..ce8a0e35e 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java @@ -50,6 +50,9 @@ public class LogAspect /** 计算操作消耗时间 */ private static final ThreadLocal TIME_THREADLOCAL = new NamedThreadLocal("Cost Time"); + /** 参数最大长度限制 */ + private static final int PARAM_MAX_LENGTH = 2000; + /** * 处理请求前执行 */ @@ -172,16 +175,16 @@ public class LogAspect */ private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception { - Map paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); String requestMethod = operLog.getRequestMethod(); + Map paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); if (StringUtils.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())) { String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); - operLog.setOperParam(StringUtils.substring(params, 0, 2000)); + operLog.setOperParam(params); } else { - operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000)); + operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, PARAM_MAX_LENGTH)); } } @@ -190,7 +193,7 @@ public class LogAspect */ private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) { - String params = ""; + StringBuilder params = new StringBuilder(); if (paramsArray != null && paramsArray.length > 0) { for (Object o : paramsArray) @@ -200,15 +203,20 @@ public class LogAspect try { String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); - params += jsonObj.toString() + " "; + params.append(jsonObj).append(" "); + if (params.length() >= PARAM_MAX_LENGTH) + { + return StringUtils.substring(params.toString(), 0, PARAM_MAX_LENGTH); + } } catch (Exception e) { + log.error("请求参数拼装异常 msg:{}, 参数:{}", e.getMessage(), paramsArray, e); } } } } - return params.trim(); + return params.toString(); } /** diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java index 98517b781..ef88bcf8f 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java @@ -7,6 +7,7 @@ import com.ruoyi.common.constant.Constants; import com.ruoyi.common.utils.LogUtils; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.http.UserAgentUtils; import com.ruoyi.common.utils.ip.AddressUtils; import com.ruoyi.common.utils.ip.IpUtils; import com.ruoyi.common.utils.spring.SpringUtils; @@ -14,7 +15,6 @@ import com.ruoyi.system.domain.SysLogininfor; import com.ruoyi.system.domain.SysOperLog; import com.ruoyi.system.service.ISysLogininforService; import com.ruoyi.system.service.ISysOperLogService; -import eu.bitwalker.useragentutils.UserAgent; /** * 异步工厂(产生任务用) @@ -37,7 +37,7 @@ public class AsyncFactory public static TimerTask recordLogininfor(final String username, final String status, final String message, final Object... args) { - final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + final String userAgent = ServletUtils.getRequest().getHeader("User-Agent"); final String ip = IpUtils.getIpAddr(); return new TimerTask() { @@ -54,9 +54,9 @@ public class AsyncFactory // 打印信息到日志 sys_user_logger.info(s.toString(), args); // 获取客户端操作系统 - String os = userAgent.getOperatingSystem().getName(); + String os = UserAgentUtils.getOperatingSystem(userAgent); // 获取客户端浏览器 - String browser = userAgent.getBrowser().getName(); + String browser = UserAgentUtils.getBrowser(userAgent); // 封装对象 SysLogininfor logininfor = new SysLogininfor(); logininfor.setUserName(username); diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java index 6892467db..0011526be 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java @@ -53,7 +53,7 @@ public class PermissionService /** * 验证用户是否具有以下任意一个权限 * - * @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表 + * @param permissions 以 PERMISSION_DELIMITER 为分隔符的权限列表 * @return 用户是否具有以下任意一个权限 */ public boolean hasAnyPermi(String permissions) @@ -69,7 +69,7 @@ public class PermissionService } PermissionContextHolder.setContext(permissions); Set authorities = loginUser.getPermissions(); - for (String permission : permissions.split(Constants.PERMISSION_DELIMETER)) + for (String permission : permissions.split(Constants.PERMISSION_DELIMITER)) { if (permission != null && hasPermissions(authorities, permission)) { @@ -121,7 +121,7 @@ public class PermissionService /** * 验证用户是否具有以下任意一个角色 * - * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 + * @param roles 以 ROLE_DELIMITER 为分隔符的角色列表 * @return 用户是否具有以下任意一个角色 */ public boolean hasAnyRoles(String roles) @@ -135,7 +135,7 @@ public class PermissionService { return false; } - for (String role : roles.split(Constants.ROLE_DELIMETER)) + for (String role : roles.split(Constants.ROLE_DELIMITER)) { if (hasRole(role)) { diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java index 8fa7e4855..ebe0932f8 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java @@ -6,6 +6,7 @@ import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; +import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysUser; @@ -39,7 +40,7 @@ public class SysPermissionService // 管理员拥有所有权限 if (user.isAdmin()) { - roles.add("admin"); + roles.add(Constants.SUPER_ADMIN); } else { @@ -60,7 +61,7 @@ public class SysPermissionService // 管理员拥有所有权限 if (user.isAdmin()) { - perms.add("*:*:*"); + perms.add(Constants.ALL_PERMISSION); } else { diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java index 5a9a8bbde..e06a8bb73 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java @@ -15,10 +15,10 @@ import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.http.UserAgentUtils; import com.ruoyi.common.utils.ip.AddressUtils; import com.ruoyi.common.utils.ip.IpUtils; import com.ruoyi.common.utils.uuid.IdUtils; -import eu.bitwalker.useragentutils.UserAgent; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @@ -178,12 +178,12 @@ public class TokenService */ public void setUserAgent(LoginUser loginUser) { - UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + String userAgent = ServletUtils.getRequest().getHeader("User-Agent"); String ip = IpUtils.getIpAddr(); loginUser.setIpaddr(ip); loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); - loginUser.setBrowser(userAgent.getBrowser().getName()); - loginUser.setOs(userAgent.getOperatingSystem().getName()); + loginUser.setBrowser(UserAgentUtils.getOperatingSystem(userAgent)); + loginUser.setOs(UserAgentUtils.getBrowser(userAgent)); } /** diff --git a/ruoyi-generator/pom.xml b/ruoyi-generator/pom.xml index 15c437383..af714da99 100644 --- a/ruoyi-generator/pom.xml +++ b/ruoyi-generator/pom.xml @@ -5,7 +5,7 @@ hair-link com.ruoyi - 3.9.0 + 3.9.1 4.0.0 diff --git a/ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm index 765a5e300..fabd92160 100644 --- a/ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm +++ b/ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm @@ -334,7 +334,7 @@ function getList() { #foreach ($column in $columns) #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) - if (null != daterange${AttrName} && '' != daterange${AttrName}) { + if (null != daterange${AttrName}.value && '' != daterange${AttrName}.value) { queryParams.value.params["begin${AttrName}"] = daterange${AttrName}.value[0] queryParams.value.params["end${AttrName}"] = daterange${AttrName}.value[1] } 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 index 936b4651f..1a9c51797 100644 --- a/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm +++ b/ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm @@ -415,7 +415,7 @@ function getList() { #foreach ($column in $columns) #if($column.htmlType == "datetime" && $column.queryType == "BETWEEN") #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) - if (null != daterange${AttrName} && '' != daterange${AttrName}) { + if (null != daterange${AttrName}.value && '' != daterange${AttrName}.value) { queryParams.value.params["begin${AttrName}"] = daterange${AttrName}.value[0] queryParams.value.params["end${AttrName}"] = daterange${AttrName}.value[1] } diff --git a/ruoyi-quartz/pom.xml b/ruoyi-quartz/pom.xml index 61311fb27..b3e4e7d7b 100644 --- a/ruoyi-quartz/pom.xml +++ b/ruoyi-quartz/pom.xml @@ -5,7 +5,7 @@ hair-link com.ruoyi - 3.9.0 + 3.9.1 4.0.0 diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml index efff398a8..193075d92 100644 --- a/ruoyi-system/pom.xml +++ b/ruoyi-system/pom.xml @@ -5,7 +5,7 @@ hair-link com.ruoyi - 3.9.0 + 3.9.1 4.0.0 diff --git a/ruoyi-ui/package.json b/ruoyi-ui/package.json index ee3f8a662..5c5aa2d0f 100644 --- a/ruoyi-ui/package.json +++ b/ruoyi-ui/package.json @@ -1,8 +1,8 @@ { - "name": "hair-link", - "version": "3.9.0", - "description": "Hair-Link 美发预约管理系统", - "author": "时光", + "name": "ruoyi", + "version": "3.9.1", + "description": "若依管理系统", + "author": "若依", "license": "MIT", "scripts": { "dev": "vue-cli-service serve", diff --git a/ruoyi-ui/src/assets/styles/ruoyi.scss b/ruoyi-ui/src/assets/styles/ruoyi.scss index ab4a1d2d6..e1ba08258 100644 --- a/ruoyi-ui/src/assets/styles/ruoyi.scss +++ b/ruoyi-ui/src/assets/styles/ruoyi.scss @@ -130,6 +130,16 @@ border-radius: 4px; } +/* horizontal el menu */ +.el-menu--horizontal .el-menu-item .svg-icon + span, +.el-menu--horizontal .el-submenu__title .svg-icon + span { + margin-left: 3px; +} + +.el-menu--horizontal .el-menu--popup { + min-width: 120px !important; +} + @media (max-width: 768px) { .pagination-container .el-pagination > .el-pagination__jump { display: none !important; diff --git a/ruoyi-ui/src/assets/styles/sidebar.scss b/ruoyi-ui/src/assets/styles/sidebar.scss index f1c821f15..3e0655ee7 100644 --- a/ruoyi-ui/src/assets/styles/sidebar.scss +++ b/ruoyi-ui/src/assets/styles/sidebar.scss @@ -61,7 +61,7 @@ } .svg-icon { - margin-right: 16px; + margin-right: 10px !important; } .el-menu { @@ -116,18 +116,20 @@ margin-left: 54px; } - .submenu-title-noDropdown { - padding: 0 !important; - position: relative; - - .el-tooltip { + .el-menu:not(.el-menu--horizontal) { + .submenu-title-noDropdown { padding: 0 !important; + position: relative; - .svg-icon { - margin-left: 20px; + .el-tooltip { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } } } - } + } .el-submenu { overflow: hidden; diff --git a/ruoyi-ui/src/components/Breadcrumb/index.vue b/ruoyi-ui/src/components/Breadcrumb/index.vue index 080595a4e..84f4831af 100644 --- a/ruoyi-ui/src/components/Breadcrumb/index.vue +++ b/ruoyi-ui/src/components/Breadcrumb/index.vue @@ -94,7 +94,6 @@ export default { display: inline-block; font-size: 14px; line-height: 50px; - margin-left: 8px; .no-redirect { color: #97a8be; cursor: text; diff --git a/ruoyi-ui/src/components/DictTag/index.vue b/ruoyi-ui/src/components/DictTag/index.vue index 73dcf91a8..7c8674412 100644 --- a/ruoyi-ui/src/components/DictTag/index.vue +++ b/ruoyi-ui/src/components/DictTag/index.vue @@ -1,7 +1,7 @@