OSS对象存储后端实现+Vue实现图片上传【基于若依管理系统开发】

文章目录

  • 基本介绍
    • 术语介绍
    • 图片上传方式介绍
      • 普通上传
      • 用户直传
      • 应用服务器签名后直传
  • OSS对象存储后端实现
    • maven
    • 配置文件
    • 配置类
    • Service
    • Controller
  • 图片上传前端
    • 图片上传组件
    • api
    • 页面使用组件
    • 组件效果

基本介绍

术语介绍

  • Bucket(存储空间):用于存储对象的容器,所有对象都属于某个存储空间中,一般是一个项目创建一个Bucket来专门存储该项目的文件
  • Object(对象):可以理解为文件,对象在Bucket内部由唯一的Key来标识
  • Region(地域):选择数据所存放的物理地址,如北京
  • Endpoint(访问域名):对外服务的访问域名,不同Region的域名不同,通过内网和外网访问相同Region的域名也不同
  • AccessKey(访问密钥):简称AK,指的是身份验证中使用的AccessKeyId和AccessKeySecret

图片上传方式介绍

普通上传

在这里插入图片描述

描述:用户现在客户端将文件上传到应用所部署的服务器,然后服务器再将文件上传到OSS中,OSS存储文件之后,返回文件地址给应用服务器,应用服务器接着将文件地址存储到数据库中。后续需要访问文件,直接从数据库中查询出访问地址,然后直接访问即可。
缺点:需要将文件上传至应用服务器,消耗应用服务器的资源,应用服务器压力大

用户直传

描述:直接将OSS的相关密钥存储到js中,直接使用js方法上传文件,用户直接在客户端就将文件上传到OSS
缺点:不安全,密钥容易被获取,不法分子可能会恶意刷流量,没办法限流

应用服务器签名后直传

在这里插入图片描述
描述:客户端向应用服务器获取签名,然后凭借签名直接将文件上传到OSS
优点:安全,且节省应用服务器性能

OSS对象存储后端实现

maven

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

配置文件

在这里插入图片描述

oss.accessKeyId=你的accessKeyId
oss.accessKeySecret=你的accessKeySecret
oss.endpoint=你的域名
oss.bucketName=你的存储空间

配置类

用来加载配置文件里面的配置,创建OSS对象并创建Bean

package com.shm.config;import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class OSSConfig {@Value("${oss.endpoint}")private String endpoint;@Value("${oss.accessKeyId}")private String accessKeyId;@Value("${oss.accessKeySecret}")private String accessKeySecret;@Beanpublic OSS ossClient() {return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);}
}

Service

【接口】

package com.shm.service;import com.ruoyi.common.core.domain.AjaxResult;
import org.springframework.stereotype.Service;public interface OssService {/*** 获取签名* @return*/public AjaxResult getPolicy();
}

【实现类】

