腾讯云服务器--COS(三)

一、COS简介

  对象存储(Cloud Object Storage,COS)是腾讯云提供的一种存储海量文件的分布式存储服务,用户可通过网络随时存储和查看数据。腾讯云 COS 使所有用户都能使用具备高扩展性、低成本、可靠和安全的数据存储服务。
  COS 通过控制台、API、SDK 和工具等多样化方式简单、快速地接入,实现了海量数据存储和管理。通过 COS 可以进行多格式文件的上传、下载和管理。腾讯云提供了直观的 Web 管理界面,同时遍布全国范围的 CDN 节点可以对文件下载进行加速。
对象存储类型:
根据访问频度的高低,COS 提供三种对象的存储级别:标准存储、低频存储、归档存储。

用户类型 免费额度 有效期
个人用户 50GB标准存储容量 6个月
企业用户 1TB标准存储容量 6个月

地址:https://cloud.tencent.com/product/cos/document

二、创建COS

  1. 开启COS服务
    https://console.cloud.tencent.com/cos5

    创建对象存储

  2. 创建存储桶


    创建存储桶
配置

一定要配置公有读,私有写,否则图片不能读取。


配置成功
  1. 创建云API密钥


    创建云API密钥
创建云API密钥

三、Maven依赖

<dependency>
      <groupId>com.qcloud</groupId>
      <artifactId>cos_api</artifactId>
      <version>5.5.5</version>
</dependency>

四、上传文件代码编写

  1. 初始化客户端
// 1 初始化用户身份信息(secretId, secretKey)。
String secretId = "COS_SECRETID";
String secretKey = "COS_SECRETKEY";
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
// 2 设置 bucket 的区域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224
// clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。
Region region = new Region("ap-guangzhou");
ClientConfig clientConfig = new ClientConfig(region);
// 3 生成 cos 客户端。
COSClient cosClient = new COSClient(cred, clientConfig);


  1. 创建存储桶
Region region = new Region("ap-guangzhou");
ClientConfig clientConfig = new ClientConfig(region);
COSClient cosClient = new COSClient(cred, clientConfig);
String bucket = "examplebucket-1250000000"; //存储桶名称,格式:BucketName-APPID
CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucket);
// 设置 bucket 的权限为 PublicRead(公有读私有写), 其他可选有私有读写, 公有读写
 createBucketRequest.setCannedAcl(CannedAccessControlList.PublicRead);
try{
    Bucket bucketResult = cosClient.createBucket(createBucketRequest);
} catch (CosServiceException serverException) {
    serverException.printStackTrace();
} catch (CosClientException clientException) {
    clientException.printStackTrace();
}

  1. 上传对象
try {
    // 指定要上传的文件
    File localFile = new File("exampleobject");
    // 指定要上传到的存储桶
    String bucketName = "examplebucket-1250000000";
    // 指定要上传到 COS 上对象键
    String key = "exampleobject";
    PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, localFile);
    PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
} catch (CosServiceException serverException) {
    serverException.printStackTrace();
} catch (CosClientException clientException) {
    clientException.printStackTrace();
}

https://cloud.tencent.com/document/product/436/35215

五、ElementUI前端上传

  1. 前端代码
<template>
  <div>
    <el-upload
    class="avatar-uploader"
    action="http://localhost:8004/goods/sku/upload"
    :show-file-list="false"
    :on-success="handleAvatarSuccess"
    :before-upload="beforeAvatarUpload"
    name='file'
  >
    <!-- 显示图片 -->
    <img v-if="imageUrl" :src="imageUrl" class="avatar" />
    <i v-else class="el-icon-plus avatar-uploader-icon"></i>
  </el-upload>
  </div>
</template>
<script>
export default {
  data() {
    return {
      imageUrl: ""
    };
  },
  methods: {
    // 头像处理
    handleAvatarSuccess(res, file) {
      this.imageUrl = URL.createObjectURL(file.raw);
    },
    beforeAvatarUpload(file) {
      const isJPG = file.type === "image/jpeg";
      // 文件大小
      const isLt2M = file.size / 1024 / 1024 < 2;
      // 文件判断
      if (!isJPG) {
        this.$message.error("上传头像图片只能是 JPG 格式!");
      }
      if (!isLt2M) {
        this.$message.error("上传头像图片大小不能超过 2MB!");
      }
      return isJPG && isLt2M;
    }
  }
};
</script>

action为上传地址。

  1. 后端控制器
@RestController
@RequestMapping("/goods/sku")
@Slf4j
@CrossOrigin
public class SkuController {

    @Autowired
    private COSFileStorage cosFileStorage;

