数据库数据生成Excel,利用OSS进行上传和下载

2,629 阅读6分钟

这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战

ps:本篇文章的主要内容为从后端获取数据,并将多条数据以Excel表格的方式存储,并实现基于OSS的上传和下载

主要知识

OSS对象存储,EasyExcel 两者都来自于阿里云,请提前进行两者的基础知识学习
EasyExcel官方文档(必看):    www.yuque.com/easyexcel/d…
OSS官方文档:请访问阿里云或腾讯云

这个文章的本质,实际上是将数据库数据封装为集合,用EasyExcel定制包装成Excel文件,并以流的方式上传至OSS和生成OSS上对应文件的下载链接,其中考虑到数据过多,Excel文件生成(包括数据库查数据)需要进行异步处理。另外,在本地测试中,多开线程进行百万级数据的传输会对电脑造成过大压力,从而导致OOM

主要内容

1.实体类

实体类需要继承BaseRowModel类

@Data
public class User extends BaseRowModel {
    @ExcelProperty("编号")
    private int id;
    @ExcelProperty("姓名")
    private String name;
    @ExcelProperty("日期")
    private Date date;
    @ExcelProperty("内容")
    private String content;

}

这里可以用注解来设定Excel表格的表头,同时官网有更多种自定义以及复杂表头的设置

2.Excel工具类

@Component
public class EasyExcelUtil<T extends BaseRowModel>{


    /**生成一个读取数据库数据产生的excel字节文件
    *  最普通的EasyExcel用法
    *  返回excel文件数据流
    */
    public ByteArrayOutputStream easyOut(List<T> list, Class<T> clazz) throws Exception{
        //上传文件的命名规则,暂时没写
        String extension = ExcelTypeEnum.XLSX.getValue();

        //建立一个字节数组输出流,将excel文件的内容存入其中
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        /**
         *   这里指定用哪个class去写,然后写到第一个sheet,名字为模板,然后文件流会自动关闭
         *   这里可以定义表格的各种样式
         *   out为字节流
         *   clazz为实体类的反射
         */
        EasyExcel.write(out, clazz).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("模板").doWrite(list);
       
        return out;
    }
}

这里写了一个非常基础的工具方法,主要的核心代码其实就一句

EasyExcel.write(out, clazz).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("模板").doWrite(list);

EasyExcel:一种最常用的工具类,主要提供了Write()方法
write() : 里面的参数,第一个为Excel表格写入的目的地,目前总结两种填法(更多填法请参考文档)

  • 字符串:直接以字符串内容为名称存储在idea中
  • IO流 : 通常为输出流,这个时候就可以选择以输入流的方式来存储到本地或者远程,比如OSS或者本地响应(responde.getOutPutStream)

registerWriteHandler:表格宽度自适应,这个不精确 sheet :这个方法也有很多传参方式,主要是提供表格的页数和页名
image
doWrite :写入的内容,比如一个以对象方式存储了多条数据的list集合

3.OSS工具类

oss工具类此处使用阿里云官方demo,可直接去网站下载,更改域名和访问密钥即可使用

@Component
public class OSSClientUtil {

    // endpoint是访问OSS的域名。如果您已经在OSS的控制台上 创建了Bucket,请在控制台上查看域名。
    // 如果您还没有创建Bucket,endpoint选择请参看文档中心的“开发人员指南 > 基本概念 > 访问域名”,
    // 链接地址是:https://help.aliyun.com/document_detail/oss/user_guide/oss_concept/endpoint.html?spm=5176.docoss/user_guide/endpoint_region
    // endpoint的格式形如“http://oss-cn-hangzhou.aliyuncs.com/”,注意http://后不带bucket名称,
    // 比如“http://bucket-name.oss-cn-hangzhou.aliyuncs.com”,是错误的endpoint,请去掉其中的“bucket-name”。
    private static String endpoint = "";

    // accessKeyId和accessKeySecret是OSS的访问密钥,您可以在控制台上创建和查看,
    // 创建和查看访问密钥的链接地址是:https://ak-console.aliyun.com/#/。
    // 注意:accessKeyId和accessKeySecret前后都没有空格,从控制台复制时请检查并去除多余的空格。
    private static String accessKeyId = "";//阿里云注册可得
    private static String accessKeySecret = "";//阿里云注册可得

    // Bucket用来管理所存储Object的存储空间,详细描述请参看“开发人员指南 > 基本概念 > OSS基本概念介绍”。
    // Bucket命名规范如下:只能包括小写字母,数字和短横线(-),必须以小写字母或者数字开头,长度必须在3-63字节之间。
    private static String bucketName = "mytesto";

