Excel模板填充:从minio上获取模板使用easyExcel填充

news2025/1/19 20:35:49

最近工作中有个excel导出的功能,要求导出的模板和客户提供的模板一致,而客户提供的模板有着复杂的表头和独特列表风格,像以往使用poi去画是非常耗时间的,比如需要考虑字体大小,单元格合并,单元格的格式等问题;

所有给大家推荐使用easyExcel填充,我们只需要把客户的模板进行简单处理,上传到文件服务器,需要导出的的时候,就从文件服务器拿模板进行填充返回前端,这样非常便捷和效率高。

easyExcel官方文档:填充Excel | Easy Excel

 一、流程展示

我准备了一个简单的模板进行测试

1.1 模板准备

1.2 模板文件上传到minio

1.3 填充后的文件

 

 二、关键代码

1.1 引入依赖minio、easyExcel

            <dependency>
                <groupId>io.minio</groupId>
                <artifactId>minio</artifactId>
                <version>7.1.0</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>easyexcel</artifactId>
                <version>3.1.0</version>
            </dependency>

 1.2 minio配置及工具类准备

 1.2.1 minio yml文件配置
minio:
  endpoint: http://xxx.xxx.xxx.xxx:xxx
  accesskey: xxx
  secretKey: xxx
  bucketName: xxx
  expires: 3
 1.2.2 minio 配置类
package com.ruoyi.common.config;


import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MinioUtils;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

/**
 * @author qujingye
 */
@Data
@Configuration
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {

    /**
     * 地址
     */
    private String endpoint;

    /**
     * 桶的名字
     */
    private static String bucketName;

    /**
     * 访问key
     */
    private String accessKey;

    /**
     * 秘钥key
     */
    private String secretKey;

    /**
     * 临时文件的过期时间(天)
     */
    private static Integer expires;

    @Bean
    public MinioUtils creatMinioClient() {
        return new MinioUtils(endpoint, bucketName, accessKey, secretKey);
    }

    /**
     * 临时文件的过期时间
     *
     * @param expires Xml内容
     */
    public void setExpires(Integer expires) {
        MinioConfig.expires = DateUtils.getDaySecond(expires);
    }

    public static Integer getExpires() {
        return expires;
    }

    public static String getBucketName() {
        return bucketName;
    }

    public void setBucketName(String bucketName) {
        MinioConfig.bucketName = bucketName;
    }
}
 1.2.3 minio 工具类 
package com.ruoyi.common.utils;


import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.exception.file.FileNameLengthLimitExceededException;
import com.ruoyi.common.exception.file.FileSizeLimitExceededException;
import com.ruoyi.common.exception.file.InvalidExtensionException;
import com.ruoyi.common.utils.file.MimeTypeUtils;
import com.ruoyi.common.utils.uuid.Seq;
import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.ZonedDateTime;
import java.util.*;

/**
 * Minio工具类
 *
 * @author qujingye
 */
public class MinioUtils {

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

    private static MinioClient minioClient;

    private static String endpoint;
    private static String bucketName;
    private static String accessKey;
    private static String secretKey;

    private static final String SEPARATOR = "/";

    /**
     * 默认大小 200M
     */
    public static final long DEFAULT_MAX_SIZE = 200 * 1024 * 1024;

    /**
     * 默认的文件名最大长度 100
     */
    public static final int DEFAULT_FILE_NAME_LENGTH = 100;


    private MinioUtils() {
    }

    public MinioUtils(String endpoint, String bucketName, String accessKey, String secretKey) {
        MinioUtils.endpoint = endpoint;
        MinioUtils.bucketName = bucketName;
        MinioUtils.accessKey = accessKey;
        MinioUtils.secretKey = secretKey;
        createMinioClient();
    }

