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

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.mfbz.cn/a/450823.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

106. Dockerfile通过多阶段构建减小Golang镜像的大小

我们如何通过引入具有多阶段构建过程的Dockerfiles来减小Golang镜像的大小&#xff1f; 让我们从一个通用的Dockerfile开始&#xff0c;它负责处理基本的事务&#xff0c;如依赖项、构建二进制文件、声明暴露的端口等&#xff0c;以便为Go中的一个非常基础的REST API提供服务。…

常见的排序算法的时间复杂度

常见的排序算法的时间复杂度 排序算法的时间复杂度通常取决于输入数据的规模&#xff08;通常表示为n&#xff09;。以下是一些常见排序算法及其平均、最好和最坏情况下的时间复杂度&#xff1a; 1、冒泡排序&#xff08;Bubble Sort&#xff09; 平均时间复杂度&#xff1a;…

进程打开文件

目录 一、预备知识 二、操作文件函数 三、操作文件系统调用 四、理解进程打开文件 函数 vs 系统调用 open的返回值 fd 如何理解一切皆文件&#xff1f; 理解struct file 内核对象 fd的分配规则 && 重定向 理解标准错误流&#xff08;2号文件描述符&#xff0…

得帆助力大族激光主数据平台建设,用数据为企业生产力赋能

本期客户 大族激光科技产业集团股份有限公司&#xff08;以下简称“大族激光”&#xff09;是一家从事工业激光加工设备与自动化等配套设备及其关键器件的研发、生产、销售&#xff0c;激光、机器人及自动化技术在智能制造领域的系统解决方案的优质提供商&#xff0c;是国内激光…

如何通过四维轻云SDK开发打造智慧景区管理平台?

智慧景区管理平台通常是基于GIS技术&#xff0c;在三维实景地图的基础上&#xff0c;接入景区各类传感设备、第三方系统数据&#xff0c;进行业务功能的梳理及开发。但对于没有GIS开发经验的团队而言&#xff0c;地图开发具有一定的技术门槛&#xff0c;尤其是需要在前端解决好…

VR全景在智慧园区中的应用

VR全景如今以及广泛的应用于生产制造业、零售、展厅、房产等领域&#xff0c;如今720云VR全景更是在智慧园区的建设中&#xff0c;以其独特的优势&#xff0c;发挥着越来越重要的作用。VR全景作为打造智慧园区的重要角色和呈现方式已经受到了越来越多智慧园区企业的选择和应用。…

记事小本本

记事小本本 实现效果 相关代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</titl…

Zookeeper详解

1.Zookeeper概述 1.Zookeeper概念 Zookeeper是 Apache Hadoop 项目下的一个子项目&#xff0c;是一个树形目录服务 Zookeeper 翻译过来就是动物园管理员&#xff0c;他是用来管 Hadoop&#xff08;大象&#xff09;、Hive(蜜蜂)、Pig(小猪)的管理员。简称zk Hadoop: 存储海…

Java项目:46 ssm005基于SSM框架的购物商城系统+jsp(含文档)

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 项目是单体ssm电商水果平台&#xff0c;包括前台商城平台及后台管理系统 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、…

Buuctf-Web-[极客大挑战 2019]EasySQL 1 题解及思路总结

​ 启动靶机 目录 题要做题过程第一步——找到页面与数据库产生交互的地方第二步——查看SQL语句闭合方式判断SQL注入闭合方式&#xff1a;方法一&#xff1a;使用\(转义字符)来判断SQL注入的闭合方式方法二&#xff1a;输入1、1、1"判断SQL语句闭合方式 第三步——进行SQ…

代理IP如何应对自动化测试和爬虫检测

目录 一、代理IP在自动化测试和爬虫中的作用 二、代理IP的优缺点分析 1.优点 2.缺点 三、应对自动化测试和爬虫检测的策略 1.选择合适的代理IP 2.设置合理的请求频率和间隔 3.模拟人类行为模式 4.结合其他技术手段 四、案例与代码示例 五、总结 在自动化测试和爬虫开…

