一文带你看懂 前后端之间图片的上传与回显

一文带你看懂 前后端之间图片的上传与回显

前言

看了很多类似的文章,发现很多文章,要不就是不对,要不就是代码写的不通俗易懂,所以有了这篇文章,我将会从原理到实战,带你了解 实战包含前端 原生 vue3 react 后端springboot 主流框架来进行实现。

原理篇

上传文件需要发送请求。在这些请求中,浏览器将数据拆分为小的“块”,然后通过连接逐个发送这些块。这是必要的,因为文件可能过大而无法一次性发送作为一个庞大的有效负载。

随时间发送的数据块组成了所谓的“流”。流在第一次理解时有点难 它们值得有一篇完整的文章(或多篇文章)来介绍,

基本上,流有点像是数据的传送带,每个块都可以在进入时被处理。就 HTTP 请求而言,后端会逐位接收请求的各个部分。

当我们使用请求上传文件时,浏览器将使用流一次发送一个块的数据。这是因为我们不能一次将整个文件放在请求对象中。multipart/form-data

我们直接去打印这个文件的请求。

我们应该看到一个包含所有表单字段及其值的对象,但对于每个文件输入,我们将看到一个表示上传文件的对象,而不是文件本身。此对象包含各种有用的信息,包括其在磁盘上的路径、名称等

image-20240322100322978

这个时候我们需要把他转换为一个FormData 对象

这样便于我们给后端传输我们需要传输的东西。

文件上传为什么要用 multipart/form-data?

The encoding type application/x-www-form-urlencoded is inefficient for sending large quantities of binary data or text containing non-ASCII characters. Thus, a new media type,multipart/form-data, is proposed as a way of efficiently sending the values associated with a filled-out form from client to server.

1867文档中也写了为什么要新增一个类型,而不使用旧有的application/x-www-form-urlencoded:因为此类型不适合用于传输大型二进制数据或者包含非ASCII字符的数据。平常我们使用这个类型都是把表单数据使用url编码后传送给后端,二进制文件当然没办法一起编码进去了。所以multipart/form-data就诞生了,专门用于有效的传输文件。

文件上传为什么要用 multipart/form-data? 可以用application/json吗

文件上传通常使用multipart/form-data格式,而不是application/json,因为multipart/form-data格式允许在HTTP请求中传输二进制文件数据,例如图像、视频或文档等。而application/json格式通常用于传输结构化的文本数据,例如JSON对象或数组。

multipart/form-data格式允许在一个请求中同时发送文本数据和二进制文件数据,这对于上传文件非常有用。它使用一种多部分的格式,将请求体划分为多个部分,每个部分可以包含不同类型的数据,例如文本字段和文件数据。

相比之下,application/json格式虽然可以用于传输文本数据,但不支持直接在请求体中传输二进制文件数据。如果尝试将文件数据编码为JSON字符串并在application/json格式的请求中发送,通常会导致数据丢失或不可用。

实战篇

本地存储

第一个我要介绍最常用的,vue3+springboot

vue3+springboot

第一个实现方式是本地存储

也就是存储到自己的服务器上。

首先我们来看前端的源码:

