ソースを参照

完善框架

master
张泽亮 2ヶ月前
コミット
aec1a8a7f0
40個のファイルの変更4079行の追加1180行の削除
  1. +49
    -0
      pom.xml
  2. +13
    -7
      ruoyi-admin/pom.xml
  3. +12
    -2
      ruoyi-admin/ruoyi-admin.iml
  4. +31
    -51
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
  5. +7
    -12
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java
  6. +1
    -1
      ruoyi-admin/src/main/resources/application-druid.yml
  7. +1
    -1
      ruoyi-admin/src/main/resources/application.yml
  8. +35
    -0
      ruoyi-business/pom.xml
  9. +111
    -0
      ruoyi-business/ruoyi-business.iml
  10. +40
    -3
      ruoyi-common/pom.xml
  11. +6
    -1
      ruoyi-common/ruoyi-common.iml
  12. +3
    -4
      ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java
  13. +24
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/BottomItem.java
  14. +44
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/PageSet.java
  15. +46
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/PdfProperty.java
  16. +24
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/pdf/ShoulderItem.java
  17. +7
    -2
      ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java
  18. +490
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/DecimalUtils.java
  19. +30
    -11
      ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java
  20. +69
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/file/BASE64DecodedMultipartFile.java
  21. +71
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ConvertToMultipartFile.java
  22. +223
    -100
      ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java
  23. +243
    -44
      ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java
  24. +72
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/MyHeaderFooter.java
  25. +70
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/MyHeaderFooterRotate.java
  26. +538
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/PdfUtils.java
  27. +44
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/pdf/Watermark.java
  28. +110
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelDictSheetHelper.java
  29. +856
    -878
      ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
  30. +66
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/translation/Translate.java
  31. +58
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/translation/TranslateUtils.java
  32. +465
    -0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/translation/Translator.java
  33. +6
    -1
      ruoyi-framework/ruoyi-framework.iml
  34. +6
    -1
      ruoyi-generator/ruoyi-generator.iml
  35. +13
    -16
      ruoyi-generator/src/main/resources/vm/java/controller.java.vm
  36. +2
    -2
      ruoyi-generator/src/main/resources/vm/java/service.java.vm
  37. +2
    -3
      ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm
  38. +179
    -38
      ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
  39. +6
    -1
      ruoyi-quartz/ruoyi-quartz.iml
  40. +6
    -1
      ruoyi-system/ruoyi-system.iml

+ 49
- 0
pom.xml ファイルの表示

@@ -34,6 +34,12 @@
<logback.version>1.2.13</logback.version>
<spring-security.version>5.7.12</spring-security.version>
<spring-framework.version>5.3.39</spring-framework.version>
<cn.hutool.all.version>5.5.4</cn.hutool.all.version>
<com.itextpdf.version>5.5.13.2</com.itextpdf.version>
<itext-asian.version>5.2.0</itext-asian.version>
<de.odysseus.juel.version>2.1.3</de.odysseus.juel.version>
<httpclient.version>4.5.13</httpclient.version>
<net.coobird.version>0.4.8</net.coobird.version>
</properties>

<!-- 依赖声明 -->
@@ -182,6 +188,49 @@
<version>${kaptcha.version}</version>
</dependency>

<!--hutool工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${cn.hutool.all.version}</version>
</dependency>

<dependency>
<groupId>de.odysseus.juel</groupId>
<artifactId>juel</artifactId>
<version>${de.odysseus.juel.version}</version>
</dependency>


<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>

<!--pdf-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>${com.itextpdf.version}</version>
</dependency>

<!--pdf 字体-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>${itext-asian.version}</version>
</dependency>

<!--thumbnailator图片处理-->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>${net.coobird.version}</version>
</dependency>



<!-- 定时任务-->
<dependency>
<groupId>com.ruoyi</groupId>


+ 13
- 7
ruoyi-admin/pom.xml ファイルの表示

@@ -61,6 +61,12 @@
<artifactId>ruoyi-generator</artifactId>
</dependency>

<!-- 业务模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-business</artifactId>
</dependency>

</dependencies>

<build>
@@ -80,17 +86,17 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.1.0</version>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warName>${project.artifactId}</warName>
</configuration>
</plugin>
</configuration>
</plugin>
</plugins>
<finalName>${project.artifactId}</finalName>
</build>

</project>
</project>

+ 12
- 2
ruoyi-admin/ruoyi-admin.iml ファイルの表示

