SpringBoot + Vue + ElementUI 实现后台管理系统模板 — 后端篇(三): 整合阿里云 OSS 服务 — 上传、下载文件、图片

(1) 相关博文地址:

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(一):搭建基本环境:https://www.cnblogs.com/l-y-h/p/12930895.html
SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(二):引入 element-ui 定义基本页面显示:https://www.cnblogs.com/l-y-h/p/12935300.html
SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(三):引入 js-cookie、axios、mock 封装请求处理以及返回结果:https://www.cnblogs.com/l-y-h/p/12955001.html
SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(四):引入 vuex 进行状态管理、引入 vue-i18n 进行国际化管理:https://www.cnblogs.com/l-y-h/p/12963576.html
SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(五):引入 vue-router 进行路由管理、模块化封装 axios 请求、使用 iframe 标签嵌套页面:https://www.cnblogs.com/l-y-h/p/12973364.html
SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 前端篇(六):使用 vue-router 进行动态加载菜单:https://www.cnblogs.com/l-y-h/p/13052196.html
SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(一): 搭建基本环境、整合 Swagger、MyBatisPlus、JSR303 以及国际化操作:https://www.cnblogs.com/l-y-h/p/13083375.html
SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(二): 整合 Redis(常用工具类、缓存)、整合邮件发送功能:https://www.cnblogs.com/l-y-h/p/13163653.html

(2)代码地址:

https://github.com/lyh-man/admin-vue-template.git

一、使用阿里云 OSS 服务

1、简介

OSS 为 Object Storage Service,即对象存储服务。是阿里云提供的海量、安全、低成本、高可靠的云存储服务。

【官方使用文档:】
    https://help.aliyun.com/document_detail/31817.html

【快速上手 OSS 参考:】
    https://www.cnblogs.com/l-y-h/p/12805028.html

2、使用 — 开通 OSS 服务、创建 AccessKey

(1)登录网站、开通 OSS 服务
通 OSS 服务,用于存储文件。

【官网地址:】
    https://www.aliyun.com/

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

(2)创建 bucket,用于保存文件。
Step1:
进入 OSS 控制台,点击创建 bucket,用于创建文件保存空间。

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

Step2:
填写 bucket 相关信息。(视财力选择功能)
注:
读写权限可以根据项目需要,酌情选择。
私有指的是 读写操作 均需要 进行身份的验证(此项目中使用)。
公共读指的是 写操作需要进行身份验证,读操作不需要(即通过 url 可以直接访问)。

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

Step3:
配置跨域访问(放行 post、get 等请求)。

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

(3)创建 AccessKey,用于获取操作 OSS 的权限。
Step1:
点击 Accesskey,会弹出一个页面,点击 开始使用子用户 AccessKey。

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

Step2:
创建用户(admin-vue-template),并选择编程访问。

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

Step3:
保存 AccessKey 相关信息,后续会使用。
建议保存在自己知道的地方,页面关闭后无法再次获取,只有重新创建了(=_=)。

【用户登录名称】
    admin-vue-template@1675783906103019.onaliyun.com

【AccessKey ID】
     LTAI4GEWZbLZocBzXKYEfmmq

【SECRET】
     rZLsruKxWex2qGYVA3UsuBgW5B3uJQ

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

Step4:
给创建的用户添加权限(OSS 权限)。

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

3、使用 — 服务端上传代码

(1)创建一张表 back_oss,用于存储 文件 url 地址。

USE admin_template;