package com.shm.service.impl;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.ruoyi.common.core.domain.AjaxResult;
import com.shm.service.OssService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;@Service
public class OssServiceImpl implements OssService {@Autowiredprivate OSS ossClient;@Value("${oss.bucketName}")private String bucketName;@Value("${oss.endpoint}")private String endpoint;@Value("${oss.accessKeyId}")private String accessId;@Overridepublic AjaxResult getPolicy() {// 拼接出Host地址String host = "https://" + bucketName + "." + endpoint;Map<String, String> respMap = null;try {/// 设置过期时间// 秒数,这里设置10秒就过期long expireTime = 10;long expireEndTime = System.currentTimeMillis() + expireTime * 1000;Date expiration = new Date(expireEndTime);/// 指定请求的条件PolicyConditions policyConditions = new PolicyConditions();// 设置内容长度允许的字节数,最大是1048576000个字节,1MB=1048576个字节,这里限制最大是100MBpolicyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 104857600);// 限制上传文件的前缀// 设置文件夹,这里按照日期分文件夹String dir = new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + "/";policyConditions.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);/// 生成policyString postPolicy = ossClient.generatePostPolicy(expiration, policyConditions);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));} catch (Exception e) {System.out.println(e.getMessage());}return AjaxResult.success("获取凭证成功", respMap);}
}

Controller

package com.shm.controller;import com.ruoyi.common.core.domain.AjaxResult;
import com.shm.service.OssService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/thirdParty/oss")
@Api("OssController")
public class OssController {@Autowiredprivate OssService ossService;@ApiOperation("获取OSS凭证")@GetMapping("/policy")@PreAuthorize("@ss.hasPermi('thirdParty:oss:policy')")public AjaxResult policy() {return ossService.getPolicy();}
}

图片上传前端

图片上传组件

该组件基于若依管理系统前端项目所提供的图片上传组件修改,原组件采用直传方式,改动后采用签名后直传的方式

<template><div class="component-upload-image"><el-uploadmultiple:action="uploadImgUrl":data="dataObj"list-type="picture-card":on-success="handleUploadSuccess":before-upload="handleBeforeUpload":limit="limit":on-error="handleUploadError":on-exceed="handleExceed"ref="imageUpload":before-remove="handleDelete":show-file-list="true":headers="headers":file-list="fileList":on-preview="handlePictureCardPreview":class="{ hide: fileList.length >= limit }"><el-icon class="avatar-uploader-icon"><plus/></el-icon></el-upload><!-- 上传提示 --><div class="el-upload__tip" v-if="showTip">请上传<template v-if="fileSize">大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b></template><template v-if="fileType">格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b></template>的文件</div><el-dialogv-model="dialogVisible"title="预览"width="800px"append-to-body><img:src="dialogImageUrl"style="display: block; max-width: 100%; margin: 0 auto"/></el-dialog></div>
</template><script setup>
import {getToken} from "@/utils/auth";
import ossApi from '@/api/thirdParty/oss'
import uuidApi from '@/utils/uuid'const props = defineProps({modelValue: [String, Object, Array],// 图片数量限制limit: {type: Number,default: 5,},// 大小限制(MB)fileSize: {type: Number,default: 5,},// 文件类型, 例如['png', 'jpg', 'jpeg']fileType: {type: Array,default: () => ["png", "jpg", "jpeg"],},// 是否显示提示isShowTip: {type: Boolean,default: true},
});const {proxy} = getCurrentInstance();
const emit = defineEmits();
const number = ref(0);
const uploadList = ref([]);
const dialogImageUrl = ref("");
const dialogVisible = ref(false);
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadImgUrl = ref(import.meta.env.VITE_APP_OSS_PATH); // 上传的图片服务器地址
const headers = ref({Authorization: "Bearer " + getToken()});
const fileList = ref([]);
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize)
);
let dataObj = {policy: '',signature: '',key: '',OSSAccessKeyId: '',dir: '',host: ''
};watch(() => props.modelValue, val => {if (val) {// 首先将值转为数组const list = Array.isArray(val) ? val : props.modelValue.split(",");console.log("list:" + JSON.stringify(list));// 然后将数组转为对象数组fileList.value = list.map(item => {if (typeof item === "string") {item = {url: item};}return item;});console.log("fileList:" + JSON.stringify(fileList));} else {fileList.value = [];return [];}
}, {deep: true, immediate: true});// 上传前loading加载
function handleBeforeUpload(file) {let isImg = false;if (props.fileType.length) {let fileExtension = "";if (file.name.lastIndexOf(".") > -1) {fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);}isImg = props.fileType.some(type => {if (file.type.indexOf(type) > -1) return true;if (fileExtension && fileExtension.indexOf(type) > -1) return true;return false;});} else {isImg = file.type.indexOf("image") > -1;}if (!isImg) {proxy.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join("/")}图片格式文件!`);return false;}if (props.fileSize) {const isLt = file.size / 1024 / 1024 < props.fileSize;if (!isLt) {proxy.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);return false;}}//获取OSS签名return new Promise((resolve, reject) => {ossApi.getPolicy().then((response) => {console.log('policy response:' + JSON.stringify(response))// debugger;dataObj.policy = response.data.policydataObj.signature = response.data.signaturedataObj.OSSAccessKeyId = response.data.accessIddataObj.key = response.data.dir + uuidApi.getUUID() + '_${filename}'dataObj.dir = response.data.dirdataObj.host = response.data.hostconsole.log('获取policy成功')// console.log('dataObj:' + JSON.stringify(dataObj))// console.log("uploadImgUrl:" + import.meta.env.VITE_APP_OSS_PATH)proxy.$modal.loading("正在上传图片,请稍候...");number.value++;resolve(true)}).catch((err) => {console.log('获取policy失败')reject(false)})})}// 文件个数超出
function handleExceed() {proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
}// 上传成功回调
function handleUploadSuccess(res, file) {console.log("handleUploadSuccess," + JSON.stringify(res) + JSON.stringify(file))uploadList.value.push({name: file.name, url: dataObj.host +'/' +dataObj.key.replace('${filename}', file.name)});console.log("上传成功")uploadedSuccessfully();
}// 删除图片
function handleDelete(file) {const findex = fileList.value.map(f => f.name).indexOf(file.name);if (findex > -1 && uploadList.value.length === number.value) {fileList.value.splice(findex, 1);let urlList = getUrlList(fileList);emit("update:modelValue", urlList);return false;}
}// 上传结束处理
function uploadedSuccessfully() {console.log("number.value:" + number.value);if (number.value > 0 && uploadList.value.length === number.value) {//将新上传的图片添加到fileListfor (let i = 0; i < uploadList.value.length; i++) {fileList.value.push(uploadList.value[i]);}//将所有图片的url拿出来,形成一个集合let urlList = getUrlList(fileList);uploadList.value = [];number.value = 0;emit("update:modelValue", urlList);proxy.$modal.closeLoading();}
}// 上传失败
function handleUploadError() {proxy.$modal.msgError("上传图片失败");proxy.$modal.closeLoading();
}// 预览
function handlePictureCardPreview(file) {dialogImageUrl.value = file.url;dialogVisible.value = true;
}function getUrlList(fileList) {let urlList = [];for (let i = 0; i < fileList.value.length; i++) {urlList.push(fileList.value[i].url);}return urlList;
}</script><style scoped lang="scss">
// .el-upload--picture-card 控制加号部分
:deep(.hide .el-upload--picture-card) {display: none;
}
</style>

下面的代码即去配置文件中读取图片上传地址,我使用读取配置的方式主要是为了方便部署时的环境切换,也可以直接写在组件里面,打包部署时修改会繁琐一点,也会容易遗忘

const uploadImgUrl = ref(import.meta.env.VITE_APP_OSS_PATH); // 上传的图片服务器地址

在这里插入图片描述
在这里插入图片描述
下面的代码的作用是将值同步给组件v-model所绑定的变量中

emit("update:modelValue", urlList);

api

【OSS请求API】

import request from '@/utils/request'/*
菜单管理相关的API请求函数
*/
const api_name = '/thirdParty/oss'export default {getPolicy(data) {return request({url: `${api_name}/policy`,method: "get",params: data})},}

【oss生成API】

/*** 获取uuid*/
export default {getUUID() {return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16)})}
}

使用UUID的作用主要是:用户上传的图片名称可能一样,但是图片内容不同,在名称前面添加UUID可以避免文件名冲突,如下图
在这里插入图片描述

页面使用组件

在这里插入图片描述
使用,通过v-model绑定变量,值的形式是url数组。limit="1"的作用是限制只能上传一张图片

<imageUpload v-model="form.logo" limit="1"></imageUpload>

查看组件的代码,limit fileSize fileType isShowTip这个值都是可以设置的,具体的含义请查看下图
在这里插入图片描述

组件效果

这个组件的功能还是比较完善的,具体效果可以查看下面的效果图
【上传图片之前】
在这里插入图片描述
【上传图片成功后】
在这里插入图片描述
【上传成功的图片可以预览和删除】

在这里插入图片描述
【预览效果】
在这里插入图片描述
【上传多个图片的效果】
在这里插入图片描述

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

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

相关文章

【论文】基于GANs的图像文字擦除 ——2010.EraseNet: End-to-End Text Removal in the Wild(已开源)

pytorch官方代码&#xff1a;https://github.com/lcy0604/EraseNet 论文&#xff1a;2010.EraseNet: End-to-End Text Removal in the Wild 网盘提取码&#xff1a;0719 一、图片文字去除效果 图10 SCUT-EnsText 真实数据集的去除 第一列原图带文字、第二列为去除后的标签&a…

爆肝整理,Postman接口测试-全局变量/接口关联/加密/解密(超细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 全局变量和环境变…

AJAX:宏任务与微任务

异步任务划分为了 宏任务&#xff1a;由浏览器环境执行的异步代码 微任务&#xff1a;由 JS 引擎环境执行的异步代码 宏任务和微任务具体划分&#xff1a; 左边表格是宏任务&#xff0c;右边是微任务 事件循环模型 /*** 目标&#xff1a;阅读并回答打印的执行顺序 */ console…

Spark编程-键值对RDD(K,V)创建及常用操作

简述 SparkRDD中可以包含任何类型的对象&#xff0c;在实际应用中&#xff0c;“键值对”是一种比较常见的RDD元素类型&#xff0c;分组和聚合操作中经常会用到&#xff0c;尤其是groupByKey和reduceByKey。 Spark操作中经常会用到“键值对RDD”&#xff08;Pair RDD&a…

CSS样式

1.高度和宽度 .c1{height:300px;width:500px;}注意事项&#xff1a; 宽度支持百分比&#xff0c;高度不支持。行内标签&#xff1a;默认无效会计标签&#xff1a;默认有效&#xff08;霸道&#xff0c;右侧区域空白&#xff0c;也不给你用&#xff09; 2.块级和行内标签 块…

【Django学习】(十四)自定义action_router

之前我们的视图类可以继承GenericViewSet或者ModelViewSet&#xff0c;我们不用再自定义通用的action方法&#xff0c;但是有时候我们需要自定义action&#xff0c;我们该如何设计呢&#xff1f; 自定义action 1、手写视图逻辑 1.1、先在视图集里自定义action方法&#xff0…

GO语言泛型

set一般没什么不方便的 但是使用GET 需要使用类型断言,将取出来的数据转为预期数据, 空接口本身是一个装箱,会产生内存逃逸和多一部分空间. 于是1.17GO使用泛型. 泛型实现: 分析可执行文件后:发现 也就是泛型会为每个数据类型都生产一套代码,导致可执行文件大小增加,并且使用…

uni-app中a标签下载文件跳转后左上角默认返回键无法继续返回

1.首先使用的是onBackPress //跟onShow同级别 onBackPress(option){ uni.switchTab({ url:/pages/....... return true }) }发现其在uni默认头部中使用是可以的 但是h5使用了"navigationStyle":"custom"后手机默认的返回并不可以&#xff0c; 2.经过查询…

LCD-STM32液晶显示中英文-(5.字符编码)

目录 字符编码 字符编码说明参考网站 字符编码 ASCII编码 ASCII编码介绍 ASCII编码表 中文编码 1. GB2312标准 区位码 2. GBK编码 3. GB18030 各个标准的对比说明 4. Big5编码 字符编码 字符编码说明参考网站 字符编码及转换测试&#xff1a;导航菜单 - 千千秀字 …

智迪科技在创业板上市:市值约31亿元,谢伟明和黎柏松为实控人

7月17日&#xff0c;珠海市智迪科技股份有限公司&#xff08;下称“智迪科技”&#xff0c;SZ:301503&#xff09;在深圳证券交易所创业板上市。本次上市&#xff0c;智迪科技的发行价为31.59元/股&#xff0c;发行数量为2000万股&#xff0c;募资总额约为6.32亿元&#xff0c;…

onnx如何改变输入的维度

最近遇到一个难题&#xff0c;就算在用行为识别onnx转rknn的时候提示维度不对&#xff0c;因为行为识别模型是5维的。而rknn只支持4维。 我们先加载模型看一下它的input和node 可以看出模型的input[1]是一个全连接&#xff0c;因此我们可以直接修改他的input[0] input hel…

Kafka 入门到起飞系列 - 生产者发送消息流程解析

生产者通过producerRecord 对象封装消息主题、消息的value&#xff08;内容&#xff09;、timestamp(时间戳)等 生产者通过send()方法发送消息&#xff0c;send()方法会经过如下几步 1. 首先将消息交给拦截器&#xff08;Interceptor&#xff09;处理, 拦截器对生产者而言&…

静态数码管——FPGA

文章目录 前言一、数码管1、数码管简介2、共阴极数码管or共阳极数码管3、共阴极与共阳极的真值表 二、系统设计1、模块框图2、RTL视图 三、源码1、seg_led_static模块2、time_count模块3、top_seg_led_static(顶层文件) 四、效果五、总结六、参考资料 前言 环境&#xff1a; 1、…

数字化时代,智能文件工具让办公升级

无论是在办公室还是在学校&#xff0c;文件管理是我们日常工作中不可或缺的一环。传统的文件整理方式可能需要花费大量的时间和精力&#xff0c;而且常常容易出现混乱和遗漏。然而&#xff0c;随着科技的不断进步&#xff0c;我们现在有幸生活在一个数字化时代&#xff0c;因此…

如何正确有效的学习java前端(合集)

大量阅读 我是一个劲头十足的读者。所以&#xff0c;我的第一个关于学习JavaScript的技巧就是关于阅读&#xff0c;这绝不是巧合。书籍和其他的资源(如文章)可以在很大程度上帮助你学习JavaScript。通过实践学习&#xff0c;书籍是我学习新学科最喜欢的方式。在学习JavaScript的…

测试用例(2)

项目管理工具 主要用tapd&#xff0c;jira少用 acp 敏捷项目管理证书 task:故事&#xff0c;一个故事有开始也有结束&#xff0c;那么在项目管理里面&#xff0c;会把每个任务按照一个task来看&#xff0c;那么这个task也可以叫story&#xff0c;具体指的就是任务有开始有结…

ChatGPT火热之下的冷思考

作为一款基于人工智能的自然语言处理(NLP)​​聊天机器人​​程序&#xff0c;ChatGPT通过大量来自互联网的文本进行训练&#xff0c;并使用深度学习和机器学习算法来理解用户的问题并提供准确的回答。并且&#xff0c;ChatGPT还内置了情感分析、关键字提取和实体识别等功能&am…

Beyond Compare 代码比较工具

一、下载 官网下载地址&#xff1a; https://www.scootersoftware.com/download.php 选择 Windows 系统&#xff0c;简体中文版本&#xff0c;点击下载。 下载完成 二、安装 步骤1&#xff1a;双击安装包 步骤2&#xff1a;进入安装向导&#xff0c;点击下一步 步骤3&a…

在LLM的支持下使游戏NPC具有记忆化的方法

问题 使用GPT这样的LLM去处理游戏中的NPC和玩家的对话是个很好的点子&#xff0c;那么如何处理记忆化的问题呢。 因为LLM的输入tokens是有限制的&#xff0c;所以伴随着问题的记忆context是有窗口大小限制的&#xff0c;将所有的记忆输入LLM并不现实。 所以这里看到了stanfo…

Zookeeper

作为分布式中间件&#xff0c;zookeeper有以下几个重要功能 服务注册服务监听 &#xff1a;观察者模式&#xff0c;有服务上线或下线可以感知&#xff0c;并进行响应回调处理服务拉取配置中心CP特性数据存储方式为标准的文件结构 安装zk需要java环境&#xff0c;可参考 linux…