前端上传pdf,解析成图片并直传oss

2,763 阅读2分钟

需求

今天小程序同事开发说pdf预览有问题,手机端会先下载pdf然后在手机本地打开,但是有的手机解析不了pdf,也就会看不上页面,从而问pc上传的时候,能把pdf解析成图片么。

分析

1.上传前需要前端解析pdf为图片

2.解析的png图片需要上产阿里云

3.保证上传顺序

解决方案

根据调研,我们把问题1的解决方案为用pdfjs,因为我们是vue开发,所以用的二次封装组件pdfjs-dist。 第一步很简单创建组件 直接引入就好了

import PDFJS from 'pdfjs-dist'

要想获取本地文件,肯定要input了所以template部分代码,这里需要自己美化一下上传样式。本文章主题功能实现,不过多优化样式了。

<template>
  <div>
    <input id="file" type="file" name="file" accept="pdf" @change="loadPDF" />
  </div>
</template>

然后就是点击上传,获取到上传文件,最文件进行blob处理。为什么要进行处理,应为pdf二次组件传参的时候你会发现他只能解析blob。

public file_name = '';

// 加载PDF文件
public async loadPDF(e: any) {
  console.log(e.target.files[0]);
  const file = e.target.files[0]
  const reader = new FileReader(); // 阅读文件的方法,实例化下

  this.file_name = e.target.files[0].name.split('.')[0]; // 获取文件名 后续用到

  reader.onload = (e) => { 
    // 成功的回调
    const url: string | null | ArrayBuffer = e.target ? e.target.result : '';
    this.showPDF(url as string)
  };

  reader.readAsDataURL(file);
}

然后就进入了showPDF的方法里了,这里就是主要逻辑,话不多说 来分析下吧

public async showPDF(url: string) {
    const pdf: any = await PDFJS.getDocument(url);  // 这里就是上面说的,url参数如果是本地需要blob格式。
    const pages = pdf.numPages; // 获取总条数
    const imgArr = []; // 最后返回的上传后的图片集合

    EventBus.$emit('warning', '上传中,请勿操作');

    for (let i = 1; i <= pages; i++) {
      // pdf转图片其实就是通过canvas转成png, html2canvas也是这个原理
      const canvas = document.createElement('canvas') 
      const page = await pdf.getPage(i)
      const viewport = page.getViewport(2)
      const context = canvas.getContext('2d')

      canvas.height = viewport.height
      canvas.width = viewport.width

      const renderContext = {
        canvasContext: context,
        viewport
      }
      await page.render(renderContext)
			
      // 这里本来要直接上传的oss的。
      // 但是上传到oss是不可以直接上传base64的,需要把base64转换成blob,然后把blob转换成file文件的形式
      const image: any = canvas.toDataURL('image/png');
      const _fileBlob = this.dataURLtoBlob(image);
      const fileOfBlob = new File([_fileBlob], `${this.file_name}_page_${i}.png`);
      
      // 直连阿里云oss 并保存返回值
      imgArr.push(await this.upload({file: fileOfBlob}))
    }
		
  	// 判断是否所有的都上传成功
    if(imgArr.length === pages) {
      EventBus.$emit('success', '上传成功');
      this.$emit('on-success', imgArr)
    } else {
      EventBus.$emit('error', '上传失败');
    }
  }

base64 转blob方法

public dataURLtoBlob(dataurl: string) {
  const arr = dataurl.split(',');
  const reg = /:(.*?);/
  const res = arr[0].match(reg);
  const mime =  res ? res[1] : '';
  const bstr = atob(arr[1]);
  let len = bstr.length;
  const u8arr = new Uint8Array(len);

  while (len--) {
    u8arr[len] = bstr.charCodeAt(len);
  }

  return new Blob([u8arr], { type: mime });
}

最后直连oss

public async upload(option: any) {
    const {file = {}} = option;

    try {
      const credentials = await getPcBasicOss({
        bucket: xxx
      });

      return new Promise(async (res, rej) => {
        const zipfile = new File([file], file.name, {type: 'image/png', lastModified: Date.now()});

        const client = new OSS({
          region: credentials.region,
          accessKeyId: credentials.AccessKeyId, // 临时accessKeyId,跟申请的唯一key不同,防止泄露
          accessKeySecret: credentials.AccessKeySecret , // 临时accessKeySecret,跟申请的唯一key不同,防止泄露
          stsToken: credentials.SecurityToken,
          bucket: credentials.bucket,
        });

        const date = new Date();
        const d = dayjs(date).format('YYYY-MM-DD').replace(/\-/g, '');
        let code = '';
        for (let i = 1; i <= 6; i++) {
          const num = Math.floor(Math.random() * 10);
          code += num;
        }
        
        // 因为阿里云上传没有返回值 所以需要自己拼
        const name = zipfile.name.substr(0, zipfile.name.lastIndexOf('.')) + '_' +  d + code + zipfile.name.substr(zipfile.name.lastIndexOf('.'), zipfile.name.length);

        const result = await client.put(`/${name}`, zipfile);

        res(result);

      })
    } catch (e) {
      console.log(e);
    }
  }