<template><div><input type="file" @change="handleFileChange"><button @click="uploadImage">上传图片</button><img :src="getImageUrl()" v-if="imageUrl"></div>
</template>
​
<script setup>
import { ref } from 'vue';
import axios from 'axios';
​
const file = ref(null);
const imageUrl = ref(null);
​
const handleFileChange = (event) => {file.value = event.target.files[0];
};
​
const uploadImage = async () => {const formData = new FormData();formData.append('image', file.value);
​try {const response = await axios.post('http://localhost:8081/api/upload', formData, {headers: {'Content-Type': 'multipart/form-data'}});
​imageUrl.value = response.data;} catch (error) {console.error('Error uploading image: ', error);}
};
​
const getImageUrl = () => {if (imageUrl.value) {// 拼接后端服务器地址和图片地址return `http://localhost:8081${imageUrl.value}`;}
};
</script>

这里我用到了axios 当然你也可以选择别的去用。相信我这个代码很简洁,我就不多说了。

我们来看后端的代码。

首先我们要在upload里面去配置一下我们存储文件的一个地址 我把这个放到了yml文件里面

upload:path: D:\onenodes\project\xiaou-easy-code\1\xiaou-spring boot-demo-backend\src\main\java\com\xiaou\upload\

这里需要注意的是,如果是本地的话,就是完整路径,如果你是想要部署上线的话,要填写你服务器的文件路径。

之后我们做一个文件映射

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/uploads/**").addResourceLocations("file:uploads/");
}

确保后端可以打开这个图片

之后是后端的代码

package com.xiaou.controller;
​
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
​
import java.io.File;
import java.io.IOException;
import java.util.UUID;
​
@RestController
@RequestMapping("/api")
@Slf4j
public class ImageController {
​@Value("${upload.path}")private String uploadPath;
​@PostMapping("/upload")public String uploadImage(@RequestParam("image") MultipartFile image) throws IOException {String imageName = UUID.randomUUID().toString() + "_" + image.getOriginalFilename();File dest = new File(uploadPath + imageName);image.transferTo(dest);
​log.info("图片后端地址 " + "/api/images" + imageName);return "/api/images/" + imageName;}
​@GetMapping("/images/{imageName}")public ResponseEntity<Resource> getImage(@PathVariable String imageName) throws IOException {File file = new File(uploadPath + imageName);Resource resource = new UrlResource(file.toURI());
​return ResponseEntity.ok().contentType(MediaType.IMAGE_JPEG).body(resource);}
}

这里设置俩个接口,一个是上传,一个就是图片的一个回显。

这里的ResponseEntity 是 Spring Framework 提供的一个类,用于表示 HTTP 响应实体。它允许你将 HTTP 响应的状态码、头部信息以及响应体等内容封装到一个对象中,然后返回给客户端。

之后我们来介绍原生的html css js

后端代码保持不变。前端代码如下:

原生html css JavaScript
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Image Uploader</title><style>/* CSS 样式 */.container {margin-top: 20px;}</style>
</head>
<body>
<div class="container"><input type="file" id="fileInput"><button id="uploadButton">上传图片</button><img id="uploadedImage" style="display: none;">
</div>
​
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>// JavaScript 代码document.getElementById('uploadButton').addEventListener('click', function() {var fileInput = document.getElementById('fileInput');var file = fileInput.files[0];var formData = new FormData();formData.append('image', file);
​axios.post('http://localhost:8081/api/upload', formData, {headers: {'Content-Type': 'multipart/form-data'}}).then(function(response) {var imageUrl = "http://localhost:8081"+response.data;console.log(imageUrl)
​document.getElementById('uploadedImage').src = imageUrl;document.getElementById('uploadedImage').style.display = 'block';}).catch(function(error) {console.error('Error uploading image: ', error);});});
</script>
</body>
</html>
react
import React, { useState } from 'react';
import axios from 'axios';
​
function ImageUploader() {const [file, setFile] = useState(null);const [imageUrl, setImageUrl] = useState(null);
​const handleFileChange = (event) => {setFile(event.target.files[0]);};
​const uploadImage = async () => {const formData = new FormData();formData.append('image', file);
​try {const response = await axios.post('http://localhost:8081/api/upload', formData, {headers: {'Content-Type': 'multipart/form-data'}});var imgUrl="http://localhost:8081/"+response.datasetImageUrl(imgUrl);} catch (error) {console.error('Error uploading image: ', error);}};
​return (<div><input type="file" onChange={handleFileChange} /><button onClick={uploadImage}>上传图片</button>{imageUrl && <img src={imageUrl} alt="Uploaded" />}</div>);
}
​
export default ImageUploader;