-- 文件上传
CREATE TABLE back_oss (
  id bigint NOT NULL COMMENT '文件 ID',
  file_url varchar(500) COMMENT 'URL 地址',
  oss_name varchar(200) COMMENT '存储在 OSS 中的文件名',
  file_name varchar(100) COMMENT '文件名',
  create_time datetime COMMENT '创建时间',
  PRIMARY KEY (id),
  UNIQUE INDEX (oss_name)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COMMENT='文件上传';

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

(2)使用 mybatis-plus 代码生成器为该表生成基本代码。
此处,我将代码生成在 modules/oss 中,也可生成在原来的路径中。

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

当然,对于 创建时间 这个字段,可以使用 mybatis-plus 的 @TableField 注解对其进行填充。
之前有过介绍,此处不再重复介绍
可参考:https://www.cnblogs.com/l-y-h/p/13083375.html#_label2_1

package com.lyh.admin_template.back.modules.oss.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.Date;

/**
 *
 * 文件上传
 *
 *
 * @author lyh
 * @since 2020-06-19
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="BackOss对象", description="文件上传")
public class BackOss implements Serializable {

    private static final long serialVersionUID=1L;

    @ApiModelProperty(value = "文件 ID")
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;

    @ApiModelProperty(value = "URL 地址")
    private String fileUrl;

    @ApiModelProperty(value = "存储在 OSS 中的文件名")
    private String ossName;

    @ApiModelProperty(value = "文件名")
    private String fileName;

    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty(value = "创建时间")
    private Date createTime;
}

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

注:
由于 mapper 生成的位置与之前代码不一致,需要在配置文件中,对其进行扫描。

@MapperScan(basePackages = {"com.lyh.admin_template.back.mapper", "com.lyh.admin_template.back.modules.oss.mapper"})

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

(3)由于涉及到 阿里云 的相关配置信息,就要考虑到配置信息修改问题。
处理一:可以使用 配置文件 存储,通过修改配置文件来修改 OSS 相关信息。

处理二:可以使用数据库存储配置信息(Json 形式),通过修改数据库数据的方式对其进行修改。
数据表设计如下:

USE admin_template;

-- 系统配置信息
CREATE TABLE back_config (
   id bigint NOT NULL COMMIT '配置信息 ID',
   param_key varchar(50) COMMENT 'key',
   param_value varchar(2000) COMMENT 'value',
   status tinyint DEFAULT 1 COMMENT '状态   0:隐藏   1:显示',
   remark varchar(500) COMMENT '备注',
   PRIMARY KEY (id),
   UNIQUE INDEX (param_key)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COMMENT='系统配置信息表';

此处仅使用 处理一。在配置文件中填写相关的配置信息。

阿里云配置信息
aliyun:
  # common 配置信息
  accessKeyId: LTAI4GEWZbLZocBzXKYEfmmq
  accessKeySecret: rZLsruKxWex2qGYVA3UsuBgW5B3uJQ
  # OSS 相关配置信息
  endPoint: http://oss-cn-beijing.aliyuncs.com
  bucketName: admin-vue-template
  domain: http://admin-vue-template.oss-cn-beijing.aliyuncs.com

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

(4)添加 OSS 依赖

com.aliyun.oss
    aliyun-sdk-oss
    3.8.0

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

(5)编写一个 OSS 工具类 (OssUtil.java),通过其来操作 文件上传。
通过 @Value 来获取配置文件(application.yml)中的值。
注:
若使用 @Value 获取到的值为 null,需在类上 标注 @Component 注解。

package com.lyh.admin_template.back.common.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.UUID;

/**
 * Oss 工具类,用于操作 OSS
 */
@Data
@Component
public class OssUtil {
    @Value("${aliyun.endPoint}")
    private String endPoint;
    @Value("${aliyun.bucketName}")
    private String bucketName;
    @Value("${aliyun.accessKeyId}")
    private String accessKeyId;
    @Value("${aliyun.accessKeySecret}")
    private String accessKeySecret;
    @Value("${aliyun.domain}")
    private String domain;

    /**
     * 设置文件上传路径(prefix + 日期 + uuid + suffix)
     */
    public String getPath(String prefix, String suffix) {
        // 生成 UUID
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        // 格式化日期
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        // 拼接文件路径
        String path = dateTimeFormatter.format(LocalDateTime.now()) + "/" + uuid;
        if (StringUtils.isNotEmpty(prefix)) {
            path = prefix + "/" + path;
        }
        return path + "-" + suffix;
    }

    /**
     * 上传文件
     */
    public String upload(InputStream inputStream, String path) {
        try {
            // 创建 OSSClient 实例。
            OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
            // 上传文件到 指定 bucket
            ossClient.putObject(bucketName, path, inputStream);
            // 关闭 OSSClient
            ossClient.shutdown();
        } catch (Exception e) {
            throw new RuntimeException("上传文件失败");
        }
        return path;
    }

    /**
     * 上传文件
     */
    public String upload(byte[] data, String path) {
        return upload(new ByteArrayInputStream(data), path);
    }

    /**
     * 上传文件,自定义 前后缀
     */
    public String uploadSuffix(byte[] data, String prefix ,String suffix) {
        return upload(data, getPath(prefix, suffix));
    }

    /**
     * 上传文件,自定义 前后缀
     */
    public String uploadSuffix(InputStream inputStream, String prefix, String suffix) {
        return upload(inputStream, getPath(prefix, suffix));
    }

    /**
     * 获取文件 url
     */
    public String getUrl(String key) {
        // 用于保存 url 地址
        URL url = null;
        try {
            // 创建 OSSClient 实例。
            OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
            // 设置 url 过期时间(10 年)
            Date expiration = new Date(new Date().getTime() + 1000L * 60 * 60 * 24 * 365 * 10);
            // 获取 url 地址
            url = ossClient.generatePresignedUrl(bucketName, key, expiration);
            // 关闭 OSSClient
            ossClient.shutdown();
        } catch (Exception e) {
            throw new RuntimeException("获取文件 url 失败");
        }
        return url != null ? url.toString() : null;
    }
}

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

(6)编写 测试代码简单测试一下。
使用 Swagger 简单测试一下(此处只上传单文件,可以使用 Swagger 进行测试,多文件可以使用 Postman 进行测试)。

package com.lyh.admin_template.back.modules.oss.controller;

import com.lyh.admin_template.back.common.exception.GlobalException;
import com.lyh.admin_template.back.common.utils.OssUtil;
import com.lyh.admin_template.back.common.utils.Result;
import com.lyh.admin_template.back.modules.oss.entity.BackOss;
import com.lyh.admin_template.back.modules.oss.service.BackOssService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
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;

import java.io.IOException;

/**
 *
 * 文件上传 前端控制器
 *
 *
 * @author lyh
 * @since 2020-06-19
 */
@RestController
@RequestMapping("/oss/back-oss")
@Api(tags = "文件上传")
public class BackOssController {

    @Autowired
    private OssUtil ossUtil;
    @Autowired
    private BackOssService backOssService;

    @ApiOperation(value = "上传文件")
    @PostMapping("/upload")
    public Result upload(@ApiParam MultipartFile file) {
        // 用于保存文件 url
        String url = null;
        // 用于保存文件信息
        BackOss backOss = new BackOss();
        try {
            // 获取文件上传路径
            url = ossUtil.uploadSuffix(file.getInputStream(), "aliyun", file.getOriginalFilename());

            // 保存文件路径到数据库中
            backOss.setFileName(file.getOriginalFilename());
            backOss.setOssName(url);
            backOss.setFileUrl(ossUtil.getUrl(url));
            backOssService.save(backOss);
        } catch (IOException e) {
            throw new GlobalException("文件上传失败");
        }
        return Result.ok().message("文件上传成功").data("file", backOss);
    }

    @ApiOperation(value = "获取所有文件信息")
    @GetMapping("/getAll")
    public Result getAll() {
        return Result.ok().data("file", backOssService.list());
    }
}

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

测试结果如下:

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

4、使用 — 服务端签名后直传(vue + element-ui 方式传送文件)

(1)简介:
前面的一种文件传输方式是将 文件 从前台传输到 后台,再由后台向 OSS 服务器传输。增加了后台服务器的压力(只适用于传输小文件、图片等)。
采用服务端签名后直传的方式,是由 前台调用后台接口,返回一个签名数据,前台根据这个签名数据直接向 OSS 服务器发送文件(适合传输大文件)。
详情参考:
https://www.cnblogs.com/l-y-h/p/12805028.html#_label2_3

(2)接口代码
可以在 工具类 OssUtil.java 中把相关逻辑封装一下。
逻辑参考下面代码中 getPolicy() 方法。

package com.lyh.admin_template.back.common.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * Oss 工具类,用于操作 OSS
 */
@Data
@Component
public class OssUtil {
    @Value("${aliyun.endPoint}")
    private String endPoint;
    @Value("${aliyun.bucketName}")
    private String bucketName;
    @Value("${aliyun.accessKeyId}")
    private String accessKeyId;
    @Value("${aliyun.accessKeySecret}")
    private String accessKeySecret;
    @Value("${aliyun.domain}")
    private String domain;

    /**
     * 设置文件上传路径(prefix + 日期 + uuid + suffix)
     */
    public String getPath(String prefix, String suffix) {
        // 生成 UUID
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        // 格式化日期
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        // 拼接文件路径
        String path = dateTimeFormatter.format(LocalDateTime.now()) + "/" + uuid;
        if (StringUtils.isNotEmpty(prefix)) {
            path = prefix + "/" + path;
        }
        return path + "-" + suffix;
    }

    /**
     * 上传文件
     */
    public String upload(byte[] data, String path) {
        return upload(new ByteArrayInputStream(data), path);
    }

    /**
     * 上传文件,自定义 前后缀
     */
    public String uploadSuffix(byte[] data, String prefix ,String suffix) {
        return upload(data, getPath(prefix, suffix));
    }

    /**
     * 上传文件,自定义 前后缀
     */
    public String uploadSuffix(InputStream inputStream, String prefix, String suffix) {
        return upload(inputStream, getPath(prefix, suffix));
    }

    /**
     * 上传文件
     */
    public String upload(InputStream inputStream, String path) {
        try {
            // 创建 OSSClient 实例。
            OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
            // 上传文件到 指定 bucket
            ossClient.putObject(bucketName, path, inputStream);
            // 关闭 OSSClient
            ossClient.shutdown();
        } catch (Exception e) {
            throw new RuntimeException("上传文件失败");
        }
        return path;
    }

    /**
     * 获取文件 url
     */
    public String getUrl(String key) {
        // 用于保存 url 地址
        URL url = null;
        try {
            // 创建 OSSClient 实例。
            OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
            // 设置 url 过期时间(10 年)
            Date expiration = new Date(new Date().getTime() + 1000L * 60 * 60 * 24 * 365 * 10);
            // 获取 url 地址
            url = ossClient.generatePresignedUrl(bucketName, key, expiration);
            // 关闭 OSSClient
            ossClient.shutdown();
        } catch (Exception e) {
            throw new RuntimeException("获取文件 url 失败");
        }
        return url != null ? url.toString() : null;
    }

    /**
     * 用于获取签名数据
     */
    public Map getPolicy() {
        return getPolicy(getPath("aliyun", "signature"));
    }

    /**
     * 用于获取签名数据,用于服务端直传文件到服务器
     */
    public Map getPolicy(String path) {
        // 用于保存
        Map map = new HashMap<>();
        try {
            // 创建 OSSClient 实例。
            OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
            // 用于设置 post 上传条件
            PolicyConditions policyConditions = new PolicyConditions();
            // 设置最大上传文件大小(1G)
            policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            // 设置文件前缀
            policyConditions.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, path);
            // 设置签名过期时间(6 小时)
            Date expiration = new Date(new Date().getTime() + 1000L * 60 * 60 * 6);
            // 生成 policy
            String postPolicy = ossClient.generatePostPolicy(expiration, policyConditions);
            // 设置编码字符集(UTF-8)
            byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
            // 设置加密格式(Base64)
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            // 计算签名
            String postSignature = ossClient.calculatePostSignature(postPolicy);
            // 封装数据
            map.put("ossaccessKeyId", accessKeyId);
            map.put("policy", encodedPolicy);
            map.put("signature", postSignature);
            map.put("key", path);
            map.put("expire", String.valueOf(expiration.getTime() / 1000));
            map.put("host", domain);
            // 关闭 OSSClient
            ossClient.shutdown();
        } catch (Exception e) {
            throw new RuntimeException("获取签名数据失败");
        }
        return map;
    }
}