Alpha突触核蛋白神经退行性疾病介绍

StressMarq——Alpha突触核蛋白&神经退行性疾病 Alpha突触核蛋白科研背景 • Alpha突触核蛋白约 15kDa, 140个氨基酸 • StressMarq/欣博盛生物在E. coli中过表达人源基因然后将蛋白从细胞质基质中纯化出来 • 未折叠的alpha突触核蛋白单体在12% SDS-PAGE上为~15 kDa的条…

CentOS本地部署Tale博客并结合内网穿透实现公网访问本地网站

文章目录 前言1. Tale网站搭建1.1 检查本地环境1.2 部署Tale个人博客系统1.3 启动Tale服务1.4 访问博客地址 2. Linux安装Cpolar内网穿透3. 创建Tale博客公网地址4. 使用公网地址访问Tale 前言 今天给大家带来一款基于 Java 语言的轻量级博客开源项目——Tale&#xff0c;Tale…

2022年吉林省大学生电子设计竞赛(D题)

一. 使用技术 PWM调速&#xff0c;PID&#xff0c;串口通信&#xff0c;陀螺仪测角度&#xff0c;蓝牙 二. 项目描述 大学的第一个比赛&#xff0c;项目采用主控stm32&#xff0c;车体采用一个四路电机驱动来驱动减速电机&#xff0c;小车依靠8路灰度循迹模块&#xff0c;实…

Keepalive+LVS群集部署

引言 Keepalived 是一个基于VRRP协议来实现的LVS服务高可用方案&#xff0c;可以解决静态路由出现的单点故障问题。 一、Keepalive概述 keepalive软件起初是专为 LVS 负载均衡软件设计的&#xff0c;用来管理并监控 LVS集群中各个服务节点的状态&#xff0c;后来又加入了可以…

【VS Code插件开发】自定义指令实现 git 命令 (九)

&#x1f431; 个人主页&#xff1a;不叫猫先生&#xff0c;公众号&#xff1a;前端舵手 &#x1f64b;‍♂️ 作者简介&#xff1a;前端领域优质作者、阿里云专家博主&#xff0c;共同学习共同进步&#xff0c;一起加油呀&#xff01; ✨优质专栏&#xff1a;VS Code插件开发极…

2m高分辨率土地利用分类矢量数据/植被类型分布数据

土地利用数据是在根据影像光谱特征&#xff0c;结合野外实测资料&#xff0c;同时参照有关地理图件&#xff0c;对地物的几何形状&#xff0c;颜色特征、纹理特征和空间分布情况进行分析&#xff0c;建立统一解译标志的基础之上&#xff0c;依据多源卫星遥感信息&#xff0c;结…

<Linux> 线程控制

目录 一、线程资源的分配 &#xff08;一&#xff09;线程私有资源 &#xff08;二&#xff09;线程共享资源 二、原生线程库 三、线程控制接口 &#xff08;一&#xff09;线程创建 - pthread_create() 1. 一个线程 2. 一批线程 &#xff08;二&#xff09;线程等待 …

webpack5零基础入门-2wepack配置项的了解

在使用webpack之前&#xff0c;我们需要对webpack的配置项有一定的认识。 1.五大核心概念 1.entry&#xff08;入口&#xff09; 指示webpack从哪个文件开始打包 2.output (输出) 指示webpack打包完的文件输出到哪里,如何命令等 3.loader(加载器) webpack本身只能处理js…

数字证书在网络安全中的重要性与实际应用

数字证书作为一种“电子身份证”&#xff0c;在当今数字化的商业环境中有着广泛的实际应用。它主要用于身份认证、加密通信、电子签名和安全访问控制等方面&#xff0c;为各行各业提供了安全可靠的数字化解决方案。 网络安全领域 在网络通信中&#xff0c;数字证书被广泛应用…
最新文章