☁️ UniApp 对象存储实战:腾讯云 COS 集成与图片上传优化 (RuoYi-Vue + UniApp)

图片上传是电商 App 中最常见的交互之一(如评价晒单、头像修改)。传统的本地文件存储在并发性能、带宽占用和 CDN 加速方面存在天然劣势。

本文将深入解析如何将 RuoYi-Vue 后端与 腾讯云 COS (Cloud Object Storage) 进行深度集成,并在 UniApp 端实现高性能的图片上传组件。

1. 为什么选择对象存储 (COS)?

相比于将文件存储在服务器本地磁盘,COS 优势明显:

  • 高可用性: 99.95% 的服务可用性,数据多重备份。
  • CDN 加速: 配合 CDN,用户查看图片几乎零延迟。
  • 带宽解耦: 上传/下载流量不占用应用服务器带宽,避免阻塞业务请求。
  • 成本低廉: 按量付费,存储和流量成本远低于云服务器磁盘。

2. 后端架构设计与实现

2.1 依赖引入与配置

首先,在 ruoyi-common/pom.xmlruoyi-system/pom.xml 中引入腾讯云 COS SDK:

<!-- 腾讯云 COS -->
<dependency>
    <groupId>com.qcloud</groupId>
    <artifactId>cos_api</artifactId>
    <version>5.6.89</version>
</dependency>

ruoyi-admin/src/main/resources/application.yml 中配置密钥信息:

# 腾讯云 COS 配置
cos:
  access_key: 'AKID****************************' # 替换为你的 SecretId
  secret_key: '********************************' # 替换为你的 SecretKey
  region_name: 'ap-guangzhou'                    # 存储桶地域
  bucket_name: 'ruoyi-vue-1250000000'            # 存储桶名称
  key_name: 'images'                             # 默认存储前缀

2.2 核心服务封装 (TencentCosService)

为了解耦业务,我们封装了 TencentCosService,屏蔽了底层的 SDK 细节,提供简洁的 uploadFile 接口。

// ruoyi-system/src/main/java/com/ruoyi/system/service/TencentCosService.java
@Service
public class TencentCosService {
    
    // ... 注入配置项 ...

    /**
     * 上传文件到 COS
     */
    public String uploadFile(MultipartFile file) throws IOException {
        // 1. 生成唯一文件名 (UUID + 扩展名)
        String originalFilename = file.getOriginalFilename();
        String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
        String cosKey = keyName + "/" + UUID.randomUUID().toString().replace("-", "") + extension;

        // 2. 初始化客户端
        COSClient cosClient = initCOSClient();
        
        try {
            // 3. 转换文件流并上传
            File tempFile = File.createTempFile("upload_", extension);
            file.transferTo(tempFile);
            PutObjectRequest request = new PutObjectRequest(bucketName, cosKey, tempFile);
            cosClient.putObject(request);
            
            // 4. 返回 CDN 访问链接
            return String.format("https://%s.cos.%s.myqcloud.com/%s", bucketName, regionName, cosKey);
        } finally {
            cosClient.shutdown();
        }
    }
}

2.3 控制层接口暴露

CommonController 中扩展 /common/upload 接口,使其支持透明切换到 COS 上传。

// ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
@PostMapping("/upload")
public AjaxResult uploadFile(MultipartFile file) throws Exception {
    try {
        // 切换为 COS 上传服务
        String fileUrl = tencentCosService.uploadFile(file);
        
        AjaxResult ajax = AjaxResult.success();
        ajax.put("url", fileUrl); // 返回完整 CDN 地址
        ajax.put("fileName", fileUrl.substring(fileUrl.lastIndexOf("/") + 1));
        return ajax;
    } catch (Exception e) {
        return AjaxResult.error(e.getMessage());
    }
}

3. 前端 UniApp 实现

前端需要处理跨平台兼容性(H5/微信小程序/App),并封装统一的上传逻辑。

3.1 上传工具类封装 (utils/upload.js)

我们封装了 uni.uploadFile,自动携带 Token 并统一处理错误。

// utils/upload.js
import config from '@/config'
import { getToken } from '@/utils/auth'

const upload = config => {
  return new Promise((resolve, reject) => {
    uni.uploadFile({
      url: config.baseUrl + config.url, // 完整后端地址
      filePath: config.filePath,        // 本地临时路径
      name: config.name || 'file',
      header: {
        Authorization: 'Bearer ' + getToken() // 鉴权 Token
      },
      success: (res) => {
        let result = JSON.parse(res.data)
        if (result.code === 200) {
          resolve(result)
        } else {
          reject(result.msg)
        }
      },
      fail: (error) => {
        reject('网络请求失败')
      }
    })
  })
}
export default upload

3.2 使用示例:头像上传

在用户个人中心 (pages/mine/info/index.vue) 中调用:

// 选择图片
chooseAvatar() {
  uni.chooseImage({
    count: 1,
    sizeType: ['compressed'], // 优先使用压缩图
    success: (res) => {
      const tempFilePath = res.tempFilePaths[0]
      this.uploadImage(tempFilePath)
    }
  })
},

// 上传逻辑
async uploadImage(filePath) {
  try {
    uni.showLoading({ title: '上传中...' })
    const res = await upload({
      url: '/common/upload',
      filePath: filePath
    })
    
    // 更新视图
    this.user.avatar = res.url
    // 提交到后端保存用户资料
    await updateUserProfile({ avatar: res.url })
    
    uni.showToast({ title: '上传成功' })
  } catch (e) {
    uni.showToast({ title: '上传失败', icon: 'none' })
  } finally {
    uni.hideLoading()
  }
}

4. 进阶优化:客户端直传 (Presigned URL)

为了进一步减轻应用服务器压力,可以使用 预签名 URL (Presigned URL) 方案:

  1. 前端请求签名: 前端向后端请求一个临时的上传授权 URL。
  2. 后端生成签名: 后端使用 COS SDK 生成带有效期的 PUT 请求签名。
  3. 前端直传 COS: 前端直接将文件 PUT 到腾讯云,绕过应用服务器。

(注:本文展示的方案为服务端中转模式,适合中小规模应用,实现简单且便于权限控制。)


总结:通过引入腾讯云 COS,我们成功解决了应用服务器的存储瓶颈。后端封装通用服务,前端统一上传工具,构建了一套高效、可维护的文件管理体系。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