diff --git a/pom.xml b/pom.xml index 0deca9e..4c7c43e 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,12 @@ 1.2.13 5.7.12 5.3.39 + 5.5.4 + 5.5.13.2 + 5.2.0 + 2.1.3 + 4.5.13 + 0.4.8 @@ -182,6 +188,49 @@ ${kaptcha.version} + + + cn.hutool + hutool-all + ${cn.hutool.all.version} + + + + de.odysseus.juel + juel + ${de.odysseus.juel.version} + + + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + + + + com.itextpdf + itextpdf + ${com.itextpdf.version} + + + + + com.itextpdf + itext-asian + ${itext-asian.version} + + + + + net.coobird + thumbnailator + ${net.coobird.version} + + + + com.ruoyi diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index c05f707..6ec1b56 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -61,6 +61,12 @@ ruoyi-generator + + + com.ruoyi + ruoyi-business + + @@ -80,17 +86,17 @@ - - org.apache.maven.plugins - maven-war-plugin - 3.1.0 + + org.apache.maven.plugins + maven-war-plugin + 3.1.0 false ${project.artifactId} - - + + ${project.artifactId} - \ No newline at end of file + diff --git a/ruoyi-admin/ruoyi-admin.iml b/ruoyi-admin/ruoyi-admin.iml index 2d51d4d..079255e 100644 --- a/ruoyi-admin/ruoyi-admin.iml +++ b/ruoyi-admin/ruoyi-admin.iml @@ -100,6 +100,7 @@ + @@ -115,6 +116,7 @@ + @@ -122,7 +124,6 @@ - @@ -132,6 +133,7 @@ + @@ -152,8 +154,16 @@ - + + + + + + + + + \ No newline at end of file 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 f75721e..c946179 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 @@ -1,9 +1,11 @@ package com.ruoyi.web.controller.common; -import java.util.ArrayList; -import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; +import com.ruoyi.framework.config.ServerConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -13,22 +15,20 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import com.ruoyi.common.config.RuoYiConfig; -import com.ruoyi.common.core.domain.AjaxResult; -import com.ruoyi.common.utils.StringUtils; -import com.ruoyi.common.utils.file.FileUploadUtils; -import com.ruoyi.common.utils.file.FileUtils; -import com.ruoyi.framework.config.ServerConfig; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.List; /** * 通用请求处理 - * + * * @author ruoyi */ @RestController @RequestMapping("/common") -public class CommonController -{ +public class CommonController { private static final Logger log = LoggerFactory.getLogger(CommonController.class); @Autowired @@ -38,17 +38,14 @@ public class CommonController /** * 通用下载请求 - * + * * @param fileName 文件名称 - * @param delete 是否删除 + * @param delete 是否删除 */ @GetMapping("/download") - public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) - { - try - { - if (!FileUtils.checkAllowDownload(fileName)) - { + public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) { + try { + if (!FileUtils.checkAllowDownload(fileName)) { throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName)); } String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); @@ -57,13 +54,10 @@ public class CommonController response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); FileUtils.setAttachmentResponseHeader(response, realFileName); FileUtils.writeBytes(filePath, response.getOutputStream()); - if (delete) - { + if (delete) { FileUtils.deleteFile(filePath); } - } - catch (Exception e) - { + } catch (Exception e) { log.error("下载文件失败", e); } } @@ -72,10 +66,8 @@ public class CommonController * 通用上传请求(单个) */ @PostMapping("/upload") - public AjaxResult uploadFile(MultipartFile file) throws Exception - { - try - { + public AjaxResult uploadFile(MultipartFile file) throws Exception { + try { // 上传文件路径 String filePath = RuoYiConfig.getUploadPath(); // 上传并返回新文件名称 @@ -87,9 +79,7 @@ public class CommonController ajax.put("newFileName", FileUtils.getName(fileName)); ajax.put("originalFilename", file.getOriginalFilename()); return ajax; - } - catch (Exception e) - { + } catch (Exception e) { return AjaxResult.error(e.getMessage()); } } @@ -98,18 +88,15 @@ public class CommonController * 通用上传请求(多个) */ @PostMapping("/uploads") - public AjaxResult uploadFiles(List files) throws Exception - { - try - { + public AjaxResult uploadFiles(List files) throws Exception { + try { // 上传文件路径 String filePath = RuoYiConfig.getUploadPath(); List urls = new ArrayList(); List fileNames = new ArrayList(); List newFileNames = new ArrayList(); List originalFilenames = new ArrayList(); - for (MultipartFile file : files) - { + for (MultipartFile file : files) { // 上传并返回新文件名称 String fileName = FileUploadUtils.upload(filePath, file); String url = serverConfig.getUrl() + fileName; @@ -124,9 +111,7 @@ public class CommonController ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER)); ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER)); return ajax; - } - catch (Exception e) - { + } catch (Exception e) { return AjaxResult.error(e.getMessage()); } } @@ -136,12 +121,9 @@ public class CommonController */ @GetMapping("/download/resource") public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response) - throws Exception - { - try - { - if (!FileUtils.checkAllowDownload(resource)) - { + throws Exception { + try { + if (!FileUtils.checkAllowDownload(resource)) { throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource)); } // 本地资源路径 @@ -153,9 +135,7 @@ public class CommonController response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); FileUtils.setAttachmentResponseHeader(response, downloadName); FileUtils.writeBytes(downloadPath, response.getOutputStream()); - } - catch (Exception e) - { + } catch (Exception e) { log.error("下载文件失败", 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 4c3e10d..f7d1b45 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 @@ -1,15 +1,5 @@ package com.ruoyi.web.controller.system; -import java.util.Map; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.core.controller.BaseController; @@ -25,10 +15,15 @@ import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.common.utils.file.MimeTypeUtils; import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.system.service.ISysUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Map; /** * 个人信息 业务处理 - * + * * @author ruoyi */ @RestController @@ -127,7 +122,7 @@ public class SysProfileController extends BaseController if (!file.isEmpty()) { LoginUser loginUser = getLoginUser(); - String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION, true); + String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION); if (userService.updateUserAvatar(loginUser.getUserId(), avatar)) { String oldAvatar = loginUser.getUser().getAvatar(); diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml index f145fdd..caed35f 100644 --- a/ruoyi-admin/src/main/resources/application-druid.yml +++ b/ruoyi-admin/src/main/resources/application-druid.yml @@ -6,7 +6,7 @@ spring: druid: # 主库数据源 master: - url: jdbc:mysql://192.168.0.119:3318/rongxin_base?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + url: jdbc:mysql://localhost:3318/rongxin_base?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: nsbjgkwh password: ns61GK32x% # 从库数据源 diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index b555ae0..0e9d540 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -1,7 +1,7 @@ # 项目相关配置 ruoyi: # 名称 - name: RuoYi + name: RongXin # 版本 version: 3.9.0 # 版权年份 diff --git a/ruoyi-business/pom.xml b/ruoyi-business/pom.xml index 30d08cd..f6d99ba 100644 --- a/ruoyi-business/pom.xml +++ b/ruoyi-business/pom.xml @@ -12,4 +12,39 @@ ruoyi-business + + + + + com.ruoyi + ruoyi-system + + + + + com.ruoyi + ruoyi-common + + + + + + + de.odysseus.juel + juel + + + + + org.apache.httpcomponents + httpclient + + + + + + diff --git a/ruoyi-business/ruoyi-business.iml b/ruoyi-business/ruoyi-business.iml new file mode 100644 index 0000000..4e90397 --- /dev/null +++ b/ruoyi-business/ruoyi-business.iml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index 72d888f..001ebde 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -35,6 +35,12 @@ spring-boot-starter-security + + + net.coobird + thumbnailator + + com.github.pagehelper @@ -47,18 +53,24 @@ spring-boot-starter-validation + + + org.projectlombok + lombok + + org.apache.commons commons-lang3 - + com.fasterxml.jackson.core jackson-databind - + com.alibaba.fastjson2 @@ -89,6 +101,12 @@ jjwt + + + javax.servlet + javax.servlet-api + + javax.xml.bind @@ -119,6 +137,25 @@ javax.servlet-api + + + com.itextpdf + itextpdf + + + + + com.itextpdf + itext-asian + + + + + cn.hutool + hutool-all + + + - \ No newline at end of file + diff --git a/ruoyi-common/ruoyi-common.iml b/ruoyi-common/ruoyi-common.iml index a7761d3..2af91eb 100644 --- a/ruoyi-common/ruoyi-common.iml +++ b/ruoyi-common/ruoyi-common.iml @@ -37,6 +37,7 @@ + @@ -54,6 +55,7 @@ + @@ -72,6 +74,7 @@ + @@ -93,6 +96,8 @@ - + + + \ No newline at end of file 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 29281cf..74ff458 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 @@ -5,7 +5,7 @@ import org.springframework.stereotype.Component; /** * 读取项目相关配置 - * + * * @author ruoyi */ @Component @@ -13,7 +13,7 @@ import org.springframework.stereotype.Component; public class RuoYiConfig { /** 项目名称 */ - private String name; + private static String name; /** 版本 */ private String version; @@ -30,8 +30,7 @@ public class RuoYiConfig /** 验证码类型 */ private static String captchaType; - public String getName() - { + public static String getName() { return name; } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/BottomItem.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/BottomItem.java new file mode 100644 index 0000000..962f0c5 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/BottomItem.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.core.domain.pdf; + +import lombok.Data; + +/** + * @description: + * @author: zzl + * @date: Created in 2024-04-30 10:57 + * @version: 1.0 + * @modified By: + */ +@Data +public class BottomItem { + private static final long serialVersionUID = 1L; + + private String leftItem; + + private String centerItem; + + private String rightItem; + + + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/PageSet.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/PageSet.java new file mode 100644 index 0000000..10e751c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/PageSet.java @@ -0,0 +1,44 @@ +package com.ruoyi.common.core.domain.pdf; + +import lombok.Data; + +/** + * @description: + * @author: zzl + * @date: Created in 2024-04-30 10:57 + * @version: 1.0 + * @modified By: + */ +@Data +public class PageSet { + + private static final long serialVersionUID = 1L; + + // 纸张类型 + private String paperType; + + // 纸张宽度 默认 A4, 换算关系 1cm ~ 28.35f + private float paperWidth = 595.0f; + + // 纸张高度 默认 A4 + private float paperHeight = 842.0f; + + // 打印方向 1 纵 2横 0缺省 + private String printDirection = "1"; + + private int tableTotalWidth = 520; + + // 左边距 + private int marginLeft = 50; + + // 右边距 + private int marginRight = 50; + + // 上边距 + private int marginTop = 30; + + // 下边距 + private int marginBottom = 20; + + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/PdfProperty.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/PdfProperty.java new file mode 100644 index 0000000..7204b20 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/PdfProperty.java @@ -0,0 +1,46 @@ +package com.ruoyi.common.core.domain.pdf; + +import lombok.Data; + +import java.util.List; + +/** + * @description: + * @author: zzl + * @date: Created in 2024-04-30 10:57 + * @version: 1.0 + * @modified By: + */ +@Data +public class PdfProperty { + + private static final long serialVersionUID = 1L; + + // 顶部大标题 + private String title; + + // 纸张定义 + private PageSet pageSet; + + // 肩部:左 中 右 + private ShoulderItem shoulder; + + // 表行高 + private float rowHeight; + + // 列宽 百分比 + private float[] columnWidth; + + // 列标题 + private String[] header; + + // 水平位置( 0:左 1:中 2:右) + private int[] aligns; + + // 列表数据 + private List contentList; + + + + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/ShoulderItem.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/ShoulderItem.java new file mode 100644 index 0000000..4861ab2 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/ShoulderItem.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.core.domain.pdf; + +import lombok.Data; + +/** + * @description: + * @author: zzl + * @date: Created in 2024-04-30 10:57 + * @version: 1.0 + * @modified By: + */ +@Data +public class ShoulderItem { + private static final long serialVersionUID = 1L; + + private String leftItem; + + private String centerItem; + + private String rightItem; + + + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java index 2e17c4a..7abd21d 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java @@ -2,7 +2,7 @@ package com.ruoyi.common.enums; /** * 业务操作类型 - * + * * @author ruoyi */ public enum BusinessType @@ -51,9 +51,14 @@ public enum BusinessType * 生成代码 */ GENCODE, - + /** * 清空数据 */ CLEAN, + + /** + * 打印 + */ + PRINT, } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DecimalUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DecimalUtils.java new file mode 100644 index 0000000..b5070e1 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DecimalUtils.java @@ -0,0 +1,490 @@ +package com.ruoyi.common.utils; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** + * 字符串工具类 + * + * @author ruoyi + */ +public class DecimalUtils { + + private static final List CN_NUMBERS = Arrays.asList("零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"); + private static final List CN_UNITS = Arrays.asList("", "拾", "佰", "仟"); + private static final List CN_BIG_UNITS = Arrays.asList("", "万", "亿", "兆"); + private static final String CN_NEGATIVE = "负"; + private static final String CN_INTEGER = "整"; + private static final String CN_FULL = "圆"; + private static final String CN_DECIMAL_UNIT = "角分"; + private static final int MAXIMUM_NUMBER = 999999999; + + /** + * 将金额数字转换为中文大写 + * @param amount 金额数字,支持最大到999999999.99 + * @return 中文大写金额字符串 + */ + public static String bigDecimalToChinese(BigDecimal amount) { + if (amount == null) { + throw new IllegalArgumentException("金额不能为空"); + } + + // 检查金额范围 + if (amount.abs().compareTo(new BigDecimal(MAXIMUM_NUMBER + ".99")) > 0) { + throw new IllegalArgumentException("金额超出最大范围(999999999.99)"); + } + + StringBuilder result = new StringBuilder(); + + // 处理负数 + if (amount.compareTo(BigDecimal.ZERO) < 0) { + result.append(CN_NEGATIVE); + amount = amount.abs(); + } + + // 转换为字符串处理 + String amountStr = amount.setScale(2, BigDecimal.ROUND_HALF_UP).toString(); + String[] parts = amountStr.split("\\."); + String integerPart = parts[0]; + String decimalPart = parts.length > 1 ? parts[1] : ""; + + // 处理整数部分 + boolean hasInteger = !"0".equals(integerPart); + if (hasInteger) { + result.append(convertIntegerPart(integerPart)).append(CN_FULL); + } + + // 处理小数部分 + String decimalStr = convertDecimalPart(decimalPart); + if (!decimalStr.isEmpty()) { + // 如果没有整数部分且有小部分,则不显示"零圆" + if (!hasInteger && decimalStr.charAt(0) != CN_NUMBERS.get(0).charAt(0)) { + result.append(decimalStr); + } else { + // 如果有整数部分或小数部分以零开头,则正常拼接 + if (!hasInteger) { + result.append(CN_NUMBERS.get(0)).append(CN_FULL); + } + result.append(decimalStr); + } + } else { + // 如果没有小数部分,添加"整" + result.append(CN_INTEGER); + } + + return result.toString(); + } + + private static String convertIntegerPart(String integerPart) { + StringBuilder sb = new StringBuilder(); + int length = integerPart.length(); + int zeroCount = 0; // 连续零的个数 + + for (int i = 0; i < length; i++) { + int digit = Character.getNumericValue(integerPart.charAt(i)); + int pos = length - i - 1; // 当前位数 + int unitPos = pos % 4; // 单位位置(个十百千) + int bigUnitPos = pos / 4; // 大单位位置(万,亿) + + if (digit == 0) { + zeroCount++; + } else { + if (zeroCount > 0) { + sb.append(CN_NUMBERS.get(0)); + zeroCount = 0; + } + sb.append(CN_NUMBERS.get(digit)).append(CN_UNITS.get(unitPos)); + } + + // 添加大单位(万,亿) + if (unitPos == 0 && bigUnitPos > 0) { + if (zeroCount < 4) { // 如果当前段不全为零 + sb.append(CN_BIG_UNITS.get(bigUnitPos)); + } + zeroCount = 0; + } + } + + // 处理全零的情况 + if (sb.length() == 0) { + return CN_NUMBERS.get(0); + } + + return sb.toString(); + } + + private static String convertDecimalPart(String decimalPart) { + StringBuilder sb = new StringBuilder(); + boolean hasNonZero = false; + + for (int i = 0; i < decimalPart.length() && i < CN_DECIMAL_UNIT.length(); i++) { + int digit = Character.getNumericValue(decimalPart.charAt(i)); + if (digit != 0) { + sb.append(CN_NUMBERS.get(digit)).append(CN_DECIMAL_UNIT.charAt(i)); + hasNonZero = true; + } else if (hasNonZero || (i > 0 && sb.length() > 0)) { + // 如果前面已经有非零数字,或者不是第一位,可以添加零 + sb.append(CN_NUMBERS.get(0)); + } + } + + // 去除末尾的零 + while (sb.length() > 0 && sb.charAt(sb.length() - 1) == CN_NUMBERS.get(0).charAt(0)) { + sb.deleteCharAt(sb.length() - 1); + } + + return sb.toString(); + } + + + + + //默认除法运算精度 + private static final int DEF_DIV_SCALE = 10; + + /** + * 提供精确的加法运算 + * + * @param v1 被加数 + * @param v2 加数 + * @return 两个参数的和 + */ + + public static double add(double v1, double v2) { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.add(b2).doubleValue(); + } + + /** + * 提供精确的加法运算 + * + * @param v1 被加数 + * @param v2 加数 + * @return 两个参数的和 + */ + public static BigDecimal add(String v1, String v2) { + BigDecimal b1 = new BigDecimal(v1); + BigDecimal b2 = new BigDecimal(v2); + return b1.add(b2); + } + + /** + * 提供精确的加法运算 + * + * @param v1 被加数 + * @param v2 加数 + * @param scale 保留scale 位小数 + * @return 两个参数的和 + */ + public static String add(String v1, String v2, int scale) { + if (scale < 0) { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b1 = new BigDecimal(v1); + BigDecimal b2 = new BigDecimal(v2); + return b1.add(b2).setScale(scale, RoundingMode.HALF_UP).toString(); + } + + /** + * 提供精确的减法运算 + * + * @param v1 被减数 + * @param v2 减数 + * @return 两个参数的差 + */ + public static double sub(double v1, double v2) { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.subtract(b2).doubleValue(); + } + + /** + * 提供精确的减法运算。 + * + * @param v1 被减数 + * @param v2 减数 + * @return 两个参数的差 + */ + public static BigDecimal sub(String v1, String v2) { + BigDecimal b1 = new BigDecimal(v1); + BigDecimal b2 = new BigDecimal(v2); + return b1.subtract(b2); + } + + /** + * 提供精确的减法运算 + * + * @param v1 被减数 + * @param v2 减数 + * @param scale 保留scale 位小数 + * @return 两个参数的差 + */ + public static String sub(String v1, String v2, int scale) { + if (scale < 0) { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b1 = new BigDecimal(v1); + BigDecimal b2 = new BigDecimal(v2); + return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); + } + + /** + * 提供精确的乘法运算 + * + * @param v1 被乘数 + * @param v2 乘数 + * @return 两个参数的积 + */ + public static double mul(double v1, double v2) { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.multiply(b2).doubleValue(); + } + + /** + * 提供精确的乘法运算 + * + * @param v1 被乘数 + * @param v2 乘数 + * @return 两个参数的积 + */ + public static BigDecimal mul(String v1, String v2) { + BigDecimal b1 = new BigDecimal(v1); + BigDecimal b2 = new BigDecimal(v2); + return b1.multiply(b2); + } + + /** + * 提供精确的乘法运算 + * + * @param v1 被乘数 + * @param v2 乘数 + * @param scale 保留scale 位小数 + * @return 两个参数的积 + */ + public static double mul(double v1, double v2, int scale) { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return round(b1.multiply(b2).doubleValue(), scale); + } + + /** + * 提供精确的乘法运算 + * + * @param v1 被乘数 + * @param v2 乘数 + * @param scale 保留scale 位小数 + * @return 两个参数的积 + */ + public static String mul(String v1, String v2, int scale) { + if (scale < 0) { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b1 = new BigDecimal(v1); + BigDecimal b2 = new BigDecimal(v2); + return b1.multiply(b2).setScale(scale, RoundingMode.HALF_UP).toString(); + } + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 + * 小数点以后10位,以后的数字四舍五入 + * + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的商 + */ + + public static double div(double v1, double v2) { + return div(v1, v2, DEF_DIV_SCALE); + } + + /** + * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 + * 定精度,以后的数字四舍五入 + * + * @param v1 被除数 + * @param v2 除数 + * @param scale 表示表示需要精确到小数点以后几位。 + * @return 两个参数的商 + */ + public static double div(double v1, double v2, int scale) { + if (scale < 0) { + throw new IllegalArgumentException("The scale must be a positive integer or zero"); + } + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue(); + } + + /** + * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 + * 定精度,以后的数字四舍五入 + * + * @param v1 被除数 + * @param v2 除数 + * @param scale 表示需要精确到小数点以后几位 + * @return 两个参数的商 + */ + public static String div(String v1, String v2, int scale) { + if (scale < 0) { + throw new IllegalArgumentException("The scale must be a positive integer or zero"); + } + BigDecimal b1 = new BigDecimal(v1); + BigDecimal b2 = new BigDecimal(v1); + return b1.divide(b2, scale, RoundingMode.HALF_UP).toString(); + } + + /** + * 提供精确的小数位四舍五入处理 + * + * @param v 需要四舍五入的数字 + * @param scale 小数点后保留几位 + * @return 四舍五入后的结果 + */ + public static double round(double v, int scale) { + if (scale < 0) { + throw new IllegalArgumentException("The scale must be a positive integer or zero"); + } + BigDecimal b = new BigDecimal(Double.toString(v)); + return b.setScale(scale, RoundingMode.HALF_UP).doubleValue(); + } + + /** + * 提供精确的小数位四舍五入处理 + * + * @param v 需要四舍五入的数字 + * @param scale 小数点后保留几位 + * @return 四舍五入后的结果 + */ + public static String round(String v, int scale) { + if (scale < 0) { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b = new BigDecimal(v); + return b.setScale(scale, RoundingMode.HALF_UP).toString(); + } + + /** + * 取余数 + * + * @param v1 被除数 + * @param v2 除数 + * @param scale 小数点后保留几位 + * @return 余数 + */ + public static String remainder(String v1, String v2, int scale) { + if (scale < 0) { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b1 = new BigDecimal(v1); + BigDecimal b2 = new BigDecimal(v2); + return b1.remainder(b2).setScale(scale, RoundingMode.HALF_UP).toString(); + } + + /** + * 取余数 BigDecimal + * + * @param v1 被除数 + * @param v2 除数 + * @param scale 小数点后保留几位 + * @return 余数 + */ + public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) { + if (scale < 0) { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + return v1.remainder(v2).setScale(scale, RoundingMode.HALF_UP); + } + + /** + * 比较大小 + * + * @param v1 被比较数 + * @param v2 比较数 + * @return 如果v1 大于v2 则 返回true 否则false + */ + public static boolean compare(String v1, String v2) { + BigDecimal b1 = new BigDecimal(v1); + BigDecimal b2 = new BigDecimal(v2); + int bj = b1.compareTo(b2); + boolean res; + res = bj > 0; + return res; + } + + /** + * 将数字格式化为千分位 + * + * @param number + * @return 字符串 + */ + public static String convert(BigDecimal number) { + if (null == number || number.compareTo(BigDecimal.ZERO) == 0) { + return ""; + } + NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.ENGLISH); + + // 设置最大分数位数,这里设置为0表示不使用分数 + numberFormat.setMaximumFractionDigits(2); + + // 设置最小整数位数,这里设置为2表示整数部分至少2位 + //numberFormat.setMinimumIntegerDigits(2); + + // 设置grouping used属性为false,表示不使用千分位分隔符 + numberFormat.setGroupingUsed(true); + + return numberFormat.format(number); + } + + /** + * 将千分位数字字符串格式化为 BigDecimal + * @param formattedNumber + * @return 字符串 + */ + public static BigDecimal convert(String formattedNumber) { + if (StringUtils.isEmpty(formattedNumber)) { + return BigDecimal.ZERO; + } + try { + NumberFormat format = NumberFormat.getInstance(Locale.US); + Number number = format.parse(formattedNumber); + BigDecimal bigDecimal = new BigDecimal(number.toString()); + return bigDecimal; + } catch (ParseException e) { + e.printStackTrace(); + return BigDecimal.ZERO; + } + + } + + public static boolean isZero_s(BigDecimal b) { + + return null == b || b.compareTo(BigDecimal.ZERO) == 0; + } + + + /** + * 将是0的处理成空 + * @param number + * @return BigDecimal + */ + public static String zeroToNull(BigDecimal number) { + if(number.compareTo(BigDecimal.ZERO) == 0){ + return null; + }else{ + return number.toString(); + } + } +} 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 f198462..1d2dd87 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,16 +1,21 @@ package com.ruoyi.common.utils; -import java.util.Collection; -import java.util.List; +import cn.hutool.core.collection.CollectionUtil; import com.alibaba.fastjson2.JSONArray; import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.core.domain.entity.SysDictData; import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.utils.spring.SpringUtils; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + /** * 字典工具类 - * + * * @author ruoyi */ public class DictUtils @@ -22,7 +27,7 @@ public class DictUtils /** * 设置字典缓存 - * + * * @param key 参数键 * @param dictDatas 字典数据列表 */ @@ -33,7 +38,7 @@ public class DictUtils /** * 获取字典缓存 - * + * * @param key 参数键 * @return dictDatas 字典数据列表 */ @@ -49,7 +54,7 @@ public class DictUtils /** * 根据字典类型和字典值获取字典标签 - * + * * @param dictType 字典类型 * @param dictValue 字典值 * @return 字典标签 @@ -65,7 +70,7 @@ public class DictUtils /** * 根据字典类型和字典标签获取字典值 - * + * * @param dictType 字典类型 * @param dictLabel 字典标签 * @return 字典值 @@ -81,7 +86,7 @@ public class DictUtils /** * 根据字典类型和字典值获取字典标签 - * + * * @param dictType 字典类型 * @param dictValue 字典值 * @param separator 分隔符 @@ -124,7 +129,7 @@ public class DictUtils /** * 根据字典类型和字典标签获取字典值 - * + * * @param dictType 字典类型 * @param dictLabel 字典标签 * @param separator 分隔符 @@ -209,7 +214,7 @@ public class DictUtils /** * 删除指定字典缓存 - * + * * @param key 字典键 */ public static void removeDictCache(String key) @@ -228,7 +233,7 @@ public class DictUtils /** * 设置cache key - * + * * @param configKey 参数键 * @return 缓存键key */ @@ -236,4 +241,18 @@ public class DictUtils { return CacheConstants.SYS_DICT_KEY + configKey; } + + public static Map dictCacheValueLabelMap(String key) { + List dictCache = getDictCache(key); + if (CollectionUtil.isEmpty(dictCache)) + return new LinkedHashMap<>(); // safety + return dictCache.stream().collect(Collectors.toMap(SysDictData::getDictValue, SysDictData::getDictLabel, (a, b) -> a, LinkedHashMap::new)); + } + + public static String getDictLabelElseOriginValue(String dictType, String dictValue) { + if (StringUtils.isEmpty(dictValue)) + return dictValue; + String res = getDictLabel(dictType, dictValue); + return StringUtils.isEmpty(res) ? dictValue : res; + } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/BASE64DecodedMultipartFile.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/BASE64DecodedMultipartFile.java new file mode 100644 index 0000000..254daa3 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/BASE64DecodedMultipartFile.java @@ -0,0 +1,69 @@ +package com.ruoyi.common.utils.file; + +import cn.hutool.core.codec.Base64; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; + +public class BASE64DecodedMultipartFile implements MultipartFile { + + private final byte[] imgContent; + private final String header; + + public BASE64DecodedMultipartFile(byte[] imgContent, String header) { + this.imgContent = imgContent; + this.header = header.split(";")[0]; + } + + @Override + public String getName() { + return System.currentTimeMillis() + Math.random() + "." + header.split("/")[1]; + } + + @Override + public String getOriginalFilename() { + return System.currentTimeMillis() + (int) Math.random() * 10000 + "." + header.split("/")[1]; + } + + @Override + public String getContentType() { + return header.split(":")[1]; + } + + @Override + public boolean isEmpty() { + return imgContent == null || imgContent.length == 0; + } + + @Override + public long getSize() { + return imgContent.length; + } + + @Override + public byte[] getBytes() throws IOException { + return imgContent; + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(imgContent); + } + + @Override + public void transferTo(File dest) throws IOException, IllegalStateException { + new FileOutputStream(dest).write(imgContent); + } + + public static MultipartFile base64ToMultipart(String base64) { + + String[] base64Array = base64.split(","); + String imageString = base64Array.length > 1 ? base64Array[1] : base64Array[0]; + byte[] imageBytes = Base64.decode(imageString); + return new BASE64DecodedMultipartFile(imageBytes, "image/png"); + + } + + + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ConvertToMultipartFile.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ConvertToMultipartFile.java new file mode 100644 index 0000000..5abbcfe --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ConvertToMultipartFile.java @@ -0,0 +1,71 @@ +package com.ruoyi.common.utils.file; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; + +/** + * @description: + * @author: zzl + * @date: Created in 2021/8/5 18:38 + * @version: v1.0 + * @modified By: + */ +public class ConvertToMultipartFile implements MultipartFile { + private byte[] fileBytes; + String name; + String originalFilename; + String contentType; + boolean isEmpty; + long size; + + public ConvertToMultipartFile(byte[] fileBytes, String name, String originalFilename, String contentType, long size) { + this.fileBytes = fileBytes; + this.name = name; + this.originalFilename = originalFilename; + this.contentType = contentType; + this.size = size; + this.isEmpty = false; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getOriginalFilename() { + return originalFilename; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public boolean isEmpty() { + return isEmpty; + } + + @Override + public long getSize() { + return size; + } + + @Override + public byte[] getBytes() throws IOException { + return fileBytes; + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(fileBytes); + } + + @Override + public void transferTo(File dest) throws IOException, IllegalStateException { + new FileOutputStream(dest).write(fileBytes); + } +} + diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java index cc1b6f4..12937d2 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java @@ -1,11 +1,5 @@ package com.ruoyi.common.utils.file; -import java.io.File; -import java.io.IOException; -import java.nio.file.Paths; -import java.util.Objects; -import org.apache.commons.io.FilenameUtils; -import org.springframework.web.multipart.MultipartFile; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.exception.file.FileNameLengthLimitExceededException; @@ -13,21 +7,39 @@ import com.ruoyi.common.exception.file.FileSizeLimitExceededException; import com.ruoyi.common.exception.file.InvalidExtensionException; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; -import com.ruoyi.common.utils.uuid.IdUtils; import com.ruoyi.common.utils.uuid.Seq; +import org.apache.commons.io.FilenameUtils; +import org.springframework.web.multipart.MultipartFile; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.file.Paths; +import java.util.Objects; + +import static java.util.Arrays.binarySearch; /** * 文件上传工具类 - * + * * @author ruoyi */ -public class FileUploadUtils -{ +public class FileUploadUtils { /** * 默认大小 50M */ public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024L; + /** + * 图片压缩红线 kb + */ + public static final long DES_FILE_SIZE = 1024; + /** * 默认的文件名最大长度 100 */ @@ -38,16 +50,58 @@ public class FileUploadUtils */ private static String defaultBaseDir = RuoYiConfig.getProfile(); - public static void setDefaultBaseDir(String defaultBaseDir) - { + public static void setDefaultBaseDir(String defaultBaseDir) { FileUploadUtils.defaultBaseDir = defaultBaseDir; } - public static String getDefaultBaseDir() - { + public static String getDefaultBaseDir() { return defaultBaseDir; } + + public static String base64Upload(String img64, String bizPath) { + try { + MultipartFile file = BASE64DecodedMultipartFile.base64ToMultipart(img64); + String filePath = RuoYiConfig.getProfile() + File.separator + bizPath; + return upload(filePath, file); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + + /** + * 根据远程文件url 下载存储到本地 + * @param urlStr "https://example.com/image.jpg" 远程文件地址 + * @return bizPath 新的存储位置 + * @return fileName a.png + * @throws Exception + */ + public static String saveFile(String urlStr, String bizPath, String fileName) throws Exception { + // 读取远程文件 + URL url = new URL(urlStr); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(5000); + InputStream is = conn.getInputStream(); + byte[] data = ImageUtils.readInputStream(is); + + bizPath = bizPath + "/" + DateUtils.dateTime(); + // 创建文件路径 + getAbsoluteFile(bizPath, fileName).getAbsolutePath(); + + // 创建文件 + File imageFile = new File(bizPath + "/" + fileName); + FileOutputStream outStream = new FileOutputStream(imageFile); + outStream.write(data); + outStream.close(); + + return getPathFileName(bizPath, fileName); + + } + + /** * 以默认配置进行文件上传 * @@ -55,14 +109,10 @@ public class FileUploadUtils * @return 文件名称 * @throws Exception */ - public static final String upload(MultipartFile file) throws IOException - { - try - { + public static final String upload(MultipartFile file) throws IOException { + try { return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); - } - catch (Exception e) - { + } catch (Exception e) { throw new IOException(e.getMessage(), e); } } @@ -71,18 +121,31 @@ public class FileUploadUtils * 根据文件路径上传 * * @param baseDir 相对应用的基目录 - * @param file 上传的文件 + * @param file 上传的文件 * @return 文件名称 * @throws IOException */ - public static final String upload(String baseDir, MultipartFile file) throws IOException - { - try - { + public static final String upload(String baseDir, MultipartFile file) throws IOException { + try { return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } catch (Exception e) { + throw new IOException(e.getMessage(), e); } - catch (Exception e) - { + } + + /** + * 根据文件路径上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @param isPng 是否转透明png + * @return 文件名称 + * @throws IOException + */ + public static final String upload(String baseDir, MultipartFile file, boolean isPng) throws IOException { + try { + return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } catch (Exception e) { throw new IOException(e.getMessage(), e); } } @@ -90,86 +153,161 @@ public class FileUploadUtils /** * 文件上传 * - * @param baseDir 相对应用的基目录 - * @param file 上传的文件 + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 * @param allowedExtension 上传文件类型 * @return 返回上传成功的文件名 - * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws FileSizeLimitExceededException 如果超出最大大小 * @throws FileNameLengthLimitExceededException 文件名太长 - * @throws IOException 比如读写文件出错时 - * @throws InvalidExtensionException 文件校验异常 + * @throws IOException 比如读写文件出错时 + * @throws InvalidExtensionException 文件校验异常 */ public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension) throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, - InvalidExtensionException - { - return upload(baseDir, file, allowedExtension, false); + InvalidExtensionException { + + // 文件名过长 + int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length(); + if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) { + throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); + } + + // 文件大小、文件后缀格式 + assertAllowed(file, allowedExtension); + + // 大于1M的 图片文件 进行压缩 + String fileType = getExtension(file); + if (binarySearch(MimeTypeUtils.IMAGE_EXTENSION, fileType) > 0) { + String fileName = file.getOriginalFilename(); + // 压缩成字节数组 + byte[] imageByte = ImageUtils.compressPicForScale(file.getBytes(), FileUploadUtils.DES_FILE_SIZE); + // 恢复成MultipartFile,且不重命名 + file = new ConvertToMultipartFile(imageByte, fileName, fileName, fileType, imageByte.length); + } + + + // 文件重命名 + String fileName = extractFilename(file); + // 判断后缀名是否正确 + int splitIndex = fileName.lastIndexOf("."); + String fType = fileName.substring(splitIndex + 1); + if (fType == null || fType.equals("")) { + fileName = fileName + file.getContentType(); + } + if (splitIndex < 0) { + fileName = fileName + "." + fileType; + } + + // 创建文件路径 + String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); + + // 存储文件 + file.transferTo(Paths.get(absPath)); + + // 返回文件路径 + return getPathFileName(baseDir, fileName); } - + /** * 文件上传 * - * @param baseDir 相对应用的基目录 - * @param file 上传的文件 - * @param useCustomNaming 系统自定义文件名 + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @param isPng 是否转透明png * @param allowedExtension 上传文件类型 * @return 返回上传成功的文件名 - * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws FileSizeLimitExceededException 如果超出最大大小 * @throws FileNameLengthLimitExceededException 文件名太长 - * @throws IOException 比如读写文件出错时 - * @throws InvalidExtensionException 文件校验异常 + * @throws IOException 比如读写文件出错时 + * @throws InvalidExtensionException 文件校验异常 */ - public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension, boolean useCustomNaming) + public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension, boolean isPng, boolean isAngle) throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, - InvalidExtensionException - { + InvalidExtensionException { + + // 文件名过长 int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length(); - if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) - { + if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) { throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); } + // 文件大小、文件后缀格式 assertAllowed(file, allowedExtension); - String fileName = useCustomNaming ? uuidFilename(file) : extractFilename(file); + // 大于1M的 图片文件 进行压缩 + String fileType = getExtension(file); + if (binarySearch(MimeTypeUtils.IMAGE_EXTENSION, fileType) > 0) { + String fileName = file.getOriginalFilename(); + // 压缩成字节数组 + byte[] imageByte = ImageUtils.compressPicForScale(file.getBytes(), FileUploadUtils.DES_FILE_SIZE); + // 恢复成MultipartFile,且不重命名 + file = new ConvertToMultipartFile(imageByte, fileName, fileName, fileType, imageByte.length); + } + // 文件重命名 + String fileName = extractFilename(file); + // 判断后缀名是否正确 + int splitIndex = fileName.lastIndexOf("."); + String fType = fileName.substring(splitIndex + 1); + if (fType == null || fType.equals("")) { + fileName = fileName + file.getContentType(); + } + if (splitIndex < 0) { + fileName = fileName + "." + fileType; + } + + // 创建文件路径 String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); - file.transferTo(Paths.get(absPath)); + + // 存储文件 + if(isAngle){ // 旋转保存 + // 创建文件路径 + File desc = getAbsoluteFile(baseDir, fileName); + + String ex = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")); + //根据图片角度自动识别翻转 + //int angle = ImageUtils.getAngle(ImageUtils.getExif(file.getInputStream())); + // 旋转可能是自动识别手机效果的,此处不是原生,所以写死 + int angle = 3; + BufferedImage bf = ImageUtils.getBufferedImg(ImageIO.read(file.getInputStream()), ImageUtils.getWidth(file.getInputStream()), ImageUtils.getHeight(file.getInputStream()), angle); + ImageIO.write(bf, ex.substring(1), desc); + }else { // 正常保存 + file.transferTo(Paths.get(absPath)); + } + + // 是否有转换透明底的需求 + if (isPng) { + //BufferedImage image = ImageIO.read(new File(absPath)); + //BufferedImage transparentImage = ImageUtils.makeBackgroundTransparent(image); + //ImageIO.write(transparentImage, "PNG", new File(absPath + "png")); + ImageUtils.convertToTransparentPng(absPath, absPath); + } + + // 返回文件路径 return getPathFileName(baseDir, fileName); - } - /** - * 编码文件名(日期格式目录 + 原文件名 + 序列值 + 后缀) - */ - public static final String extractFilename(MultipartFile file) - { - return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file)); } /** - * 编编码文件名(日期格式目录 + UUID + 后缀) + * 编码文件名 */ - public static final String uuidFilename(MultipartFile file) - { - return StringUtils.format("{}/{}.{}", DateUtils.datePath(), IdUtils.fastSimpleUUID(), getExtension(file)); + public static final String extractFilename(MultipartFile file) { + return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), + FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file)); } - public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException - { + public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException { File desc = new File(uploadDir + File.separator + fileName); - if (!desc.exists()) - { - if (!desc.getParentFile().exists()) - { + if (!desc.exists()) { + if (!desc.getParentFile().exists()) { desc.getParentFile().mkdirs(); } } return desc; } - public static final String getPathFileName(String uploadDir, String fileName) throws IOException - { + public static final String getPathFileName(String uploadDir, String fileName) throws IOException { int dirLastIndex = RuoYiConfig.getProfile().length() + 1; String currentDir = StringUtils.substring(uploadDir, dirLastIndex); return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName; @@ -184,40 +322,30 @@ public class FileUploadUtils * @throws InvalidExtensionException */ public static final void assertAllowed(MultipartFile file, String[] allowedExtension) - throws FileSizeLimitExceededException, InvalidExtensionException - { + throws FileSizeLimitExceededException, InvalidExtensionException { long size = file.getSize(); - if (size > DEFAULT_MAX_SIZE) - { + // 超过默认 50M + if (size > DEFAULT_MAX_SIZE) { throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); } String fileName = file.getOriginalFilename(); String extension = getExtension(file); - if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) - { - if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) - { + // 文件格式校验 + if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) { + if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) { throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, fileName); - } - else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) - { + } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) { throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, fileName); - } - else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) - { + } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) { throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, fileName); - } - else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) - { + } else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) { throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, fileName); - } - else - { + } else { throw new InvalidExtensionException(allowedExtension, extension, fileName); } } @@ -230,12 +358,9 @@ public class FileUploadUtils * @param allowedExtension * @return */ - public static final boolean isAllowedExtension(String extension, String[] allowedExtension) - { - for (String str : allowedExtension) - { - if (str.equalsIgnoreCase(extension)) - { + public static final boolean isAllowedExtension(String extension, String[] allowedExtension) { + for (String str : allowedExtension) { + if (str.equalsIgnoreCase(extension)) { return true; } } @@ -244,15 +369,13 @@ public class FileUploadUtils /** * 获取文件名的后缀 - * + * * @param file 表单文件 * @return 后缀名 */ - public static final String getExtension(MultipartFile file) - { + public static final String getExtension(MultipartFile file) { String extension = FilenameUtils.getExtension(file.getOriginalFilename()); - if (StringUtils.isEmpty(extension)) - { + if (StringUtils.isEmpty(extension)) { extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); } return extension; diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java index 432dfda..7f2b549 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java @@ -1,55 +1,58 @@ package com.ruoyi.common.utils.file; -import java.io.ByteArrayInputStream; -import java.io.FileInputStream; -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.util.Arrays; -import org.apache.poi.util.IOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.utils.StringUtils; +import net.coobird.thumbnailator.Thumbnails; +import org.apache.poi.util.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.net.URL; +import java.net.URLConnection; +import java.util.Arrays; /** * 图片处理工具类 * * @author ruoyi */ -public class ImageUtils -{ +public class ImageUtils { private static final Logger log = LoggerFactory.getLogger(ImageUtils.class); - public static byte[] getImage(String imagePath) - { + //以下是常量,按照阿里代码开发规范,不允许代码中出现魔法值 + private static final Integer ZERO = 0; + private static final Integer ONE_ZERO_TWO_FOUR = 1024; + private static final Integer NINE_ZERO_ZERO = 900; + private static final Integer THREE_TWO_SEVEN_FIVE = 3275; + private static final Integer TWO_ZERO_FOUR_SEVEN = 2047; + private static final Double ZERO_EIGHT_FIVE = 0.85; + private static final Double ZERO_SIX = 0.6; + private static final Double ZERO_FOUR_FOUR = 0.44; + private static final Double ZERO_FOUR = 0.4; + + public static byte[] getImage(String imagePath) { InputStream is = getFile(imagePath); - try - { + try { return IOUtils.toByteArray(is); - } - catch (Exception e) - { + } catch (Exception e) { log.error("图片加载异常 {}", e); return null; - } - finally - { + } finally { IOUtils.closeQuietly(is); } } - public static InputStream getFile(String imagePath) - { - try - { + public static InputStream getFile(String imagePath) { + try { byte[] result = readFile(imagePath); result = Arrays.copyOf(result, result.length); return new ByteArrayInputStream(result); - } - catch (Exception e) - { + } catch (Exception e) { log.error("获取图片异常 {}", e); } return null; @@ -57,17 +60,14 @@ public class ImageUtils /** * 读取文件为字节数据 - * + * * @param url 地址 * @return 字节数据 */ - public static byte[] readFile(String url) - { + public static byte[] readFile(String url) { InputStream in = null; - try - { - if (url.startsWith("http")) - { + try { + if (url.startsWith("http")) { // 网络地址 URL urlObj = new URL(url); URLConnection urlConnection = urlObj.openConnection(); @@ -75,24 +75,223 @@ public class ImageUtils urlConnection.setReadTimeout(60 * 1000); urlConnection.setDoInput(true); in = urlConnection.getInputStream(); - } - else - { + } else { // 本机地址 String localPath = RuoYiConfig.getProfile(); String downloadPath = localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX); in = new FileInputStream(downloadPath); } return IOUtils.toByteArray(in); - } - catch (Exception e) - { + } catch (Exception e) { log.error("获取文件路径异常 {}", e); return null; - } - finally - { + } finally { IOUtils.closeQuietly(in); } } + + /** + * 根据指定大小压缩图片 + * + * @param imageBytes 源图片字节数组 + * @param desFileSize 指定图片大小,单位kb + * @return 压缩质量后的图片字节数组 + */ + public static byte[] compressPicForScale(byte[] imageBytes, long desFileSize) { + if (imageBytes == null || imageBytes.length <= ZERO || imageBytes.length < desFileSize * ONE_ZERO_TWO_FOUR) { + // log.info("图片无需压缩"); + return imageBytes; + } + long srcSize = imageBytes.length; + double accuracy = getAccuracy(srcSize / ONE_ZERO_TWO_FOUR); + try { + while (imageBytes.length > desFileSize * ONE_ZERO_TWO_FOUR) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(imageBytes.length); + Thumbnails.of(inputStream).scale(accuracy).outputQuality(accuracy).toOutputStream(outputStream); + imageBytes = outputStream.toByteArray(); + } + //log.info("图片原大小={}kb | 压缩后大小={}kb", srcSize / ONE_ZERO_TWO_FOUR, imageBytes.length / ONE_ZERO_TWO_FOUR); + } catch (Exception e) { + log.error("【图片压缩】msg=图片压缩失败!", e); + } + return imageBytes; + } + + /** + * 自动调节精度(经验数值) + * + * @param size 源图片大小 + * @return 图片压缩质量比 + */ + private static double getAccuracy(long size) { + double accuracy; + if (size < NINE_ZERO_ZERO) { // 900 + accuracy = ZERO_EIGHT_FIVE; // 0.85 + } else if (size < TWO_ZERO_FOUR_SEVEN) { // 2047 + accuracy = ZERO_SIX; // 0.6 + } else if (size < THREE_TWO_SEVEN_FIVE) { // 3275 + accuracy = ZERO_FOUR_FOUR; // 0.44 + } else { + accuracy = ZERO_FOUR; // 0.4 + } + return accuracy; + } + + /** + * 将图片转换为具有透明底的PNG图片 + * + * @param inputFile 输入图片文件路径 + * @param outputFile 输出图片文件路径 + * @throws IOException 如果读写文件时发生错误 + */ + public static void convertToTransparentPng(String inputFile, String outputFile) throws IOException { + + // 读取图片 + BufferedImage image = ImageIO.read(new File(inputFile)); + // 创建一个新的图片对象,类型为 BufferedImage.TYPE_INT_ARGB + BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); + // 获取新图片的绘制上下文 + Graphics2D g2d = (Graphics2D) newImage.getGraphics(); + // 绘制原始图片到新图片上,同时处理每个像素 + for (int x = 0; x < image.getWidth(); x++) { + for (int y = 0; y < image.getHeight(); y++) { + // 获取原始图片的像素颜色 + int color = image.getRGB(x, y); + Color originalColor = new Color(color, true); + // 判断是否为白色,这里设置了一个阈值范围 + if (originalColor.getRed() > 102 && originalColor.getGreen() > 102 && originalColor.getBlue() > 102) { + // 如果是白色,则设置为透明 + g2d.setColor(new Color(0, 0, 0, 0)); + } else { + // 如果不是白色,则保持原始颜色 + g2d.setColor(originalColor); + } + // 绘制像素 + g2d.drawRect(x, y, 1, 1); + } + } + // 释放资源 + g2d.dispose(); + // 保存处理后的图片 + ImageIO.write(newImage, "PNG", new File(outputFile)); + + } + + /** + * 将图片转换为具有透明底的PNG图片 + * @param inputFile 输入图片文件 + * @param outputFile 输出图片文件路径 + * @throws IOException 如果读写文件时发生错误 + */ + public static void convertToTransparentPng(File inputFile, String outputFile) throws IOException { + // 读取输入图片 + BufferedImage image = ImageIO.read(inputFile); + + // 创建一个具有透明背景的BufferedImage + BufferedImage transparentImage = new BufferedImage( + image.getWidth(), + image.getHeight(), + BufferedImage.TYPE_INT_ARGB + ); + + // 绘制原始图片到透明背景上 + Graphics2D graphics = transparentImage.createGraphics(); + graphics.setComposite(AlphaComposite.Src); + graphics.drawImage(image, 0, 0, null); + graphics.dispose(); + + // 写入到输出文件 + ImageIO.write(transparentImage, "PNG", new File(outputFile)); + } + + + public static BufferedImage makeBackgroundTransparent(BufferedImage image) { + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage transparentImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + Graphics2D g2d = transparentImage.createGraphics(); + g2d.setComposite(AlphaComposite.Src); + g2d.drawImage(image, null, 0, 0); + g2d.dispose(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int p = image.getRGB(x, y); + int a = (p >> 24) & 0xff; + int r = (p >> 16) & 0xff; + int g = (p >> 8) & 0xff; + int b = p & 0xff; + + // Define your background color here + if (r > 255 && g > 255 && b > 255) { + // Set fully transparent for background color + p = (a << 24) | (0 << 16) | (0 << 8) | 0; + } + transparentImage.setRGB(x, y, p); + } + } + + return transparentImage; + } + + + public static BufferedImage getBufferedImg(BufferedImage src, int width, int height, int ro) { + int angle = (int) (90 * ro); + int type = src.getColorModel().getTransparency(); + int wid = width; + int hei = height; + if (ro % 2 != 0) { + int temp = width; + width = height; + height = temp; + } + Rectangle re = new Rectangle(new Dimension(width, height)); + BufferedImage BfImg = null; + BfImg = new BufferedImage(re.width, re.height, type); + Graphics2D g2 = BfImg.createGraphics(); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2.rotate(Math.toRadians(angle), re.width / 2, re.height / 2); + g2.drawImage(src, (re.width - wid) / 2, (re.height - hei) / 2, null); + g2.dispose(); + return BfImg; + } + + //获得图片的高 + public static int getHeight(InputStream is) { + BufferedImage src = null; + int height = -1; + try { + src = ImageIO.read(is); + height = src.getHeight(); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + return height; + } + + //获得图片的宽 + public static int getWidth(InputStream is) { + BufferedImage src = null; + int width = -1; + try { + src = ImageIO.read(is); + width = src.getWidth(); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + return width; + } + + public static byte[] readInputStream(InputStream inStream) throws IOException { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len; + while ((len = inStream.read(buffer)) != -1) { + outStream.write(buffer, 0, len); + } + inStream.close(); + return outStream.toByteArray(); + } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/MyHeaderFooter.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/MyHeaderFooter.java new file mode 100644 index 0000000..832a249 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/MyHeaderFooter.java @@ -0,0 +1,72 @@ +package com.ruoyi.common.utils.pdf; + +import com.itextpdf.text.*; +import com.itextpdf.text.pdf.*; +import com.ruoyi.common.config.RuoYiConfig; + +import java.io.IOException; + +/** + * @description: 显示页数 + * @author: zzl + * @date: Created in 2024-04-27 9:07 + * @version: 1.0 + * @modified By: + */ +public class MyHeaderFooter extends PdfPageEventHelper { + // 总页数 + PdfTemplate totalPage; + + Font hfFont; + + { + try { + hfFont = new Font(BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED), 8, Font.NORMAL); + } catch (DocumentException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // 打开文档时,创建一个总页数的模版 + public void onOpenDocument(PdfWriter writer, Document document) { + PdfContentByte cb = writer.getDirectContent(); + totalPage = cb.createTemplate(30, 16); + } + + // 一页加载完成触发,写入页眉和页脚 + public void onEndPage(PdfWriter writer, Document document) { + PdfPTable table = new PdfPTable(3); + try { + + table.setTotalWidth(PageSize.A4.getWidth() - 100); + table.setWidths(new int[]{24, 24, 3}); + table.setLockedWidth(true); + table.getDefaultCell().setFixedHeight(-10); + table.getDefaultCell().setBorder(Rectangle.BOTTOM); + + table.addCell(new Paragraph(RuoYiConfig.getName(), hfFont)); + table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_RIGHT); + table.addCell(new Paragraph("第" + writer.getPageNumber() + "页/", hfFont)); + + // 总页数 + PdfPCell cell = new PdfPCell(Image.getInstance(totalPage)); + cell.setBorder(Rectangle.BOTTOM); + table.addCell(cell); + + // 将页眉写到document中,位置可以指定,指定到下面就是页脚 + table.writeSelectedRows(0, -1, 50, PageSize.A4.getHeight() - 20, writer.getDirectContent()); + + } catch (Exception de) { + throw new ExceptionConverter(de); + } + } + + // 全部完成后,将总页数的pdf模版写到指定位置 + public void onCloseDocument(PdfWriter writer, Document document) { + String text = "总" + (writer.getPageNumber()) + "页" ; + ColumnText.showTextAligned(totalPage, Element.ALIGN_LEFT, new Paragraph(text, hfFont), 2, 2, 0); + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/MyHeaderFooterRotate.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/MyHeaderFooterRotate.java new file mode 100644 index 0000000..60aaeeb --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/MyHeaderFooterRotate.java @@ -0,0 +1,70 @@ +package com.ruoyi.common.utils.pdf; + +import com.itextpdf.text.*; +import com.itextpdf.text.pdf.*; +import com.ruoyi.common.config.RuoYiConfig; + +import java.io.IOException; + +/** + * @description: 显示页数 + * @author: zzl + * @date: Created in 2024-04-27 9:07 + * @version: 1.0 + * @modified By: + */ +public class MyHeaderFooterRotate extends PdfPageEventHelper { + // 总页数 + PdfTemplate totalPage; + + Font hfFont; + + { + try { + hfFont = new Font(BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED), 8, Font.NORMAL); + } catch (DocumentException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // 打开文档时,创建一个总页数的模版 + public void onOpenDocument(PdfWriter writer, Document document) { + PdfContentByte cb = writer.getDirectContent(); + totalPage = cb.createTemplate(30, 16); + } + + // 一页加载完成触发,写入页眉和页脚 + public void onEndPage(PdfWriter writer, Document document) { + PdfPTable table = new PdfPTable(3); + try { + table.setTotalWidth(PageSize.A4.getHeight() - 100); + table.setWidths(new int[]{24, 24, 3}); + table.setLockedWidth(true); + table.getDefaultCell().setFixedHeight(-10); + table.getDefaultCell().setBorder(Rectangle.BOTTOM); + + table.addCell(new Paragraph(RuoYiConfig.getName(), hfFont)); + table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_RIGHT); + table.addCell(new Paragraph("第" + writer.getPageNumber() + "页/", hfFont)); + + // 总页数 + PdfPCell cell = new PdfPCell(Image.getInstance(totalPage)); + cell.setBorder(Rectangle.BOTTOM); + table.addCell(cell); + + // 将页眉写到document中,位置可以指定,指定到下面就是页脚 + table.writeSelectedRows(0, -1, 50, PageSize.A4.getWidth() - 20, writer.getDirectContent()); + } catch (Exception de) { + throw new ExceptionConverter(de); + } + } + + // 全部完成后,将总页数的pdf模版写到指定位置 + public void onCloseDocument(PdfWriter writer, Document document) { + String text = "总" + (writer.getPageNumber()) + "页" ; + ColumnText.showTextAligned(totalPage, Element.ALIGN_LEFT, new Paragraph(text, hfFont), 2, 2, 0); + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/PdfUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/PdfUtils.java new file mode 100644 index 0000000..a70d983 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/PdfUtils.java @@ -0,0 +1,538 @@ +package com.ruoyi.common.utils.pdf; + +import com.itextpdf.text.*; +import com.itextpdf.text.pdf.BaseFont; +import com.itextpdf.text.pdf.PdfPCell; +import com.itextpdf.text.pdf.PdfPTable; +import com.itextpdf.text.pdf.PdfWriter; +import com.itextpdf.text.pdf.draw.DottedLineSeparator; +import com.itextpdf.text.pdf.draw.LineSeparator; +import com.ruoyi.common.core.domain.pdf.PageSet; +import com.ruoyi.common.core.domain.pdf.PdfProperty; +import com.ruoyi.common.core.domain.pdf.ShoulderItem; +import com.ruoyi.common.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayOutputStream; +import java.util.List; + +/** + * pdf处理工具类 + * + * @author ruoyi + */ +@Slf4j +public class PdfUtils { + + // 定义全局的字体静态变量 + private static Font titlefontMax; + private static Font titlefont; + private static Font titlefontmin; + private static Font headfont; + private static Font keyfont; + private static Font textfont; + private static Font bottomfont; + // 最大宽度 + public static int maxWidth = 520; + // 表行高 + public static float rowHeight = 20f; + // 表格宽度百分比 + public static Integer widthPercentage = 100; + + public static int leftspan = 3; + public static int centerspan = 3; + public static int rightspan = 3; + + // 行线间距 + public static float dottedLineSpace = 50f; + + // 静态代码块 + static { + try { + // 不同字体(这里定义为同一种字体:包含不同字号、不同style) + BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); + titlefontMax = new Font(bfChinese, 20, Font.BOLD); + titlefont = new Font(bfChinese, 15, Font.BOLD); + titlefontmin = new Font(bfChinese, 14, Font.BOLD); + headfont = new Font(bfChinese, 11, Font.NORMAL); + keyfont = new Font(bfChinese, 10, Font.BOLD); + textfont = new Font(bfChinese, 10, Font.NORMAL); + bottomfont = new Font(bfChinese, 11, Font.NORMAL); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // 通用 初始化 PDF文件 + public static void initPdf(HttpServletResponse response, PdfProperty pdf) throws Exception { + // 1. 新建document对象 + Document document = null; + PageSet ps = pdf.getPageSet(); + if (StringUtils.isNull(ps)) { //默认A4纵向 + document = new Document(PageSize.A4); + } else { // 自定义 + if (ps.getPrintDirection().equals("1")) {// 纵向 + document = new Document(new Rectangle(ps.getPaperWidth(), ps.getPaperHeight()), ps.getMarginLeft(), ps.getMarginRight(), ps.getMarginTop(), ps.getMarginBottom()); + } else { //2 横向 + document = new Document(new Rectangle(ps.getPaperWidth(), ps.getPaperHeight()).rotate(), ps.getMarginLeft(), ps.getMarginRight(), ps.getMarginTop(), ps.getMarginBottom()); + } + } + // 2. 创建一个输出流,将输出流与PdfWriter绑定 + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfWriter writer = PdfWriter.getInstance(document, baos); + if (ps.getPrintDirection().equals("1")) {// 纵向 + writer.setPageEvent(new MyHeaderFooter());// 页眉/页脚 + } else { //2 横向 + writer.setPageEvent(new MyHeaderFooterRotate());// 页眉/页脚 + } + // 3. 打开文档 + document.open(); + document.addTitle(pdf.getTitle() + " PDF打印"); + // 4. 向文档中添加内容 + generateComPdf(document, pdf); + // 5. 关闭文档 + document.close(); + // 6. 将PDF输出到浏览器 + response.setContentType("application/pdf"); + response.setCharacterEncoding("utf-8"); + response.setContentLength(baos.size()); + response.setHeader("Content-Disposition", "inline; filename=example.pdf"); + response.getOutputStream().write(baos.toByteArray()); + response.getOutputStream().flush(); + } + + // 通用 填充 PDF 内容 + public static void generateComPdf(Document document, PdfProperty pdf) throws Exception { + + // 创建段落:表名 + createParagraph(document, pdf.getTitle()); + // 初始参数 + int columns = pdf.getHeader().length; + centerspan = columns / 3; + leftspan = centerspan + columns % 3; + rightspan = centerspan; + if (StringUtils.isNotNull(pdf.getPageSet())) { + maxWidth = pdf.getPageSet().getTableTotalWidth(); + } + if (StringUtils.isNotNull(pdf.getRowHeight())) { + rowHeight = pdf.getRowHeight(); + } + // 创建表 + PdfPTable table = createTable(pdf.getColumnWidth()); + // 肩部名称 + ShoulderItem shoulder = pdf.getShoulder(); + if (StringUtils.isNotNull(shoulder)) { + createShoulder(table, shoulder); + } + // 列标题 + String[] header = pdf.getHeader(); + for (String h : header) { + table.addCell(createCell(h, keyfont, Element.ALIGN_CENTER)); + } + // 设置为每页固定表头 < row + if (StringUtils.isNotNull(shoulder)) { + table.setHeaderRows(2); + } else { + table.setHeaderRows(1); + } + // 列水平位置 + int[] aligns = pdf.getAligns(); + // 行内容 + List contentList = pdf.getContentList(); + for (String[] strs : contentList) { + int i = 0; + for (String str : strs) { + if (null == aligns) { + table.addCell(createCell(str, textfont)); + } else { + table.addCell(createCell(str, textfont, aligns[i])); + } + i++; + } + } + + document.add(table); + } + + + + /** + * 创建单元格(指定字体) + * + * @param value + * @param font + * @return + */ + public static PdfPCell createCell(String value, Font font) { + PdfPCell cell = new PdfPCell(); + cell.setVerticalAlignment(Element.ALIGN_MIDDLE); + cell.setHorizontalAlignment(Element.ALIGN_CENTER); + cell.setPhrase(new Phrase(value, font)); + cell.setFixedHeight(rowHeight); + cell.setNoWrap(false); // 自动换行 + return cell; + } + + /** + * 创建单元格(指定字体、水平位置) + * + * @param value + * @param font + * @param align + * @return + */ + public static PdfPCell createCell(String value, Font font, int align) { + PdfPCell cell = new PdfPCell(); + cell.setVerticalAlignment(Element.ALIGN_MIDDLE); + cell.setHorizontalAlignment(align); + cell.setPhrase(new Phrase(value, font)); + cell.setMinimumHeight(rowHeight); + cell.setNoWrap(false); // 自动换行 + return cell; + } + + + /** + * 创建单元格(指定字体、水平位置、跨x列合并) + * + * @param value + * @param font + * @param align + * @param colspan + * @return + */ + public static PdfPCell createCell(String value, Font font, int align, int colspan) { + PdfPCell cell = new PdfPCell(); + cell.setVerticalAlignment(Element.ALIGN_MIDDLE); + cell.setHorizontalAlignment(align); + cell.setColspan(colspan); + cell.setPhrase(new Phrase(value, font)); + cell.setMinimumHeight(rowHeight); + cell.setNoWrap(false); // 自动换行 + return cell; + } + + /** + * 创建单元格(指定字体、水平位置、跨x行合并) + * + * @param value + * @param font + * @param align + * @param rowspan + * @return + */ + public static PdfPCell createCell2(String value, Font font, int align, int rowspan) { + PdfPCell cell = new PdfPCell(); + cell.setVerticalAlignment(Element.ALIGN_MIDDLE); + cell.setHorizontalAlignment(align); + cell.setRowspan(rowspan); + cell.setPhrase(new Phrase(value, font)); + cell.setMinimumHeight(rowHeight); + cell.setNoWrap(false); // 自动换行 + return cell; + } + + /** + * 创建单元格(指定字体、水平位置、跨x列合并、设置单元格内边距) + * + * @param value + * @param font + * @param align + * @param colspan + * @param boderFlag + * @return + */ + public static PdfPCell createCell(String value, Font font, int align, int colspan, boolean boderFlag) { + PdfPCell cell = new PdfPCell(); + cell.setVerticalAlignment(Element.ALIGN_MIDDLE); + cell.setHorizontalAlignment(align); + cell.setColspan(colspan); + cell.setPhrase(new Phrase(value, font)); + cell.setMinimumHeight(rowHeight); + cell.setNoWrap(false); // 自动换行 + cell.setPadding(3.0f); + if (!boderFlag) { + cell.setBorder(0); + cell.setPaddingTop(4.0f); + cell.setPaddingBottom(4.0f); + } else if (boderFlag) { + cell.setBorder(0); + cell.setPaddingTop(2.0f); + cell.setPaddingBottom(2.0f); + } + return cell; + } + + public static PdfPCell createCellBottom(String value, Font font, int align, int colspan, boolean boderFlag) { + PdfPCell cell = new PdfPCell(); + cell.setVerticalAlignment(Element.ALIGN_MIDDLE); + cell.setHorizontalAlignment(align); + cell.setColspan(colspan); + cell.setPhrase(new Phrase(value, font)); + cell.setMinimumHeight(35f); + cell.setNoWrap(false); // 自动换行 + cell.setPadding(3.0f); + if (!boderFlag) { + cell.setBorder(0); + cell.setPaddingTop(4.0f); + cell.setPaddingBottom(4.0f); + } else if (boderFlag) { + cell.setBorder(0); + cell.setPaddingTop(2.0f); + cell.setPaddingBottom(2.0f); + } + return cell; + } + + /** + * 创建单元格(指定字体、水平位置、边框宽度(0表示无边框)、内边距、是否合并2列) + * + * @param value + * @param font + * @param align + * @param borderWidth + * @param paddingSize + * @param flag + * @return + */ + public PdfPCell createCell(String value, Font font, int align, float[] borderWidth, float[] paddingSize, boolean flag) { + PdfPCell cell = new PdfPCell(); + cell.setVerticalAlignment(Element.ALIGN_MIDDLE); + cell.setHorizontalAlignment(align); + cell.setPhrase(new Phrase(value, font)); + cell.setBorderWidthLeft(borderWidth[0]); + cell.setBorderWidthRight(borderWidth[1]); + cell.setBorderWidthTop(borderWidth[2]); + cell.setBorderWidthBottom(borderWidth[3]); + cell.setPaddingTop(paddingSize[0]); + cell.setPaddingBottom(paddingSize[1]); + cell.setNoWrap(false); // 自动换行 + if (flag) { + cell.setColspan(2); + } + return cell; + } + + /** + * 创建默认列宽,指定列数、水平(居中、右、左)的表格 + * + * @param colNumber + * @param align + * @return + */ + public static PdfPTable createTable(int colNumber, int align) { + PdfPTable table = new PdfPTable(colNumber); + try { + table.setTotalWidth(maxWidth); + table.setLockedWidth(true); + table.setHorizontalAlignment(align); + table.getDefaultCell().setBorder(1); + } catch (Exception e) { + e.printStackTrace(); + } + return table; + } + + /** + * 创建指定列宽、列数的表格 + * + * @param widths + * @return + */ + public static PdfPTable createTable(float[] widths) { + PdfPTable table = new PdfPTable(widths.length); + try { + table.setTotalWidth(maxWidth); + table.setWidthPercentage(widthPercentage); + table.setWidths(widths); + table.setLockedWidth(true); + table.setHorizontalAlignment(Element.ALIGN_CENTER); + table.getDefaultCell().setBorder(1); + } catch (Exception e) { + e.printStackTrace(); + } + return table; + } + + /** + * 创建段落 + * + * @return + */ + public static void createParagraph(Document document, String title) throws Exception { + Paragraph paragraph = new Paragraph(title, titlefont); + paragraph.setAlignment(1); //设置文字居中 0靠左 1,居中 2,靠右 + paragraph.setIndentationLeft(12); //设置左缩进 + paragraph.setIndentationRight(12); //设置右缩进 + paragraph.setFirstLineIndent(24); //设置首行缩进 + paragraph.setLeading(20f); //行间距 + paragraph.setSpacingBefore(5f); //设置段落上空白 + paragraph.setSpacingAfter(10f); //设置段落下空白 + document.add(paragraph); + } + + /** + * 创建段落 + * + * @return + */ + public static void createParagraph(Document document, String title, Font titlefont) throws Exception { + Paragraph paragraph = new Paragraph(title, titlefont); + paragraph.setAlignment(1); //设置文字居中 0靠左 1,居中 2,靠右 + paragraph.setIndentationLeft(12); //设置左缩进 + paragraph.setIndentationRight(12); //设置右缩进 + paragraph.setFirstLineIndent(24); //设置首行缩进 + paragraph.setLeading(20f); //行间距 + paragraph.setSpacingBefore(5f); //设置段落上空白 + paragraph.setSpacingAfter(10f); //设置段落下空白 + document.add(paragraph); + } + + /** + * 创建点线 + * + * @return + */ + public static void createDottedLine(Document document) throws Exception { + Paragraph dottedLine = new Paragraph(); + dottedLine.add(new Chunk(new DottedLineSeparator())); + dottedLine.setAlignment(1); //设置文字居中 0靠左 1,居中 2,靠右 + dottedLine.setIndentationLeft(12); //设置左缩进 + dottedLine.setIndentationRight(12); //设置右缩进 + dottedLine.setFirstLineIndent(24); //设置首行缩进 + dottedLine.setLeading(20f); //行间距 + dottedLine.setSpacingBefore(dottedLineSpace); //设置段落上空白 + dottedLine.setSpacingAfter(dottedLineSpace); //设置段落下空白 + document.add(dottedLine); + } + + /** + * 创建直线 + * + * @return + */ + public static void createLine(Document document) throws Exception { + Paragraph line = new Paragraph(); + line.add(new Chunk(new LineSeparator())); + document.add(line); + } + + + /** + * 创建表格肩部:左 中 右 + * + * @return + */ + public static void createShoulder(PdfPTable table, ShoulderItem shoulder) { + table.addCell(createCell(shoulder.getLeftItem(), headfont, Element.ALIGN_LEFT, leftspan, false)); + table.addCell(createCell(shoulder.getCenterItem(), headfont, Element.ALIGN_CENTER, centerspan, false)); + table.addCell(createCell(shoulder.getRightItem(), headfont, Element.ALIGN_RIGHT, rightspan, false)); + } + + /** + * 创建空白的表格 + * + * @return + */ + public static PdfPTable createBlankTable(float height) { + PdfPTable table = new PdfPTable(1); + table.getDefaultCell().setBorder(0); + table.addCell(createCell("", keyfont)); + table.setSpacingAfter(height); + table.setSpacingBefore(height); + return table; + } + + /** + * 创建空白的段落 + * + * @return + */ + public static void createBlankParagraph(Document document, float height) { + Paragraph p3 = new Paragraph(); + p3.setSpacingBefore(height); + p3.setSpacingAfter(height); + try { + document.add(p3); + } catch (DocumentException e) { + e.printStackTrace(); + } + } + + + /* DEMO 方法 */ + public static void generateDemoPDF(Document document) throws Exception { + + // 创建段落:表名 + createParagraph(document, "这是DEMO标题"); + + // 超链接 + Anchor anchor = new Anchor("打开百度"); + anchor.setReference("www.baidu.com"); + document.add(anchor); + + // 点线 + Paragraph p2 = new Paragraph(); + p2.add(new Chunk(new DottedLineSeparator())); + document.add(p2); + + // 直线 + Paragraph p1 = new Paragraph(); + p1.add(new Chunk(new LineSeparator())); + document.add(p1); + + + // 添加空白行 + //Paragraph p3 = new Paragraph(); + //p3.add(new Chunk(newLine())); + + Paragraph p3 = new Paragraph(); + p3.setSpacingBefore(50f); + p3.setSpacingAfter(50f); + document.add(p3); + + + // 表格 + PdfPTable table = createTable(new float[]{40, 120, 120, 120, 80, 80}); + table.addCell(createCell("姓名:张三", headfont, Element.ALIGN_LEFT, 2, false)); + table.addCell(createCell("日期:2024-01-01", headfont, Element.ALIGN_CENTER, 2, false)); + table.addCell(createCell("单位:元", headfont, Element.ALIGN_RIGHT, 2, false)); + + table.addCell(createCell("早上9:00", keyfont, Element.ALIGN_CENTER)); + table.addCell(createCell("中午11:00", keyfont, Element.ALIGN_CENTER)); + table.addCell(createCell("中午13:00", keyfont, Element.ALIGN_CENTER)); + table.addCell(createCell("下午15:00", keyfont, Element.ALIGN_CENTER)); + table.addCell(createCell("下午17:00", keyfont, Element.ALIGN_CENTER)); + table.addCell(createCell("晚上19:00", keyfont, Element.ALIGN_CENTER)); + Integer totalQuantity = 0; + for (int i = 0; i < 5; i++) { + table.addCell(createCell("起床", textfont)); + table.addCell(createCell("吃午饭", textfont)); + table.addCell(createCell("午休", textfont)); + table.addCell(createCell("下午茶", textfont)); + table.addCell(createCell("回家", textfont)); + table.addCell(createCell("吃晚饭", textfont)); + totalQuantity++; + } + table.addCell(createCell("总计", keyfont)); + table.addCell(createCell("", textfont)); + table.addCell(createCell("", textfont)); + table.addCell(createCell("", textfont)); + table.addCell(createCell(String.valueOf(totalQuantity) + "件事", textfont)); + table.addCell(createCell("", textfont)); + document.add(table); + + // 添加图片 + Image image = Image.getInstance("https://img-blog.csdn.net/20180801174617455?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNzg0ODcxMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70"); + image.setAlignment(Image.ALIGN_CENTER); + image.scalePercent(40); //依照比例缩放 + document.add(image); + + // 定位 + Anchor gotoP = new Anchor("back top"); + gotoP.setReference("#top"); + document.add(gotoP); + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/Watermark.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/Watermark.java new file mode 100644 index 0000000..9cfb362 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/Watermark.java @@ -0,0 +1,44 @@ +package com.ruoyi.common.utils.pdf; + +import com.itextpdf.text.Document; +import com.itextpdf.text.Element; +import com.itextpdf.text.Font; +import com.itextpdf.text.Phrase; +import com.itextpdf.text.pdf.ColumnText; +import com.itextpdf.text.pdf.GrayColor; +import com.itextpdf.text.pdf.PdfPageEventHelper; +import com.itextpdf.text.pdf.PdfWriter; + +/** + * @description: 水印文案 + * @author: zzl + * @date: Created in 2024-04-27 9:07 + * @version: 1.0 + * @modified By: + */ +public class Watermark extends PdfPageEventHelper { + Font FONT = new Font(Font.FontFamily.HELVETICA, 30, Font.BOLD, new GrayColor(0.95f)); + private String waterCont;//水印内容 + + public Watermark() { + + } + + public Watermark(String waterCont) { + this.waterCont = waterCont; + } + + @Override + public void onEndPage(PdfWriter writer, Document document) { + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) { + ColumnText.showTextAligned(writer.getDirectContentUnder(), + Element.ALIGN_CENTER, + new Phrase(this.waterCont == null ? "HELLO WORLD" : this.waterCont, FONT), + (50.5f + i * 350), + (40.0f + j * 150), + writer.getPageNumber() % 2 == 1 ? 45 : -45); + } + } + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelDictSheetHelper.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelDictSheetHelper.java new file mode 100644 index 0000000..e8d62d2 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelDictSheetHelper.java @@ -0,0 +1,110 @@ +package com.ruoyi.common.utils.poi; + +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.xssf.usermodel.XSSFDataValidation; + +// Excel 字典sheet助手 +public final class ExcelDictSheetHelper +{ + private final Workbook workbook; + private Sheet sheet; + private int currentColumn; + + public ExcelDictSheetHelper(Workbook workbook) + { + this.workbook = workbook; + } + + private Sheet Sheet() + { + if(null == sheet) + { + int numberOfSheets = workbook.getNumberOfSheets(); + sheet = workbook.createSheet("__HIDDEN_DICT__"); + workbook.setSheetHidden(numberOfSheets, true); + } + return sheet; + } + + // 检查下拉列表是否超过字符限制 + public static boolean CheckValidationLimit(String[] textlist) + { + if(null == textlist || textlist.length == 0) + return false; + String str = String.join(",", textlist); + return str.length() >= 255; + } + + public void AddValidation(Sheet sheet, String dictType, String[] textlist, int firstRow, int endRow, int firstCol, int endCol) + { + if(null == textlist || textlist.length == 0) + return; + + try + { + + String name = "__HIDDEN_" + dictType; + currentColumn++; + // 创建隐藏sheet, 将数组放在一列 + Sheet hidden = Sheet(); + for (int i = 0, length = textlist.length; i < length; i++) + { + Row row = hidden.getRow(i); + if(null == row) + row = hidden.createRow(i); + Cell cell = row.createCell(currentColumn - 1); + cell.setCellValue(textlist[i]); + } + + String columnNum = GenerateColumnName(currentColumn); + Name namedCell = workbook.createName(); + namedCell.setNameName(name); + // 设置名称引用的公式 + //System.err.println(String.format("%s!$%s$1:$%s$%d", hidden.getSheetName(), columnNum, columnNum, textlist.length)); + namedCell.setRefersToFormula(String.format("%s!$%s$1:$%s$%d", hidden.getSheetName(), columnNum, columnNum, textlist.length)); + + DataValidationHelper helper = sheet.getDataValidationHelper(); + DataValidationConstraint constraint = helper.createFormulaListConstraint(name); + // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + // 数据有效性对象 + DataValidation dataValidation = helper.createValidation(constraint, regions); + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } else { + dataValidation.setSuppressDropDownArrow(false); + } + sheet.addValidationData(dataValidation); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + + public static String GenerateColumnName(int i) + { + String strResult = ""; + int intRound = i / 26; + int intMod = i % 26; + if (intRound != 0) { + strResult = String.valueOf(((char) (intRound + 64))); + } + strResult += String.valueOf(((char) (intMod + 64))); + return strResult; + } + + public static int ParseColumnName(String name) + { + int column = -1; + for (int i = 0; i < name.length(); ++i) + { + int c = name.charAt(i); + column = (column + 1) * 26 + c - 'A'; + } + return column; + } +} 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 c4191ef..982abdc 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 @@ -1,81 +1,12 @@ package com.ruoyi.common.utils.poi; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.math.BigDecimal; -import java.text.DecimalFormat; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; -import javax.servlet.http.HttpServletResponse; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.RegExUtils; -import org.apache.commons.lang3.reflect.FieldUtils; -import org.apache.poi.hssf.usermodel.HSSFClientAnchor; -import org.apache.poi.hssf.usermodel.HSSFPicture; -import org.apache.poi.hssf.usermodel.HSSFPictureData; -import org.apache.poi.hssf.usermodel.HSSFShape; -import org.apache.poi.hssf.usermodel.HSSFSheet; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.ooxml.POIXMLDocumentPart; -import org.apache.poi.ss.usermodel.BorderStyle; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.CellStyle; -import org.apache.poi.ss.usermodel.CellType; -import org.apache.poi.ss.usermodel.ClientAnchor; -import org.apache.poi.ss.usermodel.DataFormat; -import org.apache.poi.ss.usermodel.DataValidation; -import org.apache.poi.ss.usermodel.DataValidationConstraint; -import org.apache.poi.ss.usermodel.DataValidationHelper; -import org.apache.poi.ss.usermodel.DateUtil; -import org.apache.poi.ss.usermodel.Drawing; -import org.apache.poi.ss.usermodel.FillPatternType; -import org.apache.poi.ss.usermodel.Font; -import org.apache.poi.ss.usermodel.HorizontalAlignment; -import org.apache.poi.ss.usermodel.IndexedColors; -import org.apache.poi.ss.usermodel.Name; -import org.apache.poi.ss.usermodel.PictureData; -import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.VerticalAlignment; -import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.ss.usermodel.WorkbookFactory; -import org.apache.poi.ss.util.CellRangeAddress; -import org.apache.poi.ss.util.CellRangeAddressList; -import org.apache.poi.util.IOUtils; -import org.apache.poi.xssf.streaming.SXSSFWorkbook; -import org.apache.poi.xssf.usermodel.XSSFClientAnchor; -import org.apache.poi.xssf.usermodel.XSSFDataValidation; -import org.apache.poi.xssf.usermodel.XSSFDrawing; -import org.apache.poi.xssf.usermodel.XSSFPicture; -import org.apache.poi.xssf.usermodel.XSSFShape; -import org.apache.poi.xssf.usermodel.XSSFSheet; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; -import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.annotation.Excel.Type; import com.ruoyi.common.annotation.Excels; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysDictData; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.exception.UtilException; import com.ruoyi.common.utils.DateUtils; @@ -85,21 +16,44 @@ import com.ruoyi.common.utils.file.FileTypeUtils; import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.common.utils.file.ImageUtils; import com.ruoyi.common.utils.reflect.ReflectUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.RegExUtils; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.poi.hssf.usermodel.*; +import org.apache.poi.ooxml.POIXMLDocumentPart; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.util.IOUtils; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.*; +import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; /** * Excel相关处理 - * + * * @author ruoyi */ -public class ExcelUtil -{ +public class ExcelUtil { private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class); - public static final String SEPARATOR = ","; + public static final String FORMULA_REGEX_STR = "=|-|\\+|@" ; - public static final String FORMULA_REGEX_STR = "=|-|\\+|@"; - - public static final String[] FORMULA_STR = { "=", "-", "+", "@" }; + public static final String[] FORMULA_STR = {"=", "-", "+", "@"}; /** * 用于dictType属性数据存储,避免重复查缓存 @@ -156,6 +110,15 @@ public class ExcelUtil */ private String title; + /** + * 标题下边一行 + */ + private String shoulderLeft; + + private String shoulderCenter; + + private String shoulderRight; + /** * 最大高度 */ @@ -187,49 +150,39 @@ public class ExcelUtil private Map statistics = new HashMap(); /** - * 实体对象 + * 数字格式 */ - public Class clazz; + private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00"); + + // 引用下拉列表助手 + private ExcelDictSheetHelper dictSheetHelper; /** - * 需要显示列属性 + * 实体对象 */ - public String[] includeFields; + public Class clazz; /** * 需要排除列属性 */ public String[] excludeFields; - public ExcelUtil(Class clazz) - { + public ExcelUtil(Class clazz) { this.clazz = clazz; } - /** - * 仅在Excel中显示列属性 - * - * @param fields 列属性名 示例[单个"name"/多个"id","name"] - */ - public void showColumn(String... fields) - { - this.includeFields = fields; - } - /** * 隐藏Excel中列属性 * * @param fields 列属性名 示例[单个"name"/多个"id","name"] + * @throws Exception */ - public void hideColumn(String... fields) - { + public void hideColumn(String... fields) { this.excludeFields = fields; } - public void init(List list, String sheetName, String title, Type type) - { - if (list == null) - { + public void init(List list, String sheetName, String title, Type type) { + if (list == null) { list = new ArrayList(); } this.list = list; @@ -242,16 +195,33 @@ public class ExcelUtil createSubHead(); } + public void init(List list, String sheetName, String title, Type type, String shoulderLeft, String shoulderCenter, String shoulderRight) { + if (list == null) { + list = new ArrayList(); + } + this.list = list; + this.sheetName = sheetName; + this.type = type; + this.title = title; + this.shoulderLeft = shoulderLeft; + this.shoulderCenter = shoulderCenter; + this.shoulderRight = shoulderRight; + createExcelField(); + createWorkbook(); + createTitle(); + createShoulder(); + createSubHead(); + } + /** * 创建excel第一行标题 */ - public void createTitle() - { - if (StringUtils.isNotEmpty(title)) - { + public void createTitle() { + if (StringUtils.isNotEmpty(title)) { + subMergedFirstRowNum++; + subMergedLastRowNum++; int titleLastCol = this.fields.size() - 1; - if (isSubList()) - { + if (isSubList()) { titleLastCol = titleLastCol + subFields.size() - 1; } Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0); @@ -259,42 +229,61 @@ public class ExcelUtil Cell titleCell = titleRow.createCell(0); titleCell.setCellStyle(styles.get("title")); titleCell.setCellValue(title); - sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), 0, titleLastCol)); + sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(), titleLastCol)); } } + /** + * 创建单元格 Shoulder + */ + public void createShoulder() { + int columns = fields.size(); + int c = columns / 3; //中间 + int r = columns - 1; // 最后一列 + Row shoulderRow = sheet.createRow(rownum); + for (int i = 0; i < columns; i++) { + Cell cell = shoulderRow.createCell(i); + if (i == 0) { + cell.setCellValue(shoulderLeft); + cell.setCellStyle(styles.get("data1")); + } else if (i == c) { + cell.setCellValue(shoulderCenter); + cell.setCellStyle(styles.get("data2")); + } else if (i == r) { + cell.setCellValue(shoulderRight); + cell.setCellStyle(styles.get("data3")); + } else { + cell.setCellValue(""); + } + } + // 合并单元格,参数依次为起始行,结束行,起始列,结束列 (索引0开始) + if (c > 2) + sheet.addMergedRegion(new CellRangeAddress(shoulderRow.getRowNum(), shoulderRow.getRowNum(), 0, c - 1)); + if (r > 2) + sheet.addMergedRegion(new CellRangeAddress(shoulderRow.getRowNum(), shoulderRow.getRowNum(), c, r - 1)); + rownum++; + } + /** * 创建对象的子列表名称 */ - public void createSubHead() - { - if (isSubList()) - { + public void createSubHead() { + if (isSubList()) { + subMergedFirstRowNum++; + subMergedLastRowNum++; Row subRow = sheet.createRow(rownum); - int column = 0; - int subFieldSize = subFields != null ? subFields.size() : 0; - for (Object[] objects : fields) - { - Field field = (Field) objects[0]; + int excelNum = 0; + for (Object[] objects : fields) { Excel attr = (Excel) objects[1]; - 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()))); - if (subFieldSize > 1) - { - CellRangeAddress cellAddress = new CellRangeAddress(rownum, rownum, column, column + subFieldSize - 1); - sheet.addMergedRegion(cellAddress); - } - column += subFieldSize; - } - else - { - Cell cell = subRow.createCell(column++); - cell.setCellValue(attr.name()); - cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); - } + Cell headCell1 = subRow.createCell(excelNum); + headCell1.setCellValue(attr.name()); + headCell1.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + excelNum++; + } + int headFirstRow = excelNum - 1; + int headLastRow = headFirstRow + subFields.size() - 1; + if (headLastRow > headFirstRow) { + sheet.addMergedRegion(new CellRangeAddress(rownum, rownum, headFirstRow, headLastRow)); } rownum++; } @@ -302,115 +291,110 @@ public class ExcelUtil /** * 对excel表单默认第一个索引名转换成list - * + * * @param is 输入流 * @return 转换后集合 */ - public List importExcel(InputStream is) - { - return importExcel(is, 0); + public List importExcel(InputStream is) { + List list = null; + try { + list = importExcel(is, 0); + } catch (Exception e) { + log.error("导入Excel异常{}", e.getMessage()); + throw new UtilException(e.getMessage()); + } finally { + IOUtils.closeQuietly(is); + } + return list; } /** * 对excel表单默认第一个索引名转换成list - * - * @param is 输入流 + * + * @param is 输入流 * @param titleNum 标题占用行数 * @return 转换后集合 */ - public List importExcel(InputStream is, int titleNum) - { - List list = null; - try - { - list = importExcel(StringUtils.EMPTY, is, titleNum); - } - catch (Exception e) - { - log.error("导入Excel异常{}", e.getMessage()); - throw new UtilException(e.getMessage()); - } - finally - { - IOUtils.closeQuietly(is); - } - return list; + public List importExcel(InputStream is, int titleNum) throws Exception { + return importExcel(StringUtils.EMPTY, is, titleNum); } /** * 对excel表单指定表格索引名转换成list - * + * * @param sheetName 表格索引名 - * @param titleNum 标题占用行数 - * @param is 输入流 + * @param titleNum 标题占用行数 + * @param is 输入流 * @return 转换后集合 */ - public List importExcel(String sheetName, InputStream is, int titleNum) throws Exception - { + public List importExcel(String sheetName, InputStream is, int titleNum) throws Exception { this.type = Type.IMPORT; this.wb = WorkbookFactory.create(is); List list = new ArrayList(); // 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0); - if (sheet == null) - { + if (sheet == null) { throw new IOException("文件sheet不存在"); } boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook); - Map> pictures = null; - if (isXSSFWorkbook) - { + Map pictures; + if (isXSSFWorkbook) { pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb); - } - else - { + } else { pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb); } // 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1 int rows = sheet.getLastRowNum(); - if (rows > 0) - { + if (rows > 0) { // 定义一个map用于存放excel列的序号和field. Map cellMap = new HashMap(); // 获取表头 Row heard = sheet.getRow(titleNum); - for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) - { + for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) { Cell cell = heard.getCell(i); - if (StringUtils.isNotNull(cell)) - { + + if (StringUtils.isNotNull(cell)) { String value = this.getCellValue(heard, i).toString(); cellMap.put(value, i); - } - else - { + } else { cellMap.put(null, i); } + + /*boolean isMerge = isMergedRegion(sheet, i, cell.getColumnIndex()); + String headVaule = "" ; + if (isMerge) { + headVaule = getMergedRegionValue(sheet, heard.getRowNum(), cell.getColumnIndex()); + log.info("合并 =》行:{},列:{},内容:{}", titleNum, i, headVaule); + } else { + headVaule = this.getCellValue(heard, i).toString(); + log.info("未合并 =》行:{},列:{},内容:{}", titleNum, i, headVaule); + } + if (StringUtils.isNotNull(headVaule)) { + cellMap.put(headVaule, i); + } else { + cellMap.put(null, i); + }*/ } + // 有数据时才处理 得到类的所有field. List fields = this.getFields(); Map fieldsMap = new HashMap(); - for (Object[] objects : fields) - { + for (Object[] objects : fields) { Excel attr = (Excel) objects[1]; Integer column = cellMap.get(attr.name()); - if (column != null) - { + if (column != null) { fieldsMap.put(column, objects); } } - for (int i = titleNum + 1; i <= rows; i++) - { + for (int i = titleNum + 1; i <= rows; i++) { // 从第2行开始取数据,默认第一行是表头. Row row = sheet.getRow(i); // 判断当前行是否是空行 - if (isRowEmpty(row)) - { + if (isRowEmpty(row)) { continue; } T entity = null; - for (Map.Entry entry : fieldsMap.entrySet()) - { + for (Map.Entry entry : fieldsMap.entrySet()) { Object val = this.getCellValue(row, entry.getKey()); // 如果不存在实例则新建. @@ -420,96 +404,60 @@ public class ExcelUtil Excel attr = (Excel) entry.getValue()[1]; // 取得类型,并根据对象类型设置值. Class fieldType = field.getType(); - if (String.class == fieldType) - { + if (String.class == fieldType) { String s = Convert.toStr(val); - if (s.matches("^\\d+\\.0$")) - { + if (s.matches("^\\d+\\.0$")){ val = StringUtils.substringBefore(s, ".0"); - } - else - { + } else { String dateFormat = field.getAnnotation(Excel.class).dateFormat(); - if (StringUtils.isNotEmpty(dateFormat)) - { + if (StringUtils.isNotEmpty(dateFormat)) { val = parseDateToStr(dateFormat, val); - } - else - { + } else { val = Convert.toStr(val); } } - } - else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) - { + } else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) { val = Convert.toInt(val); - } - else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) - { + } else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) { val = Convert.toLong(val); - } - else if (Double.TYPE == fieldType || Double.class == fieldType) - { + } else if (Double.TYPE == fieldType || Double.class == fieldType) { val = Convert.toDouble(val); - } - else if (Float.TYPE == fieldType || Float.class == fieldType) - { + } else if (Float.TYPE == fieldType || Float.class == fieldType) { val = Convert.toFloat(val); - } - else if (BigDecimal.class == fieldType) - { + } else if (BigDecimal.class == fieldType) { val = Convert.toBigDecimal(val); - } - else if (Date.class == fieldType) - { - if (val instanceof String) - { + } else if (Date.class == fieldType) { + if (val instanceof String) { val = DateUtils.parseDate(val); - } - else if (val instanceof Double) - { + } else if (val instanceof Double) { val = DateUtil.getJavaDate((Double) val); } - } - else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) - { + } else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) { val = Convert.toBool(val, false); } - if (StringUtils.isNotNull(fieldType)) - { + if (StringUtils.isNotNull(fieldType)) { String propertyName = field.getName(); - if (StringUtils.isNotEmpty(attr.targetAttr())) - { + if (StringUtils.isNotEmpty(attr.targetAttr())) { propertyName = field.getName() + "." + attr.targetAttr(); } - if (StringUtils.isNotEmpty(attr.readConverterExp())) - { + if (StringUtils.isNotEmpty(attr.readConverterExp())) { val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator()); - } - else if (StringUtils.isNotEmpty(attr.dictType())) - { - if (!sysDictMap.containsKey(attr.dictType() + val)) - { + } else if (StringUtils.isNotEmpty(attr.dictType())) { + if (!sysDictMap.containsKey(attr.dictType() + val)) { String dictValue = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator()); sysDictMap.put(attr.dictType() + val, dictValue); } val = sysDictMap.get(attr.dictType() + val); - } - else if (!attr.handler().equals(ExcelHandlerAdapter.class)) - { + } else if (!attr.handler().equals(ExcelHandlerAdapter.class)) { val = dataFormatHandlerAdapter(val, attr, null); - } - else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)) - { - StringBuilder propertyString = new StringBuilder(); - List images = pictures.get(row.getRowNum() + "_" + entry.getKey()); - for (PictureData picture : images) - { - byte[] data = picture.getData(); - String fileName = FileUtils.writeImportBytes(data); - propertyString.append(fileName).append(SEPARATOR); + } else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)) { + PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey()); + if (image == null) { + val = "" ; + } else { + byte[] data = image.getData(); + val = FileUtils.writeImportBytes(data); } - val = StringUtils.stripEnd(propertyString.toString(), SEPARATOR); } ReflectUtils.invokeSetter(entity, propertyName, val); } @@ -521,55 +469,144 @@ public class ExcelUtil } /** - * 对list数据源将其里面的数据导入到excel表单 - * - * @param list 导出数据集合 - * @param sheetName 工作表的名称 - * @return 结果 + * 判断指定的单元格是否是合并单元格 + * + * @param sheet + * @param row 行下标 + * @param column 列下标 + * @return + */ + private boolean isMergedRegion(Sheet sheet, int row, int column) { + for (int i = 0; i < sheet.getNumMergedRegions(); i++) { + CellRangeAddress mergedRegion = sheet.getMergedRegion(i); + if (mergedRegion.isInRange(row, column)) { + return true; + } + } + return false; + + /*int sheetMergeCount = sheet.getNumMergedRegions(); + for (int i = 0; i < sheetMergeCount; i++) { + CellRangeAddress range = sheet.getMergedRegion(i); + int firstColumn = range.getFirstColumn(); + int lastColumn = range.getLastColumn(); + int firstRow = range.getFirstRow(); + int lastRow = range.getLastRow(); + if(row >= firstRow && row <= lastRow){ + if(column >= firstColumn && column <= lastColumn){ + return true; + } + } + } + return false;*/ + } + + /** + * 获取合并单元格的值 + * + * @param sheet + * @param row + * @param column + * @return + */ + public String getMergedRegionValue(Sheet sheet, int row, int column) { + int sheetMergeCount = sheet.getNumMergedRegions(); + for (int i = 0; i < sheetMergeCount; i++) { + CellRangeAddress ca = sheet.getMergedRegion(i); + int firstColumn = ca.getFirstColumn(); + int lastColumn = ca.getLastColumn(); + int firstRow = ca.getFirstRow(); + int lastRow = ca.getLastRow(); + if (row >= firstRow && row <= lastRow) { + if (column >= firstColumn && column <= lastColumn) { + Row fRow = sheet.getRow(firstRow); + Cell fCell = fRow.getCell(firstColumn); + return getCellValue(fCell); + } + } + } + return null; + } + + /** + * 获取单元格的值 + * + * @param cell + * @return */ - public AjaxResult exportExcel(List list, String sheetName) - { - return exportExcel(list, sheetName, StringUtils.EMPTY); + public String getCellValue(Cell cell) { + if (cell == null) return "" ; + return cell.getStringCellValue(); } + /** * 对list数据源将其里面的数据导入到excel表单 - * - * @param list 导出数据集合 - * @param sheetName 工作表的名称 - * @param title 标题 + * list 数据集合 + * fileName 文件名称 + * sheetName 工作表名称 + * title 表标题名称 + * shoulderLeft 表左上名称 + * shoulderCenter 表中间名称 + * shoulderRight 表右上名称 + * * @return 结果 */ - public AjaxResult exportExcel(List list, String sheetName, String title) - { + public AjaxResult exportExcel(List list, String sheetName) { + this.init(list, sheetName, StringUtils.EMPTY, Type.EXPORT); + return exportExcel(""); + } + + public AjaxResult exportExcel(List list, String fileName, String sheetName) { + this.init(list, sheetName, StringUtils.EMPTY, Type.EXPORT); + return exportExcel(fileName); + } + + public AjaxResult exportExcel(List list, String fileName, String sheetName, String title) { this.init(list, sheetName, title, Type.EXPORT); - return exportExcel(); + return exportExcel(fileName); + } + + + public AjaxResult exportExcel(List list, String sheetName, String title, String shoulderLeft, String shoulderCenter, String shoulderRight) { + this.init(list, sheetName, title, Type.EXPORT, shoulderLeft, shoulderCenter, shoulderRight); + return exportExcel(""); + } + + + public AjaxResult exportExcel(List list, String fileName, String sheetName, String title, String shoulderLeft, String shoulderCenter, String shoulderRight) { + this.init(list, sheetName, title, Type.EXPORT, shoulderLeft, shoulderCenter, shoulderRight); + return exportExcel(fileName); + } + + + public String exportExcelSteam(String downloadPath, List list, String sheetName) { + this.init(list, sheetName, title, Type.EXPORT); + return exportExcelSteam(downloadPath); } /** * 对list数据源将其里面的数据导入到excel表单 - * - * @param response 返回数据 - * @param list 导出数据集合 + * + * @param response 返回数据 + * @param list 导出数据集合 * @param sheetName 工作表的名称 * @return 结果 */ - public void exportExcel(HttpServletResponse response, List list, String sheetName) - { + public void exportExcel(HttpServletResponse response, List list, String sheetName) { exportExcel(response, list, sheetName, StringUtils.EMPTY); } /** * 对list数据源将其里面的数据导入到excel表单 - * - * @param response 返回数据 - * @param list 导出数据集合 + * + * @param response 返回数据 + * @param list 导出数据集合 * @param sheetName 工作表的名称 - * @param title 标题 + * @param title 标题 * @return 结果 */ - public void exportExcel(HttpServletResponse response, List list, String sheetName, String title) - { + 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); @@ -578,48 +615,53 @@ public class ExcelUtil /** * 对list数据源将其里面的数据导入到excel表单 - * + * * @param sheetName 工作表的名称 * @return 结果 */ - public AjaxResult importTemplateExcel(String sheetName) - { + public AjaxResult importTemplateExcel(String sheetName) { return importTemplateExcel(sheetName, StringUtils.EMPTY); } + public AjaxResult importTemplateExcel(List list, String sheetName) { + return importTemplateExcel(list, sheetName, StringUtils.EMPTY); + } + /** * 对list数据源将其里面的数据导入到excel表单 - * + * * @param sheetName 工作表的名称 - * @param title 标题 + * @param title 标题 * @return 结果 */ - public AjaxResult importTemplateExcel(String sheetName, String title) - { + public AjaxResult importTemplateExcel(String sheetName, String title) { this.init(null, sheetName, title, Type.IMPORT); - return exportExcel(); + return exportExcel(""); + } + + public AjaxResult importTemplateExcel(List list, String sheetName, String title) { + this.init(list, sheetName, title, Type.IMPORT); + return exportExcel(""); } /** * 对list数据源将其里面的数据导入到excel表单 - * + * * @param sheetName 工作表的名称 * @return 结果 */ - public void importTemplateExcel(HttpServletResponse response, String sheetName) - { + public void importTemplateExcel(HttpServletResponse response, String sheetName) { importTemplateExcel(response, sheetName, StringUtils.EMPTY); } /** * 对list数据源将其里面的数据导入到excel表单 - * + * * @param sheetName 工作表的名称 - * @param title 标题 + * @param title 标题 * @return 结果 */ - public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) - { + 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); @@ -628,88 +670,191 @@ public class ExcelUtil /** * 对list数据源将其里面的数据导入到excel表单 - * + * * @return 结果 */ - public void exportExcel(HttpServletResponse response) - { - try - { + public void exportExcel(HttpServletResponse response) { + try { writeSheet(); wb.write(response.getOutputStream()); - } - catch (Exception e) - { + } catch (Exception e) { log.error("导出Excel异常{}", e.getMessage()); - } - finally - { + } finally { IOUtils.closeQuietly(wb); } } /** * 对list数据源将其里面的数据导入到excel表单 - * + * * @return 结果 */ - public AjaxResult exportExcel() - { + + public AjaxResult exportExcel(String fileName) { OutputStream out = null; - try - { + try { writeSheet(); - String filename = encodingFilename(sheetName); + String filename = "" ; + if (StringUtils.isEmpty(fileName)) { + filename = encodingFilename(sheetName); + } else { + filename = encodingFilename(fileName + "_" + sheetName); + } out = new FileOutputStream(getAbsoluteFile(filename)); wb.write(out); return AjaxResult.success(filename); + } catch (Exception e) { + log.error("导出Excel异常{}", e.getMessage()); + throw new UtilException("导出Excel失败,请联系网站管理员!"); + } finally { + IOUtils.closeQuietly(wb); + IOUtils.closeQuietly(out); } - catch (Exception e) - { + } + + public String exportExcelSteam(String downloadPath) { + OutputStream out = null; + try { + writeSheet(); + String filename = encodingFilename(sheetName); + + out = new FileOutputStream(getAbsoluteFile(filename)); + wb.write(out); + + return filename; + } catch (Exception e) { log.error("导出Excel异常{}", e.getMessage()); throw new UtilException("导出Excel失败,请联系网站管理员!"); + } finally { + IOUtils.closeQuietly(wb); + IOUtils.closeQuietly(out); } - finally - { + } + + public static AjaxResult exportPriExcel(String sheetName, String shoulderLeft, String shoulderCenter, String shoulderRight, String[] header, List rowList) { + OutputStream out = null; + Workbook wb = new SXSSFWorkbook(500); + try { + Sheet sheet = wb.createSheet(sheetName); + int columns = header.length; + + // 创建表名 + Row titleRow = sheet.createRow(0); + titleRow.setHeightInPoints(30); + Cell titleCell = titleRow.createCell(0); + + CellStyle style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + Font titleFont = wb.createFont(); + titleFont.setFontName("Arial"); + titleFont.setFontHeightInPoints((short) 16); + titleFont.setBold(true); + style.setFont(titleFont); + + titleCell.setCellStyle(style); + titleCell.setCellValue(sheetName); + sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(), columns - 1)); + + + //创建肩部 + int c = columns / 3; //中间 + int r = columns - 1; // 最后一列 + Row shoulderRow = sheet.createRow(1); + for (int i = 0; i < columns; i++) { + Cell cell = shoulderRow.createCell(i); + if (i == 0) { + cell.setCellValue(shoulderLeft); + CellStyle style1 = wb.createCellStyle(); + style1.setAlignment(HorizontalAlignment.LEFT); + Font data1 = wb.createFont(); + data1.setFontName("Arial"); + data1.setFontHeightInPoints((short) 11); + style1.setFont(data1); + cell.setCellStyle(style1); + } else if (i == c) { + cell.setCellValue(shoulderCenter); + CellStyle style2 = wb.createCellStyle(); + style2.setAlignment(HorizontalAlignment.CENTER); + Font data2 = wb.createFont(); + data2.setFontName("Arial"); + data2.setFontHeightInPoints((short) 11); + style2.setFont(data2); + cell.setCellStyle(style2); + } else if (i == r) { + cell.setCellValue(shoulderRight); + CellStyle style3 = wb.createCellStyle(); + style3.setAlignment(HorizontalAlignment.RIGHT); + Font data3 = wb.createFont(); + data3.setFontName("Arial"); + data3.setFontHeightInPoints((short) 11); + style3.setFont(data3); + cell.setCellStyle(style3); + } else { + cell.setCellValue(""); + } + } + // 合并单元格,参数依次为起始行,结束行,起始列,结束列 (索引0开始) + if (c > 2) + sheet.addMergedRegion(new CellRangeAddress(shoulderRow.getRowNum(), shoulderRow.getRowNum(), 0, c - 1)); + if (r > 2) + sheet.addMergedRegion(new CellRangeAddress(shoulderRow.getRowNum(), shoulderRow.getRowNum(), c, r - 1)); + + // 创建表头 + Row headerRow = sheet.createRow(2); + for (int i = 0; i < header.length; i++) { + headerRow.createCell(i).setCellValue(header[i]); + } + + // 填充数据 + int rowIndex = 3; + for (String[] row : rowList) { + Row dataRow = sheet.createRow(rowIndex); + for (int i = 0; i < row.length; i++) { + dataRow.createCell(i).setCellValue(row[i]); + } + rowIndex++; + } + + String filename = encodingFilenamePri(sheetName); + out = new FileOutputStream(getAbsoluteFilePri(filename)); + wb.write(out); + return AjaxResult.success(filename); + } catch (Exception e) { + log.error("导出Excel异常{}", e.getMessage()); + throw new UtilException("导出Excel失败,请联系网站管理员!"); + } finally { IOUtils.closeQuietly(wb); IOUtils.closeQuietly(out); } } + /** * 创建写入数据到Sheet */ - public void writeSheet() - { + public void writeSheet() { // 取出一共有多少个sheet. int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize)); - for (int index = 0; index < sheetNo; index++) - { + for (int index = 0; index < sheetNo; index++) { createSheet(sheetNo, index); // 产生一行 Row row = sheet.createRow(rownum); int column = 0; // 写入各个字段的列头名称 - for (Object[] os : fields) - { + for (Object[] os : fields) { Field field = (Field) os[0]; Excel excel = (Excel) os[1]; - if (Collection.class.isAssignableFrom(field.getType())) - { - for (Field subField : subFields) - { + if (Collection.class.isAssignableFrom(field.getType())) { + for (Field subField : subFields) { Excel subExcel = subField.getAnnotation(Excel.class); this.createHeadCell(subExcel, row, column++); } - } - else - { + } else { this.createHeadCell(excel, row, column++); } } - if (Type.EXPORT.equals(type)) - { + if (Type.EXPORT.equals(type)) { fillExcelData(index, row); addStatisticsRow(); } @@ -718,110 +863,68 @@ public class ExcelUtil /** * 填充excel数据 - * + * * @param index 序号 - * @param row 单元格行 + * @param row 单元格行 */ @SuppressWarnings("unchecked") - public void fillExcelData(int index, Row row) - { + public void fillExcelData(int index, Row row) { int startNo = index * sheetSize; int endNo = Math.min(startNo + sheetSize, list.size()); - int currentRowNum = rownum + 1; // 从标题行后开始 - - for (int i = startNo; i < endNo; i++) - { - row = sheet.createRow(currentRowNum); + int rowNo = (1 + rownum) - startNo; + for (int i = startNo; i < endNo; i++) { + rowNo = isSubList() ? (i > 1 ? rowNo + 1 : rowNo + i) : i + 1 + rownum - startNo; + row = sheet.createRow(rowNo); + // 得到导出对象. T vo = (T) list.get(i); + Collection subList = null; + if (isSubList()) { + if (isSubListValue(vo)) { + subList = getListCellValue(vo); + subMergedLastRowNum = subMergedLastRowNum + subList.size(); + } else { + subMergedFirstRowNum++; + subMergedLastRowNum++; + } + } int column = 0; - int maxSubListSize = getCurrentMaxSubListSize(vo); - for (Object[] os : fields) - { + for (Object[] os : fields) { Field field = (Field) os[0]; Excel excel = (Excel) os[1]; - if (Collection.class.isAssignableFrom(field.getType())) - { - try - { - Collection subList = (Collection) getTargetValue(vo, field, excel); - if (subList != null && !subList.isEmpty()) - { - int subIndex = 0; - for (Object subVo : subList) - { - Row subRow = sheet.getRow(currentRowNum + subIndex); - if (subRow == null) - { - subRow = sheet.createRow(currentRowNum + subIndex); - } - - int subColumn = column; - for (Field subField : subFields) - { - Excel subExcel = subField.getAnnotation(Excel.class); - addCell(subExcel, subRow, (T) subVo, subField, subColumn++); - } - subIndex++; + if (Collection.class.isAssignableFrom(field.getType()) && StringUtils.isNotNull(subList)) { + boolean subFirst = false; + for (Object obj : subList) { + if (subFirst) { + rowNo++; + row = sheet.createRow(rowNo); + } + List subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class); + int subIndex = 0; + for (Field subField : subFields) { + if (subField.isAnnotationPresent(Excel.class)) { + subField.setAccessible(true); + Excel attr = subField.getAnnotation(Excel.class); + this.addCell(attr, row, (T) obj, subField, column + subIndex); } - column += subFields.size(); + subIndex++; } + subFirst = true; } - catch (Exception e) - { - log.error("填充集合数据失败", e); - } - } - else - { - // 创建单元格并设置值 - addCell(excel, row, vo, field, column); - if (maxSubListSize > 1 && excel.needMerge()) - { - sheet.addMergedRegion(new CellRangeAddress(currentRowNum, currentRowNum + maxSubListSize - 1, column, column)); - } - column++; - } - } - currentRowNum += maxSubListSize; - } - } - - /** - * 获取子列表最大数 - */ - private int getCurrentMaxSubListSize(T vo) - { - int maxSubListSize = 1; - for (Object[] os : fields) - { - Field field = (Field) os[0]; - if (Collection.class.isAssignableFrom(field.getType())) - { - try - { - Collection subList = (Collection) getTargetValue(vo, field, (Excel) os[1]); - if (subList != null && !subList.isEmpty()) - { - maxSubListSize = Math.max(maxSubListSize, subList.size()); - } - } - catch (Exception e) - { - log.error("获取集合大小失败", e); + this.subMergedFirstRowNum = this.subMergedFirstRowNum + subList.size(); + } else { + this.addCell(excel, row, vo, field, column++); } } } - return maxSubListSize; } /** * 创建表格样式 - * + * * @param wb 工作薄对象 * @return 样式列表 */ - private Map createStyles(Workbook wb) - { + private Map createStyles(Workbook wb) { // 写入各条记录,每条记录对应excel表中的一行 Map styles = new HashMap(); CellStyle style = wb.createCellStyle(); @@ -839,13 +942,13 @@ public class ExcelUtil style = wb.createCellStyle(); style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); - style.setBorderRight(BorderStyle.THIN); + style.setBorderRight(BorderStyle.MEDIUM); style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); - style.setBorderLeft(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.MEDIUM); style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); - style.setBorderTop(BorderStyle.THIN); + style.setBorderTop(BorderStyle.MEDIUM); style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); - style.setBorderBottom(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.MEDIUM); style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); Font dataFont = wb.createFont(); dataFont.setFontName("Arial"); @@ -853,10 +956,33 @@ public class ExcelUtil style.setFont(dataFont); styles.put("data", style); + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.LEFT); + Font data1 = wb.createFont(); + data1.setFontName("Arial"); + data1.setFontHeightInPoints((short) 11); + style.setFont(data1); + styles.put("data1", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + Font data2 = wb.createFont(); + data2.setFontName("Arial"); + data2.setFontHeightInPoints((short) 11); + style.setFont(data2); + styles.put("data2", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.RIGHT); + Font data3 = wb.createFont(); + data3.setFontName("Arial"); + data3.setFontHeightInPoints((short) 11); + style.setFont(data3); + styles.put("data3", style); + style = wb.createCellStyle(); style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); - style.setDataFormat(dataFormat.getFormat("######0.00")); Font totalFont = wb.createFont(); totalFont.setFontName("Arial"); totalFont.setFontHeightInPoints((short) 10); @@ -872,19 +998,16 @@ public class ExcelUtil /** * 根据Excel注解创建表格头样式 - * + * * @param wb 工作薄对象 * @return 自定义样式列表 */ - private Map annotationHeaderStyles(Workbook wb, Map styles) - { + private Map annotationHeaderStyles(Workbook wb, Map styles) { Map headerStyles = new HashMap(); - for (Object[] os : fields) - { + for (Object[] os : fields) { Excel excel = (Excel) os[1]; String key = StringUtils.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor()); - if (!headerStyles.containsKey(key)) - { + if (!headerStyles.containsKey(key)) { CellStyle style = wb.createCellStyle(); style.cloneStyleFrom(styles.get("data")); style.setAlignment(HorizontalAlignment.CENTER); @@ -908,30 +1031,24 @@ public class ExcelUtil /** * 根据Excel注解创建表格列样式 - * + * * @param wb 工作薄对象 * @return 自定义样式列表 */ - private Map annotationDataStyles(Workbook wb) - { + private Map annotationDataStyles(Workbook wb) { Map styles = new HashMap(); - for (Object[] os : fields) - { + for (Object[] os : fields) { Field field = (Field) os[0]; Excel excel = (Excel) os[1]; - if (Collection.class.isAssignableFrom(field.getType())) - { + if (Collection.class.isAssignableFrom(field.getType())) { ParameterizedType pt = (ParameterizedType) field.getGenericType(); Class subClass = (Class) pt.getActualTypeArguments()[0]; List subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); - for (Field subField : subFields) - { + for (Field subField : subFields) { Excel subExcel = subField.getAnnotation(Excel.class); annotationDataStyles(styles, subField, subExcel); } - } - else - { + } else { annotationDataStyles(styles, field, excel); } } @@ -940,37 +1057,33 @@ public class ExcelUtil /** * 根据Excel注解创建表格列样式 - * + * * @param styles 自定义样式列表 * @param field 属性列信息 * @param excel 注解信息 */ - public void annotationDataStyles(Map styles, Field field, Excel excel) - { - String key = StringUtils.format("data_{}_{}_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor(), excel.cellType(), excel.wrapText()); - if (!styles.containsKey(key)) - { + public void annotationDataStyles(Map styles, Field field, Excel excel) { + String key = StringUtils.format("data_{}_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor(), excel.cellType()); + if (!styles.containsKey(key)) { CellStyle style = wb.createCellStyle(); style.setAlignment(excel.align()); style.setVerticalAlignment(VerticalAlignment.CENTER); - style.setBorderRight(BorderStyle.THIN); + style.setBorderRight(BorderStyle.MEDIUM); style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); - style.setBorderLeft(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.MEDIUM); style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); - style.setBorderTop(BorderStyle.THIN); + style.setBorderTop(BorderStyle.MEDIUM); style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); - style.setBorderBottom(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.MEDIUM); style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setFillPattern(FillPatternType.SOLID_FOREGROUND); style.setFillForegroundColor(excel.backgroundColor().getIndex()); - style.setWrapText(excel.wrapText()); Font dataFont = wb.createFont(); dataFont.setFontName("Arial"); dataFont.setFontHeightInPoints((short) 10); dataFont.setColor(excel.color().index); style.setFont(dataFont); - if (ColumnType.TEXT == excel.cellType()) - { + if (ColumnType.TEXT == excel.cellType()) { DataFormat dataFormat = wb.createDataFormat(); style.setDataFormat(dataFormat.getFormat("@")); } @@ -981,20 +1094,17 @@ public class ExcelUtil /** * 创建单元格 */ - public Cell createHeadCell(Excel attr, Row row, int column) - { + public Cell createHeadCell(Excel attr, Row row, int column) { // 创建列 Cell cell = row.createCell(column); // 写入列信息 cell.setCellValue(attr.name()); setDataValidation(attr, row, column); cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); - if (isSubList()) - { + if (isSubList()) { // 填充默认样式,防止合并单元格样式失效 - sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType(), attr.wrapText()))); - if (attr.needMerge()) - { + sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType()))); + if (attr.needMerge()) { sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column)); } } @@ -1003,46 +1113,33 @@ public class ExcelUtil /** * 设置单元格信息 - * + * * @param value 单元格值 - * @param attr 注解相关 - * @param cell 单元格信息 + * @param attr 注解相关 + * @param cell 单元格信息 */ - public void setCellVo(Object value, Excel attr, Cell cell) - { - if (ColumnType.STRING == attr.cellType() || ColumnType.TEXT == attr.cellType()) - { + public void setCellVo(Object value, Excel attr, Cell cell) { + if (ColumnType.STRING == attr.cellType() || ColumnType.TEXT == attr.cellType()) { String cellValue = Convert.toStr(value); // 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。 - if (StringUtils.startsWithAny(cellValue, FORMULA_STR)) - { + if (StringUtils.startsWithAny(cellValue, FORMULA_STR)) { cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0"); } - if (value instanceof Collection && StringUtils.equals("[]", cellValue)) - { + if (value instanceof Collection && StringUtils.equals("[]", cellValue)) { cellValue = StringUtils.EMPTY; } cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix()); - } - else if (ColumnType.NUMERIC == attr.cellType()) - { - if (StringUtils.isNotNull(value)) - { + } else if (ColumnType.NUMERIC == attr.cellType()) { + if (StringUtils.isNotNull(value)) { cell.setCellValue(StringUtils.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value)); } - } - else if (ColumnType.IMAGE == attr.cellType()) - { + } else if (ColumnType.IMAGE == attr.cellType()) { ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1), cell.getRow().getRowNum() + 1); - String propertyValue = Convert.toStr(value); - if (StringUtils.isNotEmpty(propertyValue)) - { - List imagePaths = StringUtils.str2List(propertyValue, SEPARATOR); - for (String imagePath : imagePaths) - { - byte[] data = ImageUtils.getImage(imagePath); - getDrawingPatriarch(cell.getSheet()).createPicture(anchor, cell.getSheet().getWorkbook().addPicture(data, getImageType(data))); - } + String imagePath = Convert.toStr(value); + if (StringUtils.isNotEmpty(imagePath)) { + byte[] data = ImageUtils.getImage(imagePath); + getDrawingPatriarch(cell.getSheet()).createPicture(anchor, + cell.getSheet().getWorkbook().addPicture(data, getImageType(data))); } } } @@ -1050,10 +1147,8 @@ public class ExcelUtil /** * 获取画布 */ - public static Drawing getDrawingPatriarch(Sheet sheet) - { - if (sheet.getDrawingPatriarch() == null) - { + public static Drawing getDrawingPatriarch(Sheet sheet) { + if (sheet.getDrawingPatriarch() == null) { sheet.createDrawingPatriarch(); } return sheet.getDrawingPatriarch(); @@ -1062,15 +1157,11 @@ public class ExcelUtil /** * 获取图片类型,设置图片插入类型 */ - public int getImageType(byte[] value) - { + public int getImageType(byte[] value) { String type = FileTypeUtils.getFileExtendName(value); - if ("JPG".equalsIgnoreCase(type)) - { + if ("JPG".equalsIgnoreCase(type)) { return Workbook.PICTURE_TYPE_JPEG; - } - else if ("PNG".equalsIgnoreCase(type)) - { + } else if ("PNG".equalsIgnoreCase(type)) { return Workbook.PICTURE_TYPE_PNG; } return Workbook.PICTURE_TYPE_JPEG; @@ -1079,66 +1170,103 @@ public class ExcelUtil /** * 创建表格样式 */ - public void setDataValidation(Excel attr, Row row, int column) - { - if (attr.name().indexOf("注:") >= 0) - { + public void setDataValidation(Excel attr, Row row, int column) { + if (attr.name().indexOf("注:") >= 0) { sheet.setColumnWidth(column, 6000); - } - else - { + } else { // 设置列宽 sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256)); } - if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0 || attr.comboReadDict()) - { + if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0 || attr.comboReadDict()) { String[] comboArray = attr.combo(); - if (attr.comboReadDict()) - { - if (!sysDictMap.containsKey("combo_" + attr.dictType())) - { + if (attr.comboReadDict()) { + if (!sysDictMap.containsKey("combo_" + attr.dictType())) { String labels = DictUtils.getDictLabels(attr.dictType()); sysDictMap.put("combo_" + attr.dictType(), labels); } String val = sysDictMap.get("combo_" + attr.dictType()); comboArray = StringUtils.split(val, DictUtils.SEPARATOR); } - if (comboArray.length > 15 || StringUtils.join(comboArray).length() > 255) - { + if (comboArray.length > 15 || StringUtils.join(comboArray).length() > 255) { // 如果下拉数大于15或字符串长度大于255,则使用一个新sheet存储,避免生成的模板下拉值获取不到 setXSSFValidationWithHidden(sheet, comboArray, attr.prompt(), 1, 100, column, column); - } - else - { + } else { // 提示信息或只能选择不能输入的列内容. setPromptOrValidation(sheet, comboArray, attr.prompt(), 1, 100, column, column); } } + // 如果设置了combo属性则本列只能选择不能输入 + if (attr.combo().length > 0) { + // 这里默认设了2-101列只能选择不能输入. + setXSSFValidation(sheet, attr.combo(), 1, 100, column, column); + } else if (StringUtils.isNotEmpty(attr.dictType()) && type == Type.IMPORT) { // 字典规则 + String dictType = attr.dictType(); + List dict_type = DictUtils.getDictCache(dictType); + if (StringUtils.isNotEmpty(dict_type)) { + String[] strings = dict_type.stream().map(SysDictData::getDictLabel).toArray(String[]::new); + setXSSFValidation(dictType, sheet, strings, 1, 100, column, column); + } + } + } + + public void setXSSFValidation(String dictType, Sheet sheet, String[] textlist, int firstRow, int endRow, int firstCol, int endCol) { + // 如果超过字符数限制, 则引用隐藏的sheet数据作为下拉列表数据 + if (ExcelDictSheetHelper.CheckValidationLimit(textlist)) { + if (null == dictSheetHelper) + dictSheetHelper = new ExcelDictSheetHelper(sheet.getWorkbook()); + dictSheetHelper.AddValidation(sheet, dictType, textlist, firstRow, endRow, firstCol, endCol); + } else { + setXSSFValidation(sheet, textlist, firstRow, endRow, firstCol, endCol); + } + } + + /** + * 设置某些列的值只能输入预制的数据,显示下拉框. + * + * @param sheet 要设置的sheet. + * @param textlist 下拉框显示的内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + * @return 设置好的sheet. + */ + public void setXSSFValidation(Sheet sheet, String[] textlist, int firstRow, int endRow, int firstCol, int endCol) { + DataValidationHelper helper = sheet.getDataValidationHelper(); + // 加载下拉列表内容 + DataValidationConstraint constraint = helper.createExplicitListConstraint(textlist); + // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + // 数据有效性对象 + DataValidation dataValidation = helper.createValidation(constraint, regions); + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } else { + dataValidation.setSuppressDropDownArrow(false); + } + + sheet.addValidationData(dataValidation); } /** * 添加单元格 */ - public Cell addCell(Excel attr, Row row, T vo, Field field, int column) - { + public Cell addCell(Excel attr, Row row, T vo, Field field, int column) { Cell cell = null; - try - { + try { // 设置行高 row.setHeight(maxHeight); // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列. - if (attr.isExport()) - { + if (attr.isExport()) { // 创建cell cell = row.createCell(column); - if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()) - { - if (subMergedLastRowNum >= subMergedFirstRowNum) - { - sheet.addMergedRegion(new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column)); - } + if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()) { + CellRangeAddress cellAddress = new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column); + sheet.addMergedRegion(cellAddress); } - cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType(), attr.wrapText()))); + cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType()))); // 用于读取对象中的属性 Object value = getTargetValue(vo, field, attr); @@ -1146,42 +1274,27 @@ public class ExcelUtil String readConverterExp = attr.readConverterExp(); String separator = attr.separator(); String dictType = attr.dictType(); - if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) - { - cell.getCellStyle().setDataFormat(this.wb.getCreationHelper().createDataFormat().getFormat(dateFormat)); + if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) { cell.setCellValue(parseDateToStr(dateFormat, value)); - } - else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) - { + } else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) { cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator)); - } - else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value)) - { - if (!sysDictMap.containsKey(dictType + value)) - { + } else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value)) { + if (!sysDictMap.containsKey(dictType + value)) { String lable = convertDictByExp(Convert.toStr(value), dictType, separator); sysDictMap.put(dictType + value, lable); } cell.setCellValue(sysDictMap.get(dictType + value)); - } - else if (value instanceof BigDecimal && -1 != attr.scale()) - { + } else if (value instanceof BigDecimal && -1 != attr.scale()) { cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue()); - } - else if (!attr.handler().equals(ExcelHandlerAdapter.class)) - { + } else if (!attr.handler().equals(ExcelHandlerAdapter.class)) { cell.setCellValue(dataFormatHandlerAdapter(value, attr, cell)); - } - else - { + } else { // 设置列类型 setCellVo(value, attr, cell); } addStatisticsData(column, Convert.toStr(value), attr); } - } - catch (Exception e) - { + } catch (Exception e) { log.error("导出Excel失败{}", e); } return cell; @@ -1189,36 +1302,31 @@ public class ExcelUtil /** * 设置 POI XSSFSheet 单元格提示或选择框 - * - * @param sheet 表单 - * @param textlist 下拉框显示的内容 + * + * @param sheet 表单 + * @param textlist 下拉框显示的内容 * @param promptContent 提示内容 - * @param firstRow 开始行 - * @param endRow 结束行 - * @param firstCol 开始列 - * @param endCol 结束列 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 */ public void setPromptOrValidation(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, - int firstCol, int endCol) - { + int firstCol, int endCol) { DataValidationHelper helper = sheet.getDataValidationHelper(); DataValidationConstraint constraint = textlist.length > 0 ? helper.createExplicitListConstraint(textlist) : helper.createCustomConstraint("DD1"); CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); DataValidation dataValidation = helper.createValidation(constraint, regions); - if (StringUtils.isNotEmpty(promptContent)) - { + if (StringUtils.isNotEmpty(promptContent)) { // 如果设置了提示信息则鼠标放上去提示 dataValidation.createPromptBox("", promptContent); dataValidation.setShowPromptBox(true); } // 处理Excel兼容性问题 - if (dataValidation instanceof XSSFDataValidation) - { + if (dataValidation instanceof XSSFDataValidation) { dataValidation.setSuppressDropDownArrow(true); dataValidation.setShowErrorBox(true); - } - else - { + } else { dataValidation.setSuppressDropDownArrow(false); } sheet.addValidationData(dataValidation); @@ -1226,21 +1334,19 @@ public class ExcelUtil /** * 设置某些列的值只能输入预制的数据,显示下拉框(兼容超出一定数量的下拉框). - * - * @param sheet 要设置的sheet. - * @param textlist 下拉框显示的内容 + * + * @param sheet 要设置的sheet. + * @param textlist 下拉框显示的内容 * @param promptContent 提示内容 - * @param firstRow 开始行 - * @param endRow 结束行 - * @param firstCol 开始列 - * @param endCol 结束列 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 */ - public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol) - { + 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++) - { + for (int i = 0; i < textlist.length; i++) { hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]); } // 创建名称,可被其他单元格引用 @@ -1254,20 +1360,16 @@ public class ExcelUtil CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); // 数据有效性对象 DataValidation dataValidation = helper.createValidation(constraint, regions); - if (StringUtils.isNotEmpty(promptContent)) - { + if (StringUtils.isNotEmpty(promptContent)) { // 如果设置了提示信息则鼠标放上去提示 dataValidation.createPromptBox("", promptContent); dataValidation.setShowPromptBox(true); } // 处理Excel兼容性问题 - if (dataValidation instanceof XSSFDataValidation) - { + if (dataValidation instanceof XSSFDataValidation) { dataValidation.setSuppressDropDownArrow(true); dataValidation.setShowErrorBox(true); - } - else - { + } else { dataValidation.setSuppressDropDownArrow(false); } @@ -1278,34 +1380,26 @@ public class ExcelUtil /** * 解析导出值 0=男,1=女,2=未知 - * + * * @param propertyValue 参数值 - * @param converterExp 翻译注解 - * @param separator 分隔符 + * @param converterExp 翻译注解 + * @param separator 分隔符 * @return 解析后值 */ - public static String convertByExp(String propertyValue, String converterExp, String separator) - { + public static String convertByExp(String propertyValue, String converterExp, String separator) { StringBuilder propertyString = new StringBuilder(); - String[] convertSource = converterExp.split(SEPARATOR); - for (String item : convertSource) - { + String[] convertSource = converterExp.split(","); + for (String item : convertSource) { String[] itemArray = item.split("="); - if (StringUtils.containsAny(propertyValue, separator)) - { - for (String value : propertyValue.split(separator)) - { - if (itemArray[0].equals(value)) - { + if (StringUtils.containsAny(propertyValue, separator)) { + for (String value : propertyValue.split(separator)) { + if (itemArray[0].equals(value)) { propertyString.append(itemArray[1] + separator); break; } } - } - else - { - if (itemArray[0].equals(propertyValue)) - { + } else { + if (itemArray[0].equals(propertyValue)) { return itemArray[1]; } } @@ -1315,34 +1409,26 @@ public class ExcelUtil /** * 反向解析值 男=0,女=1,未知=2 - * + * * @param propertyValue 参数值 - * @param converterExp 翻译注解 - * @param separator 分隔符 + * @param converterExp 翻译注解 + * @param separator 分隔符 * @return 解析后值 */ - public static String reverseByExp(String propertyValue, String converterExp, String separator) - { + public static String reverseByExp(String propertyValue, String converterExp, String separator) { StringBuilder propertyString = new StringBuilder(); - String[] convertSource = converterExp.split(SEPARATOR); - for (String item : convertSource) - { + String[] convertSource = converterExp.split(","); + for (String item : convertSource) { String[] itemArray = item.split("="); - if (StringUtils.containsAny(propertyValue, separator)) - { - for (String value : propertyValue.split(separator)) - { - if (itemArray[1].equals(value)) - { + if (StringUtils.containsAny(propertyValue, separator)) { + for (String value : propertyValue.split(separator)) { + if (itemArray[1].equals(value)) { propertyString.append(itemArray[0] + separator); break; } } - } - else - { - if (itemArray[1].equals(propertyValue)) - { + } else { + if (itemArray[1].equals(propertyValue)) { return itemArray[0]; } } @@ -1352,47 +1438,41 @@ public class ExcelUtil /** * 解析字典值 - * + * * @param dictValue 字典值 - * @param dictType 字典类型 + * @param dictType 字典类型 * @param separator 分隔符 * @return 字典标签 */ - public static String convertDictByExp(String dictValue, String dictType, String separator) - { + public static String convertDictByExp(String dictValue, String dictType, String separator) { return DictUtils.getDictLabel(dictType, dictValue, separator); } /** * 反向解析值字典值 - * + * * @param dictLabel 字典标签 - * @param dictType 字典类型 + * @param dictType 字典类型 * @param separator 分隔符 * @return 字典值 */ - public static String reverseDictByExp(String dictLabel, String dictType, String separator) - { + public static String reverseDictByExp(String dictLabel, String dictType, String separator) { return DictUtils.getDictValue(dictType, dictLabel, separator); } /** * 数据处理器 - * + * * @param value 数据值 * @param excel 数据注解 * @return */ - public String dataFormatHandlerAdapter(Object value, Excel excel, Cell cell) - { - try - { + public String dataFormatHandlerAdapter(Object value, Excel excel, Cell cell) { + try { Object instance = excel.handler().newInstance(); - Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class, Cell.class, Workbook.class }); + Method formatMethod = excel.handler().getMethod("format", new Class[]{Object.class, String[].class, Cell.class, Workbook.class}); value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb); - } - catch (Exception e) - { + } catch (Exception e) { log.error("不能格式化数据 " + excel.handler(), e.getMessage()); } return Convert.toStr(value); @@ -1401,21 +1481,15 @@ public class ExcelUtil /** * 合计统计信息 */ - private void addStatisticsData(Integer index, String text, Excel entity) - { - if (entity != null && entity.isStatistics()) - { + private void addStatisticsData(Integer index, String text, Excel entity) { + if (entity != null && entity.isStatistics()) { Double temp = 0D; - if (!statistics.containsKey(index)) - { + if (!statistics.containsKey(index)) { statistics.put(index, temp); } - try - { + try { temp = Double.valueOf(text); - } - catch (NumberFormatException e) - { + } catch (NumberFormatException e) { } statistics.put(index, statistics.get(index) + temp); } @@ -1424,21 +1498,18 @@ public class ExcelUtil /** * 创建统计行 */ - public void addStatisticsRow() - { - if (statistics.size() > 0) - { + public void addStatisticsRow() { + if (statistics.size() > 0) { Row row = sheet.createRow(sheet.getLastRowNum() + 1); Set keys = statistics.keySet(); Cell cell = row.createCell(0); cell.setCellStyle(styles.get("total")); cell.setCellValue("合计"); - for (Integer key : keys) - { + for (Integer key : keys) { cell = row.createCell(key); cell.setCellStyle(styles.get("total")); - cell.setCellValue(statistics.get(key)); + cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key))); } statistics.clear(); } @@ -1447,22 +1518,36 @@ public class ExcelUtil /** * 编码文件名 */ - public String encodingFilename(String filename) - { - return UUID.randomUUID() + "_" + filename + ".xlsx"; + public String encodingFilename(String filename) { + //filename = filename + "_" + UUID.randomUUID() + ".xls" ; + filename = DateUtils.dateTimeNow() + "_" + filename + ".xlsx" ; + return filename; + } + + public static String encodingFilenamePri(String filename) { + //filename = filename + "_" + UUID.randomUUID() + ".xls" ; + filename = DateUtils.dateTimeNow() + "_" + filename + ".xlsx" ; + return filename; } /** * 获取下载路径 - * + * * @param filename 文件名称 */ - public String getAbsoluteFile(String filename) - { + public String getAbsoluteFile(String filename) { + String downloadPath = RuoYiConfig.getDownloadPath() + filename; + File desc = new File(downloadPath); + if (!desc.getParentFile().exists()) { + desc.getParentFile().mkdirs(); + } + return downloadPath; + } + + public static String getAbsoluteFilePri(String filename) { String downloadPath = RuoYiConfig.getDownloadPath() + filename; File desc = new File(downloadPath); - if (!desc.getParentFile().exists()) - { + if (!desc.getParentFile().exists()) { desc.getParentFile().mkdirs(); } return downloadPath; @@ -1470,30 +1555,23 @@ public class ExcelUtil /** * 获取bean中的属性值 - * - * @param vo 实体对象 + * + * @param vo 实体对象 * @param field 字段 * @param excel 注解 * @return 最终的属性值 * @throws Exception */ - private Object getTargetValue(T vo, Field field, Excel excel) throws Exception - { - field.setAccessible(true); + private Object getTargetValue(T vo, Field field, Excel excel) throws Exception { Object o = field.get(vo); - if (StringUtils.isNotEmpty(excel.targetAttr())) - { + if (StringUtils.isNotEmpty(excel.targetAttr())) { String target = excel.targetAttr(); - if (target.contains(".")) - { + if (target.contains(".")) { String[] targets = target.split("[.]"); - for (String name : targets) - { + for (String name : targets) { o = getValue(o, name); } - } - else - { + } else { o = getValue(o, target); } } @@ -1502,16 +1580,14 @@ public class ExcelUtil /** * 以类的属性的get方法方法形式获取值 - * + * * @param o * @param name * @return value * @throws Exception */ - private Object getValue(Object o, String name) throws Exception - { - if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) - { + private Object getValue(Object o, String name) throws Exception { + if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) { Class clazz = o.getClass(); Field field = clazz.getDeclaredField(name); field.setAccessible(true); @@ -1523,8 +1599,7 @@ public class ExcelUtil /** * 得到所有定义字段 */ - private void createExcelField() - { + private void createExcelField() { this.fields = getFields(); this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList()); this.maxHeight = getRowHeight(); @@ -1533,99 +1608,51 @@ public class ExcelUtil /** * 获取字段注解信息 */ - public List getFields() - { + public List getFields() { List fields = new ArrayList(); List tempFields = new ArrayList<>(); tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); - if (StringUtils.isNotEmpty(includeFields)) - { - for (Field field : tempFields) - { - if (ArrayUtils.contains(this.includeFields, field.getName()) || field.isAnnotationPresent(Excels.class)) - { - addField(fields, field); - } - } - } - else if (StringUtils.isNotEmpty(excludeFields)) - { - for (Field field : tempFields) - { - if (!ArrayUtils.contains(this.excludeFields, field.getName())) - { - addField(fields, field); - } - } - } - else - { - for (Field field : tempFields) - { - addField(fields, field); - } - } - return fields; - } - - /** - * 添加字段信息 - */ - public void addField(List fields, Field field) - { - // 单注解 - if (field.isAnnotationPresent(Excel.class)) - { - Excel attr = field.getAnnotation(Excel.class); - if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) - { - fields.add(new Object[] { field, attr }); - } - if (Collection.class.isAssignableFrom(field.getType())) - { - subMethod = getSubMethod(field.getName(), clazz); - ParameterizedType pt = (ParameterizedType) field.getGenericType(); - Class subClass = (Class) pt.getActualTypeArguments()[0]; - this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); - } - } - - // 多注解 - if (field.isAnnotationPresent(Excels.class)) - { - Excels attrs = field.getAnnotation(Excels.class); - Excel[] excels = attrs.value(); - for (Excel attr : excels) - { - if (StringUtils.isNotEmpty(includeFields)) - { - if (ArrayUtils.contains(this.includeFields, field.getName() + "." + attr.targetAttr()) - && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) - { - fields.add(new Object[] { field, attr }); + for (Field field : tempFields) { + if (!ArrayUtils.contains(this.excludeFields, field.getName())) { + // 单注解 + if (field.isAnnotationPresent(Excel.class)) { + Excel attr = field.getAnnotation(Excel.class); + if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) { + field.setAccessible(true); + fields.add(new Object[]{field, attr}); + } + if (Collection.class.isAssignableFrom(field.getType())) { + subMethod = getSubMethod(field.getName(), clazz); + ParameterizedType pt = (ParameterizedType) field.getGenericType(); + Class subClass = (Class) pt.getActualTypeArguments()[0]; + this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); } } - else - { - if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr()) - && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) - { - fields.add(new Object[] { field, attr }); + + // 多注解 + if (field.isAnnotationPresent(Excels.class)) { + Excels attrs = field.getAnnotation(Excels.class); + Excel[] excels = attrs.value(); + for (Excel attr : excels) { + if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr()) + && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) { + field.setAccessible(true); + fields.add(new Object[]{field, attr}); + } } } } } + return fields; } /** * 根据注解获取最大行高 */ - public short getRowHeight() - { + public short getRowHeight() { double maxHeight = 0; - for (Object[] os : this.fields) - { + for (Object[] os : this.fields) { Excel excel = (Excel) os[1]; maxHeight = Math.max(maxHeight, excel.height()); } @@ -1635,8 +1662,7 @@ public class ExcelUtil /** * 创建一个工作簿 */ - public void createWorkbook() - { + public void createWorkbook() { this.wb = new SXSSFWorkbook(500); this.sheet = wb.createSheet(); wb.setSheetName(0, sheetName); @@ -1645,15 +1671,13 @@ public class ExcelUtil /** * 创建工作表 - * + * * @param sheetNo sheet数量 - * @param index 序号 + * @param index 序号 */ - public void createSheet(int sheetNo, int index) - { + public void createSheet(int sheetNo, int index) { // 设置工作表的名称. - if (sheetNo > 1 && index > 0) - { + if (sheetNo > 1 && index > 0) { this.sheet = wb.createSheet(); this.createTitle(); wb.setSheetName(index, sheetName + index); @@ -1662,59 +1686,40 @@ public class ExcelUtil /** * 获取单元格值 - * - * @param row 获取的行 + * + * @param row 获取的行 * @param column 获取单元格列号 * @return 单元格值 */ - public Object getCellValue(Row row, int column) - { - if (row == null) - { + public Object getCellValue(Row row, int column) { + if (row == null) { return row; } - Object val = ""; - try - { + Object val = "" ; + try { Cell cell = row.getCell(column); - if (StringUtils.isNotNull(cell)) - { - if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA) - { + if (StringUtils.isNotNull(cell)) { + if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA) { val = cell.getNumericCellValue(); - if (DateUtil.isCellDateFormatted(cell)) - { + if (DateUtil.isCellDateFormatted(cell)) { val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换 - } - else - { - if ((Double) val % 1 != 0) - { + } else { + if ((Double) val % 1 != 0) { val = new BigDecimal(val.toString()); - } - else - { + } else { val = new DecimalFormat("0").format(val); } } - } - else if (cell.getCellType() == CellType.STRING) - { + } else if (cell.getCellType() == CellType.STRING) { val = cell.getStringCellValue(); - } - else if (cell.getCellType() == CellType.BOOLEAN) - { + } else if (cell.getCellType() == CellType.BOOLEAN) { val = cell.getBooleanCellValue(); - } - else if (cell.getCellType() == CellType.ERROR) - { + } else if (cell.getCellType() == CellType.ERROR) { val = cell.getErrorCellValue(); } } - } - catch (Exception e) - { + } catch (Exception e) { return val; } return val; @@ -1722,21 +1727,17 @@ public class ExcelUtil /** * 判断是否是空行 - * + * * @param row 判断的行 * @return */ - private boolean isRowEmpty(Row row) - { - if (row == null) - { + private boolean isRowEmpty(Row row) { + if (row == null) { return true; } - for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) - { + for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) { Cell cell = row.getCell(i); - if (cell != null && cell.getCellType() != CellType.BLANK) - { + if (cell != null && cell.getCellType() != CellType.BLANK) { return false; } } @@ -1746,54 +1747,50 @@ public class ExcelUtil /** * 获取Excel2003图片 * - * @param sheet 当前sheet对象 + * @param sheet 当前sheet对象 * @param workbook 工作簿对象 * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData */ - public static Map> getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook) - { - Map> sheetIndexPicMap = new HashMap<>(); + public static Map getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook) { + Map sheetIndexPicMap = new HashMap(); List pictures = workbook.getAllPictures(); - if (!pictures.isEmpty() && sheet.getDrawingPatriarch() != null) - { - for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren()) - { - if (shape instanceof HSSFPicture) - { + if (!pictures.isEmpty()) { + for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren()) { + HSSFClientAnchor anchor = (HSSFClientAnchor) shape.getAnchor(); + if (shape instanceof HSSFPicture) { HSSFPicture pic = (HSSFPicture) shape; - HSSFClientAnchor anchor = (HSSFClientAnchor) pic.getAnchor(); + int pictureIndex = pic.getPictureIndex() - 1; + HSSFPictureData picData = pictures.get(pictureIndex); String picIndex = anchor.getRow1() + "_" + anchor.getCol1(); - sheetIndexPicMap.computeIfAbsent(picIndex, k -> new ArrayList<>()).add(pic.getPictureData()); + sheetIndexPicMap.put(picIndex, picData); } } + return sheetIndexPicMap; + } else { + return sheetIndexPicMap; } - return sheetIndexPicMap; } /** * 获取Excel2007图片 * - * @param sheet 当前sheet对象 + * @param sheet 当前sheet对象 * @param workbook 工作簿对象 * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData */ - public static Map> getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook) - { - Map> sheetIndexPicMap = new HashMap<>(); - for (POIXMLDocumentPart dr : sheet.getRelations()) - { - if (dr instanceof XSSFDrawing) - { + public static Map getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook) { + Map sheetIndexPicMap = new HashMap(); + for (POIXMLDocumentPart dr : sheet.getRelations()) { + if (dr instanceof XSSFDrawing) { XSSFDrawing drawing = (XSSFDrawing) dr; - for (XSSFShape shape : drawing.getShapes()) - { - if (shape instanceof XSSFPicture) - { + List shapes = drawing.getShapes(); + for (XSSFShape shape : shapes) { + if (shape instanceof XSSFPicture) { XSSFPicture pic = (XSSFPicture) shape; XSSFClientAnchor anchor = pic.getPreferredSize(); CTMarker ctMarker = anchor.getFrom(); String picIndex = ctMarker.getRow() + "_" + ctMarker.getCol(); - sheetIndexPicMap.computeIfAbsent(picIndex, k -> new ArrayList<>()).add(pic.getPictureData()); + sheetIndexPicMap.put(picIndex, pic.getPictureData()); } } } @@ -1803,32 +1800,23 @@ public class ExcelUtil /** * 格式化不同类型的日期对象 - * + * * @param dateFormat 日期格式 - * @param val 被格式化的日期对象 + * @param val 被格式化的日期对象 * @return 格式化后的日期字符 */ - public String parseDateToStr(String dateFormat, Object val) - { - if (val == null) - { - return ""; + public String parseDateToStr(String dateFormat, Object val) { + if (val == null) { + return "" ; } String str; - if (val instanceof Date) - { + if (val instanceof Date) { str = DateUtils.parseDateToStr(dateFormat, (Date) val); - } - else if (val instanceof LocalDateTime) - { + } else if (val instanceof LocalDateTime) { str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDateTime) val)); - } - else if (val instanceof LocalDate) - { + } else if (val instanceof LocalDate) { str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDate) val)); - } - else - { + } else { str = val.toString(); } return str; @@ -1837,31 +1825,25 @@ public class ExcelUtil /** * 是否有对象的子列表 */ - public boolean isSubList() - { + public boolean isSubList() { return StringUtils.isNotNull(subFields) && subFields.size() > 0; } /** * 是否有对象的子列表,集合不为空 */ - public boolean isSubListValue(T vo) - { + public boolean isSubListValue(T vo) { return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0; } /** * 获取集合的值 */ - public Collection getListCellValue(Object obj) - { + public Collection getListCellValue(Object obj) { Object value; - try - { - value = subMethod.invoke(obj, new Object[] {}); - } - catch (Exception e) - { + try { + value = subMethod.invoke(obj, new Object[]{}); + } catch (Exception e) { return new ArrayList(); } return (Collection) value; @@ -1869,23 +1851,19 @@ public class ExcelUtil /** * 获取对象的子列表方法 - * - * @param name 名称 + * + * @param name 名称 * @param pojoClass 类对象 * @return 子列表方法 */ - public Method getSubMethod(String name, Class pojoClass) - { + public Method getSubMethod(String name, Class pojoClass) { StringBuffer getMethodName = new StringBuffer("get"); getMethodName.append(name.substring(0, 1).toUpperCase()); getMethodName.append(name.substring(1)); Method method = null; - try - { - method = pojoClass.getMethod(getMethodName.toString(), new Class[] {}); - } - catch (Exception e) - { + try { + method = pojoClass.getMethod(getMethodName.toString(), new Class[]{}); + } catch (Exception e) { log.error("获取对象异常{}", e.getMessage()); } return method; diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/translation/Translate.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/translation/Translate.java new file mode 100644 index 0000000..592bfe0 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/translation/Translate.java @@ -0,0 +1,66 @@ +package com.ruoyi.common.utils.translation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 额外字典翻译字段注解, 用来代替无法使用Excel注解的情况, 优先级大于Excel注解 + * 优先级(5种): + * sql [+ referenceProperty]: 单条SQL语句: 建议加上LIMIT 1, 多条时取第一条, 必须指定占位符?. 例如: @Translate(sql = "SELECT * FROM t_finance_book WHERE id = ? LIMIT 1") + * table + tableKeyColumn + tableValueColumn [+ referenceProperty]: 拼接为 SELECT `tableValueColumn` FROM `table` WHERE `tableKeyColumn` = ? LIMIT 1. 例如: @Translate(table = "t_finance_book", tableValueColumn = "book_name", tableKeyColumn = "id") + *

+ * dictSql + tableKeyColumn + tableValueColumn: 执行查询SQL后, 取tableKeyColumn(), tableValueColumn()作为字典键值. 例如: @Translate(dictSql = "SELECT * FROM t_finance_book", tableValueColumn = "book_name", tableKeyColumn = "id") + * readConverterExp + * dictType + *

+ * 前两种方式使用待翻译字段值直查 + * 后三种都是加载对应键值字典, 然后用待翻译字段值匹配. 如果都指定了, 会按优先级混合在一起, 优先级高的覆盖优先级低的 + *

+ * 关于referenceProperty + * 如果带翻译字段为Long的其他表ID, 如: + * private Long bookId; + * 如果需要翻译bookId + * 一种方法是将bookId设为String类型 + * 还有一种方法是声明String类型的属性辅助翻译bookId, 该新属性不需要原来本身就有bookId的值, 如 + * + * @author zhao + * @Translate(table = "t_finance_book", tableKeyColumn = "id", tableValueColumn = "book_name", referenceProperty = "bookId") // 引用bookId属性的值 + * // @Translate(sql = "SELECT * FROM t_finance_book WHERE id = ? LIMIT 1", referenceProperty = "bookId") // 也可以写成这样 + * private String bookName; // bookName的值不需要读取, 只写入 + * private Long typeId; + * @Translate(dictType = "dict_xxx_type", referenceProperty = "typeId") // 引用typeId属性的值 + * private String typeName; // typeName的值不需要读取, 只写入 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Translate { + /** + * 如果是字典类型,请设置字典的type值 (如: sys_user_sex) + */ + public String dictType() default "" ; + + /** + * 读取内容转表达式 (如: 0=男,1=女,2=未知) + */ + public String readConverterExp() default "" ; + + // 查询数据库字典, 字段必须为String, 不能为Long/Integer等数字型 + // 方式1: SQL, ?号占位 + public String sql() default "" ; + + // 方式2: 表名 + 对应列名 + 名称列 + // 拼接为 SELECT tableValueColumn() FROM table() WHERE tableKeyColumn() = ? LIMIT 1 + public String table() default "" ; // 表名 + + public String tableKeyColumn() default "id" ; // 键的列名 + + public String tableValueColumn() default "name" ; // 值的列名 + + public String referenceProperty() default "" ; // 引用其他类成员属性 + + // 方式3: SQL + 对应列名 + 名称列: 无占位符, 从数据库加载键值对 + // 执行查询SQL后, 取tableKeyColumn(), tableValueColumn()作为字典键值 + public String dictSql() default "" ; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/translation/TranslateUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/translation/TranslateUtils.java new file mode 100644 index 0000000..3a8a3a8 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/translation/TranslateUtils.java @@ -0,0 +1,58 @@ +package com.ruoyi.common.utils.translation; + +import com.ruoyi.common.core.domain.BaseEntity; + +import java.util.List; +import java.util.Map; + +/** + * zhao: 字段自动字典翻译 + * 需要翻译的字段必须使用Translate注解或Excel注解标记 + * 优先级: + * 注解: Translate优先级大于Excel. 只会使用Translate或Excel其中的一种, 不会混合 + * 注解属性: readConverterExp优先级大于dictType. readConverterExp和dictType的键值对最终会混合在一起 + * 字段类型: + * 如果是String类型, 必须赋值注解的dictType或readConverterExp + * 如果是其他复合类型/List, 无需赋值dictType或readConverterExp, 仅标记注解即可, 此时将递归翻译(注意不要循环嵌套对象) + *

+ * 数据库表数据字典: 仅Translate注解支持 + * 字段必须为String类型 + */ +public final class TranslateUtils { + private TranslateUtils() { + } + + public static void translateList(List list, Object... args) { + new Translator().translateList(list, args); + } + + public static void translate(Object obj, Object... args) { + new Translator().translate(obj, args); + } + + public static void translateMapList(List> list, Map dictMap) { + new Translator().translateMapList(list, dictMap); + } + + public static void translateMap(Map map, Map dictMap) { + new Translator().translateMap(map, dictMap); + } + + public static void translateObjectList(List list) { + new Translator().translateObjectList(list); + } + + public static void translateObject(T object) { + new Translator().translateObject(object); + } + + public static void translateEntityList(List list, boolean... keepRawField) { + boolean b = null != keepRawField && keepRawField.length > 0 ? keepRawField[0] : false; + new Translator().translateEntityList(list, b); + } + + public static void translateEntity(T object, boolean... keepRawField) { + boolean b = null != keepRawField && keepRawField.length > 0 ? keepRawField[0] : false; + new Translator().translateEntity(object, b); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/translation/Translator.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/translation/Translator.java new file mode 100644 index 0000000..38ffbb7 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/translation/Translator.java @@ -0,0 +1,465 @@ +package com.ruoyi.common.utils.translation; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.utils.DictUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.reflect.ReflectUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.*; + +public final class Translator { + private static final String KEY_SQL_KEY = "$__SQL__" ; + + private final List handledObjects = new ArrayList<>(); + + public Translator() { + } + + public void translateList(List list, Object... args) { + if (CollectionUtil.isEmpty(list)) + return; + if (startTranslate(list)) + list.forEach((x) -> translate(x, args)); + } + + @SuppressWarnings("unchecked") + public void translate(Object obj, Object... args) { + if (null == obj) + return; + if (obj instanceof BaseEntity) { + Object o = DEF_PARM(args); + boolean b = o instanceof Boolean ? (Boolean) o : false; + translateEntity((BaseEntity) obj, b); + } else if (obj instanceof Map) { + Object o = DEF_PARM(args); + Map dictMap = o instanceof Map ? (Map) args[0] : null; + translateMap((Map) obj, dictMap); + } else if (obj instanceof List) { + translateList((List) obj, args); + } else { + translateObject(obj); + } + } + + public void translateEntityList(List list, boolean keepRawField) { + if (CollectionUtil.isEmpty(list)) + return; + list.forEach((x) -> translateEntity(x, keepRawField)); + } + + public void translateEntity(T object, boolean keepRawField) { + if (null == object) + return; + translateEntity_r(object, object.getClass(), keepRawField); + } + + public void translateMapList(List> list, Map dictMap) { + if (CollectionUtil.isEmpty(list)) + return; + list.forEach((x) -> translateMap(x, dictMap)); + } + + public void translateMap(Map map, Map dictMap) { + if (CollectionUtil.isEmpty(map) || CollectionUtil.isEmpty(dictMap)) + return; + if (!startTranslate(map)) + return; + dictMap.forEach((k, v) -> { + if (!map.containsKey(k)) + return; + Object o = map.get(k); + if (null == o) + return; + Class clazz = o.getClass(); + if (clazz.equals(String.class)) { + String str = (String) o; + if (StringUtils.isEmpty(str)) + return; + map.put(k, DictUtils.getDictLabelElseOriginValue(v, str)); + } else if (canRecursionClassObjectField(clazz)) + translate(o, dictMap); + }); + } + + // 判断是否翻译对象属性 + private boolean canRecursionClassObjectField(Class clazz) { +/* if(Object.class.equals(clazz)) // is Object class + return false; + if(!Object.class.isAssignableFrom(clazz)) // is internal class, e.g. int long boolean + return false; + if(clazz.getPackage().getName().startsWith("java.lang")) // class in java.lang.**, e.g. Long Integer + return false; + return true;*/ + return BaseEntity.class.isAssignableFrom(clazz) // BaseEntity + || List.class.isAssignableFrom(clazz) // List + || Map.class.isAssignableFrom(clazz) // Map + ; + } + + public void translateObjectList(List list) { + if (CollectionUtil.isEmpty(list)) + return; + list.forEach(this::translateObject); + } + + public void translateObject(T object) { + if (null == object) + return; + translateObject_r(object, object.getClass()); + } + + private void translateObject_r(T object, Class clazz) { + if (!startTranslate(object)) + return; + Field[] declaredFields = clazz.getDeclaredFields(); + for (Field field : declaredFields) { + boolean trans = hasTranslateFlag(field); // check translate flag + if (!trans) + continue; + translateField(field, object); + } + Class superclass = getSuperClass(clazz); + if (null != superclass) + translateObject_r(object, superclass); + } + + private void translateEntity_r(T object, Class clazz, boolean keepRawField) { + if (!startTranslate(object)) + return; + + Field[] declaredFields = clazz.getDeclaredFields(); + for (Field field : declaredFields) { + boolean trans = hasTranslateFlag(field); // check translate flag + if (!trans) + continue; + translateField(field, object, keepRawField); + } + Class superclass = getSuperClass(clazz); + if (null != superclass) + translateEntity_r(object, superclass, keepRawField); + } + + // 解析readConverterExp + private Map parseReadConverterExp(String str) { + Map dict = new LinkedHashMap<>(); + if (StringUtils.isNotEmpty(str)) { + List split = StrUtil.split(str, ',', true, true); + split.forEach((x) -> { + List arr = StrUtil.split(x, '=', 2, true, true); + if (CollectionUtil.isEmpty(arr)) + return; + dict.put(arr.get(0), arr.size() > 1 ? arr.get(1) : null); + }); + } + return dict; + } + + // 读取数据库数据作为字典 + private Map loadDBDict(String sql, String keyColumn, String nameColumn) { + Map dict = new LinkedHashMap<>(); + if (StringUtils.isNotEmpty(sql) && StringUtils.isNotEmpty(keyColumn) && StringUtils.isNotEmpty(nameColumn)) { + JdbcTemplate jdbcTemplate = SpringUtils.getBean(JdbcTemplate.class); + try { + List> list = jdbcTemplate.queryForList(sql); + if (CollectionUtil.isNotEmpty(list)) { + for (Map map : list) { + String key = toString_s(map.get(keyColumn)); + if (StringUtils.isEmpty(key)) + continue; + dict.put(key, toString_s(map.get(nameColumn))); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return dict; + } + + // 生成查询SQL + private String genRelationSQL(String table, String keyColumn, String nameColumn) { + if (StringUtils.isNotEmpty(table) && StringUtils.isNotEmpty(keyColumn) && StringUtils.isNotEmpty(nameColumn)) { + return String.format("SELECT `%s` FROM `%s` WHERE `%s` = ? LIMIT 1", nameColumn, table, keyColumn); + } + return null; + } + + // 直接取SQL值 + private String readDBValue(String sql, String id) { + if (StringUtils.isNotEmpty(sql) && StringUtils.isNotEmpty(id)) { + JdbcTemplate jdbcTemplate = SpringUtils.getBean(JdbcTemplate.class); + try { + List> list = jdbcTemplate.queryForList(sql, id); + if (CollectionUtil.isNotEmpty(list)) { + Map map = list.get(0); + return toString_s(map.get(map.keySet().iterator().next())); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return null; + } + + // 检查是否需要翻译 + private boolean hasTranslateFlag(Field field) { + // 静态/不可重赋值 不翻译 + int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) + return false; + + return null != field.getAnnotation(Translate.class) || null != field.getAnnotation(Excel.class); + } + + // 获取翻译来源 + // 返回: dictType对应的字典键值对和readConverterExp解析后的键值对的组合 + // 优先级: Translate > Excel | readConverterExp > dictType + private Map getTranslateDict(Field field) { + Map dict = new LinkedHashMap<>(); + String str; + + Translate translate = field.getAnnotation(Translate.class); // First check Translate annotation + if (null != translate) { + // 1. SQL 直查 + str = translate.sql(); + if (StringUtils.isNotEmpty(str)) { + dict.put(KEY_SQL_KEY, str); + } + // 2. table + tableKeyColumn + tableValueColumn 直查 + str = translate.table(); + if (StringUtils.isNotEmpty(str)) { + str = genRelationSQL(str, translate.tableKeyColumn(), translate.tableValueColumn()); + dict.put(KEY_SQL_KEY, str); + } + + // 3. dict缓存 + str = translate.dictType(); + if (StringUtils.isNotEmpty(str)) { + dict.putAll(DictUtils.dictCacheValueLabelMap(str)); + } + // 4. readConverterExp配置 + str = translate.readConverterExp(); + if (StringUtils.isNotEmpty(str)) { + dict.putAll(parseReadConverterExp(str)); + } + // 5. 数据库任意表数据 + str = translate.dictSql(); + if (StringUtils.isNotEmpty(str)) { + dict.putAll(loadDBDict(str, translate.tableKeyColumn(), translate.tableValueColumn())); + } + } + if (StringUtils.isNotEmpty(dict)) + return dict; + + Excel excel = field.getAnnotation(Excel.class); // And then check Excel annotation + if (null != excel) { + str = excel.dictType(); + if (StringUtils.isNotEmpty(str)) { + dict.putAll(DictUtils.dictCacheValueLabelMap(str)); + } + str = excel.readConverterExp(); + if (StringUtils.isNotEmpty(str)) { + dict.putAll(parseReadConverterExp(str)); + } + } + return dict; + } + + private boolean translateField(Field field, Object object) { + if (field.getType().equals(String.class)) + return translateField_String(field, object); + else + return translateField_Object(field, object); + } + + private boolean translateField_Object(Field field, Object object) { + if (field.getType().equals(String.class)) + return false; + + if (!canRecursionClassObjectField(field.getType())) + return false; + + Object val = null; + if (!field.isAccessible()) + field.setAccessible(true); + try { + val = field.get(object); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + if (null != val) { + translate(val); + return true; + } + return false; + } + + private boolean translateField_String(Field field, Object object) { + boolean trans = false; + + if (!field.getType().equals(String.class)) + return false; + + Map dict = getTranslateDict(field); + if (CollectionUtil.isEmpty(dict)) + return false; + + if (!field.isAccessible()) + field.setAccessible(true); + try { + Object val = getField(object, field); + if (null != val) { + String str = (String) val; + if (StringUtils.isNotEmpty(str)) { + if (dict.containsKey(KEY_SQL_KEY)) { + String dbVal = readDBValue(dict.get(KEY_SQL_KEY), str); + if (StringUtils.isNotEmpty(dbVal)) { + field.set(object, dbVal); + trans = true; + } + } + if (!trans && dict.containsKey(str)) { + field.set(object, dict.get(str)); + trans = true; + } + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + return false; + } + return trans; + } + + private boolean translateField(Field field, T entity, boolean keepRawField) { + if (field.getType().equals(String.class)) + return translateField_String(field, entity, keepRawField); + else + return translateField_Object(field, entity, keepRawField); + } + + private boolean translateField_Object(Field field, T entity, boolean keepRawField) { + if (field.getType().equals(String.class)) + return false; + + if (!canRecursionClassObjectField(field.getType())) + return false; + + Object val = null; + try { + val = getField(entity, field); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + if (null != val) { + translate(val, keepRawField); + return true; + } + return false; + } + + private boolean translateField_String(Field field, T entity, boolean keepRawField) { + boolean trans = false; + + if (!field.getType().equals(String.class)) + return false; + + Map dict = getTranslateDict(field); + if (CollectionUtil.isEmpty(dict)) + return false; + + try { + Object val = getField(entity, field); + if (keepRawField) + entity.getParams().put(field.getName(), val); // backup raw value to params + if (null != val) { + String str = (String) val; + if (StringUtils.isNotEmpty(str)) { + if (dict.containsKey(KEY_SQL_KEY)) { + String dbVal = readDBValue(dict.get(KEY_SQL_KEY), str); + if (StringUtils.isNotEmpty(dbVal)) { + field.set(entity, dbVal); + trans = true; + } + } + if (!trans && dict.containsKey(str)) { + field.set(entity, dict.get(str)); + trans = true; + } + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + return false; + } + return trans; + } + + private Object getField(Object obj, Field field) throws IllegalAccessException { + if (!field.isAccessible()) + field.setAccessible(true); + Translate annotation = field.getAnnotation(Translate.class); + if (null != annotation) { + String refProp = annotation.referenceProperty(); + if (StringUtils.isNotEmpty(refProp)) { + Field refField = ReflectUtils.getAccessibleField(obj, refProp); + if (null != refField) + return toString_s(refField.get(obj)); // 转换为String + } + } + return field.get(obj); + } + + private Class getSuperClass(Class clazz) { + if (null == clazz) + return null; + Class superclass = clazz.getSuperclass(); // translate parent class + if (null != superclass && !superclass.equals(Object.class)) // check is top or is Object + return superclass; + return null; + } + + // 允许翻译则返回true + private boolean startTranslate(Object o) { + if (null == o) + return false; + if (o instanceof String) // String类型总是重新翻译 + return true; + if (contains_ptr(handledObjects, o)) // 其他类型防止循环递归 + { + return false; + } + handledObjects.add(o); + return true; + } + + public void Reset() { + handledObjects.clear(); + } + + + public static boolean contains_ptr(Collection list, T target) { + if (CollectionUtil.isEmpty(list)) + return false; + return list.stream().anyMatch((x) -> x == target); + } + + public static String toString_s(Object obj) { + if (null == obj) + return "" ; + if (obj instanceof String) + return (String) obj; + else + return obj.toString(); + } + + public static T DEF_PARM(T... args) { + return null != args && args.length > 0 ? args[0] : null; + } +} diff --git a/ruoyi-framework/ruoyi-framework.iml b/ruoyi-framework/ruoyi-framework.iml index 4175059..15f1ed4 100644 --- a/ruoyi-framework/ruoyi-framework.iml +++ b/ruoyi-framework/ruoyi-framework.iml @@ -73,6 +73,7 @@ + @@ -89,6 +90,7 @@ + @@ -103,6 +105,7 @@ + @@ -123,6 +126,8 @@ - + + + \ No newline at end of file diff --git a/ruoyi-generator/ruoyi-generator.iml b/ruoyi-generator/ruoyi-generator.iml index 15046ed..aebea64 100644 --- a/ruoyi-generator/ruoyi-generator.iml +++ b/ruoyi-generator/ruoyi-generator.iml @@ -40,6 +40,7 @@ + @@ -57,6 +58,7 @@ + @@ -74,6 +76,7 @@ + @@ -94,7 +97,9 @@ - + + + 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 a891de6..caf2173 100644 --- a/ruoyi-generator/src/main/resources/vm/java/controller.java.vm +++ b/ruoyi-generator/src/main/resources/vm/java/controller.java.vm @@ -74,25 +74,22 @@ public class ${ClassName}Controller extends BaseController /** * ${functionName}导入模板 */ - @RequiresPermissions("${permissionPrefix}:view") - @GetMapping("/importTemplate") - @ResponseBody - public AjaxResult importTemplate() { + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) { ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); - return util.importTemplateExcel("${functionName}数据"); + util.importTemplateExcel(response, "${functionName}数据"); } /** * ${functionName}导入 */ @Log(title = "${functionName}", businessType = BusinessType.IMPORT) - @RequiresPermissions("${permissionPrefix}:import") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:import')") @PostMapping("/importData") - @ResponseBody public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception { ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); List<${ClassName}> list = util.importExcel(file.getInputStream(), 0); - String message = ${className}Service.import${ClassName}(list, updateSupport, getSysUser()); + String message = ${className}Service.import${ClassName}(list, updateSupport, getUsername()); return AjaxResult.success(message); } @@ -142,11 +139,11 @@ public class ${ClassName}Controller extends BaseController /** * 打印${functionName} */ - @RequiresPermissions("${permissionPrefix}:print") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:print')") @Log(title = "${functionName}", businessType = BusinessType.PRINT) @GetMapping("/print") public void printPdf(${ClassName} ${className}, HttpServletResponse response) throws Exception{ - ${className}.setBookId(getSysUser().getLoginBookid()); + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); TranslateUtils.translateList(list, false); @@ -171,12 +168,12 @@ public class ${ClassName}Controller extends BaseController List contentList = Lists.newArrayList(); list.forEach(a ->{ String[] str = new String[6]; - str[0] = a.getBookName(); - str[1] = a.getOrgCodeCertNum(); - str[2] = a.getDeptName(); - str[3] = a.getAccountant(); - str[4] = a.getAccountantPhone(); - str[5] = a.getCurrentDay(); + str[0] = ""; + str[1] = ""; + str[2] = ""; + str[3] = ""; + str[4] = ""; + str[5] = ""; contentList.add(str); }); pdf.setContentList(contentList); diff --git a/ruoyi-generator/src/main/resources/vm/java/service.java.vm b/ruoyi-generator/src/main/resources/vm/java/service.java.vm index daca176..e81597a 100644 --- a/ruoyi-generator/src/main/resources/vm/java/service.java.vm +++ b/ruoyi-generator/src/main/resources/vm/java/service.java.vm @@ -32,10 +32,10 @@ public interface I${ClassName}Service * * @param list ${functionName}数据列表 * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 - * @param user 操作用户 + * @param userName 操作用户 * @return 结果 */ - public String import${ClassName}(List<${ClassName}> list, Boolean isUpdateSupport, SysUser user); + public String import${ClassName}(List<${ClassName}> list, Boolean isUpdateSupport, String userName); /** * 新增${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 7d6054f..e6ba310 100644 --- a/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm +++ b/ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm @@ -60,16 +60,15 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service * * @param list ${functionName}数据列表 * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 - * @param user 操作用户 + * @param operName 操作用户 * @return 结果 */ @Override @Transactional - public String import${ClassName}(List<${ClassName}> list, Boolean isUpdateSupport, SysUser user) { + public String import${ClassName}(List<${ClassName}> list, Boolean isUpdateSupport, String operName) { if (StringUtils.isEmpty(list)) { throw new ServiceException("导入${functionName}数据不能为空!"); } - String operName = user.getLoginName(); int successNum = 0; int failureNum = 0; diff --git a/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm b/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm index 6c409a2..5f140f0 100644 --- a/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm +++ b/ruoyi-generator/src/main/resources/vm/vue/index.vue.vm @@ -1,3 +1,39 @@ +#macro(GET_CHAR_COLUMN_LENGTH $column)## * 如果表列是char/varchar, 获取其最大字符长度, 否则为空字符串 * 参数: 列 * 结果保存在变量名为 $_char_column_length 字符串型 +#set($_char_column_length="") +#if($column.columnType.startsWith("char") || $column.columnType.startsWith("varchar")) +#set($startLeft=$column.columnType.indexOf("(")) +#if($startLeft != -1) +#set($endRight=$column.columnType.indexOf(")", $startLeft)) +#if($endRight != -1) +#set($startLeft=$startLeft+1) +#set($_char_column_length=$column.columnType.substring($startLeft, $endRight)) +#end +#end +#end +#end## GET_CHAR_COLUMN_LENGTH +#macro(COLUMN_IS_NUMBER $column)## * 检查表列是否是数字型 * 参数: 列 * 结果保存在变量名为 $_column_is_number bool型 +#set($_column_is_number=$column.columnType.startsWith("decimal") || $column.columnType.startsWith("tinyint") || $column.columnType.startsWith("mediumint") || $column.columnType.startsWith("int") || $column.columnType.startsWith("bigint") || $column.columnType.startsWith("smallint")) +#end## COLUMN_IS_NUMBER +#macro(GET_NUMBER_COLUMN_MIN_AND_PRECISION $column)## * 如果表列是数字型, 获取其最小值和浮点数部分精度, 否则为空字符串 * 参数: 列 * 最小值结果保存在变量名为 $_number_column_min 字符串型 * 浮点数部分精度结果保存在变量名为 $_number_column_precision 字符串型 +#set($_number_column_min="") +#set($_number_column_precision="") +#if($column.columnType.contains("unsigned")) +#set($_number_column_min="0") +#end +#set($startLeft=$column.columnType.indexOf("(")) +#if($startLeft != -1) +#set($endRight=$column.columnType.indexOf(")", $startLeft)) +#if($endRight != -1) +#set($startLeft=$startLeft+1) +#set($internalText=$column.columnType.substring($startLeft, $endRight)) +#set($splitIndex=$internalText.indexOf(",")) +#if($splitIndex != -1) +#set($splitIndex=$splitIndex+1) +#set($_number_column_precision=$internalText.substring($splitIndex)) +#end +#end +#end +#end## GET_NUMBER_COLUMN_MIN_AND_PRECISION