小程序端直传文件到oss服务器
1.后端代码如下:
package cn.iocoder.yudao.module.infra.service.file;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient;
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientConfig;
import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig;
import jakarta.annotation.Resource;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
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.http.HttpMethodName;
import com.qcloud.cos.model.GeneratePresignedUrlRequest;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.util.*;
import com.qcloud.cos.region.Region;
/**
* 腾讯云COS临时密钥服务
* 用于小程序端文件上传
*
* @author
*/
@Service
@Slf4j
public class CosStsService {
@Resource
private FileConfigService fileConfigService;
private volatile COSClient cachedCosClient;
private COSClient createCli() {
if (cachedCosClient != null) {
return cachedCosClient;
}
synchronized (this) {
if (cachedCosClient != null) {
return cachedCosClient;
}
FileClient fileClient = fileConfigService.getMasterFileClient();
FileConfigDO fileConfig = fileConfigService.getFileConfig(fileClient.getId());
FileClientConfig config = fileConfig.getConfig();
S3FileClientConfig s3FileClientConfig = (S3FileClientConfig) config;
COSCredentials cred = new BasicCOSCredentials(s3FileClientConfig.getAccessKey(), s3FileClientConfig.getAccessSecret());
ClientConfig clientConfig = new ClientConfig(new Region("ap-guangzhou"));
cachedCosClient = new COSClient(cred, clientConfig);
return cachedCosClient;
}
}
// 生成预签名的上传连接
public String generatePresignedUploadUrl(String filename) {
Date expirationTime = new Date(System.currentTimeMillis() + 30 * 60 * 1000);
COSClient cosClient = createCli();
URL url = cosClient.generatePresignedUrl("region", filename, expirationTime, HttpMethodName.PUT);
return url.toString();
}
@PreDestroy
public void destroyCosClient() {
if (cachedCosClient != null) {
try {
cachedCosClient.shutdown();
} catch (Exception ignored) {
log.error(ignored.getMessage());
}
}
}
}
返回的url如下:PUT形式
region.cos.ap-guangzhou.myqcloud.com/1758946339432_7kmdqd6eupx.png?sign=q-sign-algorithm%3Dsha1%26q-ak%3DAKIDJrETUKxTn7DC8whQ0NXcWDg5HG3cx46w%26q-sign-time%3D1758946340%3B1758948140%26q-key-time%3D1758946340%3B1758948140%26q-header-list%3D%26q-url-param-list%3D%26q-signature%3D7f9007b6119fb6a2e749fa9652a2bf5f1896e1e3
经过检测,没有问题,可以通过curl上传,oss服务器已经看到对应文件,显示上传成功。
curl.exe -X PUT --upload-file “D:\HuaweiMoveData\Users\ynzha\Desktop\OIP-C.jpg” “https://*******.cos.ap-guangzhou.myqcloud.com/1758946339432_7kmdqd6eupx.png?sign=q-sign-algorithm%3Dsha1%26q-ak%3DAKIDJrETUKxTn7DC8whQ0NXcWDg5HG3cx46w%26q-sign-time%3D1758946340%3B1758948140%26q-key-time%3D1758946340%3B1758948140%26q-header-list%3D%26q-url-param-list%3D%26q-signature%3D7f9007b6119fb6a2e749fa9652a2bf5f1896e1e3”
(虽然后端也可以上传方式为POST,但是生成的url经过curl测试,上传不成功,上传结果提示:MethodNotAllowed
采用curl命令测试:
curl.exe -X POST --upload-file “D:\HuaweiMoveData\Users\ynzha\Desktop\OIP-C.jpg” “https://******.cos.ap-guangzhou.myqcloud.com/1759027720856_2q6wd8rocal.jpg?sign=q-sign-algorithm%3Dsha1%26q-ak%3DAKIDJrETUKxTn7DC8whQ0NXcWDg5HG3cx46w%26q-sign-time%3D1759027725%3B1759029525%26q-key-time%3D1759027725%3B1759029525%26q-header-list%3D%26q-url-param-list%3D%26q-signature%3D94eeadc7996fd3a1be860950476f1bf11ad31a20”)
看起来是method方式有问题,生成的url只支持put方式,但是uniapp.uploadfile的方式是post,于是只能换其他方式:先获取文件内容,再采用常规的uni.request的方式,已put方式把文件内容上传到服务器:
async clientUpload(filePath, fileName, fileHash, fileInfo) {
// 1. 获取预签名URL
const { code, data, msg } = await FileApi.getFilePresignedUrl(fileName, this.directory, fileHash)
const presignedInfo = code === 0 ? data : {}
// 3. 上传文件到COS - 使用PUT请求方式
return new Promise((resolve, reject) => {
uni.showLoading({
title: "上传中",
mask: true,
});
const fs = uni.getFileSystemManager();
const ab = new ArrayBuffer(fileInfo.size);
fs.open({
filePath: filePath,
flag: "r",
success(res) {
fs.read({
fd: res.fd,
arrayBuffer: ab,
length: fileInfo.size,
success(readRes) {
uni.request({
method: "PUT",
url: presignedInfo,
data: readRes.arrayBuffer,
header: {
"Content-Type": "multipart/form-data",
},
success: async (uploadRes) => {
uni.hideLoading();
console.log('上传到COS成功:', uploadRes)
try {
// 检查上传结果,COS成功返回通常是200或204
if (uploadRes.statusCode === 200 || uploadRes.statusCode === 204) {
// 4. 记录文件信息到后端
const finalUrl = presignedInfo.split('?')[0]
await FileApi.createFile(finalUrl, fileInfo, fileHash)
resolve({ url: finalUrl })
} else {
console.error('COS上传失败,状态码:', uploadRes.statusCode)
console.error('响应数据:', uploadRes.data)
uni.showToast({
title: "上传失败",
icon: "none",
});
reject(new Error(`上传失败,状态码: ${uploadRes.statusCode}`))
}
} catch (error) {
console.error('处理上传结果时出错:', error)
uni.hideLoading();
uni.showToast({
title: "上传失败",
icon: "none",
});
reject(error)
}
},
fail: (error) => {
uni.hideLoading();
uni.showToast({
title: "上传失败",
icon: "none",
});
console.error("上传失败:", error);
reject(error)
}
});
},
fail(error) {
uni.hideLoading();
console.log('读取文件失败:', error);
reject(error);
},
});
},
fail(error) {
uni.hideLoading();
console.log('打开文件失败:', error);
reject(error);
},
});
})
}
5720

被折叠的 条评论
为什么被折叠?



