springboot集成freemarker实现pdf的导出

我爱海鲸 2024-08-21 10:06:24 暂无标签

简介java、pdf、ftl、vue文件下载与上传

0、在项目上遇到一个需要导出pdf的需求,做一下记录。

1、项目截图:

2、相关代码:

TestController:

package xyz.haijin.controller;

import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import xyz.haijin.utils.FileUtil;
import xyz.haijin.utils.PDFUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;


/**
 * @author haijin
 * @date 2024/8/20 16:37
 * @describe 测试
 */
@RestController
@RequestMapping("/")
public class TestController {

    private static final Logger logger = LoggerFactory.getLogger(TestController.class);


    public static final String CONTENT_DISP = "Content-disposition";

    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public void test(HttpServletResponse response) {
        response.setContentType("application/octet-stream");
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setHeader(CONTENT_DISP, "test.pdf");
        try {
            exportApiPdf(response);
        } catch (Exception e) {
            logger.info("导出异常【{}】",e.getMessage(),e);
        }
    }

    /**
     * 导出pdf
     * @param httpServletResponse ignore
     */
    private void exportApiPdf(final HttpServletResponse httpServletResponse) throws Exception {
        OutputStream os = httpServletResponse.getOutputStream();
        // 创建一个FreeMarker实例, 负责管理FreeMarker模板的Configuration实例
        Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        StringTemplateLoader stringLoader = new StringTemplateLoader();

        // 获取数据
        Map<String, Object> dataMap = getMapData();

        // 格式化整体 api文档数据
        InputStream templateIs = this.getClass().getClassLoader().getResourceAsStream("assets/templates/template_test_document.ftl");
        String templateString  = FileUtil.readString(templateIs);
        stringLoader.putTemplate("template_test_document", templateString);
        cfg.setTemplateLoader(stringLoader);
        Template tpl = cfg.getTemplate("template_test_document", "utf-8");
        StringWriter writer = new StringWriter();
        // 将数据输出到html中
        tpl.process(dataMap, writer);
        String html = writer.toString().replace("/*","/ *");
        writer.flush();
        // 把html代码传入PDF中
        byte[] bytes = PDFUtils.createByString(html);
        os.write(bytes);
    }

    /**
     * 模拟获取数据
     * @return ignore
     */
    private Map<String,Object> getMapData(){
        Map<String,Object> data = new HashMap<>(3);
        data.put("testName","测试");
        data.put("testValue","显示的内容");
        data.put("testBool",true);
        return data;
    }
}

FileUtil:

package xyz.haijin.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.haijin.controller.TestController;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

/**
 * @author haijin
 * @description: 文件工具类
 * @date 2024/8/20 17:09
 */
public class FileUtil {

    private static final Logger log = LoggerFactory.getLogger(FileUtil.class);

    /**
     * 读取字符串
     * @param stream 输入流
     * @return ignore
     */
    public static String readString(InputStream stream) {
        StringBuilder sb = new StringBuilder();
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
            String s = null;
            while ((s = br.readLine()) != null) {
                sb.append(s);
            }
            br.close();
            return sb.toString();
        } catch (Exception e) {
            log.info(e.getMessage());
            return null;
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (Exception e) {
                    log.info(e.getMessage());
                }
            }
        }
    }

}

PDFUtils:

package xyz.haijin.utils;

import com.lowagie.text.*;
import com.lowagie.text.html.simpleparser.HTMLWorker;
import com.lowagie.text.html.simpleparser.StyleSheet;
import com.lowagie.text.pdf.*;
import com.lowagie.text.pdf.events.PdfPageEventForwarder;

import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @author haijin
 * @description: pdf工具类
 * @date 2024/8/20 17:13
 */
public class PDFUtils {

