从零实现SpringBoot + VUE前后端分离项目中使用腾讯云COS技术!

0、前言

在实际项目开发过程中,通常会有许多用户上传文件的真实场景需求,比如最常见的就是用户头像的上传,那么这些文件应该存储到哪里?如何存储?显然可以存储到我们运行应用程序的服务器中,但是这必然会占用很多服务器存储空间,另外就是在分布式微服务开发时,我们多个程序运行在不同的服务器上,那么就需要对这些文件最好有个专门的存储位置以便使用。所以就有了各种各样的云存储,当然我们国内的大公司也都提供了对应的云服务比如可以使用阿里云的OSS和腾讯云的COS等,还有比如又拍云、七牛云等特供的云存储服务。

在这里,我基于SpringBoot + VUE前后端分离项目,演示如何结合腾讯云的COS云存储服务。由于不涉及任何业务逻辑,所以整体下来非常简单,前端项目不需要router、store、Axios等,只需要一个上传头像的页面,后端不需要对接数据库,只是简单的写个对接腾讯云COS和前端的接口。文章将会从零搭建项目开始,只要按照步骤一步一步做下来,小白也可以run起来哦,废话不多说,开始!

1、环境准备

大家都知道,开发环境不同或是采用的某一框架的版本不一致都很有可能是BUG的来源,所以如果在自己电脑测试,尽量确保版本和我这里的一致,一旦出现问题,多留意是不是环境不同,版本不一致造成的,切记不可盲目去百度直接各种Copy,结果未必能解决问题,反而跑偏方向,带来新的其他问题,在网络上查找技术解决方案时,多留意版本与环境问题!

1.1前端

注意:NPM是随同NodeJS一起安装的包管理工具,无需额外下载安装。

1.2后端

1.3工具

  • OS:Windows 10 专业版

  • 前端IDE:Visual Studio Code (version 1.59)

  • 后端IDE: IntelliJ IDEA(2021.1.2)

 

在下面的内容中,有关代码部分,首先大部分都是直接放得最终运行的代码,虽然在编码中遇到了各种各样大大小小的坑,在处理过程中,未能完全记录其过程,但是几乎都是又注释标注的,方便大家学习。其次代码肯定不是绝对完美的,针对不同的问题,有不同的解决方案,如果有问题希望大佬指正。

2、前端开发

注意:大家需要在自己电脑提前安装好Node.js、npm、以及vueCLI,并确保正确添加到环境变量。这部分比较简单,这里不再演示。

 

2.1 使用VUECLI脚手架搭建项目

  • 新建项目文件夹COSDemo

  • 在控制台中进入项目文件夹COSDemo下执行命令:

    $ vue create cosvue

  • 然后依次进行如下选择:

    • Manually select features

    • (*) Babel

    • 2.x

    • In package.json

    • N

  • 等待项目安装相关依赖

  • 根据提示依次执行以下命令

 $ cd cos
 $ npm run serve

等待项目启动,然后浏览器访问 http://localhost:8080/

看到以上界面就说明前端项目搭建完成了。

2.2安装Element UI

使用Visual Studio Code打开项目文件夹,然后进行如下操作。

  • 在控制台中执行如下命令:

  • npm i element-ui -S
  • 修改项目主配置文件main.js
  • import Vue from 'vue'
    import App from './App.vue'
    // 引入ElementUI
    import ElementUI from 'element-ui';
    import 'element-ui/lib/theme-chalk/index.css';
    
    // 注册到Vue
    Vue.use(ElementUI);
    
    Vue.config.productionTip = false
    
    new Vue({
      render: h => h(App),
    }).$mount('#app')

最后前端项目目录结构:

2.3修改App.vue组件

为了方便起见,我们这里就直接在主页面上添加一个表单模拟添加用户信息,以及重点是上传头像到后端,然后后端对接腾讯云的COS服务,将我们的文件存储到腾讯云。所以,我这里直接在前端项目的App.vue文件中添加基础表单即可,也就意味着路由什么的根本用不到,修改后的App.vue代码如下,其他页面组件都可以删除比如components等文件夹。