    @PostMapping("upload")
    public Result upload(MultipartFile file){
        String filename=file.getOriginalFilename();
        log.info(filename);
        try {
            cosFileStorage.fileUpload(file.getInputStream(), StringUtil.getFileExt(filename));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Result.ok("上传成功");
    }
}

  1. 上传类
@Component
public class COSFileStorage {
    // 1 初始化用户身份信息(secretId, secretKey)。
    private static String secretId = "自己id";
    private static String secretKey = "自己key";
    private static String bucketName = "自己桶";
    private static String regionName = "自己区域";

    public COSClient getCosClient() {
        COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
        // 2 设置 bucket 的区域, COS 地域的简称请参照
        // https://cloud.tencent.com/document/product/436/6224
        // clientConfig 中包含了设置 region, https(默认 http),
        // 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。
        Region region = new Region(regionName);
        ClientConfig clientConfig = new ClientConfig(region);
        // 3 生成 cos 客户端。
        COSClient cosClient = new COSClient(cred, clientConfig);
        return cosClient;
    }

    /**
     * 上传
     * @param input
     * @return
     */
    public String fileUpload(InputStream input,String extname) {
        // 指定要上传到 COS 上对象键
        UUID uuid = UUID.randomUUID();
        String key = uuid.toString().replace("-", "")+extname;
        ObjectMetadata objectMetadata = new ObjectMetadata();
        // 设置 Content type, 默认是 application/octet-stream
        objectMetadata.setContentType("image/jpeg");
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, input,objectMetadata);
        PutObjectResult putObjectResult = this.getCosClient().putObject(putObjectRequest);
        String imagePath = "https://" + bucketName + ".cos" + regionName + ".myqcloud.com/" + key;
        return imagePath;
    }
}

六、上传删除

  1. 前端组件
<template>
  <div>
    <!-- 上传图片:
     action: 必选参数,上传的地址
     auto-upload: 是否在选取文件后立即进行上传,默认 true
     http://localhost:8004/goods/sku/file
    -->
    <el-upload
      action="http://localhost:8004/goods/sku/file"
      list-type="picture-card"
      :auto-upload="true"
      :on-success="handleImageSuccess"
      :before-upload="beforeImageUpload"
      :on-preview="handlePictureCardPreview"
      :on-remove="handleRemove"
    >
      <i class="el-icon-plus"></i>
    </el-upload>

    <el-dialog :visible.sync="dialogVisible">
      <img width="100%" :src="dialogImageUrl" alt />
    </el-dialog>
  </div>
</template>
<script>
import { fileDelete } from "@/api/goods/fileUpload.js";

export default {
  data() {
    return {
      dialogImageUrl: "",
      dialogVisible: false
    };
  },
  methods: {
    // 图片删除
    handleRemove(file, fileList) {
      // 获取URL 文件名
        let fileNames = this.imageUrl.split("/");
        let fileName = fileNames[fileNames.length - 1];
        fileDelete(fileName).then(res => {
          if ((res.code = "0000")) {
            console.log(res);
          }
        });
    },
    // 预览对主框
    handlePictureCardPreview(file) {
      this.dialogImageUrl = file.url;
      this.dialogVisible = true;
    },
    // 上传成功
    handleImageSuccess(res, file) {
      console.log(res);
      //   this.imageUrl = URL.createObjectURL(file.raw);
      // 远程地址
      this.imageUrl = res.data;
    },
    // 上传前
    beforeImageUpload(file) {
      const isJPG = file.type === "image/jpeg";
      const isLt2M = file.size / 1024 / 1024 < 2;

      if (!isJPG) {
        this.$message.error("上传头像图片只能是 JPG 格式!");
      }
      if (!isLt2M) {
        this.$message.error("上传头像图片大小不能超过 2MB!");
      }
      return isJPG && isLt2M;
    },
    // 删除前
    beforeRemove(file, fileList) {
      return this.$confirm(`确定移除 ${file.name}?`);
    }
  }
};
</script>
  1. API接口调用
import request from "@/utils/request.js";
// 文件上传地址
const upFileUrl='http://localhost:8004/goods/sku/file';

// 文件删除
export function fileDelete(filename){
    return request({
        url:'http://localhost:8004/goods/sku/file/'+filename,
        method:'delete',
    });
}
  1. 后端控制器
@RestController
@RequestMapping("/goods/sku")
@Slf4j
@CrossOrigin
public class SkuController {

    @Autowired
    private COSFileStorage cosFileStorage;