    /**
     * 通过字符串生成pdf文档
     * @param content 内容
     * @return ignore
     * @throws Exception 异常
     */
    public static byte[] createByString(String content) throws Exception {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            writeToOutPutStreamAsPdf(content, bos);
            return bos.toByteArray();
        }
    }

    /**
     * 创建pdf
     * @param htmlStr 字符串
     * @param os 字节流
     * @throws Exception 异常
     */
    private static void writeToOutPutStreamAsPdf(String htmlStr, ByteArrayOutputStream os) throws Exception {
        final StringReader reader = new StringReader(htmlStr);
        final StyleSheet styleSheet = new StyleSheet();
        final Map<String, Object> interfaceProps = new HashMap<>();

        final List<Element> elements = HTMLWorker.parseToList(reader, styleSheet, interfaceProps);

        //定义pdf文件尺寸,采用A4横切
        // 左、右、上、下间距
        Document document = new Document(PageSize.A4, 25, 25, 15, 40);
        //定义输出路径
        PdfWriter writer = PdfWriter.getInstance(document, os);
        PdfPageEventForwarder header = new PdfPageEventForwarder();
        writer.setPageEvent(header);
        writer.addViewerPreference(PdfName.PRINTSCALING, PdfName.NONE);
        document.open();
        // 字体
        BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
        Font fontChinese = new Font(bfChinese, 12, Font.NORMAL);
        // 边框
        Rectangle border = new Rectangle(0f, 0f);
        border.setBorderWidthLeft(1f);
        border.setBorderWidthBottom(1f);
        border.setBorderWidthRight(1f);
        border.setBorderWidthTop(1f);
        // 设置
        for (Element element : elements) {
            if (element instanceof Paragraph) {
                for (Element chunkElement : element.getChunks()) {
                    if (chunkElement != null) {
                        Chunk chunk = (Chunk) chunkElement;
                        chunk.setFont(fontChinese);
                    }
                }
            }
            if (element instanceof PdfPTable) {
                PdfPTable pdfTable = (PdfPTable) element;
                for (PdfPRow row : pdfTable.getRows()) {
                    if (row != null) {
                        for (PdfPCell cell : row.getCells()) {
                            if (cell != null) {
                                cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
                                cell.setHorizontalAlignment(Element.ALIGN_LEFT);
                                cell.cloneNonPositionParameters(border);
                                cell.setUseBorderPadding(true);
                                cell.setPadding(0f);
                                if (cell.getColumn() != null && cell.getCompositeElements() != null) {
                                    for (Object object : cell.getCompositeElements()) {
                                        if (object != null) {
                                            for (Element chunkElement : ((Element) object).getChunks()) {
                                                if (chunkElement != null) {
                                                    Chunk chunk = (Chunk) chunkElement;
                                                    chunk.setFont(fontChinese);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            document.add(element);
        }
        document.close();
    }

}

Application:

package xyz.haijin;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author haijin
 * @date 2024/8/20 16:37
 */
@SpringBootApplication(scanBasePackages = {"xyz.haijin"})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

template_test_document.ftl:

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <meta http-equiv="Content-Style-Type" content="text/css"/>
    <meta name="generator" content="Aspose.Words for .NET 15.1.0.0"/>
    <title></title></head>
<body>
<div><p style="margin:0pt; orphans:0; text-align:center; widows:0"><span style="font-family:楷体-简; font-size:16pt">[${testName}] 测试pdf</span>
<br/>
</p>
    <div style="text-align:center">
        <table cellspacing="0" cellpadding="0" style="border-collapse:collapse; margin:0 auto; width:546.55pt">
            <tr style="height:31.7pt">
                <td colspan="5"
                    style="border-bottom-color:#000000; border-bottom-style:solid; border-bottom-width:0.75pt; border-left-color:#000000; border-left-style:solid; border-left-width:0.75pt; border-right-color:#000000; border-right-style:solid; border-right-width:0.75pt; border-top-color:#000000; border-top-style:solid; border-top-width:0.75pt; padding-left:5.03pt; padding-right:5.03pt; vertical-align:middle; width:88.5pt">
                    <p style="margin:0pt; orphans:0; text-align:center; widows:0"><span
                            style="font-family:等线; font-size:10.5pt">test 名称: </span></p></td>
                <td colspan="7"
                    style="border-bottom-color:#000000; border-bottom-style:solid; border-bottom-width:0.75pt; border-left-color:#000000; border-left-style:solid; border-left-width:0.75pt; border-right-color:#000000; border-right-style:solid; border-right-width:0.75pt; border-top-color:#000000; border-top-style:solid; border-top-width:0.75pt; padding-left:5.03pt; padding-right:5.03pt; vertical-align:middle; width:435.7pt">
                    <p style="margin:0pt; orphans:0; text-align:left; margin-left: 3pt; widows:0"><span
                            style="font-family:等线; font-size:10.5pt">${testName?default('')} </span></p></td>
            </tr>

            <tr style="height:31.7pt">
                <td colspan="5"
                    style="border-bottom-color:#000000; border-bottom-style:solid; border-bottom-width:0.75pt; border-left-color:#000000; border-left-style:solid; border-left-width:0.75pt; border-right-color:#000000; border-right-style:solid; border-right-width:0.75pt; border-top-color:#000000; border-top-style:solid; border-top-width:0.75pt; padding-left:5.03pt; padding-right:5.03pt; vertical-align:middle; width:88.5pt">
                    <p style="margin:0pt; orphans:0; text-align:center; widows:0"><span
                            style="font-family:等线; font-size:10.5pt">描述: </span></p></td>
                <td colspan="7"
                    style="border-bottom-color:#000000; border-bottom-style:solid; border-bottom-width:0.75pt; border-left-color:#000000; border-left-style:solid; border-left-width:0.75pt; border-right-color:#000000; border-right-style:solid; border-right-width:0.75pt; border-top-color:#000000; border-top-style:solid; border-top-width:0.75pt; padding-left:5.03pt; padding-right:5.03pt; vertical-align:middle; width:435.7pt">
                    <p style="margin:0pt; orphans:0; text-align:left; margin-left: 3pt; widows:0"><span
                            style="font-family:等线; font-size:10.5pt">${testValue?default('')} </span></p></td>
            </tr>
            <tr style="height:31.7pt">
                <td colspan="5"
                    style="border-bottom-color:#000000; border-bottom-style:solid; border-bottom-width:0.75pt; border-left-color:#000000; border-left-style:solid; border-left-width:0.75pt; border-right-color:#000000; border-right-style:solid; border-right-width:0.75pt; border-top-color:#000000; border-top-style:solid; border-top-width:0.75pt; padding-left:5.03pt; padding-right:5.03pt; vertical-align:middle; width:88.5pt">
                    <p style="margin:0pt; orphans:0; text-align:center; widows:0"><span
                                style="font-family:等线; font-size:10.5pt">是否必须: </span></p></td>
                <td colspan="7"
                    style="border-bottom-color:#000000; border-bottom-style:solid; border-bottom-width:0.75pt; border-left-color:#000000; border-left-style:solid; border-left-width:0.75pt; border-right-color:#000000; border-right-style:solid; border-right-width:0.75pt; border-top-color:#000000; border-top-style:solid; border-top-width:0.75pt; padding-left:5.03pt; padding-right:5.03pt; vertical-align:middle; width:435.7pt">
                    <p style="margin:0pt; orphans:0; text-align:left; margin-left: 3pt; widows:0"><span
                                style="font-family:等线; font-size:10.5pt">${testBool?string('是', '否')} </span></p></td>
            </tr>
        </table>
    </div>
</div>
</body>
</html>

application.yml:

server:
  port: 8081 #提供者的端口

logback-spring.xml:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<include resource="org/springframework/boot/logging/logback/defaults.xml" />
	<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
	<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-logs}/}spring.log}"/>
	<springProfile name="!local">
		<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
			<encoder>
				<pattern>${FILE_LOG_PATTERN}</pattern>
			</encoder>
			<file>${LOG_FILE}</file>
			<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
				<!--日志文件输出的文件名 -->
				<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
				<maxHistory>30</maxHistory>
				<maxFileSize>100MB</maxFileSize>
				<totalSizeCap>5GB</totalSizeCap>
			</rollingPolicy>
		</appender>
		<root level="INFO">
			<appender-ref ref="FILE"/>
		</root>
	</springProfile>
	<springProfile name="local">
		<root level="INFO">
			<appender-ref ref="CONSOLE"/>
		</root>
	</springProfile>
</configuration>

pom.xml:

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>xyz.haijin</groupId>
    <artifactId>pdf</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <maven.compiler.compilerVersion>8</maven.compiler.compilerVersion>

        <spring-boot.version>2.6.7</spring-boot.version>
        <open-pdf.version>1.3.29</open-pdf.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>com.github.librepdf</groupId>
            <artifactId>openpdf</artifactId>
            <version>${open-pdf.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${spring-boot.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
            <version>${spring-boot.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.logging.log4j</groupId>
                    <artifactId>log4j-to-slf4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-pmd-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>appassembler-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3、导出的结果:

4、vue下载:

request.js:

import axios from 'axios'
import { Message } from 'element-ui'
import QS from 'qs'
import store from '@/store'
import user from '@/utils/services/user'
import { logout } from '@/api/common'
import { getQueryString } from '@/utils/index'

const axiosTest = axios.create({
  withCredentials: true,
  timeout: 120000,
})

axiosTest.interceptors.request.use(
  (config) => {
    config.headers.operationModule = encodeURI(store.state.operationModule)
    config.headers.logFlag = store.state.flag
    store.commit('SET_FLAG', 0)
    config.headers['Content-type'] = 'application/json; charset=utf-8'
    if (config.method === 'get')
      config.data = true

    if (getQueryString('access_token')) {
      config.headers.Authorization = `Bearer ${getQueryString('access_token')}`
    }

    return config
  },
  (error) => {
    console.log || console.log(JSON.stringify(error))
    return Promise.reject(error)
  },
)

axiosTest.interceptors.response.use(
  (response) => {
    // const reg = RegExp(/token已失效|token 已失效|token 已注销/)
    const reg = /token已失效|token 已失效|token 已注销/
    if (response.data.code === undefined && response.data !== 'success') {
      if (response.request.responseType !== 'blob') {
        const errorMsg = JSON.stringify(response.data)
        const message = '返回数据失败啦!!错误信息【{0}】'.format(
          errorMsg.length > 300 ? errorMsg.substr(0, 300) : errorMsg,
        )
        errorMessageHandle(message)
      }
    }
    else if (
      response.data.code !== '0'
			&& !response.config.url.includes('test/config/refresh')
    ) {
      if (
        !response.config.url.includes('/test')
				&& reg.test(response.data.message)
      ) {
        if (response.data.message.includes('token')) {
          window.localStorage.setItem('invalid_token', response.data.message)
          window.location.href = '/'
        }
        else {
          logout(getQueryString('access_token')).then(() => {
            goError(response.data.message, response.data.code)
          })
        }
      }

      if (response.data.code === '-1')
        return response

      // 去除接口超时提示
      if (!response.data.message.includes('Read timed out')) {
        errorMessageHandle(
          '返回数据失败啦!!错误码=【{0}】, 错误信息=【{1}】'.format(
            response.data.code,
            response.data.message,
          ),
        )
      }
      // return Promise.reject(new Error('error'));
      return Promise.reject(response.data.message || 'error')
    }
    return response
  },
  (error) => {
    const errorMsg = JSON.stringify(error.response.data || error.response || error)
    if (error.response.status === 401) {
      errorMessageHandle(
        '返回数据异常啦!!错误信息【{0}】'.format(
          errorMsg.length > 300 ? errorMsg.substr(0, 300) : errorMsg,
        ),
      )
      goError(escape(error.response.data.message), error.response.data.code)
    }
    else {
      errorMessageHandle(
        '返回数据异常啦!!错误信息【{0}】'.format(
          errorMsg.length > 300 ? errorMsg.substr(0, 300) : errorMsg,
        ),
      )
    }
    console.log || console.log(JSON.stringify(error))
    return Promise.reject(error)
  },
)

export function goError(message, code) {
  document.cookie = `message=${message}`
  document.cookie = `code=${code}`
  user.gotoLogin('error.html')
}

export function download(downloadUrl, data, filename = '下载数据', vue, type, flag) {
  if (type === null || type === undefined)
    type = 'application/vnd.ms-excel,charset=utf-8'

  if (flag === '123') {
    const url = store.state.service.apiUrlHost + downloadUrl
    return axios({
      method: 'post',
      url,
      data,
      headers: { Authorization: `Bearer ${getQueryString('access_token')}` },
      responseType: 'arraybuffer',
    }).then((res) => {
      let errorObject = null
      let isFaild = false
      const enc = new TextDecoder('utf-8')
      const errorMessage = enc.decode(new Uint8Array(res.data))
      if (errorMessage.includes('message')) {
        errorObject = JSON.parse(errorMessage)
        isFaild = true
      }
      if (isFaild) {
        errorMessageHandle(`下载失败[${errorObject.message}]`)
      }
      else {
        const blob = new Blob([res.data], { type })
        const link = document.createElement('a')
        link.href = window.URL.createObjectURL(blob)
        link.download = filename
        link.click()
      }
    })
  }
  else {
    const url = store.state.service.apiUrlHost + downloadUrl
    return axios({
      method: 'post',
      url,
      data,
      headers: { Authorization: `Bearer ${getQueryString('access_token')}` },
      responseType: 'arraybuffer',
    }).then((res) => {
      let errorObject = null
      const enc = new TextDecoder('utf-8')
      const errorMessage = enc.decode(new Uint8Array(res.data))
      const isFaild
				= Object.hasOwnProperty.call(res.data, 'message')
				|| Object.hasOwnProperty.call(res.data, 'msg')
      if (errorMessage.includes('message')) {
        errorObject = JSON.parse(errorMessage)
        errorMessageHandle(`下载失败[${errorObject.message}]`)
      }
      else if (isFaild) {
        errorMessageHandle(`下载失败[${res.data.message}]`)
      }
      else {
        const blob = new Blob([res.data], { type })
        const link = document.createElement('a')
        link.href = window.URL.createObjectURL(blob)
        link.download = filename
        link.click()
        if (vue !== null && vue !== undefined) {
          vue.exportLoading = false
          if (type === null || type === undefined)
            vue.exportName = '下载文档'
					 else
            vue.exportName = '导出'
        }
      }
    })
  }
}

// 导出为word
export function downloadWord(downloadUrl, data, filename = '下载数据', vue, type) {
  if (type === null || type === undefined)
    type = 'application/msword,charset=utf-8'

  const url = store.state.service.apiUrlHost + downloadUrl
  return axios({
    method: 'post',
    url,
    data,
    headers: { Authorization: `Bearer ${getQueryString('access_token')}` },
    responseType: 'arraybuffer',
  }).then((res) => {
    const blob = new Blob([res.data], { type })
    const link = document.createElement('a')
    link.href = window.URL.createObjectURL(blob)
    link.download = filename
    link.click()
    if (vue !== null && vue !== undefined) {
      vue.exportLoading = false
      if (type === null || type === undefined)
        vue.exportName = '下载文档'
			 else
        vue.exportName = '导出'
    }
  })
}

/**
 * 下载cert
 * @param {String} downloadUrl 下载链接
 * @param {Object} params 提交参数
 * @param {String} filename 保存的文件名,默认:文件.zip
 * @param {String} type MIME 类型,默认:application/zip
 * @returns Promise
 */
export function downloadCert(downloadUrl, params, filename = '文件', type) {
  const url = `${store.state.service.apiUrlHost}${downloadUrl}`

  if (!type)
    type = 'application/json,application/zip'

  return axios({
    method: 'GET',
    url,
    params,
    headers: { Authorization: `Bearer ${getQueryString('access_token')}` },
    responseType: 'arraybuffer',
  }).then((res) => {
    if (res.headers['content-type'])
      type = res.headers['content-type']

    if (res.headers['content-type'] === 'application/json')
      throw new Error('下载失败')

    const blob = new Blob([res.data], { type })
    const link = document.createElement('a')
    link.href = window.URL.createObjectURL(blob)
    link.download = filename
    link.click()
  })
}

export function uploadFile(content) {
  console.log('myUpload...')
  console.log(content)
  return axios({
    method: 'post',
    url: content.action,
    timeout: 20000,
    data: content.file,
  })
    .then(() => {
      content.onSuccess('文件上传成功')
    })
    .catch((error) => {
      if (error.response) {
        // The request was made and the server responded with a status code
        // that falls out of the range of 2xx
        content.onError(`文件上传失败(${error.response.status}),${error.response.data}`)
      }
      else if (error.request) {
        // The request was made but no response was received
        // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
        // http.ClientRequest in node.js
        content.onError('文件上传失败,服务器端无响应')
      }
      else {
        // Something happened in setting up the request that triggered an Error
        content.onError('文件上传失败,请求封装失败')
      }
    })
}

/**
 * 文件格式不确定的文件下载
 * @param {String} downloadUrl 下载链接
 * @param {Object} params 提交参数
 * @param {String} name 保存的文件名
 * @returns Promise
 */
export function downloadVariousDocuments(downloadUrl, method, params, name = '下载数据') {
  const url = `${store.state.service.apiUrlHost}${downloadUrl}`
  return axios({
    url,
    method,
    params,
    headers: { Authorization: `Bearer ${getQueryString('access_token')}` },
    responseType: 'blob',
  })
    .then((res) => {
      if (res.data.type === 'application/json') {
        // 判断数据来源是Blob类型还是json类型
        // 如果是json类型,使用FileReader来获取里面的信息
        // FileReader 一般处理 文件流信息
        const reader = new FileReader()
        reader.onload = (event) => {
          const result = JSON.parse(event.target.result)
          if (!result.success)
            Message.error(result.message || '下载失败')
        }
        reader.readAsText(res.data)
      }
      else {
        // 数据来源是blob 直接下载
        const filename = name === '文件样例' ? res.headers['content-disposition'].split(';')[1].split('=')[1] : `${name}.${res.headers['content-disposition'].split(';')[1].split('.')[1]}`
        const blob = new Blob([res.data])
        const Temp = document.createElement('a')
        Temp.href = window.URL.createObjectURL(blob)
        Temp.download = filename
        document.body.appendChild(Temp)
        Temp.click()
        document.body.removeChild(Temp)
        window.URL.revokeObjectURL(Temp)
      }
    })
    .catch(() => {
      Message.error('接口返回失败')
    })
}

function errorMessageHandle(message) {
  messageHandle(message, 'error')
}

function messageHandle(message, type) {
  Message({
    message,
    type,
    duration: 3000,
    customClass: 'message-override',
  })
}

export function qsStringify(data) {
  return QS.stringify(data)
}

export default axiosTest

5、ftl语法大全:

<!DOCTYPE html> 
<html lang="en">
<head>
    <title>Freemarker 语法大全</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <style>
        html {
            font-size: 14px;
            font-weight: 400;
        }
        .exp {
            font-size: 12px;
            color: lightgray;
        }
    </style>
</head>
<body>
<p>当前时间:${.now?string("yyyy-MM-dd HH:mm:ss.sss")}</p>
<dl>
    <dt>list长度:<span class="exp">${list?size}</span></dt>
    <dt>列表</dt>
        <#list list as item>
        	<dd>${item }, 索引:${item_index },hasNext:${item_has_next}</dd>
        </#list>

    <dt>数字遍历</dt>
        <#list 1..3 as item>
            <dd>数字${item}</dd>
        </#list>

    <dt>map</dt>
        <#list map?keys as key>
            <dd>${map[key]}, 索引:${key_index },hasNext:${key_has_next}</dd>
        </#list>
</dl>
<dl>
    <dt>字符串</dt>
    <dd>普通字符串:<span class="exp">${name}</span></dd>
    <dd>非html编码:<span class="exp">${htmlText}</span></dd>
    <dd>html编码:<span class="exp">${htmlText?html}</span></dd>
    <dd>首字母大写:<span class="exp">${name?cap_first}</span></dd>
    <dd>首字母小写:<span class="exp">${name?uncap_first}</span></dd>
    <dd>全小写:<span class="exp">${name?lower_case}</span></dd>
    <dd>全大写:<span class="exp">${name?upper_case}</span></dd>
    <dd>去除首位空格:<span class="exp">${name?trim}</span></dd>
    <dd>空字符串:<span class="exp">${null?if_exists}</span></dd>
    <dd>是否包含某个字符串:<span class="exp">${name?contains("wWw")?string}</span></dd>
    <dd>默认值:<span class="exp">${null?default("空值默认")}</span></dd>
    <dd>“${name}”字符串长度:<span class="exp">${name?length}</span></dd>
    <dd>定义字符串:<span class="exp">str=码一码<#assign str="码一码"/></span></dd>
    <dd>字符串拼接(1):<span class="exp">${"字符串拼接 + " + str}</span></dd>
    <dd>字符串拼接(2):<span class="exp">${"字符串拼接 + ${str}"}</span></dd>
    <dd>字符串截取单个字符(1):<span class="exp">${str[1]}</span></dd>
    <dd>字符串截取(2):<span class="exp">${str?substring(1)}</span></dd>
    <dd>字符串截取(3):<span class="exp">${str?substring(1,2)}</span></dd>
    <dd>indexOf:<span class="exp">${str?index_of("一")}</span></dd>
    <dd>split分割字符串:<span class="exp">
    <#list "a|b|c"?split("|") as item>
        ${item}
    </#list>
    </span></dd>
    <dd>if...elseif...else:<span class="exp">
			<#if null == ''>
				匹配if显示
            <#elseif null == '1'>
				匹配elseif显示
            <#else>
				匹配else显示
            </#if></span>
    </dd>
</dl>

<dl>
    <dt>switch</dt>
    <dd>
        <#switch str>
            <#case "你好">
                匹配“你好”
                <#break >
            <#case "码一码">
                匹配“码一码”
                <#break >
            <#default>
                默认匹配
        </#switch>
    </dd>
</dl>

<dl>
    <dt>数字</dt>
    <dd>普通数字:<span class="exp">${num}</span></dd>
    <dd>数字类型:<span class="exp">${num?string.number}</span></dd>
    <dd>货币类型:<span class="exp">${num?string.currency}</span></dd>
    <dd>百分比类型:<span class="exp">${num?string.percent}</span></dd>
    <dd>格式化数字:<span class="exp">${num?string("#.###")}</span></dd>
    <dd>取数字的整数部分:<span class="exp">${num?int}</span></dd>
</dl>

<dl>
    <dt>运算符</dt>
    <dd>不等于:!= <span class="exp">例如:${(1 != 2)?string('1 != 2', '1 == 2')}</span></dd>
    <dd>等于:== <span class="exp">例如:${(1 == 1)?string('1 == 1', '1 != 1')}</span></dd>
    <dd>大于(1):> <span
            class="exp">例如:${(2 > 1)?string('2 > 1', '2 < 1')}。<strong>注:使用> 时必须加括号,否则可能会被当成普通的标签闭合符号而引起报错</strong></span>
    </dd>
    <dd>大于(2):gt <span class="exp">例如:${(2 gt 1)?string('2 gt 1', '2 lte 1')}</span></dd>
    <dd>大于等于:gte <span class="exp">例如:${(2 gte 2)?string('2 gte 2', '2 lt 2')}</span></dd>
    <dd>小于(1):< <span
            class="exp">例如:${(1 < 2)?string('1 < 2', '1 > 2')}。<strong>注:使用< 时必须加括号,否则可能会被当成普通的标签闭合符号而引起报错</strong></span>
    </dd>
    <dd>小于(2):lt <span class="exp">例如:${(1 lt 2)?string('1 lt 2', '1 gte 2')}</span></dd>
    <dd>小于等于:lte <span class="exp">例如:${(2 lte 2)?string('2 lte 2', '2 gt 2')}</span></dd>
</dl>

<dl>
    <dt>boolean</dt>
    <dd>普通boolean输出:<span class="exp">${bol}</span></dd>
    <dd>boolean判断输出:<span class="exp">${bol?string('true的时候显示','false的时候显示')}</span></dd>
</dl>

<dl>
    <dt>日期</dt>
    <dd>${dateObj?date}</dd>
    <dd>${dateObj?time}</dd>
    <dd>${dateObj?string("yyyy-MM-dd HH:mm:ss.SSS")}</dd>
</dl>

<dl>
    <dt>import</dt>
    <dd>
        <#import "import.ftl" as importObj>
        <p>${importObj.importStr}</p>
        <p>${importObj.importStr1}</p>
    </dd>
</dl>

<dl>
    <dt>macro宏模板</dt>
    <dd>
        <#macro listMacro title items>
            <p>${title?cap_first}:
            <ul>
               <#list items as item>
                   <li>${item?cap_first}</li>
               </#list>
            </ul>
            <#nested >
        </#macro>
    </dd>
    <dd>
        <@listMacro items=["item1", "item2", "item3"] title="Items">
            nested标签表示可以插入自定义的内容
        </@listMacro>
    </dd>
</dl>


 include 
<#include "eclipse.ftl">
</body>
</html>

你好:我的2025

上一篇:sftp相关

下一篇:前端相关框架