1、引入依赖
<dependency> <groupId>com.tencent.cloud</groupId> <artifactId>cos-sts-java</artifactId> <version>3.0.8</version> </dependency>
2、配置application.yml
cos: appId: XXXXXXXXXXXXXXX #腾讯云的SecretId secretId: XXXXXXXXXXXXXXXXXXXXXXXXXX #腾讯云的SecretId secretKey: XXXXXXXXXXXXXXXXXXXXXX #腾讯云的bucket (存储桶) bucket: XXXXXXXXX #腾讯云的region(bucket所在地区) region: ap-shanghai #腾讯云的临时密钥时长(单位秒) durationSeconds: 1800 #腾讯云的访问基础链接: baseUrl: https://XXXXXXXXXXXXXXXX.cos.ap-shanghai.myqcloud.com filePathPrefix: dev # filePathPrefix: prod
3、COS工具类
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.exception.CosServiceException;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.model.PutObjectResult;
import com.qcloud.cos.region.Region;
import com.XXXXXXXXXXXXXXXXXXXXXXXXXXX.DateUtils;
import com.tencent.cloud.CosStsClient;
import org.json.JSONObject;
import org.springframework.web.multipart.MultipartFile;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
/**
* 腾讯云 cos工具类
*/
public class CosUtil {
//腾讯云的SecretId
private static String secretId;
//腾讯云的SecretKey
private static String secretKey;
//腾讯云的bucket (存储桶)
private static String bucket;
//腾讯云的region(bucket所在地区)
private static String region;
//腾讯云的allowPrefix(允许上传的路径)
private static String allowPrefix;
//腾讯云的临时密钥时长(单位秒)
private static String durationSeconds;
//腾讯云的访问基础链接:
private static String baseUrl;
//文件上传前缀路径
private static String filePathPrefix;
//初始化
static {
Yaml yaml = new Yaml();
//文件路径是相对类目录(src/main/java)的相对路径
InputStream in = CosUtil.class.getClassLoader().getResourceAsStream("application.yml");//或者app.yaml
Map<String, Object> map = yaml.loadAs(in, Map.class);
secretId = ((Map<String, Object>) map.get("cos")).get("secretId").toString();
secretKey = ((Map<String, Object>) map.get("cos")).get("secretKey").toString();
bucket = ((Map<String, Object>) map.get("cos")).get("bucket").toString();
region = ((Map<String, Object>) map.get("cos")).get("region").toString();
durationSeconds = ((Map<String, Object>) map.get("cos")).get("durationSeconds").toString();
baseUrl = ((Map<String, Object>) map.get("cos")).get("baseUrl").toString();
filePathPrefix = ((Map<String, Object>) map.get("cos")).get("filePathPrefix").toString();
allowPrefix = "/*";
}
/**
* 上传文件 使用临时密钥上传
*
* @param file
* @return 成功返回文件路径, 失败返回null
*/
public static String uploadFile(File file) {
//获取最后一个.的位置
int lastIndexOf = file.getName().lastIndexOf(".");
//获取文件的后缀名 .jpg
String suffix = file.getName().substring(lastIndexOf);
String filePath = filePathPrefix + "/" + DateUtils.getYear() + "/" + DateUtils.getMonth() + "/";
String fileName = UUID.randomUUID() + suffix;
filePath = filePath.startsWith("/") ? filePath : "/" + filePath + fileName;
//获取临时密钥
JSONObject temp = getTempKey();
// 用户基本信息:解析临时密钥中的相关信息
String tmpSecretId = temp.getJSONObject("credentials").getString("tmpSecretId");
String tmpSecretKey = temp.getJSONObject("credentials").getString("tmpSecretKey");
String sessionToken = temp.getJSONObject("credentials").getString("sessionToken");
// 1 初始化用户身份信息(secretId, secretKey)
COSCredentials cred = new BasicCOSCredentials(tmpSecretId, tmpSecretKey);
// 2 设置 bucket 区域
ClientConfig clientConfig = new ClientConfig(new Region(region));
// 3 生成 cos 客户端
COSClient cosclient = new COSClient(cred, clientConfig);
// 上传 object, 建议 20M 以下的文件使用该接口
PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, filePath, file);
// 设置 x-cos-security-token header 字段
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setSecurityToken(sessionToken);
putObjectRequest.setMetadata(objectMetadata);
String rtValue = null;
try {
PutObjectResult putObjectResult = cosclient.putObject(putObjectRequest);
// 成功:putobjectResult 会返回文件的 etag
String etag = putObjectResult.getETag();
rtValue = baseUrl + filePath;
} catch (CosServiceException e) {
//失败,抛出 CosServiceException
e.printStackTrace();
} catch (CosClientException e) {
//失败,抛出 CosClientException
e.printStackTrace();
} finally {
// 关闭客户端
cosclient.shutdown();
//返回文件的网络访问url
System.err.println(rtValue);
return rtValue;
}
}
public static String uploadMultipartFile(File file, MultipartFile multipartFile) {
UUID id = UUID.randomUUID();
String[] idd = id.toString().split("-");
String str = idd[0] + idd[1] + idd[2];
String filePath = filePathPrefix + "/" + DateUtils.getYear() + "/" + DateUtils.getMonth() + "/";
filePath = filePath.startsWith("/") ? filePath : "/" + filePath + str + multipartFile.getOriginalFilename();
//获取临时密钥
JSONObject temp = getTempKey();
// 用户基本信息:解析临时密钥中的相关信息
String tmpSecretId = temp.getJSONObject("credentials").getString("tmpSecretId");
String tmpSecretKey = temp.getJSONObject("credentials").getString("tmpSecretKey");
String sessionToken = temp.getJSONObject("credentials").getString("sessionToken");
// 1 初始化用户身份信息(secretId, secretKey)
COSCredentials cred = new BasicCOSCredentials(tmpSecretId, tmpSecretKey);
// 2 设置 bucket 区域
ClientConfig clientConfig = new ClientConfig(new Region(region));
// 3 生成 cos 客户端
COSClient cosclient = new COSClient(cred, clientConfig);
// 上传 object, 建议 20M 以下的文件使用该接口
PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, filePath, file);
// 设置 x-cos-security-token header 字段
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setSecurityToken(sessionToken);
putObjectRequest.setMetadata(objectMetadata);
String rtValue = null;
try {
PutObjectResult putObjectResult = cosclient.putObject(putObjectRequest);
// 成功:putobjectResult 会返回文件的 etag
String etag = putObjectResult.getETag();
rtValue = baseUrl + filePath;
} catch (CosServiceException e) {
//失败,抛出 CosServiceException
e.printStackTrace();
} catch (CosClientException e) {
//失败,抛出 CosClientException
e.printStackTrace();
} finally {
// 关闭客户端
cosclient.shutdown();
//返回文件的网络访问url
System.err.println(rtValue);
return rtValue;
}
}
/**
* 生成临时密钥
* <p>
* 官网demo:https://cloud.tencent.com/document/product/436/14048
*
* @return
*/
private static JSONObject getTempKey() {
JSONObject credential = new JSONObject();
TreeMap<String, Object> config = new TreeMap<String, Object>();
try {
// 替换为您的 SecretId
config.put("SecretId", secretId);
// 替换为您的 SecretKey
config.put("SecretKey", secretKey);
// 临时密钥有效时长,单位是秒,默认1800秒,最长可设定有效期为7200秒
config.put("durationSeconds", Integer.parseInt(durationSeconds));
// 换成您的 bucket
config.put("bucket", bucket);
// 换成 bucket 所在地区
config.put("region", region);
// 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径,例子:a.jpg 或者 a/* 或者 * 。
// 如果填写了“*”,将允许用户访问所有资源;除非业务需要,否则请按照最小权限原则授予用户相应的访问权限范围。
config.put("allowPrefix", allowPrefix);
// 密钥的权限列表。简单上传、表单上传和分片上传需要以下的权限,其他权限列表请看 https://cloud.tencent.com/document/product/436/31923
String[] allowActions = new String[]{
// 简单上传
"name/cos:PutObject",
// 表单上传、小程序上传
"name/cos:PostObject",
// 分片上传
"name/cos:InitiateMultipartUpload",
"name/cos:ListMultipartUploads",
"name/cos:ListParts",
"name/cos:UploadPart",
"name/cos:CompleteMultipartUpload"
};
config.put("allowActions", allowActions);
credential = CosStsClient.getCredential(config);
//成功返回临时密钥信息,如下打印密钥信息
//LogManager.debug(TAG, credential.toString());
System.out.println(credential);
} catch (Exception e) {
//失败抛出异常
throw new IllegalArgumentException("no valid secret !");
}
return credential;
}
/**
* 用缓冲区来实现这个转换, 即创建临时文件
* 使用 MultipartFile.transferTo()
*
* @param multipartFile
* @return
*/
public static File transferToFile(MultipartFile multipartFile) throws IOException {
String originalFilename = multipartFile.getOriginalFilename();
String prefix = originalFilename.split("\\.")[0];
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
File file = File.createTempFile(prefix, suffix);
multipartFile.transferTo(file);
return file;
}
public static void deleteFile(String fileName) {
COSClient cosClient = null;
try {
// 1 初始化用户身份信息(secretId, secretKey)。
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
// 2 设置bucket的区域, COS地域的简称请参照
// https://cloud.tencent.com/document/product/436/6224
// clientConfig中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者接口文档 FAQ中说明。
ClientConfig clientConfig = new ClientConfig(new Region(region));
// 3 生成 cos 客户端。
cosClient = new COSClient(cred, clientConfig);
cosClient.deleteObject(bucket, fileName);
} finally {
if (cosClient != null) {
// 关闭客户端(关闭后台线程)
cosClient.shutdown();
}
}
}
/**
* 上传测试
**/
public static void main(String[] args) {
//创建文件
File file = new File("D:\\微信图片_20220317095722.png");
//第一种:固定密钥
/**
* 如果filePath是空或者是null,就以当前日期的形式存放 如:yyyy/yyyymmdd/xxx.文件后缀
* 如果指定文件夹:如 ttt/
* ps:要加斜线 /
* 表示存放在目录下的ttt文件夹下
**/
uploadFile(file);
}
}
4、Controller使用COS的工具类
import com.common.core.domain.ReturnResult;
import com.utils.CosUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@RestController
@RequestMapping("/cos/cos")
@Api(value = "/cos/cos", description = "腾讯云文件操作")
public class CosController {
/**
* 腾讯云上传对象
*
* @param multipartFile
* @return
* @throws Exception
*/
@ApiOperation(value = "腾讯云上传对象")
@PreAuthorize("@ss.hasPermi('file:file:uploadFile')")
@PostMapping(value = "/uploadFile")
public ReturnResult uploadFile(@RequestParam MultipartFile multipartFile, String... flag) {
File file = null;
try {
file = CosUtil.transferToFile(multipartFile);
if (flag != null) {
String filePath = CosUtil.uploadMultipartFile(file, multipartFile);
return ReturnResult.success(filePath);
} else {
String filePath = CosUtil.uploadFile(file);
return ReturnResult.success(filePath);
}
} catch (IOException e) {
return ReturnResult.error(e.getMessage());
}
}
/**
* 腾讯云删除对象
*
* @param fileName
* @return
*/
@ApiOperation(value = "腾讯云删除对象")
@PreAuthorize("@ss.hasPermi('file:file:deleteFile')")
@GetMapping("/deleteFile")
@PostMapping(value = "/deleteFile")
public ReturnResult deleteFile(@RequestParam String fileName) {
CosUtil.deleteFile(fileName);
return ReturnResult.success();
}
}
5、前端上传文件到COS的组件
<template>
<div class="upload-file">
<el-upload
:action="uploadFileUrl"
:before-upload="handleBeforeUpload"
v-model:file-list="fileList"
:limit="limit"
:data="{flag:true}"
:on-error="handleUploadError"
:on-exceed="handleExceed"
:on-success="handleUploadSuccess"
:show-file-list="false"
name="multipartFile"
:headers="headers"
:multiple="true"
class="upload-file-uploader"
ref="upload"
>
<!-- 上传按钮 -->
<el-button :type="isBackground ? 'primary' : 'text'" >上传文件</el-button>
<!-- 上传提示 -->
<div class="el-upload__tip" v-if="showTip">
请上传
<template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b><br/></template>
<template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
的文件
</div>
</el-upload>
<!-- 文件列表 -->
<transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
<template v-for="(file, index) in fileList" :key="file.uid">
<li class="el-upload-list__item ele-upload-list__item-content" v-if="file.url">
<el-link :href="file.url" :underline="false" target="_blank">
<span class="el-icon-document"> {{ file.name}} </span>
</el-link>
<div>
<el-button link :href="file.url" :underline="false" @click="downLoad(file)" type="primary">下载</el-button>
<el-button link :underline="false" @click="handleDelete(index)" type="danger">删除</el-button>
</div>
</li>
</template>
</transition-group>
</div>
</template>
<script setup>
import { getToken } from "@/utils/auth";
import { saveAs } from 'file-saver'
const props = defineProps({
modelValue: [String, Object, Array],
// 数量限制
limit: {
type: Number,
default: 5,
},
// 大小限制(MB)
fileSize: {
type: Number,
default: 20,
},
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: {
type: Array,
default: () => ["doc", "xls", "ppt", "txt", "pdf","docx",'xlsx',"jpg",'jpge',"png"],
},
// 是否显示提示
isShowTip: {
type: Boolean,
default: true
},
//是否显示背景边框
isBackground:{
type:Boolean,
default:true
},
});
const { proxy } = getCurrentInstance();
const emit = defineEmits();
const baseUrl = 'https://XXXXXXXXXXXXXXXXXXX.cos.ap-shanghai.myqcloud.com';
const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + "/cos/cos/uploadFile"); // 上传的图片服务器地址
const headers = ref({ Authorization: "Bearer " + getToken() });
const fileList = ref([]);
const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize)
);
watch(() => props.modelValue, val => {
if (val) {
let temp = 1;
// 首先将值转为数组
const list = Array.isArray(val) ? val : props.modelValue.split(',');
// 然后将数组转为对象数组
fileList.value = list.map(item => {
if (typeof item === "string") {
item = { name: getFileName(item), url: item };
}
item.uid = item.uid || new Date().getTime() + temp++;
return item;
});
} else {
fileList.value = [];
return [];
}
});
// 上传前校检格式和大小
function handleBeforeUpload(file) {
// 校检文件类型
if (props.fileType.length) {
let fileExtension = "";
if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
}
const isTypeOk = props.fileType.some((type) => {
if (file.type.indexOf(type) > -1) return true;
if (fileExtension && fileExtension.indexOf(type) > -1) return true;
return false;
});
if (!isTypeOk) {
proxy.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join("/")}格式文件!`);
return false;
}
}
// 校检文件大小
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
return false;
}
}
return true;
}
// 文件个数超出
function handleExceed() {
proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
}
// 上传失败
function handleUploadError(err) {
proxy.$modal.msgError("上传失败");
}
// 上传成功回调
function handleUploadSuccess(res, file) {
if(res.code!==200){
proxy.$modal.msgError(res.msg);
const index = fileList.value.findIndex(item=>item.uid===file.uid)
handleDelete(index)
return
}
proxy.$modal.msgSuccess("上传成功");
// fileList.value.push({ name: res.fileName, url: res.fileName,file});
console.log(fileList.value)
console.log(file)
const cFile = fileList.value.find(item=>item.uid===file.uid)
cFile.url = res.data;
// console.log(fileList.value)
if(fileList.value.every(item=>item.status==='success')){
emit("update:modelValue", listToString(fileList.value));
}
}
// 删除文件
function handleDelete(index) {
fileList.value.splice(index, 1);
emit("update:modelValue", listToString(fileList.value));
}
// 获取文件名称
function getFileName(name) {
if (name.lastIndexOf("/") > -1) {
const prefix = 16
return name.slice(name.lastIndexOf("/") + prefix+1).toLowerCase();
} else {
return "";
}
}
//下载
function downLoad(file){
saveAs(file.url, file.name, {});
}
// 对象转成指定字符串分隔
function listToString(list, separator) {
let strs = "";
separator = separator || ",";
for (let i in list) {
strs += list[i].url + separator;
}
return strs !== '' ? strs.substr(0, strs.length - 1) : '';
}
</script>
<style scoped lang="scss">
.upload-file-uploader {
margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {
border: 1px solid #e4e7ed;
line-height: 2;
margin-bottom: 10px;
position: relative;
}
.upload-file-list .ele-upload-list__item-content {
display: flex;
justify-content: space-between;
align-items: center;
color: inherit;
}
.ele-upload-list__item-content-action .el-link {
margin-right: 10px;
}
.el-upload__tip{
margin-left: 10px;
}
</style>