一、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
-
开启COS服务
https://console.cloud.tencent.com/cos5
-
创建存储桶
一定要配置公有读,私有写,否则图片不能读取。
-
创建云API密钥
三、Maven依赖
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.5.5</version>
</dependency>
四、上传文件代码编写
- 初始化客户端
// 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);
- 创建存储桶
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();
}
- 上传对象
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前端上传
- 前端代码
<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为上传地址。
- 后端控制器
@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("上传成功");
}
}
- 上传类
@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;
}
}
六、上传删除
- 前端组件
<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>
- 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',
});
}
- 后端控制器
@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("删除成功");
}
}
- 后端上传删除类
添除文件方法。
/**
* 删除文件
* @param fileName
*/
public void fileDelete(String fileName){
this.getCosClient().deleteObject(bucketName,fileName);
}
七、下载图片(Base64方式)
为什么使用Base64方式,因为腾讯COS服务器,有跨域问题。
Base64转换工具:
http://tool.chinaz.com/tools/imgtobase
- 工具类
/**
* 将文件转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]", "");
}
}
- 下载方法
/**
* 下载文件转为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;
}
- 控制器
@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("下载失败");
}
- 前端界面代码
<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>
- 前端接口调用代码
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',
});
}
-
测试界面
八、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;
}
}
八、常见问题
- 通过网关上传到后端服务错误:has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '
原因:
因为网关做一统一跨域配置 ,所在请不在要参数上添加注解:
@RequestPart
public Result upload(@RequestPart MultipartFile file)