SpringBoot整合阿里云文件上传OSS以及获取oss临时访问url

news2024/12/23 22:18:09

SpringBoot整合阿里云文件上传OSS

  1. 引入相关依赖
        <!--阿里云 OSS依赖-->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.10.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
  2. 相关配置
aliyun:
  oss:
    end-point: oss-cn-hangzhou.aliyuncs.com
    access-key-id: L**********
    access-key-secret: O**********
    bucket-name: oss-test-img
  3. 配置类OSSConfig.java
package com.vehicle.manager.core.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author zr 2024/2/29
 */
@ConfigurationProperties(prefix = "aliyun.oss")
@Configuration
@Data
public class OSSConfig {
    private String endPoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;
}
  4. 文件上传相关接口FileService
package com.vehicle.manager.core.service;

import org.springframework.web.multipart.MultipartFile;

/**
 * @author zr 2024/2/29
 */
public interface FileService {
    /**
     * 阿里云OSS文件上传
     * @param file
     * @return
     */
    String upload(MultipartFile file);
}
  5. 文件上传接口实现类FileServiceImpl
package com.vehicle.manager.core.service.impl;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.PutObjectResult;
import com.vehicle.manager.core.config.OSSConfig;
import com.vehicle.manager.core.service.FileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

/**
 * 文件上传业务类
 * @author zr 2024/2/29
 */
@Service
@Slf4j
public class FileServiceImpl implements FileService {
    @Autowired
    private OSSConfig ossConfig;

    /**
     * 阿里云OSS文件上传
     *
     * @param file
     */
    @Override
    public String upload(MultipartFile file) {

        //获取相关配置
        String bucketName = ossConfig.getBucketName();
        String endPoint = ossConfig.getEndPoint();
        String accessKeyId = ossConfig.getAccessKeyId();
        String accessKeySecret = ossConfig.getAccessKeySecret();

        //创建OSS对象
        OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);

        //获取原生文件名
        String originalFilename = file.getOriginalFilename();
        //JDK8的日期格式
        LocalDateTime time = LocalDateTime.now();
        DateTimeFormatter dft = DateTimeFormatter.ofPattern("yyyy/MM/dd");

        //拼装OSS上存储的路径
        String folder = dft.format(time);
        String fileName = generateUUID();
        String extension = originalFilename.substring(originalFilename.lastIndexOf("."));

        //在OSS上bucket下的文件名
        String uploadFileName = "user/" + folder + "/" + fileName + extension;

        try {
            PutObjectResult result = ossClient.putObject(bucketName, uploadFileName, file.getInputStream());
            //拼装返回路径
            if (result != null) {
                return "https://"+bucketName+"."+endPoint+"/"+uploadFileName;
            }
        } catch (IOException e) {
            log.error("文件上传失败:{}",e.getMessage());
        } finally {
            //OSS关闭服务,不然会造成OOM
            ossClient.shutdown();
        }
        return null;
    }

    /**
     * 获取随机字符串
     * @return
     */
    private String generateUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }
}

  6. 文件上传接口
  • 此处我整合了swagger,不需要的话可以去掉@Api@ApiOperation注解
  • Result可以用自己的,或者直接返回字符串
package com.vehicle.manager.api.controller;

import com.vehicle.manager.core.model.Result;
import com.vehicle.manager.core.model.enumeration.CommonResultStatus;
import com.vehicle.manager.core.service.FileService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author zr 2024/2/29
 */
@Slf4j
@RestController
@Api(tags = "文件管理")
@RequestMapping("/file")
public class FileController {
    @Autowired
    private FileService fileService;

    /**
     * 文件上传接口
     * @param file
     * @return
     */
    @PostMapping("/upload")
    @ApiOperation(value = "文件上传")
    public Result upload(@RequestPart("file") MultipartFile file){
        String imgFileStr = fileService.upload(file);
        if(imgFileStr== null || "".equals(imgFileStr)){
            return Result.failure(CommonResultStatus.FILE_UPLOAD_FAILED);
        }else{
            return Result.success(imgFileStr);
        }
    }
}
  7. 测试接口

image.png

直接拿那返回的url去访问,发现AccessDenied,这种情况就是没有开放bucket的公共读的权限,有如下几种解决方案:

  • 直接bucket开启公共读权限,所有人都可以访问,但是不安全
  • bucket指定白名单,指定服务器ip可以访问(我认为比较好的一种方式)
  • 使用STS以及签名URL临时授权访问OSS资源(本次我使用的)

image.png

使用STS以及签名URL临时授权访问OSS资源

