谷粒商城七商品服务品牌管理之oss文件存储

使用renren-generator生成crud页面

todo谷粒商城二本地虚拟机环境搭建及项目初始化在逆向工程的时候,resources下有一个view文件夹,下面都是可以直接使用的vue文件,我们将它复制到renren-fast-vue中就可以直接生成crud界面。

我们在系统管理–菜单管理下新增,上级菜单是商品系统,名称是品牌管理,url是product/brand

然后将我们逆向生成的brand.vue和brand-add-or-update.vue复制到我们项目的src--view--modules--product

此时就生成了和这个项目其他界面相似的带有crud的界面,

在这里插入图片描述

但是刚进去我们是看不到新增和批量删除按钮的,是因为权限的问题,将src--utils--index.js的isAuth方法返回true

开通阿里云对象存储 oss

在这里插入图片描述

  • 在单体应用,部署在了一台服务器上,收到前端的请求后保存到单体项目的某个位置。

  • 分布式应用,文件上传到一台服务器,下次前端请求到另一台服务器的时候,就获取不到;解决这个问题,无论前端的请求落到后端的哪台服务器上,我们都将文件存储在文件系统中

文件系统可以自己搭建,也可以使用第三方做好的云存储功能,

阿里云官网找这个服务开通,找API参考

资源术语

  • Bucket,存储空间,存储对象的容器,所有对象都必须属于某个存储空间,我们一般一个项目对应一个存储空间。
  • Object,对象/文件
  • Region,地域,在创建存储空间的时候可以选择存储服务器的地域,
  • Endpoint,访问域名
  • AccessKey,密钥

创建Bucket

在阿里云对象存储 oss创建,存储类型选择低频访问存储,因为我们这是学习,不需要太高的效率啥的,
读写权限设置公共读,代表任何人都可以读,写的时候是需要带密钥的

设置跨域请求

选中你的bucket–数据安全–跨域设置–创建规则

来源 *
允许Method POST
允许headers *

阿里云对象上传方式

普通上传方式

用户上传文件–我们的服务拿到文件–我们的服务将文件上传到oss
在这里插入图片描述

不能直接在浏览器端直接上传到oss,会泄露我们的oss的地址,账号密码。

服务端签名后直传

前端发起文件上传的请求,先找我们的服务器请求一个上传策略,服务器利用阿里云的账号密码生成一个防伪的签名,包含oss授权令牌,以及上传位置等信息
然后前端拿着签名和文件直接上传到oss,oss可以验证签名的正确性,我们使用这个
在这里插入图片描述

测试简单文件上传

<dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.15.0</version>
        </dependency>

需要修改的参数有

  1. endpoint,bucket的概览,选择外网访问的那个
  2. accessKeyId,accessKeySecret

需要开通阿里云个人的accessKey,在登录之后的头像那儿,开通后创建子账户,选中open api调用访问,创建成功之后就会有accessKeyId和accessKeySecret

新创建的用户是没有权限的,我们需要添加AliyunOSSFullAccess,oss的所有权限

  1. bucketName,objectName(带后缀名的文件名称)
  2. filePath,上传文件的完整路径
package com.atlinxi.gulimall.product;

import com.atlinxi.gulimall.product.service.BrandService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;

import java.io.FileInputStream;
import java.io.InputStream;


@SpringBootTest
class GulimallProductApplicationTests {

    @Autowired
    private BrandService brandService;

    @Test
    void contextLoads() throws Exception{


        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "oss-cn-beijing.aliyuncs.com";
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        String accessKeyId = "yourAccessKeyId";
        String accessKeySecret = "yourAccessKeySecret";
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "examplebucket";
        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
        String objectName = "exampledir/exampleobject.txt";
        // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
        // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
        String filePath = "D:\\localpath\\examplefile.txt";

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            InputStream inputStream = new FileInputStream(filePath);
            // 创建PutObject请求。
            ossClient.putObject(bucketName, objectName, inputStream);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }


}

SpringCloud Alibaba-OSS

我们用这个更简单

获取服务端签名

对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储 服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优 化存储成本。

我们创建一个spring子模块,模块名为gulimall-third-party,该服务为第三方服务

请求获取oss policy(服务端签名)

http://localhost:88/api/thirdparty/oss/policy

{
  "accessId": "LTAI5tA7ascz74svh1dtG2s4",
  "policy": "eyJleHBpcmF0aW9uIjoiMjAyMi0xMC0yNVQwNToyNTowNC4xMzNaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIyMDIyLTEwLTI1LyJdXX0=",
  "signature": "kQXgo3mCZTCh/yKcWdE/et+vYIY=",
  "dir": "2022-10-25/",
  "host": "https://gulimall-linxi.oss-cn-beijing.aliyuncs.com",
  "expire": "1666675504"
}

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.atlinxi.gulimall</groupId>
    <artifactId>gulimall-third-party</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gulimall-third-party</name>
    <description>第三方服务</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2020.0.4</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.atlinxi.gulimall</groupId>
            <artifactId>gulimall-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>com.baomidou</groupId>
                    <artifactId>mybatis-plus-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--        这个版本和springcloudalibaba的版本不统一,不知道以后会不会出什么问题-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <!--                之前用的一直是这个版本,springcloudoss依赖就是无法导入,换成下面那个就好了-->
                <version>2021.1</version>
                <!--                这是老师用的版本,导入服务就启动不了 了-->
                <!--                <version>2.1.0.RELEASE</version>-->
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

# application.yml
# 下面oss的配置都是阿里云的,用自己的
server:
  port: 30000
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    alicloud:
      access-key: LT2s4
      secret-key: 5bljCK8
      oss:
        endpoint: oss-cn-beijing.aliyuncs.com
        bucket: gulimall-linxi
  application:
    name: gulimall-third-party

