小程序端直传文件到oss服务器

小程序端直传文件到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);
                },
            });
        })
    }



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值