從零到壹搭建一個商城架構--文件存儲(SpringCloud Alibaba-OSS)

1、簡介

對象存儲服務(Object Storage Service,OSS)是一種海量、安全、低成本、高可靠的雲存儲服務,適合存放任務類型的文件。容量和處理能力彈性擴展,多種存儲類型供選擇,全面優化存儲成本。

2、文件存儲形式

文件存儲形式主要是存儲的位置不同,一個是存儲到我們自己的服務器,另一個是存儲到雲服務器,比如阿里雲存儲,七牛等一些雲存儲。我們系統中主要是使用雲存儲的方式,這樣是比較好擴展的,而且現在的存儲文件的服務器也比較便宜,如果放在我們本地服務器,後面存儲的文件增多了,還要擴容,比較麻煩,所以我建議放到雲存儲上,主要的對比圖如下:
在這裏插入圖片描述

3、阿里雲的存儲服務

  • 第一種方式
    這種上傳的方式是:直接把文件上傳到我們的服務器,再由我們服務器上傳到雲存儲上,這樣我們服務器的壓力會比較大,所以不推薦,這裏主要是說明我們以前上傳文件的形式。
    在這裏插入圖片描述
  • 第二種方式
    這種方式是:我們要上傳文件的時候,用戶先要嚮應用服務器請求上傳簽名,然後直接上傳到雲存儲服務器上,文件不經過我們服務器,這樣服務器壓力會小很多。
    在這裏插入圖片描述
    服務端簽名後直傳的原理如下:
  1. 用戶發送上傳Policy請求到應用服務器。
  2. 應用服務器返回上傳Policy和簽名給用戶。
  3. 用戶直接上傳數據到OSS。

4、在阿里雲上申請祕鑰

具體申請祕鑰的操作查看官網文檔

我們申請完密鑰後必須當時就要記錄下來,只顯示一次,後面要用到。
一般情況下,我們需要創建一個子用戶,給他授權操作所有文件存儲的權限

5、在程序中使用

  • 前提條件

    應用服務器對應的域名可通過公網訪問。

    確保應用服務器已經安裝Java 1.6以上版本(執行命令java -version進行查看)。

    確保PC端瀏覽器支持JavaScript。

  • 首先我們創建一個工程,這個工程專門用來集成第三方應用的工程,我起名叫hslmall-third-party

  • 創建好後在pom文件中加入如下依賴

<dependencies>
<dependency>
    <!--加入公共依賴-->
	<groupId>com.hsl.halmall</groupId>
		<artifactId>hslmall-common</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <exclusions>
			<exclusion>
				<groupId>com.baomidou</groupId>
				<artifactId>mybatis-plus-boot-starter</artifactId>
			</exclusion>
		</exclusions>
	</dependency>
    <!--阿里雲存儲-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
    </dependency>
</dependencies>
<dependencyManagement>
            <!--阿里管理 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
  • 創建application.yml文件,加入如下內容
server:
  port: 30000
spring:
  application:
    name: hslmall-third-party
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  • 在nacos註冊中心和配置中心配置相關內容

    • 創建第三方集成的命名空間 thirdParty,並把生成的 862b6e4c-3c31-490f-bddf-c419e2be76b9配置到我們的bootstrap.properties文件中

    • 在配置列表的thirdParty添加oss.yml配置

      spring:
        cloud:
          alicloud:
            access-key: 自己申請的access-key
            secret-key: 自己申請的secret-key
            oss:
              endpoint: oss-cn-beijing.aliyuncs.com
              bucket: hslmall
      
  • 新建bootstrap.properties文件,加入如下內容

spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=862b6e4c-3c31-490f-bddf-c419e2be76b9
spring.application.name=hslmall-third-party