@@ -100,6 +100,7 @@
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-core:5.7.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-crypto:5.7.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-web:5.7.12" level="project" />
<orderEntry type="library" name="Maven: net.coobird:thumbnailator:0.4.8" level="project" />
<orderEntry type="library" name="Maven: com.github.pagehelper:pagehelper-spring-boot-starter:1.4.7" level="project" />
<orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-jdbc:2.5.15" level="project" />
@@ -115,6 +116,7 @@
<orderEntry type="library" name="Maven: org.hibernate.validator:hibernate-validator:6.2.5.Final" level="project" />
<orderEntry type="library" name="Maven: jakarta.validation:jakarta.validation-api:2.0.2" level="project" />
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.4.3.Final" level="project" />
<orderEntry type="library" name="Maven: org.projectlombok:lombok:1.18.26" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.12.0" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.12.7.1" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.12.7" level="project" />
@@ -122,7 +124,6 @@
<orderEntry type="library" name="Maven: commons-io:commons-io:2.19.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.poi:poi-ooxml:4.1.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.poi:poi:4.1.2" level="project" />
<orderEntry type="library" name="Maven: commons-codec:commons-codec:1.15" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-collections4:4.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-math3:3.6.1" level="project" />
<orderEntry type="library" name="Maven: com.zaxxer:SparseBitSet:1.2" level="project" />
@@ -132,6 +133,7 @@
<orderEntry type="library" name="Maven: com.github.virtuald:curvesapi:1.06" level="project" />
<orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.28" level="project" />
<orderEntry type="library" name="Maven: io.jsonwebtoken:jjwt:0.9.1" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" name="Maven: javax.xml.bind:jaxb-api:2.3.1" level="project" />
<orderEntry type="library" name="Maven: javax.activation:javax.activation-api:1.2.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-redis:2.5.15" level="project" />
@@ -152,8 +154,16 @@
<orderEntry type="library" name="Maven: org.reactivestreams:reactive-streams:1.0.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-pool2:2.9.0" level="project" />
<orderEntry type="library" name="Maven: eu.bitwalker:UserAgentUtils:1.21" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itextpdf:5.5.13.2" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itext-asian:5.2.0" level="project" />
<orderEntry type="library" name="Maven: cn.hutool:hutool-all:5.5.4" level="project" />
<orderEntry type="module" module-name="ruoyi-generator" />
<orderEntry type="library" name="Maven: org.apache.velocity:velocity-engine-core:2.3" level="project" />
<orderEntry type="module" module-name="ruoyi-business" />
<orderEntry type="library" name="Maven: de.odysseus.juel:juel:2.1.3" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpclient:4.5.13" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpcore:4.4.16" level="project" />
<orderEntry type="library" name="Maven: commons-logging:commons-logging:1.2" level="project" />
<orderEntry type="library" name="Maven: commons-codec:commons-codec:1.15" level="project" />
</component>
</module>

