Iview+Vue+Springboot上传文件到腾讯COS

前言

这个功能吧,我想了很久。以前用一个很蠢的方案实现过,但是不够优雅漂亮。哎,你猜怎么着,我最近琢磨出了一个比较靠谱的套路。走着

前端

上传组件

<Upload
  ref="uploadImage"
  type="drag"
  :action="`${saveAction}`"
  :data="{filename: currentFileName}"
  accept="image/png, image/jpeg, image/jpg"
  :max-size="2048"
  :format="['jpg','jpeg','png']"
  :on-success="handleSuccess"
  :before-upload="beforeUpload"
  :on-error="handleError"
  :with-credentials="true"
>
  <div style="padding: 20px 0">
    <Icon
      type="ios-cloud-upload"
      size="52"
      style="color: #3399ff"
    ></Icon>
    <p style="text-align: center">1.点击或将文件拖拽到这里</p>
  </div>
</Upload>

其中saveAction是后端对应的接口地址,比如/img/upload。:data则是访问时带的参数,accept规定可用文件类型。
比较关键的是before-upload这个方法,它会阻断上传的过程,转而成为手动,这样的好处是如果上传的这个文件你想换,就可以手动删掉重新选

// 获取真实文件,.name是文件原始名称
beforeUpload(file) {
  this.files = file
  this.currentFileName = file.name
  return false
},

删除部分

<p
  v-if="files !== null"
  style="margin: 10px"
>
  {{ files.name }}
  <span style="margin-left: 20px">
    <Button
      type="error"
      size="small"
      @click="removeItem()"
    >2.是否删除</Button>
  </span>
</p>

对应的js

removeItem() {
  this.files = null
},

最终上传:

<Button
  type="success"
  size="small"
  :disabled="files === null"
  @click="confirmUpload"
>3.确认提交</Button>

对应的js

confirmUpload() {
  this.$Modal.confirm({
    title: '确定提交吗?',
    onOk: () => {
      this.$refs.uploadImage.post(this.files)
    },
    onCancel: () => {
      this.$Message.warning('已取消')
    },
  })
},

这里我就要吹一下自己了,翻阅源码时发现iview原本的上传就是用内置的ajax的post方法实现的,这里我直接跳过了源码的封装,把post拿出来用,效果还不错~

后端

后端我琢磨了挺久,一直没找到特别好的办法。主要是我原本的思路是把file作为一个完整的文件传上去,但是后来我发现COS的源码里可以接受输入流,一下子反应过来可以把文件转成InputStream再上传,一下子优雅了很多:

/**
 * 上传图片
 *
 * @param file 文件原件
 * @param creator 创建人
 * @param filename 文件名
 * @return
 */
@ApiOperation(value = "上传图片")
@PostMapping("/uploadImage/{creator}")
public ApiReturn<String> uploadHeadImage(
        @RequestParam(value = "file") MultipartFile file,
        @PathVariable String creator,
        @RequestParam String filename
) {
    byte[] bytes;
    try {
    	// 把文件转成byte数组
        bytes = file.getBytes();
    } catch (Exception e) {
        log.error("Upload file error", e);
        return new ApiReturn<>(e.getMessage());
    }
    // 再把byte数组转成输入流
    InputStream uploadStream = new ByteArrayInputStream(bytes);
    URL url = null;
    try {
    	// 这里调用了我自己编写的COS工具,下面会写
        url = cosUtil.saveFile(filename, uploadStream);
    } catch (Exception e) {
        log.error(e.getMessage());
        return new ApiReturn<>(e.getMessage());
    }
    // 如果上传成功,把url落表,如果落表失败,把云上的图也删掉
    if (url != null) {
        int count = blackService.saveImage(ImageRecord.builder()
                .url(url.toString())
                .name(filename)
                .creator(StringUtils.hasText(creator) ? creator : "")
                .createTime(new Date())
                .build());
        if (count == 0) {
            // 存储失败,把云服务中的该图片删除
            cosUtil.delFile(filename);
            return new ApiReturn<>("未知原因存储失败");
        }
    }
    return new ApiReturn<>(url.toString());
}

附:腾讯COS专用工具类

@Component
@Slf4j
public class CosUtil {

    /**
     * Cos桶位名称
     */
    @Value("${cos.bucket-name}")
    private String bucketName;

    /**
     * 存取桶
     */
    @Value("${cos.bucket}")
    private String bucket;