controller

package com.atlinxi.gulimall.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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDate;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

@RestController
public class OssController {

    // 直接注入OssClient,会报找不到
    // 因为OssContextAutoConfiguration自动注入的时候,注入的是OSS接口
    @Autowired
    private OSS ossClient;

    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endpoint;

    @Value("${spring.cloud.alicloud.oss.bucket}")
    private String bucket;

    @Value("${spring.cloud.alicloud.access-key}")
    private String accessId;

    @Value("${spring.cloud.alicloud.secret-key}")
    private String secretkey;



    @RequestMapping("/oss/policy")
    public Map<String,String> policy(){
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        // 这些参数都是用来构造ossClient的
//        String accessId = "yourAccessKeyId";
//        String accessKey = "yourAccessKeySecret";
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
//        String endpoint = "oss-cn-hangzhou.aliyuncs.com";

        // 填写Bucket名称,例如examplebucket。
//        String bucket = "gulimall-linxi";
//        https://gulimall-linxi.oss-cn-beijing.aliyuncs.com/bug.jpg
        // 填写Host地址,格式为https://bucketname.endpoint。
        String host = "https://" + bucket + "." + endpoint;
        // 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
//        String callbackUrl = "https://192.168.0.0:8888";
        // 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
        // 我们将每天的图片设置为一个文件夹
        LocalDate currentDate = LocalDate.now();
        String dir = currentDate + "/";

        Map<String, String> respMap = null;

        // 创建ossClient实例。
        // 我们使用自动注入的方式获取ossClient
//        OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);
        try {
            long expireTime = 30;
            long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
            Date expiration = new Date(expireEndTime);
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

            String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes("utf-8");
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = ossClient.calculatePostSignature(postPolicy);

            respMap = new LinkedHashMap<String, String>();
            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));

            // respMap.put("expire", formatISO8601Date(expiration));

            // 下面是跨域的操作,我们已在网关解决
//            JSONObject jasonCallback = new JSONObject();
//            jasonCallback.put("callbackUrl", callbackUrl);
//            jasonCallback.put("callbackBody",
//                    "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
//            jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
//            String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
//            respMap.put("callback", base64CallbackBody);
//
//            JSONObject ja1 = JSONObject.fromObject(respMap);
//            // System.out.println(ja1.toString());
//            response.setHeader("Access-Control-Allow-Origin", "*");
//            response.setHeader("Access-Control-Allow-Methods", "GET, POST");
//            response(request, response, ja1.toString());

        } catch (Exception e) {
            // Assert.fail(e.getMessage());
            System.out.println(e.getMessage());
        }

        return respMap;
    }
}

网关配置

spring:
  cloud:
    gateway:
      routes:
        - id: test_route
          uri: https://www.baidu.com
          predicates:
            #              根据参数来匹配
            - Query=url,baidu

        # 和admin_route顺序不能乱,否则页面访问报404,因为被它拦截了
        # 我们一般把精确的路由放在上面,优先级高
        # 匹配了这个路由之后,不会匹配下面的路由
        - id: product_route
          uri: lb://gulimall-product
          predicates:
            - Path=/api/product/**
          # 前端的请求是 http://localhost:88/api/product/category/list/tree
          # 后端实际需要的请求是,http://localhost:12000/product/category/list/tree
          filters:
            - RewritePath=/api/(?<segment>.*),/$\{segment}

            #  http://localhost:88/api/thirdparty/oss/policy
        - id: third_party_route
          #  lb 负载均衡
          uri: lb://gulimall-third-party
           # 匹配所有以api开头的请求
          predicates:
            - Path=/api/thirdparty/**
          filters:
            #  路径重写
            # (?<segment>.*)  $\{segment} 相当于片段
            - RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}

        #  前端项目发送请求都以 /api 开头
        - id: admin_route
          #  lb 负载均衡  到renren-fast服务
          uri: lb://renren-fast
          # 匹配所有以api开头的请求
          predicates:
            - Path=/api/**
          filters:
            #  路径重写
            # http://localhost:88/api/captcha.jpg 在网关匹配到相应的规则后
            #  就变成了 http://localhost:8080/api/captcha.jpg
            # 但实际上我们需要真正访问的是 http://localhost:8080/renren-fast/captcha.jpg
            # (?<segment>.*)  $\{segment} 相当于片段
            - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}

前端代码

singleUpload.vue

<template> 
  <div>
    <!-- 
      点击上传,选择好图片,会触发 before-upload
     -->
    <el-upload
      action="http://gulimall-linxi.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;
        // 这个是请求是获取后端的 oss 上传图片的签名
        return new Promise((resolve, reject) => {
          policy().then(response => {
            console.log("响应的数据",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;
            console.log("响应的数据22.。。",_self.dataObj)
            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>

policy.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);
        })
    });
}

brand-add-or-update.vue

下面是导入singleUpload.vue组件的部分代码

<el-form-item label="品牌logo地址" prop="logo">
        <!-- <singleUpload></singleUpload> -->
        <!-- 也可以使用短横线 -->
        <single-upload v-model="dataForm.logo"></single-upload>
      </el-form-item>


<script>
// 导入单图片上传
// @ 代表src路径,说是自定义的,但我不知道在哪儿定义的,不关心
// import SingleUpload from "@/components/upload/singleUpload"
// 这个是敲上面的短横线写法,自动导入的
import singleUpload from "../../../components/upload/singleUpload.vue";


export default {
  components: { singleUpload },

一整面墙的原典标榜他的学问,一面课本标榜孤独,一面小说等于灵魂。没有一定要上过他的课。没有一定要谁家的女儿。

房思琪的初恋乐园
林奕含

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值