+ 31
- 51
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<MultipartFile> files) throws Exception
{
try
{
public AjaxResult uploadFiles(List<MultipartFile> files) throws Exception {
try {
// 上传文件路径
String filePath = RuoYiConfig.getUploadPath();
List<String> urls = new ArrayList<String>();
List<String> fileNames = new ArrayList<String>();
List<String> newFileNames = new ArrayList<String>();
List<String> originalFilenames = new ArrayList<String>();
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);
}
}


+ 7
- 12
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();


+ 1
- 1
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%
# 从库数据源


+ 1
- 1
ruoyi-admin/src/main/resources/application.yml ファイルの表示

@@ -1,7 +1,7 @@
# 项目相关配置
ruoyi:
# 名称
name: RuoYi
name: RongXin
# 版本
version: 3.9.0
# 版权年份


+ 35
- 0
ruoyi-business/pom.xml ファイルの表示

@@ -12,4 +12,39 @@
<artifactId>ruoyi-business</artifactId>


<dependencies>

<!-- 系统模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-system</artifactId>
</dependency>

<!-- 通用工具-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
</dependency>

<!--hutool工具包-->
<!--<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>-->

<dependency>
<groupId>de.odysseus.juel</groupId>
<artifactId>juel</artifactId>
</dependency>


<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>



</dependencies>

</project>

+ 111
- 0
ruoyi-business/ruoyi-business.iml ファイルの表示

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="Spring" name="Spring">
<configuration />
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="ruoyi-system" />
<orderEntry type="module" module-name="ruoyi-common" />
<orderEntry type="library" name="Maven: org.springframework:spring-context-support:5.3.39" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-beans:5.3.39" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context:5.3.39" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-expression:5.3.39" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-core:5.3.39" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.3.39" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-web:5.3.39" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-security:2.5.15" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.5.15" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:2.5.15" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.5.15" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:2.5.15" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.13" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.13" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-to-slf4j:2.17.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.17.2" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.36" level="project" />
<orderEntry type="library" name="Maven: jakarta.annotation:jakarta.annotation-api:1.3.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-aop:5.3.39" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-config:5.7.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-core:5.7.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-crypto:5.7.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-web:5.7.12" level="project" />
<orderEntry type="library" name="Maven: net.coobird:thumbnailator:0.4.8" level="project" />
<orderEntry type="library" name="Maven: com.github.pagehelper:pagehelper-spring-boot-starter:1.4.7" level="project" />
<orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-jdbc:2.5.15" level="project" />
<orderEntry type="library" name="Maven: com.zaxxer:HikariCP:4.0.3" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-jdbc:5.3.39" level="project" />
<orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-autoconfigure:2.3.1" level="project" />
<orderEntry type="library" name="Maven: org.mybatis:mybatis:3.5.13" level="project" />
<orderEntry type="library" name="Maven: org.mybatis:mybatis-spring:2.1.1" level="project" />
<orderEntry type="library" name="Maven: com.github.pagehelper:pagehelper-spring-boot-autoconfigure:1.4.7" level="project" />
<orderEntry type="library" name="Maven: com.github.pagehelper:pagehelper:5.3.3" level="project" />
<orderEntry type="library" name="Maven: com.github.jsqlparser:jsqlparser:4.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-validation:2.5.15" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-el:9.0.106" level="project" />
<orderEntry type="library" name="Maven: org.hibernate.validator:hibernate-validator:6.2.5.Final" level="project" />
<orderEntry type="library" name="Maven: jakarta.validation:jakarta.validation-api:2.0.2" level="project" />
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.4.3.Final" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.5.1" level="project" />
<orderEntry type="library" name="Maven: org.projectlombok:lombok:1.18.26" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.12.0" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.12.7.1" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.12.7" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.12.7" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.fastjson2:fastjson2:2.0.57" level="project" />
<orderEntry type="library" name="Maven: commons-io:commons-io:2.19.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.poi:poi-ooxml:4.1.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.poi:poi:4.1.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-collections4:4.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-math3:3.6.1" level="project" />
<orderEntry type="library" name="Maven: com.zaxxer:SparseBitSet:1.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.poi:poi-ooxml-schemas:4.1.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.xmlbeans:xmlbeans:3.1.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-compress:1.19" level="project" />
<orderEntry type="library" name="Maven: com.github.virtuald:curvesapi:1.06" level="project" />
<orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.28" level="project" />
<orderEntry type="library" name="Maven: io.jsonwebtoken:jjwt:0.9.1" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" name="Maven: javax.xml.bind:jaxb-api:2.3.1" level="project" />
<orderEntry type="library" name="Maven: javax.activation:javax.activation-api:1.2.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-redis:2.5.15" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-redis:2.5.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-keyvalue:2.5.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-commons:2.5.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-tx:5.3.39" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-oxm:5.3.39" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.36" level="project" />
<orderEntry type="library" name="Maven: io.lettuce:lettuce-core:6.1.10.RELEASE" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-common:4.1.92.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-handler:4.1.92.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-resolver:4.1.92.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-buffer:4.1.92.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-transport-native-unix-common:4.1.92.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-codec:4.1.92.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-transport:4.1.92.Final" level="project" />
<orderEntry type="library" name="Maven: io.projectreactor:reactor-core:3.4.29" level="project" />
<orderEntry type="library" name="Maven: org.reactivestreams:reactive-streams:1.0.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-pool2:2.9.0" level="project" />
<orderEntry type="library" name="Maven: eu.bitwalker:UserAgentUtils:1.21" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itextpdf:5.5.13.2" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itext-asian:5.2.0" level="project" />
<orderEntry type="library" name="Maven: cn.hutool:hutool-all:5.5.4" level="project" />
<orderEntry type="library" name="Maven: de.odysseus.juel:juel:2.1.3" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpclient:4.5.13" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpcore:4.4.16" level="project" />
<orderEntry type="library" name="Maven: commons-logging:commons-logging:1.2" level="project" />
<orderEntry type="library" name="Maven: commons-codec:commons-codec:1.15" level="project" />
</component>
</module>

+ 40
- 3
ruoyi-common/pom.xml ファイルの表示

@@ -35,6 +35,12 @@
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!--thumbnailator图片处理-->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
</dependency>

<!-- pagehelper 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
@@ -47,18 +53,24 @@
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<!--常用工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- JSON工具类 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- 阿里JSON解析器 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
@@ -89,6 +101,12 @@
<artifactId>jjwt</artifactId>
</dependency>

<!-- servlet包 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>

<!-- Jaxb -->
<dependency>
<groupId>javax.xml.bind</groupId>
@@ -119,6 +137,25 @@
<artifactId>javax.servlet-api</artifactId>
</dependency>

<!--pdf-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
</dependency>

<!--pdf 字体-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
</dependency>

<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>


</dependencies>

</project>
</project>

+ 6
- 1
ruoyi-common/ruoyi-common.iml ファイルの表示

@@ -37,6 +37,7 @@
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-core:5.7.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-crypto:5.7.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-web:5.7.12" level="project" />
<orderEntry type="library" name="Maven: net.coobird:thumbnailator:0.4.8" level="project" />
<orderEntry type="library" name="Maven: com.github.pagehelper:pagehelper-spring-boot-starter:1.4.7" level="project" />
<orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-jdbc:2.5.15" level="project" />
@@ -54,6 +55,7 @@
<orderEntry type="library" name="Maven: jakarta.validation:jakarta.validation-api:2.0.2" level="project" />
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.4.3.Final" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.5.1" level="project" />
<orderEntry type="library" name="Maven: org.projectlombok:lombok:1.18.26" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.12.0" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.12.7.1" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.12.7" level="project" />
@@ -72,6 +74,7 @@
<orderEntry type="library" name="Maven: com.github.virtuald:curvesapi:1.06" level="project" />
<orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.28" level="project" />
<orderEntry type="library" name="Maven: io.jsonwebtoken:jjwt:0.9.1" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" name="Maven: javax.xml.bind:jaxb-api:2.3.1" level="project" />
<orderEntry type="library" name="Maven: javax.activation:javax.activation-api:1.2.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-redis:2.5.15" level="project" />
@@ -93,6 +96,8 @@
<orderEntry type="library" name="Maven: org.reactivestreams:reactive-streams:1.0.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-pool2:2.9.0" level="project" />
<orderEntry type="library" name="Maven: eu.bitwalker:UserAgentUtils:1.21" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itextpdf:5.5.13.2" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itext-asian:5.2.0" level="project" />
<orderEntry type="library" name="Maven: cn.hutool:hutool-all:5.5.4" level="project" />
</component>
</module>

+ 3
- 4
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;
}



+ 24
- 0
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;



}

+ 44
- 0
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;


}

+ 46
- 0
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<String[]> contentList;




}

+ 24
- 0
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;



}

+ 7
- 2
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,
}

+ 490
- 0
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<String> CN_NUMBERS = Arrays.asList("零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖");
private static final List<String> CN_UNITS = Arrays.asList("", "拾", "佰", "仟");
private static final List<String> 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();
}
}
}

+ 30
- 11
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<String, String> dictCacheValueLabelMap(String key) {
List<SysDictData> 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;
}
}

+ 69
- 0
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");

}



}

+ 71
- 0
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);
}
}


+ 223
- 100
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;


+ 243
- 44
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();
}
}

+ 72
- 0
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);
}

}

+ 70
- 0
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);
}

}

+ 538
- 0
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<statistic> {

// 定义全局的字体静态变量
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<String[]> 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);
}

}