    @PostMapping("file")
    public Result upload(MultipartFile file){
        String filename=file.getOriginalFilename();
        log.info(filename);
        try {
            String path=cosFileStorage.fileUpload(file.getInputStream(), StringUtil.getFileExt(filename));
            if(StringUtils.isNotEmpty(path)){
                return Result.ok("上传成功",path);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Result.fail("上传失败");
    }

    @DeleteMapping("file/{filename}")
    public Result upload(@PathVariable("filename")  String filename){
        // 删除文件
        cosFileStorage.fileDelete(filename);
        return Result.ok("删除成功");
    }
}
  1. 后端上传删除类
    添除文件方法。
    /**
     * 删除文件
     * @param fileName
     */
    public void fileDelete(String fileName){
        this.getCosClient().deleteObject(bucketName,fileName);
    }
上传删除

七、下载图片(Base64方式)

为什么使用Base64方式,因为腾讯COS服务器,有跨域问题。
Base64转换工具:
http://tool.chinaz.com/tools/imgtobase

  1. 工具类
/**
 * 将文件转Base64
 */
public class FileToBase64Utils {
    /**
     * <p>将文件转成base64 字符串</p>
     * @param path 文件路径
     * @return
     * @throws Exception
     */
    public static String encodeBase64File(String path) throws Exception {
        File file = new File(path);
        FileInputStream inputFile = new FileInputStream(file);
        byte[] buffer = new byte[(int)file.length()];
        inputFile.read(buffer);
        inputFile.close();
        return new BASE64Encoder().encode(buffer).replaceAll("[\\s*\\t\\n\\r]", "");
    }

    /**
     * 输入流转为Base64
     * @param in 文件流
     * @return
     * @throws Exception
     */
    public static String encodeBase64File(InputStream in) throws Exception {
        final ByteArrayOutputStream data = new ByteArrayOutputStream();
        // 缓存数组
        final byte[] by = new byte[1024];
        // 将内容读取内存中
        int len = -1;
        while ((len = in.read(by)) != -1) {
            data.write(by, 0, len);
        }
        in.close();
        return new BASE64Encoder().encode(data.toByteArray()).replaceAll("[\\s*\\t\\n\\r]", "");
    }
}
  1. 下载方法
    /**
     * 下载文件转为Base64编码
     *
     * @param fileName
     * @return
     * @throws Exception
     */
    public String fileDownload(String fileName) throws Exception {
        // 桶:bucketName 文件名:fileName
        GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, fileName);
        COSObject cosObject = this.getCosClient().getObject(getObjectRequest);
        // 获取数据流
        COSObjectInputStream cosObjectInput = cosObject.getObjectContent();
        //获取文件大小
        Long fileSize = cosObject.getObjectMetadata().getContentLength();
        // 获取文件类型
        String type = cosObject.getObjectMetadata().getContentType();
        // 转为Base64编码 data:image/jpg;base64,
        String base64Str ="data:"+type+";base64,"+ FileToBase64Utils.encodeBase64File(cosObjectInput);
        return base64Str;
    }
  1. 控制器
    @GetMapping("file/{filename}")
    public Result download(@PathVariable("filename")  String filename){
        // 下载文件
        try {
           String str= cosFileStorage.fileDownload(filename);
            return Result.ok("下载成功",str);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Result.fail("下载失败");
    }
  1. 前端界面代码
<template>
  <div>
    <!-- action上传地址 -->
    <el-upload
      action="http://localhost:8004/goods/sku/file"
      list-type="picture-card"
      :on-success="handleImageSuccess"
      :before-upload="beforeImageUpload"
      :auto-upload="true"
    >
      <!-- 十字图标 -->
      <i slot="default" class="el-icon-plus"></i>
      <!-- 文件 -->
      <div slot="file" slot-scope="{file}">
        <img class="el-upload-list__item-thumbnail" :src="file.url" alt />
        <span class="el-upload-list__item-actions">
          <span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
            <i class="el-icon-zoom-in"></i>
          </span>
          <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleDownload(file)">
            <i class="el-icon-download"></i>
          </span>
          <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleRemove(file)">
            <i class="el-icon-delete"></i>
          </span>
        </span>
      </div>
    </el-upload>
    <el-dialog :visible.sync="dialogVisible">
      <img width="100%" :src="dialogImageUrl" alt />
    </el-dialog>
  </div>
</template>

<script>
import { fileDelete,fileDownload } from "@/api/goods/fileUpload.js";
import { unescape } from "querystring";

export default {
  data() {
    return {
      fileList: [],
      dialogImageUrl: "",
      dialogVisible: false,
      disabled: false
    };
  },
  methods: {
    handleRemove(file) {
      // 删除文件
      console.log(file.response.data);
      let fileNames = file.response.data.split("/");
      fileDelete(fileNames[fileNames.length - 1]).then(res => {
        if (res.data.code == "0000") {
          this.fileList.splice(file, 1);
        }
      });
    },
    handlePictureCardPreview(file) {
      // 预览文件
      this.dialogImageUrl = file.url;
      this.dialogVisible = true;
    },
    handleDownload(file) {
      console.log("下载成功");
      let fileNames = file.response.data.split("/");
      let fileName = fileNames[fileNames.length - 1];
      fileDownload(fileName).then(res => {
        if (res.data.code == "0000") {
            this.downloadFile(fileName,res.data.data);
        }
      });
    },
    handleImageSuccess(res, file, fileList) {
      // 上传成功
      this.fileList = fileList;
      console.log("上传成功" + fileList.length);
    },
    beforeImageUpload(file) {
      // 上传前格式与大小校验

      const isJPG = file.type === "image/jpeg";
      const isLt2M = file.size / 1024 / 1024 < 2;

      if (!isJPG) {
        this.$message.error("上传头像图片只能是 JPG 格式!");
      }
      if (!isLt2M) {
        this.$message.error("上传头像图片大小不能超过 2MB!");
      }
      return isJPG && isLt2M;
    },
    // 下载
    downloadFile(fileName, content) {
      let aLink = document.createElement("a");
      let blob = this.base64ToBlob(content); //new Blob([content]);

      let evt = document.createEvent("HTMLEvents");
      evt.initEvent("click", true, true); //initEvent 不加后两个参数在FF下会报错  事件类型,是否冒泡,是否阻止浏览器的默认行为
      aLink.download = fileName;
      aLink.href = URL.createObjectURL(blob);

      // aLink.dispatchEvent(evt);
      aLink.click();
    },
    // base64转blob
    base64ToBlob(code) {
      let parts = code.split(";base64,");
      let contentType = parts[0].split(":")[1];
      let raw = window.atob(parts[1]);
      let rawLength = raw.length;

      let uInt8Array = new Uint8Array(rawLength);

      for (let i = 0; i < rawLength; ++i) {
        uInt8Array[i] = raw.charCodeAt(i);
      }
      return new Blob([uInt8Array], { type: contentType });
    }
  }
};
</script>

  1. 前端接口调用代码
import request from "@/utils/request.js";
// 文件上传地址
const upFileUrl='http://localhost:8004/goods/sku/file';
// 文件删除
export function fileDelete(filename){
    return request({
        url:'http://localhost:8004/goods/sku/file/'+filename,
        method:'delete',
    });
}
// 文件下载
export function fileDownload(filename){
    return request({
        url:'http://localhost:8004/goods/sku/file/'+filename,
        method:'get',
    });
}
  1. 测试界面


    下载图片

八、axios文件上传

1. 前端代码

API:

import request from '@/utils/request'
/**
 * 获取项目数据
 */
export function upload(formData) {
    return request({ url: "/upload/upload", method: "post",data:formData});
}

请求拦截:

    if (config.url.indexOf("upload") != -1) {
        // 设置为文件类型
        config.headers["Content-Type"] = "multipart/form-data";
    }

界面事件:

<template>
<input type="file" @change="upload($event)" />
</template>
<script>
import { upload } from "@/api/upload.js";
export default {
 methods: {
        upload(event) {
      console.log(event);
      let file = event.target.files[0];   // 获取文件
      let type = file.name.split(".")[1]; // 获取文件类型
      console.log(file);
      console.log(type);
      let size = file.size; // 文件大小

      let formData = new FormData();
      formData.append('file', file)
      upload(formData).then(res=>{
          console.log(res);
      })
    },
    }
}
</script>

2. 后端代码

@RestController
@RequestMapping(value = "/upload")
@Slf4j
@CrossOrigin
public class UploadController {

    @PostMapping("upload")
    public String upload(MultipartFile file) throws IOException {
        String filename=file.getOriginalFilename();
        InputStream inputStream=file.getInputStream();
        COSStorage cosStorage = new COSStorage();
        String path = cosStorage.upload(inputStream, StringUtil.getFileExt(filename));
        log.info(path);
        return path;
    }
}

八、常见问题

  1. 通过网关上传到后端服务错误:has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '
    原因:
    因为网关做一统一跨域配置 ,所在请不在要参数上添加注解:
    @RequestPart
public Result upload(@RequestPart MultipartFile file)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,835评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,900评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,481评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,303评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,375评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,729评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,877评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,633评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,088评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,443评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,563评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,251评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,827评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,712评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,943评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,240评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,435评论 2 348

推荐阅读更多精彩内容