Pre Merge pull request !43 from Jason/feature/export

This commit is contained in:
Jason 2021-10-27 16:00:30 +00:00 committed by Gitee
commit a98d6bc6e6
18 changed files with 471 additions and 7 deletions

View File

@ -99,6 +99,17 @@
<artifactId>spring-boot-admin-starter-server</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 -->
</dependency>
<!-- 导出 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-biz-export</artifactId>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>

View File

@ -1,22 +1,28 @@
package cn.iocoder.yudao.adminserver.modules.system.controller.notice;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.adminserver.modules.system.controller.notice.vo.SysNoticeCreateReqVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.notice.vo.SysNoticePageReqVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.notice.vo.SysNoticeRespVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.notice.vo.SysNoticeUpdateReqVO;
import cn.iocoder.yudao.adminserver.modules.system.convert.notice.SysNoticeConvert;
import cn.iocoder.yudao.adminserver.modules.system.service.notice.SysNoticeService;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -69,4 +75,26 @@ public class SysNoticeController {
return success(SysNoticeConvert.INSTANCE.convert(noticeService.getNotice(id)));
}
@GetMapping(value = "/exportPdf", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE )
@ApiOperation(value = "导出通知公告pdf")
public void exportPdf(@RequestParam("id") Long id, HttpServletResponse response) throws IOException {
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
String fileName = URLEncoder.encode("通知公告.pdf", StandardCharsets.UTF_8.name());
response.setHeader("Content-Disposition", String.format("attachment;filename=%s",fileName));
ServletOutputStream outputStream = response.getOutputStream();
noticeService.exportPdf(id, outputStream);
}
@GetMapping(value = "/exportWord", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@ApiOperation("导出通知公告word")
public void exportWord(@RequestParam("id") Long id, HttpServletResponse response) throws IOException {
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
String fileName = URLEncoder.encode("通知公告.docx", StandardCharsets.UTF_8.name());
response.setHeader("Content-Disposition", String.format("attachment;filename=%s",fileName));
ServletOutputStream outputStream = response.getOutputStream();
noticeService.exportWord(id, outputStream);
}
}

View File

@ -1,10 +1,12 @@
package cn.iocoder.yudao.adminserver.modules.system.service.notice;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.adminserver.modules.system.controller.notice.vo.SysNoticeCreateReqVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.notice.vo.SysNoticePageReqVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.notice.vo.SysNoticeUpdateReqVO;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.notice.SysNoticeDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import java.io.OutputStream;
/**
* 通知公告 Service 接口
@ -49,4 +51,19 @@ public interface SysNoticeService {
*/
SysNoticeDO getNotice(Long id);
/**
* 导出 通知公告 pdf
* @param id
* @param outputStream
*/
void exportPdf(Long id, OutputStream outputStream);
/**
* 导出 通知公告 word
* @param id
* @param outputStream
*/
void exportWord(Long id, OutputStream outputStream);
}

View File