(3)编写测试接口用于测试。

package com.lyh.admin_template.back.modules.oss.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lyh.admin_template.back.common.utils.OssUtil;
import com.lyh.admin_template.back.common.utils.Result;
import com.lyh.admin_template.back.modules.oss.entity.BackOss;
import com.lyh.admin_template.back.modules.oss.service.BackOssService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 *
 * 文件上传 前端控制器
 *
 *
 * @author lyh
 * @since 2020-06-19
 */
@RestController
@RequestMapping("/oss/back-oss")
@Api(tags = "文件上传")
public class BackOssController {

    @Autowired
    private OssUtil ossUtil;
    @Autowired
    private BackOssService backOssService;

    @ApiOperation(value = "获取签名数据")
    @GetMapping("/policy")
    public Result policy() {
        return Result.ok().data("policyData", ossUtil.getPolicy());
    }

    @ApiOperation(value = "保存并获取文件 url")
    @PostMapping("/saveUrl")
    public Result saveUrl(@RequestParam String key, @RequestParam String fileName) {
        BackOss backOss = new BackOss();
        backOss.setOssName(key);
        backOss.setFileName(fileName);
        backOss.setFileUrl(ossUtil.getUrl(key));
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("oss_name", key);
        backOssService.saveOrUpdate(backOss, queryWrapper);
        return Result.ok().data("file", backOssService.getOne(queryWrapper));
    }
}