    /**
     * 创建minioClient
     */
    public void createMinioClient() {
        try {
            if (null == minioClient) {
                log.info("minioClient create start");
                minioClient = MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey)
                        .build();
                createBucket();
                log.info("minioClient create end");
            }
        } catch (Exception e) {
            log.error("连接MinIO服务器异常:{}", e);
        }
    }

    /**
     * 获取上传文件的基础路径
     *
     * @return url
     */
    public static String getBasisUrl() {
        return endpoint + SEPARATOR + bucketName + SEPARATOR;
    }

    /**
     * 初始化Bucket
     *
     * @throws Exception 异常
     */
    private static void createBucket()
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException, RegionConflictException {
        if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 验证bucketName是否存在
     *
     * @return boolean true:存在
     */
    public static boolean bucketExists()
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 创建bucket
     *
     * @param bucketName bucket名称
     */
    public static void createBucket(String bucketName)
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException, RegionConflictException {
        if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 获取存储桶策略
     *
     * @param bucketName 存储桶名称
     * @return json
     */
    private JSONObject getBucketPolicy(String bucketName)
            throws IOException, InvalidKeyException, InvalidResponseException, BucketPolicyTooLargeException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, InsufficientDataException, ErrorResponseException {
        String bucketPolicy = minioClient
                .getBucketPolicy(GetBucketPolicyArgs.builder().bucket(bucketName).build());
        return JSONObject.parseObject(bucketPolicy);
    }

    /**
     * 获取全部bucket
     * <p>
     * https://docs.minio.io/cn/java-client-api-reference.html#listBuckets
     */
    public static List<Bucket> getAllBuckets()
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        return minioClient.listBuckets();
    }

    /**
     * 根据bucketName获取信息
     *
     * @param bucketName bucket名称
     */
    public static Optional<Bucket> getBucket(String bucketName)
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        return minioClient.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
    }

    /**
     * 根据bucketName删除信息
     *
     * @param bucketName bucket名称
     */
    public static void removeBucket(String bucketName)
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * 判断文件是否存在
     *
     * @param bucketName 存储桶
     * @param objectName 对象
     * @return true:存在
     */
    public static boolean doesObjectExist(String bucketName, String objectName) {
        boolean exist = true;
        try {
            minioClient
                    .statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
        } catch (Exception e) {
            exist = false;
        }
        return exist;
    }

    /**
     * 判断文件夹是否存在
     *
     * @param bucketName 存储桶
     * @param objectName 文件夹名称(去掉/)
     * @return true:存在
     */
    public static boolean doesFolderExist(String bucketName, String objectName) {
        boolean exist = false;
        try {
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());
            for (Result<Item> result : results) {
                Item item = result.get();
                if (item.isDir() && objectName.equals(item.objectName())) {
                    exist = true;
                }
            }
        } catch (Exception e) {
            exist = false;
        }
        return exist;
    }

    /**
     * 根据文件前置查询文件
     *
     * @param bucketName bucket名称
     * @param prefix     前缀
     * @param recursive  是否递归查询
     * @return MinioItem 列表
     */
    public static List<Item> getAllObjectsByPrefix(String bucketName, String prefix,
                                                   boolean recursive)
            throws ErrorResponseException, InsufficientDataException, InternalException, InvalidBucketNameException, InvalidKeyException, InvalidResponseException,
            IOException, NoSuchAlgorithmException, ServerException, XmlParserException {
        List<Item> list = new ArrayList<>();
        Iterable<Result<Item>> objectsIterator = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
        if (objectsIterator != null) {
            for (Result<Item> o : objectsIterator) {
                Item item = o.get();
                list.add(item);
            }
        }
        return list;
    }

    /**
     * 获取文件流
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @return 二进制流
     */
    public static InputStream getObject(String bucketName, String objectName)
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        return minioClient
                .getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 断点下载
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @param offset     起始字节的位置
     * @param length     要读取的长度
     * @return 流
     */
    public InputStream getObject(String bucketName, String objectName, long offset, long length)
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        return minioClient.getObject(
                GetObjectArgs.builder().bucket(bucketName).object(objectName).offset(offset).length(length)
                        .build());
    }

    /**
     * 获取路径下文件列表
     *
     * @param bucketName bucket名称
     * @param prefix     文件名称
     * @param recursive  是否递归查找,如果是false,就模拟文件夹结构查找
     * @return 二进制流
     */
    public static Iterable<Result<Item>> listObjects(String bucketName, String prefix,
                                                     boolean recursive) {
        return minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
    }

    /**
     * 通过MultipartFile,上传文件
     *
     * @param bucketName 存储桶
     * @param file       文件
     * @param objectName 对象名
     */
    public static ObjectWriteResponse putObject(String bucketName, MultipartFile file,
                                                String objectName, String contentType)
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        InputStream inputStream = file.getInputStream();


        return minioClient.putObject(
                PutObjectArgs.builder().bucket(bucketName).object(objectName).contentType(contentType)
                        .stream(
                                inputStream, inputStream.available(), -1)
                        .build());
    }

    /**
     * 上传本地文件
     *
     * @param bucketName 存储桶
     * @param objectName 对象名称
     * @param fileName   本地文件路径
     */
    public static ObjectWriteResponse putObject(String bucketName, String objectName,
                                                String fileName)
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        return minioClient.uploadObject(
                UploadObjectArgs.builder()
                        .bucket(bucketName).object(objectName).filename(fileName).build());
    }

    /**
     * 通过流上传文件
     *
     * @param bucketName  存储桶
     * @param objectName  文件对象
     * @param inputStream 文件流
     */
    public static ObjectWriteResponse putObject(String bucketName, String objectName,
                                                InputStream inputStream)
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        return minioClient.putObject(
                PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(
                        inputStream, inputStream.available(), -1)
                        .build());
    }

    /**
     * 创建文件夹或目录
     *
     * @param bucketName 存储桶
     * @param objectName 目录路径
     */
    public static ObjectWriteResponse putDirObject(String bucketName, String objectName)
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        return minioClient.putObject(
                PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(
                        new ByteArrayInputStream(new byte[]{}), 0, -1)
                        .build());
    }

    /**
     * 获取文件信息, 如果抛出异常则说明文件不存在
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     */
    public static ObjectStat statObject(String bucketName, String objectName)
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        return minioClient
                .statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 拷贝文件
     *
     * @param bucketName    bucket名称
     * @param objectName    文件名称
     * @param srcBucketName 目标bucket名称
     * @param srcObjectName 目标文件名称
     */
    public static ObjectWriteResponse copyObject(String bucketName, String objectName,
                                                 String srcBucketName, String srcObjectName)
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        return minioClient.copyObject(
                CopyObjectArgs.builder()
                        .source(CopySource.builder().bucket(bucketName).object(objectName).build())
                        .bucket(srcBucketName)
                        .object(srcObjectName)
                        .build());
    }

    /**
     * 删除文件
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     */
    public static void removeObject(String bucketName, String objectName)
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        minioClient
                .removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 批量删除文件
     *
     * @param bucketName bucket
     * @param keys       需要删除的文件列表
     * @return
     */
  /*public static Iterable<Result<DeleteError>> removeObjects(String bucketName, List<String> keys) {
    List<DeleteObject> objects = new LinkedList<>();
    keys.forEach(s -> {
      objects.add(new DeleteObject(s));
    });
    return minioClient.removeObjects(
        RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build());
  }*/
    public static void removeObjects(String bucketName, List<String> keys) {
        List<DeleteObject> objects = new LinkedList<>();
        keys.forEach(s -> {
            objects.add(new DeleteObject(s));
            try {
                removeObject(bucketName, s);
            } catch (Exception e) {
                log.error("批量删除失败!error:{}", e);
            }
        });
    }

    /**
     * 获取文件外链
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @param expires    过期时间 <=7 秒级
     * @return url
     */
    public static String getPresignedObjectUrl(String bucketName, String objectName,
                                               Integer expires)
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, InvalidExpiresRangeException, ServerException, InternalException, NoSuchAlgorithmException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        return minioClient.presignedGetObject(bucketName, objectName, expires);
    }

    /**
     * 给presigned URL设置策略
     *
     * @param bucketName 存储桶
     * @param objectName 对象名
     * @param expires    过期策略
     * @return map
     */
    public static Map<String, String> presignedGetObject(String bucketName, String objectName,
                                                         Integer expires)
            throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, InvalidExpiresRangeException, ServerException, InternalException, NoSuchAlgorithmException, XmlParserException, InvalidBucketNameException, ErrorResponseException {
        PostPolicy policy = new PostPolicy(bucketName, objectName,
                ZonedDateTime.now().plusDays(7));
        policy.setContentType("image/png");
        return minioClient.presignedPostPolicy(policy);
    }

    /**
     * 获得上传的URL
     *
     * @param bucketName 存储桶
     * @param objectName 对象名
     * @param expires    过期策略(秒)
     * @return
     */
    public static String presignedPutObject(String bucketName, String objectName, Integer expires) throws ServerException, InvalidBucketNameException, InsufficientDataException, ErrorResponseException, InvalidExpiresRangeException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        if (expires == null) {
            // 7天
            return minioClient.presignedPutObject(bucketName, objectName);
        }
        return minioClient.presignedPutObject(bucketName, objectName, expires);
    }


    /**
     * 将URLDecoder编码转成UTF8
     *
     * @param str
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException {
        String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
        return URLDecoder.decode(url, "UTF-8");
    }

    /**
     * 获取上传地址并做文件校验
     *
     * @param file allowedExtension
     * @return String
     */
    public static Map<String, Object> getUploadPath(MultipartFile file, String basePath, String[] allowedExtension)
            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
            InvalidExtensionException {
        int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
        if (fileNamelength > MinioUtils.DEFAULT_FILE_NAME_LENGTH) {
            throw new FileNameLengthLimitExceededException(MinioUtils.DEFAULT_FILE_NAME_LENGTH);
        }
        assertAllowed(file, allowedExtension);
        return extractFilename(file, basePath);
    }

    /**
     * 文件大小校验
     *
     * @param file 上传的文件
     * @return
     * @throws FileSizeLimitExceededException 如果超出最大大小
     * @throws InvalidExtensionException
     */
    public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
            throws FileSizeLimitExceededException, InvalidExtensionException
    {
        long size = file.getSize();
        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)
            {
                throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
                        fileName);
            }
            else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
            {
                throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
                        fileName);
            }
            else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
            {
                throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
                        fileName);
            }
            else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION)
            {
                throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
                        fileName);
            }
            else
            {
                throw new InvalidExtensionException(allowedExtension, extension, fileName);
            }
        }
    }

    /**
     * 判断MIME类型是否是允许的MIME类型
     *
     * @param extension
     * @param allowedExtension
     * @return
     */
    public static final boolean isAllowedExtension(String extension, String[] allowedExtension)
    {
        for (String str : allowedExtension)
        {
            if (str.equalsIgnoreCase(extension))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 编码文件名
     */
    public static final Map<String, Object> extractFilename(MultipartFile file, String basePath)
    {
        Map<String, Object> map=new HashMap<>();
        map.put("uploadPath",StringUtils.format("{}/{}/{}.{}",basePath, DateUtils.datePathMonth(), Seq.getId(Seq.uploadSeqType), getExtension(file)));
        map.put("newFileName",StringUtils.format("{}.{}", Seq.getId(Seq.uploadSeqType), getExtension(file)));
        return map;
    }

    public static final Map<String, Object> extractFilenameText(String fileName, String basePath)
    {
        Map<String, Object> map=new HashMap<>();
        map.put("uploadPath",StringUtils.format("{}/{}/{}.{}",basePath, DateUtils.datePathMonth(), Seq.getId(Seq.uploadSeqType), fileName));
        map.put("newFileName",StringUtils.format("{}.{}", Seq.getId(Seq.uploadSeqType),fileName));
        return map;
    }

    /**
     * 获取文件名的后缀
     *
     * @param file 表单文件
     * @return 后缀名
     */
    public static final String getExtension(MultipartFile file)
    {
        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
        if (StringUtils.isEmpty(extension))
        {
            extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
        }
        return extension;
    }


}

 1.3 模板填充导出实现接口

 1.3.1 controller
package com.ruoyi.web.controller.common;

import com.ruoyi.system.service.ExcelTemplateDemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;

/**
 * @author qujingye
 * @Classname ExcelTemplateDemoController
 * @Description 测试 从minio获取文件模板-easyExcel模板填充
 * @Date 2023/12/31 12:37
 */
@RestController
@RequestMapping("/excelTemplateDemo")
public class ExcelTemplateDemoController {

    @Autowired
    private ExcelTemplateDemoService excelTemplateDemoService;

    @GetMapping("/test")
    public void exportChangeDetail(String resource, HttpServletResponse response) {
        excelTemplateDemoService.excelTemplateDemoImport(response);
    }

}
 1.3.2 service
package com.ruoyi.system.service;

import javax.servlet.http.HttpServletResponse;

/**
 * @author qujingye
 * @title ExcelTemplateDemoService
 * @date 2023/12/31 12:40
 * @description TODO
 */
public interface ExcelTemplateDemoService {
    void excelTemplateDemoImport(HttpServletResponse response);
}
package com.ruoyi.system.service.impl;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.ruoyi.common.config.MinioConfig;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MinioUtils;
import com.ruoyi.system.domain.ProductInfo;
import com.ruoyi.system.service.ExcelTemplateDemoService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * @author qujingye
 * @Classname ExcelTemplateDemoServiceImpl
 * @Description TODO
 * @Date 2023/12/31 12:42
 */
@Service
public class ExcelTemplateDemoServiceImpl implements ExcelTemplateDemoService {

    public static final String EXCEL_TEMPLATE_PATH = "/exportExcelTemplate/商品出库单.xlsx";

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

    /**
     * 从minio获取模板进行填充
     *
     * @param response
     * @return void
     */
    @Override
    public void excelTemplateDemoImport(HttpServletResponse response) {
        //输入流
        InputStream inputStream = null;
        ServletOutputStream outputStream = null;
        ExcelWriter excelWriter = null;
        List<ProductInfo> result = new ArrayList<>();
        //组装测试商品列表数据 编号 名称及规格 单位	数量 单价 金额 备注
        getProductInfoTestData(result);
        //组装测试其他数据
        HashMap<String, Object> totalMap = new HashMap<>();
        getOtheroTestData(totalMap);
        try {
            inputStream = MinioUtils.getObject(MinioConfig.getBucketName(), EXCEL_TEMPLATE_PATH);
            //输出流
            // 获取文件名并转码
//            String name = URLEncoder.encode("商品出库单.xlsx", "UTF-8");
//            response.setHeader("Content-Disposition", "attachment;filename=" + name);
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            outputStream = response.getOutputStream();
            // 创建填充配置
            FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
            // 创建写对象
            excelWriter = EasyExcel.write(outputStream)
                    .withTemplate(inputStream).build();
            // 创建Sheet对象
            WriteSheet sheet = EasyExcel.writerSheet(0).build();
            sheet.setSheetName("商品出库单");
            // 多组填充excel
            excelWriter.fill(totalMap, sheet);
            excelWriter.fill(result, fillConfig, sheet);
        } catch (Exception e) {
            log.info("导出失败={}", e.getMessage());
        } finally {
            if (excelWriter != null) {
                excelWriter.finish();
            }
            //关闭流
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void getOtheroTestData(HashMap<String, Object> totalMap) {
        //客户名称
        totalMap.put("name","小张");
        //客户编码
        totalMap.put("code","001XP");
        //日期
        totalMap.put("date", DateUtils.getChineseDate());
        //总数量
        totalMap.put("sumNums",20);
        //合计:
        totalMap.put("sumMoney",2000);
        //主管:
        totalMap.put("governor","王主管");
        //财务:
        totalMap.put("finance","李财务");
        //保管员
        totalMap.put("guardian","王保管员");
        //保管员
        totalMap.put("HandlerName","刘经手人");
    }


    private void getProductInfoTestData(List<ProductInfo> result) {
        result.add(new ProductInfo("001", "商品1", "单位1", 2, BigDecimal.valueOf(100), BigDecimal.valueOf(200), "无"));
        result.add(new ProductInfo("002", "商品2", "单位1", 2, BigDecimal.valueOf(100), BigDecimal.valueOf(200), "无"));
        result.add(new ProductInfo("003", "商品3", "单位1", 2, BigDecimal.valueOf(100), BigDecimal.valueOf(200), "无"));
        result.add(new ProductInfo("004", "商品4", "单位1", 2, BigDecimal.valueOf(100), BigDecimal.valueOf(200), "无"));
        result.add(new ProductInfo("005", "商品5", "单位1", 2, BigDecimal.valueOf(100), BigDecimal.valueOf(200), "无"));
        result.add(new ProductInfo("006", "商品6", "单位1", 2, BigDecimal.valueOf(100), BigDecimal.valueOf(200), "无"));
        result.add(new ProductInfo("007", "商品7", "单位1", 2, BigDecimal.valueOf(100), BigDecimal.valueOf(200), "无"));
        result.add(new ProductInfo("008", "商品8", "单位1", 2, BigDecimal.valueOf(100), BigDecimal.valueOf(200), "无"));
        result.add(new ProductInfo("009", "商品9", "单位1", 2, BigDecimal.valueOf(100), BigDecimal.valueOf(200), "无"));
        result.add(new ProductInfo("010", "商品10", "单位1", 2, BigDecimal.valueOf(100), BigDecimal.valueOf(200), "无"));
    }
}

📝结束语:

总结不易😄觉得有用的话,还请各位大佬动动发财的小手
给小瞿扣个三连:⛳️ 点赞⭐️收藏 ❤️ 关注
这个对我真的非常重要,拜托啦!
本人水平有限,如有纰漏,欢迎各位大佬评论指正!💞

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1348138.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows客户端操作系统的历史版本简介

文章目录 Windows操作系统的历史版本从windows 10开始&#xff0c;版本有些不一样的变化windows 10有哪些版本Windows 10终止服务的版本Windows 10当前服务的版本Windows 10开始的变化Windows 11有哪些版本 Windows 11有哪些用户反馈的缺点推荐阅读 从Windows 1.0到最新的Windo…

bilibili深入理解计算机系统笔记(3):使用C语言实现静态链接器

本文是2022年的项目笔记&#xff0c;2024年1月1日整理文件的时候发现之&#xff0c;还是决定发布出来。 Github链接&#xff1a;https://github.com/shizhengLi/csapp_bilibili 文章目录 可执行链接文件(ELF)ELF headerSection header符号表symtab二进制数如何和symtab结构成员…

DevOps系列之 JNI实现Java调用C的实现案例

JNI&#xff08;Java Native Interface&#xff09;允许Java代码与其他语言编写的代码进行交互。以下是一个简单的JNI示例&#xff0c;演示如何使用JNI在Java中调用C/C函数。 最终的目录结构如下&#xff1a; JNI&#xff08;Java Native Interface&#xff09;允许Java代码与其…

python中的变量

最近学习了一套课程&#xff0c;体系比较完善&#xff0c;写一下读书笔记&#xff0c;方便后续的复习。 课程涉及的面比较广&#xff0c;包括python的基础、后端框架django、flask&#xff1b;前端开发&#xff0c;html、css、JavaScript、vue、reac&#xff1b;数据库&#x…

yolov5旋转目标检测-遥感图像检测-无人机旋转目标检测-附代码和原理

综述 为了解决旋转目标检测问题&#xff0c;研究者们提出了多种方法和算法。以下是一些常见的旋转目标检测方法&#xff1a; 基于滑动窗口的方法&#xff1a;在图像上以不同的尺度和角度滑动窗口&#xff0c;通过分类器判断窗口中是否存在目标。这种方法简单直观&#xff0c;…

NNote插件:让网络阅读变得更高效,轻松同步至Notion笔记

NNote笔记 在这个互联网时代&#xff0c;我们每天都在浏览器中阅读大量的文章和资讯&#xff0c;时常会遇到让人眼前一亮的观点和想法。然而&#xff0c;当我们试图将这些精彩内容记录下来时&#xff0c;却常常感受到复制粘贴的繁琐。为了解决这个问题&#xff0c;NNote插件应运…

SpringBoot入门指南(学习笔记)

概述 Springboot是Spring的一个子项目&#xff0c;用于快速构建Spring应用程序 入门 ①创建SpringBoot工程 ②编写Controller RestController public class HelloContoller {RequestMapping("/hello")public String hello() {return "hello";} }③运行…

【数据结构】图论与并查集

一、并查集 1.原理 简单的讲并查集&#xff0c;就是查询两个个元素&#xff0c;是否在一个集合当中&#xff0c;这里的集合用树的形式进行表示。并查集的本质就是森林, 即多棵树。 我们再来简单的举个例子: 假设此时的你是大一新生&#xff0c;刚进入大学&#xff0c;肯定是…

JAVA:利用JUnit进行高效的单元测试

1、简述 在软件开发中&#xff0c;单元测试是确保代码质量和可维护性的关键步骤。JUnit作为Java领域最流行的单元测试框架之一&#xff0c;提供了简单而强大的测试工具&#xff0c;可以帮助开发者在项目开发过程中及时发现和修复代码中的问题。本文将介绍JUnit的基本用法以及一…

《Redis实战》学习笔记

特点 &#xff1a;1、是一个高性能的key/value内存型数据库 2、支持丰富的数据类型(string,List,Set,ZSet,Hash) 3、支持持久化 内存数据&#xff0c; 可以持久化到硬盘中 4、单进程&#xff0c;单线程 效率高 redis实现分布式锁 一、redis的相关指令 1、flushDB 清空当前…

一起学量化之ATR指标

1. ATR指标定义 ATR,全称Average True Range,即均幅指标,是一个衡量市场波动性的技术分析工具。它通过计算一定周期内的平均真实波幅,来反映市场波动的程度。ATR指标由J. Welles Wilder Jr开发,主要用于研判买卖时机和管理交易风险。下图中蓝色的线即为ATR。 2. ATR指标应…

【PTA-C语言】实验七-函数与指针I

如果代码存在问题&#xff0c;麻烦大家指正 ~ ~有帮助麻烦点个赞 ~ ~ 目录——实验七-函数与指针I 6-1 弹球距离&#xff08;分数 10&#xff09;6-2 使用函数输出一个整数的逆序数&#xff08;分数 10&#xff09;6-3 使用函数求最大公约数&#xff08;分数 10&#xff09;6-4…

Wi-Fi 7用到的一些技术术语汇总

文章目录 low-complexity ap coordinationMulti-AP coordinationHARQ&#xff08;Hybrid Automatic Repeat Request&#xff09;multiple resource units per STAFCC open 6GhzU-NII-5,U-NII-6&#xff1f;4096-QAM&#xff08;Quadrature Amplitude Modulation&#xff0c;正交…

在FC中手工创建虚拟机模板

1、Linux去除个性化信息 &#xff08;1&#xff09;编辑网卡配置文件&#xff0c;只保留以下内容&#xff08;以RHEL 7为例&#xff09; &#xff08;2&#xff09;清除主机密钥信息&#xff08;开机会自动生成&#xff09; &#xff08;3&#xff09;清除Machine ID&#xff…

数据统计的一些专业术语学习

数据统计的一些专业术语学习 1. 极差2. 方差3. 标准差4. 均值绝对差 1. 极差 数据统计的极差&#xff0c;又称全距&#xff0c;是指一组数据中最大值和最小值之差。 举个例子&#xff0c;如果我们有一组数据&#xff1a;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c…

【动态规划】【字符串】C++算法:正则表达式匹配

作者推荐 视频算法专题 涉及知识点 动态规划 字符串 LeetCode10:正则表达式匹配 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 ‘.’ 和 ‘’ 的正则表达式匹配。 ‘.’ 匹配任意单个字符 ’ 匹配零个或多个前面的那一个元素 所谓匹配&#xff0c;是…

【SD】一致性角色 - 同一人物 不同姿势 - 2

首先生成4张不同姿势的图片 masterpiece,high quality,(white background:1.6),(simple background:1.4),1gril,solo,black footwear,black hair,brown eyes,closed mouth,full body,glasses,jacket,long hair,long sleeves,lookig at viewer,plaid,plaid skirt,pleated shirt,…

人是需要被肯定和认可的,赞美也是一种动力

前几天转发了一些网上的文章&#xff0c;突然有了10个关注我的人&#xff0c;赞美数和收藏量也上去了一些。 这是一种很意外的惊喜。 看了一下主题是&#xff1a; 1,如何将.NET8创建的控制台程序部署成WINDOWS服务。 2,.NET进阶篇06-async异步、thread多线程 3,易语言启动线程传…

病理HE学习贴(自备)

目录 正常结构 癌症HE 在线学习 以胃癌的学习为例 正常结构 1&#xff1a;胃粘膜正常结构和细胞分化 ●表面覆盖小凹上皮细胞(主要标志物&#xff1a;MUC5AC)以保护黏膜。 ●胃底腺固有腺体由黏液颈细胞(MUC6)、主细胞(Pepsinogen l)和壁细胞(Proton pump α-subunit)组…

css动态传参,attr的妙用

今天再做一个编辑器的功能的时候&#xff0c;发现有一段非常奇妙的代码&#xff0c;使用attr获取div标签的data-label值。 css的attr?What fuck?这又是什么鬼东西&#xff0c;emmm。 查询后官方是这么回答的&#xff1a; CSS 表达式 attr() 用来获取选择到的元素的某一 HTM…