设计思路:

  • 因为生成的临时url会过期,所以我这里没有把生成的临时url存入数据库,而是存入缓存redis中
    • 其中key为OSS上bucket下的文件,这里我就简称为ObjectName,这个名称在bucket中是不会变的(key存入数据库)
    • 如果返回对象需要url,可以在vo中添加一个相关url字段,返回时获取临时url传入该字段
    • value为我们生成的临时url地址
  • 因为是临时url的缘故就需要涉及到过期时间的问题
    • 临时url的过期时间我查了一下最大是7天,我设置的7天,可以视情况而定
    • redis的过期时间我设置的6天,建议让redis过期时间小于url过期时间(不然就会出现url过期的情况)
  1. 整合redis相关依赖
        <!-- 集成redis依赖  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
  2. redisUtil,只涉及到相关的
package com.vehicle.manager.core.util;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.lettuce.LettuceConnection;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

/**
 *  统一说明一: 方法中的key、 value都不能为null。
 *  统一说明二: 不能跨数据类型进行操作, 否者会操作失败/操作报错。
 *             如: 向一个String类型的做Hash操作,会失败/报错......等等
 * @author zr 2024/3/4
 */

@Slf4j
@Component
@SuppressWarnings("unused")
public class RedisUtil implements ApplicationContextAware {

    /**
     * 使用StringRedisTemplate(,其是RedisTemplate的定制化升级)
     */
    private static StringRedisTemplate redisTemplate;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        RedisUtil.redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
    }
    

    /**
     * string相关操作
     * <p>
     * 提示: redis中String的数据结构可参考resources/data-structure/String(字符串)的数据结构(示例一).png
     * redis中String的数据结构可参考resources/data-structure/String(字符串)的数据结构(示例二).png
     */
    public static class StringOps {

        /**
         * 设置key-value
         * <p>
         * 注: 若已存在相同的key, 那么原来的key-value会被丢弃。
         *
         * @param key   key
         * @param value key对应的value
         */
        public static void set(String key, String value) {
            log.info("set(...) => key -> {}, value -> {}", key, value);
            redisTemplate.opsForValue().set(key, value);
        }
        

        /**
         * 设置key-value
         * <p>
         * 注: 若已存在相同的key, 那么原来的key-value会被丢弃
         *
         * @param key     key
         * @param value   key对应的value
         * @param timeout 过时时长
         * @param unit    timeout的单位
         */
        public static void setEx(String key, String value, long timeout, TimeUnit unit) {
            log.info("setEx(...) => key -> {}, value -> {}, timeout -> {}, unit -> {}",
                    key, value, timeout, unit);
            redisTemplate.opsForValue().set(key, value, timeout, unit);
        }

        /**
         * 根据key,获取到对应的value值
         *
         * @param key key-value对应的key
         * @return 该key对应的值。
         * 注: 若key不存在, 则返回null。
         */
        public static String get(String key) {
            log.info("get(...) => key -> {}", key);
            String result = redisTemplate.opsForValue().get(key);
            log.info("get(...) => result -> {} ", result);
            return result;
        }
    }
    
}
  3. 配置文件新增redis相关配置以及oss的`expiration`
spring:
  redis:
    host: 127.0.0.1
    password: 123456
    port: 6379
aliyun:
  oss:
    end-point: oss-cn-hangzhou.aliyuncs.com
    access-key-id: L**********
    access-key-secret: O**********
    bucket-name: oss-test-img
    expiration: 7  #临时url有效期(天)
  4. 配置类新增`expiration`
package com.vehicle.manager.core.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author zr 2024/2/29
 */
@ConfigurationProperties(prefix = "aliyun.oss")
@Configuration
@Data
public class OSSConfig {
    private String endPoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;
    private Integer expiration;
}
  5. 改动FileServiceImpl的upload返回临时url
package com.vehicle.manager.core.service.impl;

import com.alibaba.fastjson.JSON;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.vehicle.manager.core.config.OSSConfig;
import com.vehicle.manager.core.service.FileService;
import com.vehicle.manager.core.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 文件上传业务类
 *
 * @author zr 2024/2/29
 */
@Service
@Slf4j
public class FileServiceImpl implements FileService {
    @Autowired
    private OSSConfig ossConfig;
    /**
     * 阿里云OSS文件上传
     *
     * @param file
     */
    @Override
    public String upload(MultipartFile file) {

        //获取相关配置
        String bucketName = ossConfig.getBucketName();
        String endPoint = ossConfig.getEndPoint();
        String accessKeyId = ossConfig.getAccessKeyId();
        String accessKeySecret = ossConfig.getAccessKeySecret();

        //创建OSS对象
        OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);

        //获取原生文件名
        String originalFilename = file.getOriginalFilename();
        //JDK8的日期格式
        LocalDateTime time = LocalDateTime.now();
        DateTimeFormatter dft = DateTimeFormatter.ofPattern("yyyy-MM");

        //拼装OSS上存储的路径
        String folder = dft.format(time);
        String fileName = generateUUID();
        String extension = originalFilename.substring(originalFilename.lastIndexOf("."));