(4)前台代码(vue + element-ui):
此处仅用于测试接口。并未整合到实际代码中(后续在整合到前台代码中)。
此处采用普通 html,并引入 vue、element-ui 相关 cdn 进行演示。


    
        
        Document
        
        
        
        
        
    
    
        
:on-success="handleSuccess" :on-error="handleError" multiple :limit="3" :on-exceed="handleExceed" :file-list="fileList" :data="policyData" accept=".jpg,.JPG,.jpeg,.JPEG,.png,.PNG,.gif,.GIF"> 点击上传
只能上传jpg/png/gif文件,且不超过 5 MB

(5)测试接口:
由于并未整合 axios 发送请求,所以手动通过 swagger 触发接口并将数据粘贴到相应地方进行测试。
首先调用后台接口 policy 获取到签名数据,将该数据复制并替换前台代码 policyData 中。
然后执行上传文件即可。
最后调用 saveUrl 接口,将 url 以及文件信息保存到 数据库中。

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

5、json 数据显示问题 — 日期少 8 小时、 id 值与数据库值不一致

(1)问题:
如下图所示:
返回的 json 数据,可以看到 id 值与 日期值 与数据库有明显的区别。

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

(2)解决 id 与数据库不一致问题。
原因分析:
由于后台代码,id 生成策略选择 type = IdType.ASSIGN_ID,其会通过雪花算法生成一个长的 Long 型数字。而这个数字传递到前台超过了 js 的数字存储范围,使数字精度丢失。