测试功能通过,可以 过个好年了,附上整个文件代码

<script lang="ts">
import {Vue, Component} from 'vue-property-decorator';
import PDFJS from 'pdfjs-dist'
import * as OSS from 'ali-oss';
import dayjs from 'dayjs';

import { getPcBasicOss } from '@/model';
import { EventBus } from 'feok-lib';

export interface DataType {
  path: string; // 路径
  name: string; // 名称
}

@Component
export default class PdfToImg extends Vue {
  public file_name = '';

  // 加载PDF文件
  public async loadPDF(e: any) {
    console.log(e.target.files[0]);
    const file = e.target.files[0]
    const reader = new FileReader();

    this.file_name = e.target.files[0].name.split('.')[0];

    reader.onload = (e) => {
      const url: string | null | ArrayBuffer = e.target ? e.target.result : '';
      this.showPDF(url as string)
    };

    reader.readAsDataURL(file);
  }

  public async showPDF(url: string) {
    const pdf: any = await PDFJS.getDocument(url);
    const pages = pdf.numPages;
    const imgArr = [];

    EventBus.$emit('warning', '上传中,请勿操作');

    for (let i = 1; i <= pages; i++) {
      const canvas = document.createElement('canvas')
      const page = await pdf.getPage(i)
      const viewport = page.getViewport(2)
      const context = canvas.getContext('2d')

      canvas.height = viewport.height
      canvas.width = viewport.width

      const renderContext = {
        canvasContext: context,
        viewport
      }
      await page.render(renderContext)

      const image: any = canvas.toDataURL('image/png');
      const _fileBlob = this.dataURLtoBlob(image);
      const fileOfBlob = new File([_fileBlob], `${this.file_name}_page_${i}.png`);
      imgArr.push(await this.upload({file: fileOfBlob}))
    }

    if(imgArr.length === pages) {
      EventBus.$emit('success', '上传成功');
      this.$emit('on-success', imgArr)
    } else {
      EventBus.$emit('error', '上传失败');
    }
  }

  public dataURLtoBlob(dataurl: string) {
    const arr = dataurl.split(',');
    const reg = /:(.*?);/
    const res = arr[0].match(reg);
    const mime =  res ? res[1] : '';
    const bstr = atob(arr[1]);
    let len = bstr.length;
    const u8arr = new Uint8Array(len);

   while (len--) {
     u8arr[len] = bstr.charCodeAt(len);
   }

   return new Blob([u8arr], { type: mime });
  }
  
  public async upload(option: any) {
    const {file = {}} = option;

    try {
      const credentials = await getPcBasicOss({
        bucket: process.env.NODE_ENV === 'production' ? 'asal':'zxtest001'
      });

      return new Promise(async (res, rej) => {
        const zipfile = new File([file], file.name, {type: 'image/png', lastModified: Date.now()});

        const client = new OSS({
          region: credentials.region,
          accessKeyId: credentials.AccessKeyId,
          accessKeySecret: credentials.AccessKeySecret ,
          stsToken: credentials.SecurityToken,
          bucket: credentials.bucket,
        });

        const date = new Date();
        const d = dayjs(date).format('YYYY-MM-DD').replace(/\-/g, '');
        let code = '';
        for (let i = 1; i <= 6; i++) {
          const num = Math.floor(Math.random() * 10);
          code += num;
        }
        const name = zipfile.name.substr(0, zipfile.name.lastIndexOf('.')) + '_' +  d + code + zipfile.name.substr(zipfile.name.lastIndexOf('.'), zipfile.name.length);

        const result = await client.put(`/l-web-pc/${name}`, zipfile);

        res(result);

      })
    } catch (e) {
      console.log(e);
    }
  }
}
</script>

<template>
  <div>
    <input id="file" type="file" name="file" accept="pdf" @change="loadPDF" />
  </div>
</template>

<style lang="less">

</style>

遇到的坑

之前一直提示PDFJS是undefined,各种查才发现,版本问题,最后只能吧版本锁定在"pdfjs-dist": "2.0.943"

参考

developer.aliyun.com/article/716…

www.cnblogs.com/ssszjh/p/11…

blog.csdn.net/ju__ju/arti…


如果这篇文章有用,欢迎评论, 点赞, 加关注

我是leo:祝大家早日升职加薪,提前给大家拜个早年。