@ -1,18 +1,24 @@
package cn.iocoder.yudao.adminserver.modules.system.service.notice.impl;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.adminserver.modules.system.controller.notice.vo.SysNoticeCreateReqVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.notice.vo.SysNoticePageReqVO;
import cn.iocoder.yudao.adminserver.modules.system.controller.notice.vo.SysNoticeUpdateReqVO;
import cn.iocoder.yudao.adminserver.modules.system.convert.notice.SysNoticeConvert;
import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.notice.SysNoticeMapper;
import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.notice.SysNoticeDO;
import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.notice.SysNoticeMapper;
import cn.iocoder.yudao.adminserver.modules.system.service.notice.SysNoticeService;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.export.service.TemplateExportPdfService;
import cn.iocoder.yudao.framework.export.service.TemplateExportWordService;
import com.google.common.annotations.VisibleForTesting;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Entities;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.OutputStream;
import static cn.iocoder.yudao.adminserver.modules.system.enums.SysErrorCodeConstants.NOTICE_NOT_FOUND;
@ -27,6 +33,12 @@ public class SysNoticeServiceImpl implements SysNoticeService {
@Resource
private SysNoticeMapper noticeMapper;
@Resource
private TemplateExportPdfService exportPdfService;
@Resource
private TemplateExportWordService exportWordService;
@Override
public Long createNotice(SysNoticeCreateReqVO reqVO) {
SysNoticeDO notice = SysNoticeConvert.INSTANCE.convert(reqVO);
@ -61,6 +73,33 @@ public class SysNoticeServiceImpl implements SysNoticeService {
return noticeMapper.selectById(id);
}
@Override
public void exportPdf(Long id, OutputStream outputStream) {
// 校验是否存在
checkNoticeExists(id);
final SysNoticeDO sysNoticeDO = noticeMapper.selectById(id);
final String content = sysNoticeDO.getContent();
Document doc = Jsoup.parse(content);
doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml).escapeMode(Entities.EscapeMode.xhtml);
//html 转化 xhtml
sysNoticeDO.setContent(doc.getElementsByTag("body").html());
exportPdfService.exportPdf(outputStream, sysNoticeDO, "notice.ftl");
}
@Override
public void exportWord(Long id, OutputStream outputStream) {
// 校验是否存在
checkNoticeExists(id);
final SysNoticeDO sysNoticeDO = noticeMapper.selectById(id);
final String content = sysNoticeDO.getContent();
Document doc = Jsoup.parse(content);
doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml).escapeMode(Entities.EscapeMode.xhtml);
//html 转化 xhtml
sysNoticeDO.setContent(doc.getElementsByTag("body").html());
exportWordService.exportWord(outputStream, sysNoticeDO,"notice.ftl");
}
@VisibleForTesting
public void checkNoticeExists(Long id) {
if (id == null) {

View File

@ -0,0 +1,19 @@
<html>
<head>
<title>Notice</title>
<style type="text/css">
@font-face {
font-family: 'msyh';
src: url(msyh.ttf);
}
* {
font-family: 'msyh', sans-serif;
}
</style>
</head>
<body>
<#-- Greet the user with his/her name -->
<div align="center" style="font-size:25px">${title}</div>
<p>${content}</p>
</body>
</html>

View File

@ -52,6 +52,11 @@
<aliyun-java-sdk-core.version>4.5.25</aliyun-java-sdk-core.version>
<aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version>
<yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version>
<!-- 导出相关 -->
<openhtmltopdf.version>1.0.8</openhtmltopdf.version>
<jsoup.version>1.13.1</jsoup.version>
<doc4j.import.xhtml.version>8.2.1</doc4j.import.xhtml.version>
</properties>
<dependencyManagement>
@ -92,7 +97,11 @@
<artifactId>yudao-spring-boot-starter-biz-sms</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-biz-export</artifactId>
<version>${revision}</version>
</dependency>
<!-- Spring 核心 -->
<dependency>
<!-- 用于生成自定义的 Spring @ConfigurationProperties 配置类的说明文件 -->
@ -421,6 +430,40 @@
<version>${aliyun-java-sdk-dysmsapi.version}</version>
</dependency>
<!-- SMS SDK end -->
<!-- export begin -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-pdfbox</artifactId>
<version>${openhtmltopdf.version}</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>${jsoup.version}</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-ImportXHTML</artifactId>
<version>${doc4j.import.xhtml.version}</version>
<exclusions>
<exclusion>
<artifactId>openhtmltopdf-pdfbox</artifactId>
<groupId>com.openhtmltopdf</groupId>
</exclusion>
<exclusion>
<artifactId>openhtmltopdf-core</artifactId>
<groupId>com.openhtmltopdf</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- export end -->
</dependencies>
</dependencyManagement>

View File

@ -28,6 +28,7 @@
<module>yudao-spring-boot-starter-biz-operatelog</module>
<module>yudao-spring-boot-starter-biz-dict</module>
<module>yudao-spring-boot-starter-biz-sms</module>
<module>yudao-spring-boot-biz-export</module>
</modules>
<artifactId>yudao-framework</artifactId>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>yudao-framework</artifactId>
<groupId>cn.iocoder.boot</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-spring-boot-biz-export</artifactId>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-pdfbox</artifactId>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-ImportXHTML</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,10 @@
package cn.iocoder.yudao.framework.export.service;
import java.io.OutputStream;
public interface TemplateExportPdfService {
void exportPdf(OutputStream outputStream, Object dataModel, String tplName);
}

View File

@ -0,0 +1,10 @@
package cn.iocoder.yudao.framework.export.service;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import java.io.OutputStream;
public interface TemplateExportWordService {
void exportWord(OutputStream outputStream, Object dataModel, String tplName);
}

View File

@ -0,0 +1,46 @@
package cn.iocoder.yudao.framework.export.service;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.export.service.exception.ExportException;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.io.OutputStream;
@Slf4j
public abstract class XhtmlExportPdfService implements TemplateExportPdfService {
private String baseDocumentUri;
public XhtmlExportPdfService(){
}
public XhtmlExportPdfService(String baseDocumentUri) {
this.baseDocumentUri = baseDocumentUri;
}
@Override
public void exportPdf(OutputStream outputStream, Object dataModel, String tplName){
try {
PdfRendererBuilder builder = new PdfRendererBuilder();
String html = getXHtmlContent(dataModel, tplName);
builder.withHtmlContent(html, baseDocumentUri);
builder.toStream(outputStream);
builder.run();
}catch (IOException | TemplateException ex){
log.error(" 导出 pdf 报错", ex);
throw new ExportException("export pdf error", ex);
}
}
public void setBaseDocumentUri(String baseDocumentUri) {
this.baseDocumentUri = baseDocumentUri;
}
protected abstract String getXHtmlContent(Object dataModel, String tplName) throws IOException, TemplateException;
}

View File

@ -0,0 +1,50 @@
package cn.iocoder.yudao.framework.export.service;
import cn.iocoder.yudao.framework.export.service.exception.ExportException;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;
import org.docx4j.convert.in.xhtml.XHTMLImporterImpl;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import java.io.IOException;
import java.io.OutputStream;
@Slf4j
public abstract class XhtmlExportWordService implements TemplateExportWordService {
private String baseDocumentUri;
public XhtmlExportWordService() {
}
public XhtmlExportWordService(String baseDocumentUri) {
this.baseDocumentUri = baseDocumentUri;
}
@Override
public void exportWord(OutputStream outputStream, Object dataModel, String tplName) {
try {
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();
String content = getXHtmlContent(dataModel, tplName);
XHTMLImporterImpl xHTMLImporter = new XHTMLImporterImpl(wordMLPackage);
wordMLPackage.getMainDocumentPart().getContent().addAll(xHTMLImporter.convert(content,baseDocumentUri));
wordMLPackage.save(outputStream);
} catch (Docx4JException | IOException | TemplateException ex) {
log.error(" 导出 word 报错", ex);
throw new ExportException("export word error", ex);
}
}
public void setBaseDocumentUri(String baseDocumentUri) {
this.baseDocumentUri = baseDocumentUri;
}
protected abstract String getXHtmlContent(Object dataModel, String tplName) throws IOException, TemplateException;
}

View File

@ -0,0 +1,63 @@
package cn.iocoder.yudao.framework.export.service.config;
import cn.iocoder.yudao.framework.export.service.TemplateExportPdfService;
import cn.iocoder.yudao.framework.export.service.TemplateExportWordService;
import cn.iocoder.yudao.framework.export.service.XhtmlExportPdfService;
import cn.iocoder.yudao.framework.export.service.XhtmlExportWordService;
import cn.iocoder.yudao.framework.export.service.freemarker.FreemarkerXhtmlExportPdfService;
import cn.iocoder.yudao.framework.export.service.freemarker.FreemarkerXhtmlExportWordService;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import org.docx4j.convert.in.xhtml.XHTMLImporterImpl;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({freemarker.template.Configuration.class, XhtmlExportPdfService.class, XhtmlExportWordService.class})
@AutoConfigureAfter({ FreeMarkerAutoConfiguration.class})
public class YudaoExportAutoConfiguration {
private final ApplicationContext applicationContext;
private final FreeMarkerProperties properties;
public YudaoExportAutoConfiguration(ApplicationContext applicationContext, FreeMarkerProperties properties) {
this.applicationContext = applicationContext;
this.properties = properties;
}
@Bean
@ConditionalOnClass({PdfRendererBuilder.class})
@ConditionalOnMissingBean({TemplateExportPdfService.class})
@ConditionalOnBean(freemarker.template.Configuration.class)
TemplateExportPdfService exportPdfService(freemarker.template.Configuration configuration) throws IOException {
final String[] templateLoaderPath = properties.getTemplateLoaderPath();
//get the first template loader path
String path = templateLoaderPath[0];
String baseDocumentUri = applicationContext.getResource(path).getURI().toString();
return new FreemarkerXhtmlExportPdfService(configuration,baseDocumentUri);
}
@Bean
@ConditionalOnClass({WordprocessingMLPackage.class, XHTMLImporterImpl.class})
@ConditionalOnMissingBean({TemplateExportWordService.class})
@ConditionalOnBean(freemarker.template.Configuration.class)
TemplateExportWordService exportWordService(freemarker.template.Configuration configuration) throws IOException {
final String[] templateLoaderPath = properties.getTemplateLoaderPath();
//get the first template loader path
String path = templateLoaderPath[0];
String baseDocumentUri = applicationContext.getResource(path).getURI().toString();
return new FreemarkerXhtmlExportWordService(configuration,baseDocumentUri);
}
}

View File

@ -0,0 +1,13 @@
package cn.iocoder.yudao.framework.export.service.exception;
public class ExportException extends RuntimeException {
public ExportException(String msg) {
super(msg);
}
public ExportException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@ -0,0 +1,41 @@
package cn.iocoder.yudao.framework.export.service.freemarker;
import cn.iocoder.yudao.framework.export.service.XhtmlExportPdfService;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
public class FreemarkerXhtmlExportPdfService extends XhtmlExportPdfService {
private final Configuration configuration;
public FreemarkerXhtmlExportPdfService(Configuration configuration) {
this.configuration = configuration;
}
public FreemarkerXhtmlExportPdfService(Configuration configuration,
String baseDocumentUri) {
super(baseDocumentUri);
this.configuration = configuration;
}
@Override
protected String getXHtmlContent(Object dataModel, String tplName) throws IOException, TemplateException {
Template template = configuration.getTemplate(tplName, StandardCharsets.UTF_8.name());
StringWriter stringWriter = new StringWriter(256);
template.process(dataModel, stringWriter);
return stringWriter.toString();
}
}

View File

@ -0,0 +1,39 @@
package cn.iocoder.yudao.framework.export.service.freemarker;
import cn.iocoder.yudao.framework.export.service.XhtmlExportWordService;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
public class FreemarkerXhtmlExportWordService extends XhtmlExportWordService {
private final Configuration configuration;
public FreemarkerXhtmlExportWordService(Configuration configuration, String baseDocumentUri) {
super(baseDocumentUri);
this.configuration = configuration;
}
public FreemarkerXhtmlExportWordService(Configuration configuration) {
this.configuration = configuration;
}
@Override
protected String getXHtmlContent(Object dataModel, String tplName) throws IOException, TemplateException {
Template template = configuration.getTemplate(tplName, StandardCharsets.UTF_8.name());
StringWriter stringWriter = new StringWriter(256);
template.process(dataModel, stringWriter);
return stringWriter.toString();
}
}

View File

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.yudao.framework.export.service.config.YudaoExportAutoConfiguration