解决思路:
在 Long 类型转为 Json 之前,将其 变为 String 类型,这样前台获取的即为 String 类型,从而保证精度。

解决方式一:(有局限性,需要对每个实体类进行标注)
在实体类上标注 @JsonSerialize 注解,并指定序列化方式。

@TableId(value = "id", type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

解决方式二:(通用)
编写一个 Jackson2ObjectMapperBuilderCustomizer 对象,并交给 Spring 管理。

@Configuration
public class Config {
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer builderCustomizer() {
        return builder -> {
            // 所有 Long 类型转换成 String 到前台
            builder.serializerByType(Long.class, ToStringSerializer.instance);
        };
    }
}

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

(3)解决日期少 8 小时问题。
原因分析:
少 8 小时,即时区的问题。

解决思路:
给其时区添加 8 小时,同时可以指定 日期输出格式。

解决方式一:(有局限性,需要对每个实体类进行标注)
在实体类上标注 @JsonFormat 注解,并指定转换格式 以及 时区。

@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
private Date createTime;

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

解决方式二:(通用)
在 配置文件中,配置时区以及格式。

spring:
  # 设置 json 中日期显示格式
  jackson:
    # 设置显示格式
    date-format: yyyy-MM-dd HH:mm:ss
    # 设置时区
    time-zone: GMT+8

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

(4)再次获取数据。
上面两个问题,本项目中均采用解决方式二去解决。

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

6、删除文件

(1)删除文件
删除 oss 文件的同时也要删除数据库中的数据。

(2)代码实现:
Step1:
在工具类中编写 oss 删除逻辑。

package com.lyh.admin_template.back.common.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * Oss 工具类,用于操作 OSS
 */
@Data
@Component
public class OssUtil {
    @Value("${aliyun.endPoint}")
    private String endPoint;
    @Value("${aliyun.bucketName}")
    private String bucketName;
    @Value("${aliyun.accessKeyId}")
    private String accessKeyId;
    @Value("${aliyun.accessKeySecret}")
    private String accessKeySecret;
    @Value("${aliyun.domain}")
    private String domain;

    /**
     * 设置文件上传路径(prefix + 日期 + uuid + suffix)
     */
    public String getPath(String prefix, String suffix) {
        // 生成 UUID
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        // 格式化日期
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        // 拼接文件路径
        String path = dateTimeFormatter.format(LocalDateTime.now()) + "/" + uuid;
        if (StringUtils.isNotEmpty(prefix)) {
            path = prefix + "/" + path;
        }
        return path + "-" + suffix;
    }

    /**
     * 上传文件
     */
    public String upload(byte[] data, String path) {
        return upload(new ByteArrayInputStream(data), path);
    }

    /**
     * 上传文件,自定义 前后缀
     */
    public String uploadSuffix(byte[] data, String prefix ,String suffix) {
        return upload(data, getPath(prefix, suffix));
    }

    /**
     * 上传文件,自定义 前后缀
     */
    public String uploadSuffix(InputStream inputStream, String prefix, String suffix) {
        return upload(inputStream, getPath(prefix, suffix));
    }

    /**
     * 上传文件
     */
    public String upload(InputStream inputStream, String path) {
        try {
            // 创建 OSSClient 实例。
            OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
            // 上传文件到 指定 bucket
            ossClient.putObject(bucketName, path, inputStream);
            // 关闭 OSSClient
            ossClient.shutdown();
        } catch (Exception e) {
            throw new RuntimeException("上传文件失败");
        }
        return path;
    }

    /**
     * 获取文件 url
     */
    public String getUrl(String key) {
        // 用于保存 url 地址
        URL url = null;
        try {
            // 创建 OSSClient 实例。
            OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
            // 设置 url 过期时间(10 年)
            Date expiration = new Date(new Date().getTime() + 1000L * 60 * 60 * 24 * 365 * 10);
            // 获取 url 地址
            url = ossClient.generatePresignedUrl(bucketName, key, expiration);
            // 关闭 OSSClient
            ossClient.shutdown();
        } catch (Exception e) {
            throw new RuntimeException("获取文件 url 失败");
        }
        return url != null ? url.toString() : null;
    }

    /**
     * 用于获取签名数据
     */
    public Map getPolicy() {
        return getPolicy(getPath("aliyun", "signature"));
    }

    /**
     * 用于获取签名数据,用于服务端直传文件到服务器
     */
    public Map getPolicy(String path) {
        // 用于保存
        Map map = new HashMap<>();
        try {
            // 创建 OSSClient 实例。
            OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
            // 用于设置 post 上传条件
            PolicyConditions policyConditions = new PolicyConditions();
            // 设置最大上传文件大小(1G)
            policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            // 设置文件前缀
            policyConditions.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, path);
            // 设置签名过期时间(6 小时)
            Date expiration = new Date(new Date().getTime() + 1000L * 60 * 60 * 6);
            // 生成 policy
            String postPolicy = ossClient.generatePostPolicy(expiration, policyConditions);
            // 设置编码字符集(UTF-8)
            byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
            // 设置加密格式(Base64)
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            // 计算签名
            String postSignature = ossClient.calculatePostSignature(postPolicy);
            // 封装数据
            map.put("ossaccessKeyId", accessKeyId);
            map.put("policy", encodedPolicy);
            map.put("signature", postSignature);
            map.put("key", path);
            map.put("expire", String.valueOf(expiration.getTime() / 1000));
            map.put("host", domain);
            // 关闭 OSSClient
            ossClient.shutdown();
        } catch (Exception e) {
            throw new RuntimeException("获取签名数据失败");
        }
        return map;
    }

    /**
     * 删除 OOS 中的文件
     */
    public void deleteObject(String objectName) {
        try {
            // 创建 OSSClient 实例。
            OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);

            // 删除指定 bucket 中的文件
            ossClient.deleteObject(bucketName, objectName);

            // 关闭 OSSClient
            ossClient.shutdown();
        } catch (Exception e) {
            throw new RuntimeException("删除文件失败");
        }
    }
}

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