        //在OSS上bucket下的文件名
        String uploadFileName = "vehicle-manager/" + folder + "/" + fileName + extension;
        String temporaryUrl = null;
        try {
            PutObjectResult result = ossClient.putObject(bucketName, uploadFileName, file.getInputStream());
            //拼装返回路径
            if (result != null) {
//                原路径
//                return "https://"+bucketName+"."+endPoint+"/"+uploadFileName;
                temporaryUrl  = getTemporaryUrl(uploadFileName);
            }
        } catch (Exception e) {
            log.error("文件上传失败:{}", e.getMessage());
        } finally {
            //OSS关闭服务,不然会造成OOM
            ossClient.shutdown();
        }
        return temporaryUrl;
    }

    /**
     * 获取临时url
     *
     * @return
     */
    @Override
    public String getTemporaryUrl(String uploadFileName) {
        String value = RedisUtil.StringOps.get(uploadFileName);
        if (ObjectUtils.isNotEmpty(value)){
            return value;
        }
        //获取相关配置
        String bucketName = ossConfig.getBucketName();
        String endPoint = ossConfig.getEndPoint();
        String accessKeyId = ossConfig.getAccessKeyId();
        String accessKeySecret = ossConfig.getAccessKeySecret();
        Integer expirationDay = ossConfig.getExpiration();

        //创建OSS对象
        OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);

        // 设置过期时间
        Date expiration = new Date(System.currentTimeMillis() + expirationDay * 3600 * 1000); // 1 小时后过期
        // 生成临时访问 URL
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, uploadFileName);
        request.setExpiration(expiration);
        URL signedUrl = ossClient.generatePresignedUrl(request);
        String urlString = signedUrl.toString();
        //缓存过期时间比oss过期时间少一天
        RedisUtil.StringOps.setEx(uploadFileName, urlString, expirationDay - 1, TimeUnit.DAYS);
        // 关闭 OSS 客户端
        ossClient.shutdown();

        return urlString;
    }

    /**
     * 获取随机字符串
     *
     * @return
     */
    private String generateUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }
}
  6. 测试

image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1510966.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL】超详细_数据库的约束_MySQL的详细查询

复习前面MySQL的基础操作&#xff0c;目的是让我们有印象&#xff01;&#xff01;在这篇文章中&#xff0c;我主要写的是数据库的约束和查询操作的详细、深入讲解&#xff01; 基础操作 &#xff08;复习->【MySQL】超详细-基础操作&#xff09; 插入 insert -> inser…

https代理相对socks5代理有什么优势?

随着互联网的快速发展&#xff0c;代理服务已成为许多人在访问敏感或地理位置受限的网站时所依赖的工具。其中&#xff0c;HTTPS代理和SOCKS5代理是两种最常用的代理服务类型。本文将探讨HTTPS代理相对SOCKS5代理的优势。 1、安全性 HTTPS代理使用SSL/TLS协议对客户端和代理服…

C++ 矩形类

思维导图&#xff1a; #include <iostream> using namespace std; class Rect { private:int width;int height; public:void init(int w,int h){widthw;heighth;}void set_w(int w){widthw;}void set_h(int h){heighth;}void show(){cout << "perimeter &qu…

基于51单片机的LED点阵显示屏设计

目录 摘要 II Abstract III 第一章 绪论 1 1.1 课题背景 1 1.2 选题意义 1 1.3 论文主要内容 1 第二章 方法论证对比 3 2.1 单片机编程语言 3 2.2 控制系统设计 3 2.3 显示方式 3 第三章 系统硬件设计 4 3.1 总体硬件设计 4 3.2 系统各硬件电路介绍 5 3.2.1 电源电路设计介绍 …

蓝牙系列七:开源蓝牙协议栈BTStack数据处理(Wireshark抓包分析)

继续蓝牙系列的研究。 在上篇博客&#xff0c;通过阅读BTStack的源码&#xff0c;大体了解了其框架&#xff0c;对于任何一个BTStack的应用程序都有一个main函数&#xff0c;这个main函数是统一的。这个main函数做了某些初始化之后&#xff0c;最终会调用到应用程序提供的btst…

prometheus 原理(架构,promql表达式,描点原理)

大家好&#xff0c;我是蓝胖子&#xff0c;提到监控指标&#xff0c;不得不说prometheus&#xff0c;今天这篇文章我会对prometheus 的架构设计&#xff0c;promql表达式原理和监控图表的绘图原理进行详细的解释。来让大家对prometheus的理解更加深刻。 架构设计 先来看看&am…

Docker容器化技术(使用Dockerfile制作镜像)