+ 44
- 0
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);
}
}
}
}

+ 110
- 0
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;
}
}

+ 856
- 878
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 66
- 0
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")
* <p>
* dictSql + tableKeyColumn + tableValueColumn: 执行查询SQL后, 取tableKeyColumn(), tableValueColumn()作为字典键值. 例如: @Translate(dictSql = "SELECT * FROM t_finance_book", tableValueColumn = "book_name", tableKeyColumn = "id")
* readConverterExp
* dictType
* <p>
* 前两种方式使用待翻译字段值直查
* 后三种都是加载对应键值字典, 然后用待翻译字段值匹配. 如果都指定了, 会按优先级混合在一起, 优先级高的覆盖优先级低的
* <p>
* 关于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 "" ;
}

+ 58
- 0
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, 仅标记注解即可, 此时将递归翻译(注意不要循环嵌套对象)
* <p>
* 数据库表数据字典: 仅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<Map<String, Object>> list, Map<String, String> dictMap) {
new Translator().translateMapList(list, dictMap);
}

public static void translateMap(Map<String, Object> map, Map<String, String> dictMap) {
new Translator().translateMap(map, dictMap);
}

public static <T> void translateObjectList(List<T> list) {
new Translator().translateObjectList(list);
}

public static <T> void translateObject(T object) {
new Translator().translateObject(object);
}

public static <T extends BaseEntity> void translateEntityList(List<T> list, boolean... keepRawField) {
boolean b = null != keepRawField && keepRawField.length > 0 ? keepRawField[0] : false;
new Translator().translateEntityList(list, b);
}

public static <T extends BaseEntity> void translateEntity(T object, boolean... keepRawField) {
boolean b = null != keepRawField && keepRawField.length > 0 ? keepRawField[0] : false;
new Translator().translateEntity(object, b);
}
}

+ 465
- 0
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<Object> 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<String, String> dictMap = o instanceof Map ? (Map<String, String>) args[0] : null;
translateMap((Map<String, Object>) obj, dictMap);
} else if (obj instanceof List) {
translateList((List<?>) obj, args);
} else {
translateObject(obj);
}
}

public <T extends BaseEntity> void translateEntityList(List<T> list, boolean keepRawField) {
if (CollectionUtil.isEmpty(list))
return;
list.forEach((x) -> translateEntity(x, keepRawField));
}

public <T extends BaseEntity> void translateEntity(T object, boolean keepRawField) {
if (null == object)
return;
translateEntity_r(object, object.getClass(), keepRawField);
}

public void translateMapList(List<Map<String, Object>> list, Map<String, String> dictMap) {
if (CollectionUtil.isEmpty(list))
return;
list.forEach((x) -> translateMap(x, dictMap));
}