Step2:
调用工具类,并删除数据库的数据。

package com.lyh.admin_template.back.modules.oss.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lyh.admin_template.back.common.utils.OssUtil;
import com.lyh.admin_template.back.common.utils.Result;
import com.lyh.admin_template.back.modules.oss.service.BackOssService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 *
 * 文件上传 前端控制器
 *
 *
 * @author lyh
 * @since 2020-06-19
 */
@RestController
@RequestMapping("/oss/back-oss")
@Api(tags = "文件上传")
public class BackOssController {

    @Autowired
    private OssUtil ossUtil;
    @Autowired
    private BackOssService backOssService;

    @ApiOperation(value = "删除文件")
    @DeleteMapping("/delete/object")
    public Result deleteObject(@RequestParam String key) {
        ossUtil.deleteObject(key);
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("oss_name", key);
        backOssService.remove(queryWrapper);
        return Result.ok();
    }
}

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

Step3:
简单测试一下。
先上传一个文件,然后根据其 oss_name 将文件删除。

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片

7、下载文件

(1)最简单的方式:
只支持部分类型(比如:image/jpeg),有些类型会直接打开(比如:video/mp4)
直接使用 window.open(url) ,此时会触发浏览器下载功能(文件名默认不可更改)。

【举例:】
  【举例:】
  window.open("http://admin-vue-template.oss-cn-beijing.aliyuncs.com/aliyun/20200624/025a5edee3a34df19ed8b0d51d4c8053-signature?Expires=1908339884&OSSAccessKeyId=LTAI4GEWZbLZocBzXKYEfmmq&Signature=budNdWygT241vRNOVW9OCioZ4jQ%3D")