Docker中的镜像分层 Docker 支持通过扩展现有镜像&#xff0c;创建新的镜像。实际上&#xff0c;Docker Hub 中 99% 的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的。 1、Docker 镜像为什么分层 镜像分层最大的一个好处就是共享资源。 比如说有多个镜像都从相…

python 通过代理服务器 连接 huggingface下载模型,并运行 pipeline

想在Python 代码中运行时下载模型&#xff0c;启动代理服务器客户端后 1. 检查能否科学上网 $ curl -x socks5h://127.0.0.1:1080 https://www.example.com <!doctype html> <html> <head><title>Example Domain</title><meta charset"…

Python: 如何绘制核密度散点图和箱线图?

01 数据样式 这是数据样式&#xff1a; 要求&#xff08;我就懒得再复述一遍了&#xff0c;直接贴图&#xff09;&#xff1a; Note&#xff1a;数据中存在无效值NA&#xff08;包括后续的DEM&#xff09;&#xff0c;需要注意 02 提取DEM 这里我就使用gdal去提取一下DEM列…

./ 相对路径与node程序的启动目录有关

node:internal/fs/sync:78 return binding.openSync( ^ Error: ENOENT: no such file or directory, open D:\前端的学习之路\项目\codeHub\keys\private_key.pem at Object.open (node:internal/fs/sync:78:18) at Object.openSync (node:fs:565:…

Java后台面试相关知识点解析

文章目录 JavaJava中四种引用类型及使用场景集合HashMap源码及扩容策略HashMap死循环问题ConcurrentHashMap与HashtableConCurrentHashMap 1.8 相比 1.7判断单链表是否有环&#xff0c;并且找出环的入口 IO线程池线程池的几种创建方式判断线程是否可以回收线程池的7大核心参数线…

菜鸟学会Linux的方法

系统安装是初学者的门槛&#xff0c;系统安装完毕后&#xff0c; 很多初学者不知道该如何学习&#xff0c;不知道如何快速进阶&#xff0c; 下面作者总结了菜鸟学好Linux技能的大绝招&#xff1a; 初学者完成Linux系统分区及安装之后&#xff0c;需熟练掌握Linux系统管理必备命…

蓝桥省赛倒计时 35 天-bfs 和 dfs

#include <iostream> using namespace std; int t; int m,n; char mp[55][55];//不能写成 int 数组 bool vis[55][55]; int dx[ ]{1,0,-1,0},dy[ ]{0,1,0,-1}; int res;void dfs_1(int x,int y){vis[x][y] true;//陆地向四个方向拓展for(int i0;i<4;i){int nx xdx[i…

蓝桥杯练习系统(算法训练)ALGO-973 唯一的傻子

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 腿铮找2255有点事&#xff0c;但2255太丑了&#xff0c;所以腿铮不知道他的长相。正愁不知道到如何找他的时候&#xff0c;…

基于React低代码平台开发:直击最新高效应用构建

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java对AI的调用开发》 《RabbitMQ》《Spring》《SpringMVC》《项目实战》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录…

2024鸿蒙迎来大爆发,有必要转行鸿蒙开发吗?

鸿蒙开发&#xff0c;这个名字最近在科技圈引起了不小的轰动。 那么&#xff0c;鸿蒙开发到底是什么呢&#xff1f;它又能给我们带来怎样的影响呢&#xff1f; 鸿蒙开发&#xff0c;简单来说&#xff0c;就是基于鸿蒙操作系统的一种应用开发方式。鸿蒙系统&#xff0c;作为华为…

记录 Dubbo+Zookeeper 学习Demo

DubboZookeeper ZookeeperZookeeper 下载可能出现的问题 辅助程序下载dubbo-admin项目打包工程打包常见问题 SpringBoot集成Dubbo项目依赖定义服务接口服务端实现服务端配置依赖代码实现 消费端实现服务端配置依赖代码实现 启动 结合Dubbo官网学习如何完成SpringBootDubboZooke…

webstorm 保存自动格式化

webstorm 保存自动格式化 全局安装 prettier npm i -g prettierwebstorm设置

谷歌seo外链重要还是内容重要?

想做网站&#xff0c;内容跟外链缺一不可&#xff0c;如果真的要说哪个更重要&#xff0c;那内容依旧是网站的核心&#xff0c;而外链则是额外的加分项 内容永远是王道&#xff0c;不管谷歌seo的算法怎么变&#xff0c;只要你的内容没问题&#xff0c;那就肯定不会牵扯到你的网…

【牛客】HJ73 计算日期到天数转换

目录 题目链接:计算日期到天数转换_牛客题霸_牛客网 (nowcoder.com) 解题思路: 代码实现: 题目链接:计算日期到天数转换_牛客题霸_牛客网 (nowcoder.com) 解题思路: 用一个数组存放每月的天数 输入的日期天数 当月的天数 当月之前的累积天数 如果包含二月&#xff0c;再去判…