<template>
  <div id="app">
    <el-card shadow="hover">
      <el-form ref="form" :model="form" >
      <el-form-item label="头像">
        <el-upload
          class="avatar-uploader"
          action="http://localhost:8088/file/upload"
          :show-file-list="false"
          :on-success="handleAvatarSuccess"
          :before-upload="beforeAvatarUpload">
          <img v-if="imageUrl" :src="imageUrl" class="avatar">
          <i v-else class="el-icon-plus avatar-uploader-icon"></i>
        </el-upload>
        </el-form-item>
        <el-form-item label="用户名">
          <el-input v-model="form.username"></el-input>
        </el-form-item>
        <el-form-item label="密 码">
          <el-input v-model="form.password"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="onSubmit" disabled>注册</el-button>
          <!-- <el-button>取消</el-button> -->
        </el-form-item>

        <el-tag type="danger">这里其实只需要一个上传头像即可</el-tag>
        <el-tag type="danger">为了丰富内容,就添加了用户名和密码</el-tag>
        <el-tag type="danger">但是并没有对接后端接口</el-tag><br>
        <el-tag type="success">😂😂😂😂😂😂</el-tag>
        
      </el-form>
    </el-card>
  </div>
</template>

<style>
  .avatar-uploader .el-upload {
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
  }
  .avatar-uploader .el-upload:hover {
    border-color: #409EFF;
  }
  .avatar-uploader-icon {
    font-size: 28px;
    color: #8c939d;
    width: 178px;
    height: 178px;
    line-height: 178px !important;
    text-align: center;
  }
  .avatar {
    width: 178px;
    height: 178px;
    display: block;
  }

  #app {
  width: 400px;
  height: 600px;
  margin: 100px auto;
  font-family: Avenir, Helvetica, Arial, sans-serif;
  text-align: center;
}
</style>
<script>
  export default {
    data() {
      return {
        form: {
          username: '',
          password: ''
        },
        imageUrl: ''
      }
    },
    methods: {
      onSubmit() {
        console.log('submit!');
      },
      handleAvatarSuccess(res, file) {
        // this.imageUrl = URL.createObjectURL(file.raw);
        this.imageUrl = res;
        this.$message.success("上传成功!");
      },
      beforeAvatarUpload(file) {
        const isJPGorPNG = file.type === 'image/jpeg' || file.type === 'image/png';
        const isLt2M = file.size / 1024 / 1024 < 2;

        if (!isJPGorPNG) {
          this.$message.error('上传头像图片只能是 JPG 和 PNG 格式!');
        }
        if (!isLt2M) {
          this.$message.error('上传头像图片大小不能超过 2MB!');
        }
        return isJPGorPNG && isLt2M;
      }
    }
  }
</script>

3、后端开发

3.0腾讯云对象存储介绍

对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。

官网地址:对象存储数据处理COS数据处理数据处理方案-腾讯云 (tencent.com)

3.1开通COS服务

(1)申请腾讯云账号:https://cloud.tencent.com/

(2)实名认证

(3)搜索COS然后开通“对象存储COS”服务

(4)进入管理控制台

(5)创建Bucket

  • 进入管理控制台,找到存储桶列表, 创建存储桶

  • 输入桶名称,选择:公有读取,其他默认

  • 点击 桶名称,进入详情页,可测试上传文件

(6)创建API秘钥

  • 进入API秘钥管理

  • 新建秘钥

  • 保存key 和 id 备用

3.2搭建后端项目

在IDEA中快速搭建Spring Initializr项目。

引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- 腾讯云COS依赖 -->
    <dependency>
        <groupId>com.qcloud</groupId>
        <artifactId>cos_api</artifactId>
        <version>5.6.89</version>
    </dependency>
    <!-- 日期工具栏依赖 -->
    <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
        <version>2.9.9</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.76</version>
    </dependency>
</dependencies>

 

配置文件

server.port=8088

spring.servlet.multipart.max-file-size=1024MB
spring.servlet.multipart.max-request-size=1024MB

tencent.cos.region=你选择的地区
tencent.cos.secretid=刚才记录的id
tencent.cos.secretkey=刚才记录的key
tencent.cos.bucketname=你设置的bucketName

 

读取配置工具类(非必须)