(2)使用 Blob 流处理文件
Step1:
访问 url 地址时可能产生跨域问题。此处采用一个粗暴的方法,直接关闭 Chrome 安全策略进行测试(非必须操作)。
关闭 Chrome 安全策略(替换 chrome.exe 位置,命令行执行,会弹出一个浏览器窗口)

"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --no-sandbox --disable-web-security --disable-gpu --user-data-dir=~/chromeTemp

Step2:
用 CDN 方式引入 axios 发送请求。
此处只简单在 html 页面中使用并测试,项目中可以对其进行适当修改。

Step3:
编写 Blob 转为文件的逻辑。


    
        
        Document
        
        
        
        
        
        
    
    
        
:on-success="handleSuccess" :on-error="handleError" multiple :limit="3" :on-exceed="handleExceed" :file-list="fileList" :data="policyData" accept=".jpg,.JPG,.jpeg,.JPEG,.png,.PNG,.gif,.GIF"> 点击上传
只能上传jpg/png/gif文件,且不超过 5 MB
下载

Step4:
测试效果如下,使用 get 请求,根据 url 获取文件流,并将其内容置为 超链接,通过超链接的形式进行下载。(可以自定义文件名)

SpringBoot + Vue + ElementUI 实现后台管理系统模板 -- 后端篇(三): 整合阿里云 OSS 服务 -- 上传、下载文件、图片
this.blobToFile(res.data, res.data.type)