    /**
     * COS物理位置
     */
    @Value("${cos.bucket-position}")
    private String bucketPosition;

    /**
     * cos调用人唯一标识
     */
    @Value("${cos.secret-id}")
    private String secretId;

    /**
     * cos密钥
     */
    @Value("${cos.secret-key}")
    private String secretKey;

    /**
     * App唯一标识
     */
    @Value("${cos.app-id}")
    private String appId;

    /**
     * 转换管理器
     */
    private TransferManager transferManager;

    /**
     * Cos客户端
     */
    private COSClient cosClient;

    /**
     * 初始化client
     */
    @PostConstruct
    private void init() {
        log.info("初始化COS Client中,参数为: {}",
                String.format("bucketName: %s,appid: %s, bucket: %s", bucketName, appId, bucket));
        Region region = new Region(bucketPosition);
        cosClient = new COSClient(new BasicCOSCredentials(secretId, secretKey), new ClientConfig(region));
        createBucket();
        ExecutorService threadPool = getThreadPool();
        // 传入一个 threadpool, 若不传入线程池, 默认 TransferManager 中会生成一个单线程的线程池。
        transferManager = new TransferManager(cosClient, threadPool);
    }

    private void createBucket() {
        if (cosClient.listBuckets().size() == 0) {
            CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
            // 设置 bucket 的权限为PublicRead(公有读私有写)
            createBucketRequest.setCannedAcl(CannedAccessControlList.PublicRead);
            try {
                cosClient.createBucket(createBucketRequest);
            } catch (CosClientException serverException) {
                serverException.printStackTrace();
            }
        }
    }

    private ExecutorService getThreadPool() {
        return new ThreadPoolExecutor(
                Runtime.getRuntime().availableProcessors(),
                200,
                0L,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(1024),
                new ThreadFactoryBuilder()
                        .setNameFormat("demo-pool-%d").build(),
                new ThreadPoolExecutor.AbortPolicy()
        );
    }

    /**
     * 使用文件上传
     *
     * @param key  文件主键
     * @param file 文件主体
     * @return 可用链接
     * @throws InterruptedException 抛出异常
     */
    public URL saveFile(String key, File file) throws InterruptedException {
        transferManager.upload(
                new PutObjectRequest(bucketName, String.format("%s/%s", bucket, key), file)
        ).waitForUploadResult();
        // 关闭 TransferManger
        transferManager.shutdownNow();
        return cosClient.generatePresignedUrl(bucketName, String.format("%s/%s", bucket, key),
                new Date(System.currentTimeMillis() + 5 * 60 * 10000));
    }

    /**
     * 使用输入流上传
     *
     * @param key         文件主键
     * @param inputStream 输入流
     * @return 可用链接
     * @throws InterruptedException 抛出异常
     */
    public URL saveFile(String key, InputStream inputStream) throws InterruptedException, ParseException {
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setExpirationTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2030-01-01 23:59:59"));
        transferManager.upload(bucketName,
                String.format("%s/%s", bucket, key),
                inputStream, objectMetadata).waitForUploadResult();
        return cosClient.generatePresignedUrl(bucketName, String.format("%s/%s", bucket, key),
                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2030-01-01 23:59:59"));
    }

    /**
     * 获取文件
     *
     * @param key
     * @return
     */
    public COSObject getCosObject(String key) {
        return cosClient.getObject(bucketName, key);
    }

    /**
     * 文件是否存在
     *
     * @param key
     * @return
     */
    public boolean ifImageExist(String key) {
        return cosClient.doesObjectExist(bucketName, String.format("%s/%s", bucket, key));
    }

    /**
     * 刷新图片URL
     *
     * @param key 文件名
     * @return 刷新后的URL
     */
    public URL getUpdateFileUrl(String key) {
        return cosClient.generatePresignedUrl(bucketName, String.format("%s/%s", bucket, key),
                new Date(System.currentTimeMillis() + 31104 * 1000000L));
    }

    /**
     * 删除文件
     *
     * @param key 文件主键
     */
    public void delFile(String key) {
        cosClient.deleteObject(bucketName, String.format("%s/%s", bucket, key));
    }
}

吐槽

腾讯COS的文档真的不大靠谱,如果你想自己写工具类,强烈建议对照源码找API,不要用文档的API。
本文首发于CSDN,作者:亭台烟雨中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值