    // 生成OSSClient,您可以指定一些参数,详见“SDK手册 > Java-SDK > 初始化”,
    // 链接地址是:https://help.aliyun.com/document_detail/oss/sdk/java-sdk/init.html?spm=5176.docoss/sdk/java-sdk/get-start
    OSS ossClient = null;
    public void fileUpload(ByteArrayOutputStream out){
        ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        //判断桶名是否存在,如果不存在就新建一个
        //
        if (ossClient.doesBucketExist(bucketName)) {
            System.out.println("您已经创建Bucket:" + bucketName + "。");
        } else {
            System.out.println("您的Bucket不存在,创建Bucket:" + bucketName + "。");
            // 创建Bucket。详细请参看“SDK手册 > Java-SDK > 管理Bucket”。
            // 链接地址是:https://help.aliyun.com/document_detail/oss/sdk/java-sdk/manage_bucket.html?spm=5176.docoss/sdk/java-sdk/init
            ossClient.createBucket(bucketName);
        }

        // 查看Bucket信息。详细请参看“SDK手册 > Java-SDK > 管理Bucket”。
        BucketInfo info = ossClient.getBucketInfo(bucketName);
        System.out.println("Bucket " + bucketName + "的信息如下:");
        System.out.println("\t数据中心:" + info.getBucket().getLocation());
        System.out.println("\t创建时间:" + info.getBucket().getCreationDate());
        System.out.println("\t用户标志:" + info.getBucket().getOwner());

        //具体的文件上传,获取Excel文件的数据
        InputStream is = new ByteArrayInputStream(out.toByteArray());

        ossClient.putObject(bucketName, "456.xlsx", is);
        System.out.println("Object:" + "456.xlsx" + "存入OSS成功!!!");
        ossClient.shutdown();
    }


    //获取可以下载的URL
    public String getDownURL(String fileName){

        ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        Date expiration = new Date(new Date().getTime() + 3600l * 1000);//过期时间设为1小时
        // 生成URL
        URL url = ossClient.generatePresignedUrl(bucketName, fileName, expiration);
        if (url != null) {
            return url.toString();
        }
        return null;

    }

4.接口具体执行

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private EasyExcelUtil easyExcelUtil;

    @Autowired
    private OSSClientUtil ossClient;

    @GetMapping("/test")
    public void test() throws Exception {


        List<User> list = userService.findAll();

        //文件上传到OSS
        ossClient.fileUpload(easyExcelUtil.easyOut(list,User.class));
        //根据这个文件的名字获取下载链接,链接有效时长一小时
        String s = ossClient.getDownURL("456.xlsx");


        System.out.println("文件上传成功!!!");
        System.out.println(s);
        //return s;
    }


}

这里加上一个简单的本地下载的例子

 public void easyOutHttp(HttpServletResponse response, List<T> data, Class<T> clazz) throws Exception{
        try {
            String fileName = URLEncoder.encode("付款申请", "UTF-8");
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
            //转化为数据
            EasyExcel.write(response.getOutputStream(), clazz).autoCloseStream(Boolean.FALSE)
                    .sheet("sheet").doWrite(data);
        } catch (Exception e){
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = new HashMap<String, String>();
            map.put("status", "failure");
            map.put("message", "下载文件失败" + e.getMessage());
            //response.getWriter().println(JSON.toJSONString(map));
        }

    }

这个方法与上面的Excel方法的唯一区别就是将生成的Excel表格数据以输出流的方式写入到response中,在访问页面时可以直接跳出下载窗口,同时也可以设计为在线打开,具体设置属性如下:
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
Content-disposition:这个属性时能设置你写入的内容是作为网页的一部分或作为附件下载并且本地保存
本地下载具体内容可以参考简书博主的文章www.jianshu.com/p/d4a85d025…

另外补充两点:

1.这是单个excel的文件生成,在实际业务中,可能会有多个Excel文件生成,然后打包为压缩包下载,但是EasyExcel在使用WorkBook或者ExcelWriter时当设计到的流处理较多,可能会偶尔出现流未关闭的问题,这里直接采用流压缩的方法。
在一个for循环中,遍历每一个Excel文件的数据集合,在循环中逐次将每个Excel文件的流加入压缩流中

 // 使用EasyExcel生成excel文件,将文件放入一个内部字节流中
 ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
 //使用XLSX的文件格式进行数据的写入
 EasyExcel.write(byteOutputStream, clazz).excelType(ExcelTypeEnum.XLSX).sheet("模板").doWrite(batchlist);
 //将一个文件写入一个流中
 ZipEntry zipEntry = new ZipEntry(s + ".xlsx");
 zipOutputStream.putNextEntry(zipEntry);
 //写入到压缩流中
 byteOutputStream.writeTo(zipOutputStream);

2.使用oss本质上是为了减少本地下载的压力,但是读取数据并生成Excel文件同样会占用大量的时间,所以建议生成Excel这一步可以异步进行。

文章内容较为粗糙,如有误欢迎指正!

以上完结。