Original: https://www.cnblogs.com/l-y-h/p/13202746.html
Author: 累成一条狗
Title: SpringBoot + Vue + ElementUI 实现后台管理系统模板 — 后端篇(三): 整合阿里云 OSS 服务 — 上传、下载文件、图片

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/598442/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • VSCode实现GDB图形界面远程调试

    如何利用VSCode实现GDB图形界面远程调试 前言 在习惯了集成开发环境的图形界面调试时,首次使用GDB远程调试必定很不习惯,下面讲述如何利用VSCode实现GDB图形界面远程调…

    Linux 2023年6月7日
    096
  • 【转】高并发下秒杀商品,你必须知道的9个细节

    0.前言 高并发下如何设计秒杀系统?这是一个高频面试题。这个问题看似简单,但是里面的水很深,它考查的是高并发场景下,从前端到后端多方面的知识。 秒杀一般出现在商城的促销活动中,指定…

    Linux 2023年6月13日
    079
  • Flink Table Api & SQL 初体验,Blink的使用

    概述 Flink具有Table API和SQL-用于统一流和批处理。 Table API是用于Scala和Java的语言集成查询API,它允许以非常直观的方式组合来自关系运算符(例…

    Linux 2023年6月7日
    0103
  • NJU软件分析笔记(2)

    NJU Static Analysis Notes(2)——Data Flow Analysis Ⅰ 课程链接本次课程主要内容 Overview of Data Flow Anal…

    Linux 2023年6月8日
    0100
  • JDK8以上提高开发效率

    1 接口的默认方法和静态方法 1.1 接口中可以定义默认方法和静态方法。 默认方法使用default修饰,静态方法和默认方法可以多个; 静态方法通过接口直接调用,默认方法通过接口实…

    Linux 2023年6月13日
    088
  • 什么是进程

    进程:是指计算机中已运行的程序。进程本身不是基本的运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。 进程是程序动态…

    Linux 2023年6月7日
    094
  • [云计算]腾讯云从业者认证-思维导图

    第一章 云计算基础介绍 第二章 腾讯云服务器产品介绍 第三章 腾讯云网络产品介绍 第四章 腾讯云CDN加速产品介绍 第五章 腾讯云存储产品介绍 第六章 腾讯云数据库产品介绍 第七章…

    Linux 2023年6月13日
    0102
  • Spring Security登录的流程

    Spring Security登录的流程 1、UsernamePasswordAuthenticationFilter这过滤器开始 attemptAuthentication方法 …

    Linux 2023年6月7日
    0187
  • MySQL manager or server PID file could not be found!

    [root@centos var]# service mysqld stop MySQL manager or server PID file could not be found…

    Linux 2023年6月13日
    081
  • Color 16 Base Code 颜色代码大全

    颜色预览表,请参考以下图片。 十六进制颜色编码字符串如下所示(前置的英语单词都是颜色) ‘aliceblue’: ‘#F0F8FF’…

    Linux 2023年6月7日
    0112
  • ajax跨域问题

    public class CrosFilter implements Filter { @Override public void destroy() { // TODO Auto…

    Linux 2023年6月7日
    090
  • docker容器编排原来这么丝滑~

    前言: 请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 概念介绍: Docker 这个东西所扮演的角色,容易理解,它是一个容器引擎,也就是说实际上我们的容器最终是由Doc…

    Linux 2023年6月14日
    088
  • Linux安装管理

    Linux系列 包管理工具 单个软件包 管理工具 RedHat系列 Redhat Centos Fedora yum rpm .rpm Debian系列 Ubuntu apt-ge…

    Linux 2023年6月8日
    086
  • Question04-查询平均成绩小于60分的同学的学生编号和学生姓名和平均成绩

    * SELECT stu.SID, stu.Sname, IFNULL(CAST(AVG(sc.score) AS DECIMAL(18,2)), 0) 平均成绩 FROM Stu…

    Linux 2023年6月7日
    0117
  • NoteOfMySQL-10-触发器与事件

    触发器是由事件来触发某个操作,这些事件包括insert语句、update语句、delete语句,当数据库系统执行这些事件时,就会激活触发器执行相应的操作。事件调度器(event s…

    Linux 2023年6月14日
    090
  • 解决word插入新图片后原有图片题注的交叉引用错乱的问题

    引言 在日常工作和生活中,我们经常使用word来撰写文档、论文。为了更好地管理文档中的图片以及在正文中引用图片标题,需要借助题注来实现。通过题注,可以在正文中交叉引用图片,并为引用…

    Linux 2023年6月7日
    0136
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球