二进制存储到数据库

这个经过我的测试不是很好实现。

数据库字段会超出。考虑过压缩图片,但是这样完全没必要。所以这个直接跳过。

image-20240322090612941

第三方存储 cos

这里用到工具类,你也可以自己封装,我这里用到了阿里云的oss

package com.xiaou.util;
​
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
​
import java.io.InputStream;
​
public class AliOssUtil {private static final String ENDPOINT = "xxx";private static final String ACCESS_KEY_ID = "xx";private static final String SECRET_ACCESS_KEY = "xxx";private static final String BUCKET_NAME = "xxx";
​//上传文件,返回文件的公网访问地址public static String uploadFile(String objectName, InputStream inputStream){// 创建OSSClient实例。OSS ossClient = new OSSClientBuilder().build(ENDPOINT,ACCESS_KEY_ID,SECRET_ACCESS_KEY);//公文访问地址String url = "";try {// 创建存储空间。ossClient.createBucket(BUCKET_NAME);ossClient.putObject(BUCKET_NAME, objectName, inputStream);url = "https://"+BUCKET_NAME+"."+ENDPOINT.substring(ENDPOINT.lastIndexOf("/")+1)+"/"+objectName;} 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();}}return url;}
}

之后直接调用就可以

package com.xiaou.controller;
​
import com.xiaou.util.AliOssUtil;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
​
@RestController
@RequestMapping("/api")
public class FileUploadController {
​@PostMapping("/upload")public String uploadFile(@RequestParam("file") MultipartFile file) {try {// 调用阿里云 OSS 工具类上传文件String objectName = file.getOriginalFilename();String url = AliOssUtil.uploadFile(objectName, file.getInputStream());return "File uploaded successfully! URL: " + url;} catch (Exception e) {e.printStackTrace();return "Error uploading file: " + e.getMessage();}}
}

之后来看前端实现,我只写一下vue3的。其他的都大同小异:

<template><div><input type="file" @change="handleFileChange"><button @click="uploadFile">上传文件</button><div v-if="uploadStatus">{{ uploadStatus }}</div></div>
</template>
​
<script>
import axios from 'axios';
​
export default {data() {return {file: null,uploadStatus: ''};},methods: {handleFileChange(event) {this.file = event.target.files[0];},async uploadFile() {if (!this.file) {this.uploadStatus = '请选择要上传的文件';return;}
​const formData = new FormData();formData.append('file', this.file);
​try {const response = await axios.post('http://localhost:8080/api/upload', formData, {headers: {'Content-Type': 'multipart/form-data'}});this.uploadStatus = response.data;} catch (error) {this.uploadStatus = '上传文件出错: ' + error.message;}}}
};
</script>
​

至此,文件上传完成。

后记

这里说一个题外话,关于我自己开了一个新的项目,在业务开发中,我们有很多很固定的代码,这些东西我们大部分情况下会去选择复制一些,但是由于网络上的资源良莠不齐,而且很多代码没有详细的讲解,所以我打算开一个这样的通用模板项目。

目前项目在初期阶段,这个也是这个项目的第一个通用解决方法。各位如果有兴趣可以来看一看我这个项目,提个pr issue 一起共创这个项目。

我也会经常去更新这个项目,去抽离出一些优秀的解决方案。

xiaou61/xiaou-easy-code: 全栈通用解决方案合集 包含在开发工作中解决常用问题的较优方案 包括springboot vue3 react java javescript (github.com)

以及文档地址:

Xiaou-EasyCode-Docs (xiaou61.top)

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

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

相关文章

Gold Effects

HDRP、URP、LWRP和标准支持 完全可定制的金币效果。几乎每个属性都是可调整的,您可以更改这些效果的颜色、渐变、噪波纹理和整体形状。支持HDRP、URP和LWRP,当然也支持标准渲染器。易于拖放设置,带有定制示例的演示场景。使用标准Unity Animator为箱子制作动画,因此您可以轻…

Python爬虫与数据可视化源码免费领取

引言 作为一名在软件技术领域深耕多年的专业人士&#xff0c;我不仅在软件开发和项目部署方面积累了丰富的实践经验&#xff0c;更以卓越的技术实力获得了&#x1f3c5;30项软件著作权证书的殊荣。这些成就不仅是对我的技术专长的肯定&#xff0c;也是对我的创新精神和专业承诺…

电子科技大学链时代工作室招新题C语言部分---题号H

1. 题目 最有操作的一道题&#xff0c;有利于对贪心算法有个初步了解。 这道题的开篇向我们介绍了一个叫汉明距离的概念。 汉明距离指的就是两个相同长度的字符串的不同字符的个数。 例如&#xff0c;abc和acd&#xff0c;b与c不同&#xff0c;c与d不同&#xff0c;所以这两个…

每周一算法:迭代加深A*

题目链接 AcWing 180. 排书 题目描述 给定 n n n 本书&#xff0c;编号为 1 ∼ n 1\sim n 1∼n。 在初始状态下&#xff0c;书是任意排列的。 在每一次操作中&#xff0c;可以抽取其中连续的一段&#xff0c;再把这段插入到其他某个位置。 我们的目标状态是把书按照 1 ∼…

牛客题霸-SQL进阶篇(刷题记录一)

本文基于前段时间学习总结的 MySQL 相关的查询语法&#xff0c;在牛客网找了相应的 MySQL 题目进行练习&#xff0c;以便加强对于 MySQL 查询语法的理解和应用。 由于涉及到的数据库表较多&#xff0c;因此本文不再展示&#xff0c;只提供 MySQL 代码与示例输出。 部分题目因…

青海200MW光伏项目 35kV开关站图像监控及安全警示系统

一、背景 随着我国新能源产业的快速发展&#xff0c;光伏发电作为清洁能源的重要组成部分&#xff0c;得到了国家政策的大力扶持。青海作为我国光伏资源丰富地区&#xff0c;吸引了众多光伏项目的投资建设。在此背景下&#xff0c;为提高光伏发电项目的运行效率和安全性能&…

基于Java中的SSM框架实现万卷图书馆书籍借阅管理系统项目【项目源码+论文说明】

基于Java中的SSM框架实现万卷图书馆书籍借阅管理系统演示 摘要 图书管理系统&#xff0c;是一个由人、计算机等组成的能进行管理信息的收集、传递、加工、保存、维护和使用的系统。利用信息控制企业的行为&#xff1b;帮助企业实现其规划目标。 图书馆管理系统&#xff0c;能…

二、typescript基础语法

一、条件语句 二、函数 1、有名函数 function add(x:number, y:number):number {return x y;}2、匿名函数 let add function (x:number, y:number):number {return x y;}函数可选参数 function buildName(firstname: string, lastname?:string) {if (lastname) {return fi…

asp.net mvc 重新引导视图路径,改变视图路径

asp.net mvc 重新引导视图路径&#xff0c;改变视图路径 使用指定的控制器上下文和母版视图名称来查找指定的视图 通过本文学习&#xff0c;你可以根据该技法&#xff0c;去实现&#xff0c;站点自定义皮肤&#xff0c;手机站和电脑站&#xff0c;其他设备站点&#xff0c;在不…

3.面向对象中级

文章目录 包访问修饰符封装继承继承使用细节继承内存布局及细节 Supersuper使用细节super与this比较 overwrite多态对象的多态&#xff1a;向上转型&#xff1a;向下转型&#xff1a;多态细节动态绑定机制 Object类equalshashcodetoStringfinalize 包 区分相同名字的类&#x…

LeetCode讲解算法1-排序算法(Python版)

文章目录 一、引言问题提出 二、排序算法1.选择排序&#xff08;Selection Sort&#xff09;2.冒泡排序3.插入排序&#xff08;Insertion Sort&#xff09;4.希尔排序&#xff08;Shell Sort&#xff09;5.归并排序&#xff08;Merge Sort&#xff09;6.快速排序&#xff08;Qu…

linux之shell脚本基础

1.构建基础脚本 1.1 创建shell脚本 1.1.1 第一行需要指定使用的shell # 用作注释行.shell并不会处理脚本中的注释行,但是第一行的注释,会告诉shell使用哪个shell来运行脚本. #!/bin/bash 1.1.2 让shell找到你的脚本 直接运行脚本会提示-bash: a.sh: command not found.因…

Selenium 自动化 —— Selenium IDE录制、回放、导出Java源码

Hello Selenium 示例 之前我们在专栏的第一篇文章中演示了使用使用Selenium进行百度搜索的Hello world示例。 代码不复杂非常简单&#xff1a; public static void main(String[] args) {WebDriver driver null;try {// 设置Chrome驱动的路径 // System.setPro…

Javaweb学习记录(三)请求响应案例

下面为一个请求响应案例&#xff0c;postman发送请求&#xff0c;服务器响应将一个xml文件中的数据通过读取解析&#xff0c;将其用Result类标准的格式返回前端&#xff0c;在前端用json的方式显示 后端Controller代码 1、通过本类的字节码文件得到类加载器并寻找到需要解析的…

如何使用 ArcGIS Pro 生成TIN

三角网是一种常用于表示地表地形的数字地球模型&#xff08;DEM&#xff09;方式&#xff0c;我们可以通过 ArcGIS Pro 将等高线和高程点转换为TIN&#xff0c;这里为大家介绍一下转换方法&#xff0c;希望能对你有所帮助。 数据来源 教程所使用的数据是从水经微图中下载的高…

MATLAB环境下基于振动信号的轴承状态监测和故障诊断

故障预测与健康管理PHM分为故障预测和健康管理与维修两部分&#xff0c;PHM首先借助传感器采集关键零部件的运行状态数据&#xff0c;如振动信号、温度图像、电流电压信号、声音信号及油液分析等&#xff0c;提取设备的运行监测指标&#xff0c;进而实现对设备关键零部件运行状…

python 爬取杭州小区挂牌均价

下载chrome驱动 通过chrome浏览器的 设置-帮助-关于Google Chrome 查看你所使用的Chrome版本 驱动可以从这两个地方找: 【推荐】https://storage.googleapis.com/chrome-for-testing-publichttp://npm.taobao.org/mirrors/chromedriver import zipfile import os import r…

前端面试拼图-知识广度

摘要&#xff1a;最近&#xff0c;看了下慕课2周刷完n道面试题&#xff0c;记录并添加部分可参考的文档&#xff0c;如下... 1. 移动端H5 click有300ms延迟&#xff0c; 如何解决&#xff1f; 背景&#xff1a;double tap to zoom 移动端H5中的300ms点击延迟问题通常是由浏览…

【地图】腾讯地图 - InfoWindow 自定义信息窗口内容时,内容 html 嵌套混乱问题

目录 需求描述问题问题代码页面展示 解决原因解决办法解决代码页面展示 代码汇总注 需求描述 腾讯地图上画点位&#xff0c;点击点位展示弹框信息 问题 问题代码 // 打开弹框 openInfoWindow(position, content) {this.infoWindow new TMap.InfoWindow({map: this.map,posit…

当内外网的域名相同时,如何在外网解析同域名的网址

当内部网络和外部网络存在相同的域名&#xff0c;并且希望内部用户通过内部DNS服务器解析到外部网络上的该域名对应的公网IP地址时&#xff0c;需要在内部DNS服务器上采取一些特殊配置策略来实现这一目标。以下是一种通用的解决方案&#xff1a; 条件转发&#xff08;Condition…