Spring WebFlux 上传文件到COS腾讯云

springWebFlux 上传文件 的坑

先说说请求参数的坑

1,springmvc 中使用MultipartFile 当接受参数
2,但是使用webflux的时候 MultipartFile接受会报错415 转换异常的错误 这时候必须用

@RequestPart("file") FilePart file

主要是因为 底层容器不同了 mvc 用的是servlet 而webflux 用的是netty

Config类

这边通过cos的对接文档 直接将TransferManager 注入到spring 容器中

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.HttpProtocol;
import com.qcloud.cos.region.Region;
import com.qcloud.cos.transfer.TransferManager;
import com.qcloud.cos.transfer.TransferManagerConfiguration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 腾讯云存储
 */
@Configuration
public class CosConfiguration {


    @Value("${cos.secretId}")
    private String secretId;

    @Value("${cos.secretKey}")
    private String secretKey;

    @Value("${cos.region}")
    private String region;


    @Value("${cos.account}")
    private String account;

    @Resource
    private  CosSupplierConfiguration cosSupplierConfiguration;


    // 创建 TransferManager 实例,这个实例用来后续调用高级接口
    @Bean
    public TransferManager transferManager() {
        // 创建一个 COSClient 实例,这是访问 COS 服务的基础实例。
        // 详细代码参见本页: 简单操作 -> 创建 COSClient
        COSClient cosClient = getCosClinet();

        // 自定义线程池大小,建议在客户端与 COS 网络充足(例如使用腾讯云的 CVM,同地域上传 COS)的情况下,设置成16或32即可,可较充分的利用网络资源
        // 对于使用公网传输且网络带宽质量不高的情况,建议减小该值,避免因网速过慢,造成请求超时。
        ExecutorService threadPool = Executors.newFixedThreadPool(16);

        // 传入一个 threadpool, 若不传入线程池,默认 TransferManager 中会生成一个单线程的线程池。
        TransferManager transferManager = new TransferManager(cosClient, threadPool);

        // 设置高级接口的配置项
        // 分块上传阈值和分块大小分别为 5MB 和 1MB
        TransferManagerConfiguration transferManagerConfiguration = new TransferManagerConfiguration();
        transferManagerConfiguration.setMultipartUploadThreshold(1024 * 1024);
        transferManagerConfiguration.setMinimumUploadPartSize(5 * 1024 * 1024);
        transferManager.setConfiguration(transferManagerConfiguration);

        return transferManager;
    }


    public COSClient getCosClinet() {
        // 1 初始化用户身份信息(secretId, secretKey)。
        // SECRETID和SECRETKEY请登录访问管理控制台 https://console.cloud.tencent.com/cam/capi 进行查看和管理
        COSCredentials cred = new BasicCOSCredentials(cosSupplierConfiguration.getSecretId(), cosSupplierConfiguration.getSecretKey());
        // 2 设置 bucket 的地域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224
        // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。
        Region r = new Region(cosSupplierConfiguration.getRegion());
        ClientConfig clientConfig = new ClientConfig(r);
        // 这里建议设置使用 https 协议
        // 从 5.6.54 版本开始,默认使用了 https
        clientConfig.setHttpProtocol(HttpProtocol.https);
        // 3 生成 cos 客户端。
        return new COSClient(cred, clientConfig);
    }


}

在appliaction 中添加cos配置

#腾讯云存储
cos:
  secretId: 
  secretKey: 
  region:
  account: 
  buck-name:

下面为映射配置值的实体类

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "cos")
public class CosSupplierConfiguration {


    private String secretId;


    private String secretKey;


    private String region;


    private String account;

    private String buckName;

}

controller层:

通过请求头拿到图片大小,或者使用 ServerHttpRequest request 直接获取请求对象

    @ApiOperation(value = "上传文件")
    @PostMapping(value = "/upload", produces = MediaType.APPLICATION_JSON_VALUE)
    public Mono<ApiEntity<String>> upload(@RequestPart("file") FilePart file, @RequestHeader(value = "Content-Length", required = false) long contentLength) {
        if (contentLength > Constants.Cos.FILE_UPLOAD_MAX_SIZE) {
            return Mono.error(new ApiException("文件大小不能超过10M"));
        }
        return fileService.upload(file).switchIfEmpty(Mono.error(new ApiException("文件上传失败"))).map(ApiEntity::ok);
    }
service层:

这里没有使用 @Autowired 或者@Resource 获得bean 而是构造方法 加final 的方式

很坑的地方:FilePart 是一个接口 在网上搜了很多都是 都是先通过创建临时文件 然后再通过临时File 上传到远程仓库,要通过本地临时文件 中转一下 然后我就想用
FilePart.content() 拿到Inputstream 的方法直接上传到远程 但是上传的过程中 上传成功了但是 上传的文件一直损坏 然后我看了 content()方法 返回的是一个Flux 类型的DataBuffer 也就是 在接受的时候 文件已经被切块了,所以需要注意 在使用inputstream 的时候 一定要进行map,reduce操作 然后用SequenceInputStream 将流块 排序也就是对应代码下面的 map(DataBuffer::asInputStream).reduce(SequenceInputStream::new),不然上传的文件就只是一部分

import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.IdUtil;
import com.dotc.group.metaverse.sender.config.Constants;
import com.dotc.group.metaverse.sender.config.CosSupplierConfiguration;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.model.UploadResult;
import com.qcloud.cos.transfer.TransferManager;
import com.qcloud.cos.transfer.Upload;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.io.SequenceInputStream;
import java.util.Objects;
@Service
@AllArgsConstructor
@Slf4j
public class FileService {

    private final CosSupplierConfiguration supplierConfiguration;

    private final TransferManager transferManager;

    public Mono<String> upload(FilePart file) {
        String key = Constants.Cos.SIGNATURE + "/" + IdUtil.simpleUUID() + "." + FileNameUtil.getSuffix(file.filename());
        return file.content().map(DataBuffer::asInputStream).reduce(SequenceInputStream::new).flatMap(inputStream -> {
            ObjectMetadata objectMetadata = new ObjectMetadata();
            PutObjectRequest putObjectRequest = new PutObjectRequest(supplierConfiguration.getBuckName(), key, inputStream, objectMetadata);
            UploadResult uploadResult = null;
            try {
                // 高级接口会返回一个异步结果Upload
                // 可同步地调用 waitForUploadResult 方法等待上传完成,成功返回UploadResult, 失败抛出异常
                Upload upload = transferManager.upload(putObjectRequest);
                uploadResult = upload.waitForUploadResult();
            } catch (Exception e) {
                log.info("文件上传出错", e);
                return Mono.empty();
            }
            if (!Objects.isNull(uploadResult)) {
                return Mono.just(String.format(Constants.Cos.COS_PATH_FORMAT, supplierConfiguration.getBuckName(), supplierConfiguration.getRegion(), uploadResult.getKey()));
            }
            return Mono.empty();
        });
    }
}
  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值