public void translateMap(Map<String, Object> map, Map<String, String> 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 <T> void translateObjectList(List<T> list) {
if (CollectionUtil.isEmpty(list))
return;
list.forEach(this::translateObject);
}

public <T> void translateObject(T object) {
if (null == object)
return;
translateObject_r(object, object.getClass());
}

private <T> 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 <T extends BaseEntity> 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<String, String> parseReadConverterExp(String str) {
Map<String, String> dict = new LinkedHashMap<>();
if (StringUtils.isNotEmpty(str)) {
List<String> split = StrUtil.split(str, ',', true, true);
split.forEach((x) -> {
List<String> 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<String, String> loadDBDict(String sql, String keyColumn, String nameColumn) {
Map<String, String> dict = new LinkedHashMap<>();
if (StringUtils.isNotEmpty(sql) && StringUtils.isNotEmpty(keyColumn) && StringUtils.isNotEmpty(nameColumn)) {
JdbcTemplate jdbcTemplate = SpringUtils.getBean(JdbcTemplate.class);
try {
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
if (CollectionUtil.isNotEmpty(list)) {
for (Map<String, Object> 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<Map<String, Object>> list = jdbcTemplate.queryForList(sql, id);
if (CollectionUtil.isNotEmpty(list)) {
Map<String, Object> 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<String, String> getTranslateDict(Field field) {
Map<String, String> 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<String, String> 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 <T extends BaseEntity> 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 <T extends BaseEntity> 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 <T extends BaseEntity> boolean translateField_String(Field field, T entity, boolean keepRawField) {
boolean trans = false;

if (!field.getType().equals(String.class))
return false;

Map<String, String> 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 <T> boolean contains_ptr(Collection<T> 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> T DEF_PARM(T... args) {
return null != args && args.length > 0 ? args[0] : null;
}
}

+ 6
- 1
ruoyi-framework/ruoyi-framework.iml ファイルの表示

@@ -73,6 +73,7 @@
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-core:5.7.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-crypto:5.7.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-web:5.7.12" level="project" />
<orderEntry type="library" name="Maven: net.coobird:thumbnailator:0.4.8" level="project" />
<orderEntry type="library" name="Maven: com.github.pagehelper:pagehelper-spring-boot-starter:1.4.7" level="project" />
<orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-jdbc:2.5.15" level="project" />
@@ -89,6 +90,7 @@
<orderEntry type="library" name="Maven: jakarta.validation:jakarta.validation-api:2.0.2" level="project" />
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.4.3.Final" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.5.1" level="project" />
<orderEntry type="library" name="Maven: org.projectlombok:lombok:1.18.26" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.12.0" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.fastjson2:fastjson2:2.0.57" level="project" />
<orderEntry type="library" name="Maven: commons-io:commons-io:2.19.0" level="project" />
@@ -103,6 +105,7 @@
<orderEntry type="library" name="Maven: org.apache.commons:commons-compress:1.19" level="project" />
<orderEntry type="library" name="Maven: com.github.virtuald:curvesapi:1.06" level="project" />
<orderEntry type="library" name="Maven: io.jsonwebtoken:jjwt:0.9.1" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" name="Maven: javax.xml.bind:jaxb-api:2.3.1" level="project" />
<orderEntry type="library" name="Maven: javax.activation:javax.activation-api:1.2.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-redis:2.5.15" level="project" />
@@ -123,6 +126,8 @@
<orderEntry type="library" name="Maven: org.reactivestreams:reactive-streams:1.0.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-pool2:2.9.0" level="project" />
<orderEntry type="library" name="Maven: eu.bitwalker:UserAgentUtils:1.21" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itextpdf:5.5.13.2" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itext-asian:5.2.0" level="project" />
<orderEntry type="library" name="Maven: cn.hutool:hutool-all:5.5.4" level="project" />
</component>
</module>

+ 6
- 1
ruoyi-generator/ruoyi-generator.iml ファイルの表示

@@ -40,6 +40,7 @@
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-core:5.7.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-crypto:5.7.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-web:5.7.12" level="project" />
<orderEntry type="library" name="Maven: net.coobird:thumbnailator:0.4.8" level="project" />
<orderEntry type="library" name="Maven: com.github.pagehelper:pagehelper-spring-boot-starter:1.4.7" level="project" />
<orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-jdbc:2.5.15" level="project" />
@@ -57,6 +58,7 @@
<orderEntry type="library" name="Maven: jakarta.validation:jakarta.validation-api:2.0.2" level="project" />
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.4.3.Final" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.5.1" level="project" />
<orderEntry type="library" name="Maven: org.projectlombok:lombok:1.18.26" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.12.7.1" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.12.7" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.12.7" level="project" />
@@ -74,6 +76,7 @@
<orderEntry type="library" name="Maven: com.github.virtuald:curvesapi:1.06" level="project" />
<orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.28" level="project" />
<orderEntry type="library" name="Maven: io.jsonwebtoken:jjwt:0.9.1" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" name="Maven: javax.xml.bind:jaxb-api:2.3.1" level="project" />
<orderEntry type="library" name="Maven: javax.activation:javax.activation-api:1.2.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-redis:2.5.15" level="project" />
@@ -94,7 +97,9 @@
<orderEntry type="library" name="Maven: org.reactivestreams:reactive-streams:1.0.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-pool2:2.9.0" level="project" />
<orderEntry type="library" name="Maven: eu.bitwalker:UserAgentUtils:1.21" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itextpdf:5.5.13.2" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itext-asian:5.2.0" level="project" />
<orderEntry type="library" name="Maven: cn.hutool:hutool-all:5.5.4" level="project" />
<orderEntry type="library" name="Maven: com.alibaba:druid-spring-boot-starter:1.2.23" level="project" />
<orderEntry type="library" name="Maven: com.alibaba:druid:1.2.23" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.5.15" level="project" />


+ 13
- 16
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<String[]> 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);


+ 2
- 2
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}


+ 2
- 3
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;


+ 179
- 38
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
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
@@ -48,20 +84,23 @@
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['${permissionPrefix}:add']">新增</el-button>
</el-col>
<!--
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate" v-hasPermi="['${permissionPrefix}:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete" v-hasPermi="['${permissionPrefix}:remove']">删除</el-button>
</el-col>
-->

<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['${permissionPrefix}:export']">导出</el-button>
<el-button type="info" plain icon="el-icon-upload2" size="mini" @click="handleImport" v-hasPermi="['${permissionPrefix}:import']">导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="el-icon-upload2" size="mini" @click="handleImport" v-hasPermi="['${permissionPrefix}:import']">导入</el-button>
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['${permissionPrefix}:export']">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-printer" size="mini" @click="handlePrint()" v-hasPermi="['${permissionPrefix}:export']">打印</el-button>
<el-button type="success" plain icon="el-icon-printer" size="mini" @click="handlePrint()" v-hasPermi="['${permissionPrefix}:print']">打印</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@@ -80,21 +119,17 @@
#if($column.pk)
<el-table-column label="${comment}" align="center" prop="${javaField}" />
#elseif($column.list && $column.htmlType == "datetime")
<el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="${comment}" align="center" prop="${javaField}" />
#elseif($column.list && $column.htmlType == "imageUpload")
<el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
<template slot-scope="scope">
<div v-if="!!scope.row.${javaField}"><el-tooltip effect="light" :content="item" placement="bottom" v-for="(item, index) in scope.row.${javaField}.split(',')" :key="index"><el-image style="height: 48px; width: 48px; margin: 2px; display: inline-block;" fit="scale-down" :src="$store.getters.baseRoutingUrl + item" :preview-src-list="scope.row.${javaField}.split(',').map((x) => $store.getters.baseRoutingUrl + x)"/></el-tooltip></div>
<div v-if="!!scope.row.${javaField}"><el-tooltip effect="light" :content="item" placement="bottom" v-for="(item, index) in scope.row.${javaField}.split(',')" :key="index"><el-image style="height: 48px; width: 48px; margin: 2px; display: inline-block;" fit="scale-down" :src="baseRoutingUrll + item" :preview-src-list="scope.row.${javaField}.split(',').map((x) => baseRoutingUrll + x)"/></el-tooltip></div>
</template>
</el-table-column>
#elseif($column.list && $column.htmlType == "fileUpload")
<el-table-column label="${comment}" header-align="center" align="left" prop="${javaField}" >
<template slot-scope="scope">
<div v-if="!!scope.row.${javaField}"><el-tooltip effect="light" :content="item.substr(item.lastIndexOf('/') + 1)" placement="bottom" v-for="(item, index) in scope.row.${javaField}.split(',')" :key="index"><a :href="$store.getters.baseRoutingUrl + item" target="_blank" style="height: 32px; width: 32px; margin: 2px; display: inline-block; text-align: center;"><img :src="getFileIcon(item)" style="height: 100%;"/></a></el-tooltip></div>
<div v-if="!!scope.row.${javaField}"><el-tooltip effect="light" :content="item.substr(item.lastIndexOf('/') + 1)" placement="bottom" v-for="(item, index) in scope.row.${javaField}.split(',')" :key="index"><a :href="baseRoutingUrll + item" target="_blank" style="height: 32px; width: 32px; margin: 2px; display: inline-block; text-align: center;"><img :src="getFileIcon(item)" style="height: 100%;"/></a></el-tooltip></div>
</template>
</el-table-column>
#elseif($column.list && "" != $column.dictType)
@@ -124,11 +159,27 @@
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>

<!-- 导入EXCEL组件 -->
<importExcel :visible="importExcelData.visible" :importExcelDatas="importExcelData" :datas="importExcelData.data" @improtExcelChildFn="improtExcelChildFun"></importExcel>
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
<el-upload ref="upload" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip text-center" slot="tip">
<div class="el-upload__tip" slot="tip">
<el-checkbox v-model="upload.updateSupport" />是否更新已经存在的${functionName}
</div>
<span>仅允许导入xls、xlsx格式文件。</span>
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm">确 定</el-button>
<el-button @click="upload.open = false">取 消</el-button>
</div>
</el-dialog>

<!-- 查看${functionName}对话框 -->
<el-dialog :title="title" :visible.sync="viewOpen" width="800px" append-to-body>
<el-descriptions :column="descColumn" border :labelStyle="{width: `${descLabelWidth}%`}" :contentStyle="{width: `${(100 / descColumn) - descLabelWidth}%`}">
<el-descriptions id="printDetail" :column="descColumn" border :labelStyle="{width: `${descLabelWidth}%`}" :contentStyle="{width: `${(100 / descColumn) - descLabelWidth}%`}">
#foreach($column in $columns)
#set($field=$column.javaField)
#if($column.insert && !$column.pk)
@@ -140,13 +191,14 @@
#set($comment=$column.columnComment)
#end
#set($dictType=$column.dictType)
<el-descriptions-item label="${comment}">#if(($column.htmlType == "select" || $column.htmlType == "checkbox" || $column.htmlType == "radio") && "" != $dictType)<el-tooltip effect="light" :content="form.${field}" placement="right"><dict-tag :options="${field}Options" :value="form.${field}"/></el-tooltip>#elseif($column.htmlType == "imageUpload")<div v-if="!!form.${field}"><el-tooltip effect="light" :content="item" placement="bottom" v-for="(item, index) in form.${field}.split(',')" :key="index"><el-image style="height: 64px; width: 64px; margin: 2px; display: inline-block;" fit="scale-down" :src="$store.getters.baseRoutingUrl + item" :preview-src-list="form.${field}.split(',').map((x) => $store.getters.baseRoutingUrl + x)"/></el-tooltip></div>#elseif($column.htmlType == "editor")<el-tooltip placement="bottom" effect="light"><div slot="content" v-html="form.${field}"/><div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 230px;">{{ form.${field} }}</div></el-tooltip>#elseif($column.htmlType == "fileUpload")<div v-if="!!form.${field}"><el-tooltip effect="light" :content="item.substr(item.lastIndexOf('/') + 1)" placement="bottom" v-for="(item, index) in form.${field}.split(',')" :key="index"><a :href="$store.getters.baseRoutingUrl + item" target="_blank" style="height: 48px; width: 48px; margin: 2px; display: inline-block; text-align: center;"><img :src="getFileIcon(item)" style="height: 100%;"/></a></el-tooltip></div>#else{{ form.${field} }}#end</el-descriptions-item>
<el-descriptions-item label="${comment}">#if(($column.htmlType == "select" || $column.htmlType == "checkbox" || $column.htmlType == "radio") && "" != $dictType)<el-tooltip effect="light" :content="form.${field}" placement="right"><dict-tag :options="dict.type.dictType" :value="form.${field}"/></el-tooltip>#elseif($column.htmlType == "imageUpload")<div v-if="!!form.${field}"><el-tooltip effect="light" :content="item" placement="bottom" v-for="(item, index) in form.${field}.split(',')" :key="index"><el-image style="height: 64px; width: 64px; margin: 2px; display: inline-block;" fit="scale-down" :src="baseRoutingUrll + item" :preview-src-list="form.${field}.split(',').map((x) => baseRoutingUrll + x)"/></el-tooltip></div>#elseif($column.htmlType == "editor")<el-tooltip placement="bottom" effect="light"><div slot="content" v-html="form.${field}"/><div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 230px;">{{ form.${field} }}</div></el-tooltip>#elseif($column.htmlType == "fileUpload")<div v-if="!!form.${field}"><el-tooltip effect="light" :content="item.substr(item.lastIndexOf('/') + 1)" placement="bottom" v-for="(item, index) in form.${field}.split(',')" :key="index"><a :href="baseRoutingUrll + item" target="_blank" style="height: 48px; width: 48px; margin: 2px; display: inline-block; text-align: center;"><img :src="getFileIcon(item)" style="height: 100%;"/></a></el-tooltip></div>#else{{ form.${field} }}#end</el-descriptions-item>
#end
#end
#end
</el-descriptions>
<!-- 弹框操作按钮 -->
<div slot="footer" class="dialog-footer">
<el-button @click="doPrint">打 印</el-button>
<el-button @click="cancel">关 闭</el-button>
</div>
</el-dialog>
@@ -166,9 +218,18 @@
#end
#set($dictType=$column.dictType)
#if($column.htmlType == "input")
#COLUMN_IS_NUMBER($column)
#if($_column_is_number)
#GET_NUMBER_COLUMN_MIN_AND_PRECISION($column)
<el-form-item label="${comment}" prop="${field}">
<el-input v-model="form.${field}" placeholder="请输入${comment}" />
<el-input-number v-model="form.${field}" placeholder="请输入${comment}" controls-position="right" #if($_number_column_min != "") :min="${_number_column_min}"#end #if($_number_column_precision != "") :precision="${_number_column_precision}"#end/>
</el-form-item>
#else
#GET_CHAR_COLUMN_LENGTH($column)
<el-form-item label="${comment}" prop="${field}">
<el-input v-model="form.${field}" placeholder="请输入${comment}" #if($_char_column_length != "") show-word-limit :maxlength="${_char_column_length}"#end/>
</el-form-item>
#end
#elseif($column.htmlType == "imageUpload")
<el-form-item label="${comment}" prop="${field}">
<image-upload v-model="form.${field}"/>
@@ -235,7 +296,7 @@
</el-form-item>
#elseif($column.htmlType == "textarea")
<el-form-item label="${comment}" prop="${field}">
<el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
<el-input v-model="form.${field}" type="textarea" :autosize="{ minRows: 2, maxRows: 3}" #if($_char_column_length != "") :maxlength="${_char_column_length}"#end show-word-limit placeholder="请输入内容" />
</el-form-item>
#end
#end
@@ -306,7 +367,7 @@

<script>
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName}, print${BusinessName} } from "@/api/${moduleName}/${businessName}"
import importExcel from "@/components/importExcel/importExcel";
import { getToken } from "@/utils/auth"
export default {
name: "${BusinessName}",
#if(${dicts} != '')
@@ -348,6 +409,8 @@ export default {
descLabelWidth: 15,
// 对话框显示只读的详情
viewOpen: false,
// 项目路径
baseRoutingUrl: process.env.VUE_APP_BASE_API,
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
@@ -363,7 +426,7 @@ export default {
//orderByColumn: "id",
//isAsc: "desc",
// 翻译字典
//toDict: "1",
//toTranslateDict: "1",
#foreach ($column in $columns)
#if($column.query)
$column.javaField: null#if($foreach.count != $columns.size()),#end
@@ -389,22 +452,21 @@ export default {
#end
},
// EXCEL导入
importExcelData: {
//弹窗显示隐藏
visible: false,
//标题
title: "${functionName}导入",
//上传时附带的额外参数
data: {},
upload: {
// 是否显示弹出层(用户导入)
open: false,
// 弹出层标题(用户导入)
title: "",
// 是否禁用上传
isUploading: false,
// 是否更新已经存在的用户数据
updateSupport: 0,
// 设置上传的请求头部
headers: { Authorization: "Bearer " + getToken() },
// 上传的地址
url: process.env.VUE_APP_BASE_API + "/${moduleName}/${businessName}/importData",
// 下载模板接口地址 空不显示按钮
DownloadTemplateUrl: "/${moduleName}/${businessName}/importTemplate",
//更新数据文案 空不显示更新按钮
updateText: "是否更新已经存在的${functionName}数据",
// 导入规则说明 (可为空)
promptText: "1、模板表头不允许变动!<br>2、仅允许导入“xls”或“xlsx”格式文件!",
url: process.env.VUE_APP_BASE_API + "/${moduleName}/${businessName}/importData"
},

}
},
created() {
@@ -597,17 +659,96 @@ export default {
...this.queryParams
}, `${businessName}_#[[${new Date().getTime()}]]#.xlsx`)
},
/** 打印表单 */
doPrint() {
const originalTitle = document.title;
try {
document.title = this.title || '通用附件详情';
const printElement = document.getElementById('printDetail');
const printFrame = document.createElement('iframe');
printFrame.style.position = 'absolute';
printFrame.style.width = '0';
printFrame.style.height = '0';
printFrame.style.border = 'none';
printFrame.style.left = '-9999px';
printFrame.onload = function() {
try {
const frameDoc = printFrame.contentDocument || printFrame.contentWindow.document;
const contentClone = printElement.cloneNode(true);
const style = document.createElement('style');
style.innerHTML = `
@page {
size: auto;
margin: 10mm;
}
body {
font-family: Arial, sans-serif;
line-height: 1.5;
margin: 0;
padding: 0;
}
.el-descriptions {
width: 100% !important;
}
.el-descriptions-item__label {
width: ${this.descLabelWidth}% !important;
}
.el-descriptions-item__content {
width: ${(100 / this.descColumn) - this.descLabelWidth}% !important;
}
/* 确保图片在打印时显示完整 */
img, .el-image {
max-width: 100% !important;
height: auto !important;
}
`;
frameDoc.head.appendChild(style);
frameDoc.body.appendChild(contentClone);
setTimeout(() => {
printFrame.contentWindow.focus();
printFrame.contentWindow.print();
setTimeout(() => {
document.body.removeChild(printFrame);
document.title = originalTitle;
}, 1000);
}, 500);
} catch (e) {
document.body.removeChild(printFrame);
document.title = originalTitle;
this.$message.error('打印过程中发生错误');
}
};
document.body.appendChild(printFrame);
} catch (e) {
document.title = originalTitle;
this.$message.error('打印过程中发生错误');
}
},
/** 打印按钮操作 */
handlePrint() {
print${BusinessName}(this.queryParams).then(response => {})
},
/* 导入EXCEL组件 */
improtExcelChildFun(payload) {
this.$set(this.importExcelData, "visible", payload);
this.getList();
},
handleImport() {
this.$set(this.importExcelData, "visible", true);
this.upload.title = "${functionName}导入"
this.upload.open = true
},
/** 下载模板操作 */
importTemplate() {
this.download('${moduleName}/${businessName}/importTemplate', {
}, `${businessName}_template_${new Date().getTime()}.xlsx`)
},
// 文件上传中处理
handleFileUploadProgress(event, file, fileList) {
this.upload.isUploading = true
},
// 文件上传成功处理
handleFileSuccess(response, file, fileList) {
this.upload.open = false
this.upload.isUploading = false
this.$refs.upload.clearFiles()
this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true })
this.getList()
},
#foreach($column in $columns)
#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "fileUpload")
@@ -615,7 +756,7 @@ export default {
if(!file) return '';
switch(file.toLowerCase().substr(file.lastIndexOf('.') + 1)) {
case 'jpg': case 'png': case 'jpeg': case 'bmp': case 'gif':
return this.$store.getters.baseRoutingUrl + file;
return this.baseRoutingUrll + file;
case 'doc': case 'docx':
return require('@/assets/images/icon_word.jpg');
case 'xls': case 'xlsx':
@@ -625,7 +766,7 @@ export default {
case 'zip': case 'rar': case '7z': case 'bz2': case 'gz':
return require('@/assets/images/icon_zip.jpg');
default:
return require('@/assets/images/file.png');
return require('@/assets/images/icon_rest.jpg');
}
},
#break


+ 6
- 1
ruoyi-quartz/ruoyi-quartz.iml ファイルの表示

@@ -42,6 +42,7 @@
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-core:5.7.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-crypto:5.7.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-web:5.7.12" level="project" />
<orderEntry type="library" name="Maven: net.coobird:thumbnailator:0.4.8" level="project" />
<orderEntry type="library" name="Maven: com.github.pagehelper:pagehelper-spring-boot-starter:1.4.7" level="project" />
<orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-jdbc:2.5.15" level="project" />
@@ -59,6 +60,7 @@
<orderEntry type="library" name="Maven: jakarta.validation:jakarta.validation-api:2.0.2" level="project" />
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.4.3.Final" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.5.1" level="project" />
<orderEntry type="library" name="Maven: org.projectlombok:lombok:1.18.26" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.12.0" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.12.7.1" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.12.7" level="project" />
@@ -77,6 +79,7 @@
<orderEntry type="library" name="Maven: com.github.virtuald:curvesapi:1.06" level="project" />
<orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.28" level="project" />
<orderEntry type="library" name="Maven: io.jsonwebtoken:jjwt:0.9.1" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" name="Maven: javax.xml.bind:jaxb-api:2.3.1" level="project" />
<orderEntry type="library" name="Maven: javax.activation:javax.activation-api:1.2.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-redis:2.5.15" level="project" />
@@ -97,6 +100,8 @@
<orderEntry type="library" name="Maven: org.reactivestreams:reactive-streams:1.0.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-pool2:2.9.0" level="project" />
<orderEntry type="library" name="Maven: eu.bitwalker:UserAgentUtils:1.21" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itextpdf:5.5.13.2" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itext-asian:5.2.0" level="project" />
<orderEntry type="library" name="Maven: cn.hutool:hutool-all:5.5.4" level="project" />
</component>
</module>

+ 6
- 1
ruoyi-system/ruoyi-system.iml ファイルの表示

@@ -39,6 +39,7 @@
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-core:5.7.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-crypto:5.7.12" level="project" />
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-web:5.7.12" level="project" />
<orderEntry type="library" name="Maven: net.coobird:thumbnailator:0.4.8" level="project" />
<orderEntry type="library" name="Maven: com.github.pagehelper:pagehelper-spring-boot-starter:1.4.7" level="project" />
<orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-jdbc:2.5.15" level="project" />
@@ -56,6 +57,7 @@
<orderEntry type="library" name="Maven: jakarta.validation:jakarta.validation-api:2.0.2" level="project" />
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.4.3.Final" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.5.1" level="project" />
<orderEntry type="library" name="Maven: org.projectlombok:lombok:1.18.26" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.12.0" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.12.7.1" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.12.7" level="project" />
@@ -74,6 +76,7 @@
<orderEntry type="library" name="Maven: com.github.virtuald:curvesapi:1.06" level="project" />
<orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.28" level="project" />
<orderEntry type="library" name="Maven: io.jsonwebtoken:jjwt:0.9.1" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" name="Maven: javax.xml.bind:jaxb-api:2.3.1" level="project" />
<orderEntry type="library" name="Maven: javax.activation:javax.activation-api:1.2.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-redis:2.5.15" level="project" />
@@ -95,6 +98,8 @@
<orderEntry type="library" name="Maven: org.reactivestreams:reactive-streams:1.0.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-pool2:2.9.0" level="project" />
<orderEntry type="library" name="Maven: eu.bitwalker:UserAgentUtils:1.21" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itextpdf:5.5.13.2" level="project" />
<orderEntry type="library" name="Maven: com.itextpdf:itext-asian:5.2.0" level="project" />
<orderEntry type="library" name="Maven: cn.hutool:hutool-all:5.5.4" level="project" />
</component>
</module>

読み込み中…
キャンセル
保存