spring.cloud.nacos.config.extension-configs[0].data-id=oss.yml
spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[0].refresh=true
  • 在啓動主程序中添加**@EnableDiscoveryClient**註解,註冊到註冊中心

  • 創建一個controller

    package com.hsl.hslmall.thirdparty.controller;
    
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.common.utils.BinaryUtil;
    import com.aliyun.oss.model.MatchMode;
    import com.aliyun.oss.model.PolicyConditions;
    import com.hsl.hslmall.common.utils.R;
    import lombok.extern.log4j.Log4j2;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    @RequestMapping("oss")
    @RestController
    @Log4j2
    public class OSSController {
    
        @Autowired
        OSS ossClient;
    
        @Value("${spring.cloud.alicloud.oss.endpoint}")
        private String endpoint;
        @Value("${spring.cloud.alicloud.access-key}")
        private String accessId;
        @Value("${spring.cloud.alicloud.secret-key}")
        private String secretKey;
        @Value("${spring.cloud.alicloud.oss.bucket}")
        private String bucket;
    
    
        /**
         * 上傳文件之前,需要先請求一個簽名,然後在直接上傳到阿里雲上
         * @return
         */
        @GetMapping("/policy")
        public R policy(){
    
            //host的格式爲bucketname.endpoint
            String host="https://"+bucket+"."+endpoint;
            SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
            //用戶上傳文件時指定的前綴
            String dir=sdf.format(new Date())+"/";
    
            Map<String,String> respMap=null;
            try {
                long expireTime=30;
                long expireEndTime=System.currentTimeMillis()+expireTime*1000;
                Date expiration=new Date(expireEndTime);
                PolicyConditions policyConditions=new PolicyConditions();
                //設置上傳文件的大小,這個大小可以自己設定
                policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE,0,1048576000);
                //設置保存文件的文件夾
                policyConditions.addConditionItem(MatchMode.StartWith,PolicyConditions.COND_KEY,dir);
    
                String postPolicy=ossClient.generatePostPolicy(expiration,policyConditions);
                byte[] binaryPolicy=postPolicy.getBytes("utf-8");
                String encodedPolicy= BinaryUtil.toBase64String(binaryPolicy);
                String postSignature=ossClient.calculatePostSignature(postPolicy);
    
                respMap=new LinkedHashMap<>();
                respMap.put("accessid",accessId);
                respMap.put("policy",encodedPolicy);
                respMap.put("signature",postSignature);
                respMap.put("dir",dir);
                respMap.put("host",host);
                respMap.put("expire",String.valueOf(expireEndTime/1000));
    
            }catch (Exception e){
                log.error("獲取上傳文件簽名失敗,錯誤信息:"+e.getMessage());
                e.printStackTrace();
            }
            return R.ok().put("data",respMap);
        }
    }
    
  • 我們配置網關

    spring:
      application:
        name: hslmall-gateway
      cloud:
        nacos:
          # 配置註冊中心的地址和命名空間
          discovery:
            server-addr: 127.0.0.1:8848
    #        namespace: 52a5124d-5f65-4b41-bdca-01caacb3f3c4
            # 配置網關路由信息
        gateway:
          routes:
            # 配置第三方服務路由
            - id: third_party_route
              uri: lb://hslmall-third-party
              predicates:
                - Path=/api/thirdparty/**
              filters:
                - RewritePath=/api/thirdparty/(?<segment>.*), /$\{segment}
    

我們的訪問路徑是:localhost:88/api/thirdparty/oss/policy,啓動註冊中心,網關和第三方服務

  • 前端vue使用element-ui的upoad組件

    • singleUpload.vue 單文件上傳
      複製這個文件內容到自己本地/components/upload/下,後面頁面會應用這個組件
    <template> 
      <div>
        <el-upload
          action="https://hslmall.oss-cn-beijing.aliyuncs.com"
          :data="dataObj"
          list-type="picture"
          :multiple="false" :show-file-list="showFileList"
          :file-list="fileList"
          :before-upload="beforeUpload"
          :on-remove="handleRemove"
          :on-success="handleUploadSuccess"
          :on-preview="handlePreview">
          <el-button size="small" type="primary">點擊上傳</el-button>
          <div slot="tip" class="el-upload__tip">只能上傳jpg/png文件,且不超過10MB</div>
        </el-upload>
        <el-dialog :visible.sync="dialogVisible">
          <img width="100%" :src="fileList[0].url" alt="">
        </el-dialog>
      </div>
    </template>
    <script>
       import {policy} from './policy'
       import { getUUID } from '@/utils'
    
      export default {
        name: 'singleUpload',
        props: {
          value: String
        },
        computed: {
          imageUrl() {
            return this.value;
          },
          imageName() {
            if (this.value != null && this.value !== '') {
              return this.value.substr(this.value.lastIndexOf("/") + 1);
            } else {
              return null;
            }
          },
          fileList() {
            return [{
              name: this.imageName,
              url: this.imageUrl
            }]
          },
          showFileList: {
            get: function () {
              return this.value !== null && this.value !== ''&& this.value!==undefined;
            },
            set: function (newValue) {
            }
          }
        },
        data() {
          return {
            dataObj: {
              policy: '',
              signature: '',
              key: '',
              ossaccessKeyId: '',
              dir: '',
              host: '',
              // callback:'',
            },
            dialogVisible: false
          };
        },
        methods: {
          emitInput(val) {
            this.$emit('input', val)
          },
          handleRemove(file, fileList) {
            this.emitInput('');
          },
          handlePreview(file) {
            this.dialogVisible = true;
          },
          beforeUpload(file) {
            let _self = this;
            return new Promise((resolve, reject) => {
              policy().then(response => {
                _self.dataObj.policy = response.data.policy;
                _self.dataObj.signature = response.data.signature;
                _self.dataObj.ossaccessKeyId = response.data.accessid;
                _self.dataObj.key = response.data.dir+getUUID()+'_${filename}';
                _self.dataObj.dir = response.data.dir;
                _self.dataObj.host = response.data.host;
                resolve(true)
              }).catch(err => {
                reject(false)
              })
            })
          },
          handleUploadSuccess(res, file) {
            console.log("上傳成功...")
            this.showFileList = true;
            this.fileList.pop();
            this.fileList.push({name: file.name, url: this.dataObj.host + '/' + this.dataObj.key.replace("${filename}",file.name) });
            this.emitInput(this.fileList[0].url);
          }
        }
      }
    </script>
    <style>
    
    </style>
    
  • 多文件上傳

<template>
  <div>
    <el-upload
      action="https://hslmall.oss-cn-beijing.aliyuncs.com"
      :data="dataObj"
      list-type="picture-card"
      :file-list="fileList"
      :before-upload="beforeUpload"
      :on-remove="handleRemove"
      :on-success="handleUploadSuccess"
      :on-preview="handlePreview"
      :limit="maxCount"
      :on-exceed="handleExceed"
    >
 <i class="el-icon-plus"></i>
    </el-upload>
    <el-dialog :visible.sync="dialogVisible">
      <img width="100%" :src="dialogImageUrl" alt />
    </el-dialog>
  </div>
</template>
<script>
import { policy } from "./policy";
import { getUUID } from '@/utils'
export default {
  name: "multiUpload",
  props: {
    //圖片屬性數組
    value: Array,
    //最大上傳圖片數量
    maxCount: {
      type: Number,
      default: 30
    }
  },
  data() {
    return {
      dataObj: {
        policy: "",
        signature: "",
        key: "",
        ossaccessKeyId: "",
        dir: "",
        host: "",
        uuid: ""
      },
      dialogVisible: false,
      dialogImageUrl: null
    };
  },
  computed: {
    fileList() {
      let fileList = [];
      for (let i = 0; i < this.value.length; i++) {
        fileList.push({ url: this.value[i] });
      }

      return fileList;
    }
  },
  mounted() {},
  methods: {
    emitInput(fileList) {
      let value = [];
      for (let i = 0; i < fileList.length; i++) {
        value.push(fileList[i].url);
      }
      this.$emit("input", value);
    },
    handleRemove(file, fileList) {
      this.emitInput(fileList);
    },
    handlePreview(file) {
      this.dialogVisible = true;
      this.dialogImageUrl = file.url;
    },
    beforeUpload(file) {
      let _self = this;
      return new Promise((resolve, reject) => {
        policy()
          .then(response => {
            console.log("這是什麼${filename}");
            _self.dataObj.policy = response.data.policy;
            _self.dataObj.signature = response.data.signature;
            _self.dataObj.ossaccessKeyId = response.data.accessid;
            _self.dataObj.key = response.data.dir + "/"+getUUID()+"_${filename}";
            _self.dataObj.dir = response.data.dir;
            _self.dataObj.host = response.data.host;
            resolve(true);
          })
          .catch(err => {
            console.log("出錯了...",err)
            reject(false);
          });
      });
    },
    handleUploadSuccess(res, file) {
      this.fileList.push({
        name: file.name,
        // url: this.dataObj.host + "/" + this.dataObj.dir + "/" + file.name; 替換${filename}爲真正的文件名
        url: this.dataObj.host + "/" + this.dataObj.key.replace("${filename}",file.name)
      });
      this.emitInput(this.fileList);
    },
    handleExceed(files, fileList) {
      this.$message({
        message: "最多隻能上傳" + this.maxCount + "張圖片",
        type: "warning",
        duration: 1000
      });
    }
  }
};
</script>
<style>
</style>
  • policy.js 引用的js
import http from '@/utils/httpRequest.js'
export function policy () {
  return new Promise((resolve, reject) => {
    http({
      url: http.adornUrl('/thirdparty/oss/policy'),
      method: 'get',
      params: http.adornParams({})
    }).then(({ data }) => {
      resolve(data)
    })
  })
}
  • 在我們的頁面中使用,並引用剛纔寫的組件

    <template>
      <el-dialog
        :title="!dataForm.id ? '新增' : '修改'"
        :close-on-click-modal="false"
        :visible.sync="visible">
        <el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="140px">
        <el-form-item label="品牌名" prop="name">
          <el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
        </el-form-item>
        <el-form-item label="品牌logo地址" prop="logo">
          <!-- <el-input v-model="dataForm.logo" placeholder="品牌logo地址"></el-input> -->
          <singleUpload v-model="dataForm.logo"></singleUpload>
        </el-form-item>
        <el-form-item label="介紹" prop="descript">
          <el-input v-model="dataForm.descript" placeholder="介紹"></el-input>
        </el-form-item>
        <el-form-item label="顯示狀態" prop="showStatus">
          <el-switch v-model="dataForm.showStatus" :active-value="1" :inactive-value="0" active-color="#13ce66" inactive-color="#ff4949"></el-switch>
          <!-- <el-input v-model="dataForm.showStatus" placeholder="顯示狀態">!-->
        </el-form-item>
        <el-form-item label="檢索首字母" prop="firstLetter">
          <el-input v-model="dataForm.firstLetter" placeholder="檢索首字母"></el-input>
        </el-form-item>
        <el-form-item label="排序" prop="sort">
          <el-input v-model="dataForm.sort" placeholder="排序"></el-input>
        </el-form-item>
        </el-form>
        <span slot="footer" class="dialog-footer">
          <el-button @click="visible = false">取消</el-button>
          <el-button type="primary" @click="dataFormSubmit()">確定</el-button>
        </span>
      </el-dialog>
    </template>
    
    <script>
      import singleUpload from '@/components/upload/singleUpload';
      export default {
        data () {
          return {
            visible: false,
            dataForm: {
              brandId: 0,
              name: '',
              logo: '',
              descript: '',
              showStatus: '',
              firstLetter: '',
              sort: ''
            },
            dataRule: {
              name: [
                { required: true, message: '品牌名不能爲空', trigger: 'blur' }
              ],
              logo: [
                { required: true, message: '品牌logo地址不能爲空', trigger: 'blur' }
              ],
              descript: [
                { required: true, message: '介紹不能爲空', trigger: 'blur' }
              ],
              showStatus: [
                { required: true, message: '顯示狀態不能爲空', trigger: 'blur' }
              ],
              firstLetter: [
                { required: true, message: '檢索首字母不能爲空', trigger: 'blur' }
              ],
              sort: [
                { required: true, message: '排序不能爲空', trigger: 'blur' }
              ]
            }
          }
        },
        components:{
          singleUpload
        },
        methods: {
          init (id) {
            this.dataForm.brandId = id || 0
            this.visible = true
            this.$nextTick(() => {
              this.$refs['dataForm'].resetFields()
              if (this.dataForm.brandId) {
                this.$http({
                  url: this.$http.adornUrl(`/product/brand/info/${this.dataForm.brandId}`),
                  method: 'get',
                  params: this.$http.adornParams()
                }).then(({data}) => {
                  if (data && data.code === 0) {
                    this.dataForm.name = data.brand.name
                    this.dataForm.logo = data.brand.logo
                    this.dataForm.descript = data.brand.descript
                    this.dataForm.showStatus = data.brand.showStatus
                    this.dataForm.firstLetter = data.brand.firstLetter
                    this.dataForm.sort = data.brand.sort
                  }
                })
              }
            })
          },
          // 表單提交
          dataFormSubmit () {
            this.$refs['dataForm'].validate((valid) => {
              if (valid) {
                this.$http({
                  url: this.$http.adornUrl(`/product/brand/${!this.dataForm.brandId ? 'save' : 'update'}`),
                  method: 'post',
                  data: this.$http.adornData({
                    'brandId': this.dataForm.brandId || undefined,
                    'name': this.dataForm.name,
                    'logo': this.dataForm.logo,
                    'descript': this.dataForm.descript,
                    'showStatus': this.dataForm.showStatus,
                    'firstLetter': this.dataForm.firstLetter,
                    'sort': this.dataForm.sort
                  })
                }).then(({data}) => {
                  if (data && data.code === 0) {
                    this.$message({
                      message: '操作成功',
                      type: 'success',
                      duration: 1500,
                      onClose: () => {
                        this.visible = false
                        this.$emit('refreshDataList')
                      }
                    })
                  } else {
                    this.$message.error(data.msg)
                  }
                })
              }
            })
          }
        }
      }
    </script>
    
    

6、遇到的問題

  • 跨域請求報錯

    根據官方文檔,配置跨域相關的內容

  • 上傳文件報400 Bad Response,返回內容提示上傳內容超過最大限制

    糾結了半小時,不知道哪裏有問題,上網查也沒查到,最後在官網中也有碰到類似問題的,囉裏囉嗦一大堆,也沒解決,最後看到我寫的controller中的代碼是自己敲的,有個配置

    policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE,0,1048576000);
    

    這是配置上傳文件的大小的,我當時寫的最大是1,鬱悶了,後來根據官網實例,寫成了和它官網一樣的就可以了,

說明:

respMap.put("accessid",accessId);
respMap.put("policy",encodedPolicy);
respMap.put("signature",postSignature);
respMap.put("dir",dir);
respMap.put("host",host);
respMap.put("expire",String.valueOf(expireEndTime/1000));

返回的簽名數據的key和value一定不能寫錯,否則無法上傳
好了,以上就是在繼承OOS服務到我們系統中的過程,希望有使用同樣方法的猿們看到可以有共鳴

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章