package cn.imyjs.utils;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @Classname COSUtils
 * @Description 读取配置文件application.properties中的配置
 * @Date 2022/8/14 16:31
 * @Created by YJS
 * @WebSite www.imyjs.cn
 */
@Component
public class COSUtils implements InitializingBean {
    @Value("${tencent.cos.region}")
    private String region;

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

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

    @Value("${tencent.cos.bucketname}")
    private String bucketName;

    public static String END_POINT;
    public static String ACCESS_KEY_ID;
    public static String ACCESS_KEY_SECRET;
    public static String BUCKET_NAME;

    @Override
    public void afterPropertiesSet() throws Exception {
        END_POINT = region;
        ACCESS_KEY_ID = secretId;
        ACCESS_KEY_SECRET = secretKey;
        BUCKET_NAME = bucketName;
    }
}

 

Service层代码

FileService接口

package cn.imyjs.service;

import org.springframework.web.multipart.MultipartFile;

/**
 * @Classname FileService
 * @Description TODO
 * @Date 2022/8/14 16:39
 * @Created by YJS
 * @WebSite www.imyjs.cn
 */
public interface FileService {
    String upload(MultipartFile file);
}

FileServiceImpl实现类

package cn.imyjs.service.impl;

import cn.imyjs.service.FileService;
import cn.imyjs.utils.COSUtils;
import com.alibaba.fastjson.JSON;
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.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.model.PutObjectResult;
import com.qcloud.cos.region.Region;
import org.joda.time.DateTime;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.UUID;

/**
 * @Classname FileServiceImpl
 * @Description TODO
 * @Date 2022/8/14 16:40
 * @Created by YJS
 * @WebSite www.imyjs.cn
 */
@Service
public class FileServiceImpl implements FileService {

    @Override
    public String upload(MultipartFile file) {
       
        String endpoint = COSUtils.END_POINT;
        String bucketName = COSUtils.BUCKET_NAME;
        String secretId = COSUtils.ACCESS_KEY_ID;
        String secretKey = COSUtils.ACCESS_KEY_SECRET;
        COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);

        // 2 设置 bucket 的地域
        // clientConfig 中包含了设置 region, https(默认 http),超时, 代理等 set 方法
        Region region = new Region(COSUtils.END_POINT);
        ClientConfig clientConfig = new ClientConfig(region);
        // 这里建议设置使用 https 协议
        // 从 5.6.54 版本开始,默认使用了 https
        clientConfig.setHttpProtocol(HttpProtocol.https);
        // 3 生成 cos 客户端。
        COSClient cosClient = new COSClient(cred, clientConfig);

        try{
            // 指定要上传的文件
            InputStream inputStream = file.getInputStream();
            // 指定文件将要存放的存储桶
            // 指定文件上传到 COS 上的路径,即对象键。
            String key = UUID.randomUUID().toString().replaceAll("-","")+
                    file.getOriginalFilename();
            String dateUrl = new DateTime().toString("yyyy/MM/dd");
            key = dateUrl+"/"+key;

            ObjectMetadata objectMetadata = new ObjectMetadata();
            PutObjectRequest putObjectRequest =
                    new PutObjectRequest(bucketName, key, inputStream,objectMetadata);
            PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
            System.out.println(JSON.toJSONString(putObjectResult));
            String url = "https://"+bucketName+"."+"cos"+"."+endpoint+".myqcloud.com"+"/"+key;
            return url;
        } catch (Exception clientException) {
            clientException.printStackTrace();
            return null;
        }
    }
}

 

Controller层代码

package cn.imyjs.controller;

import cn.imyjs.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

/**
 * @Classname FileContrller
 * @Description TODO
 * @Date 2022/8/14 16:45
 * @Created by YJS
 * @WebSite www.imyjs.cn
 */
@RestController
@CrossOrigin
@RequestMapping("file")
public class FileController {
    @Autowired
    private FileService fileService;

    @PostMapping("upload")
    public String uploadFile(MultipartFile file){
        return fileService.upload(file);
    }
}

 

4、测试完成

下载DEMO

download
来源:默认下载

微信关注

编程那点事儿

阅读剩余
THE END