@@ -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> | |||
@@ -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> |
@@ -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> |
@@ -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); | |||
} | |||
} | |||
@@ -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(); | |||
@@ -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,7 +1,7 @@ | |||
# 项目相关配置 | |||
ruoyi: | |||
# 名称 | |||
name: RuoYi | |||
name: RongXin | |||
# 版本 | |||
version: 3.9.0 | |||
# 版权年份 | |||
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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; | |||
} | |||
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -2,7 +2,7 @@ package com.ruoyi.common.enums; | |||
/** | |||
* 业务操作类型 | |||
* | |||
* | |||
* @author ruoyi | |||
*/ | |||
public enum BusinessType | |||
@@ -51,9 +51,14 @@ public enum BusinessType | |||
* 生成代码 | |||
*/ | |||
GENCODE, | |||
/** | |||
* 清空数据 | |||
*/ | |||
CLEAN, | |||
/** | |||
* 打印 | |||
*/ | |||
PRINT, | |||
} |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
@@ -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; | |||
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 "" ; | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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> |
@@ -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" /> | |||
@@ -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); | |||
@@ -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} | |||
@@ -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; | |||
@@ -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 | |||
@@ -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> |
@@ -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> |