图片上传成功却无法显示:静态资源路径配置问题解析

1、故事的背景

最近,有个学弟做了一个简单的后台管理页面。于是他开始巴拉巴拉撘框架,写代码,一顿操作猛如虎,终于将一个简单的壳子搭建完毕。但是在实现功能:点击头像弹出上传图片进行头像替换的时候,卡壳了~

原来啊,他发现图片上传成功并且图片的信息都保存进数据库后,页面没有加载出图片,这可就给他犯了难,捣鼓了半天,还是没有搞定。

2、问题剖析

于是他就过来向我求助,让我帮他一起来研究代码,看看是哪个步骤出了问题。这里,我将简单的贴出关键的代码信息。

2.1 前端代码的剖析

前端主要是实现了一个用户信息编辑页面,主功能包括用户信息展示和编辑,以及头像上传和显示。我们来逐步分解并详细解释前端代码:

  • 首先看模板部分,使用了Element UI的表单组件`<el-form>`,绑定user数据模型。使用`<el-upload>`组件实现头像上传功能,`action`属性定义上传地址,`accept`属性限制上传文件类型为图片,`show-file-list`设置为false用来隐藏文件列表。
  • 接着看`<script>`部分,导出vue组件,定义了初始化各个属性,同时使用了钩子,在组件创建时调用`getUser()`方法获取用户信息。至于`methods`则是处理了`getUser()`方法的实现逻辑。
  • 至于样式方面,没啥好说的,就是设置了上传组件的样式。
<template><div class="me"><el-form :model="user" label-width="auto" style="max-width: 600px" enctype="multipart/form-data"><el-form-item label="头像"><el-upload class="avatar-uploader" :action="uploadUrl" accept="image/*" show-file-list=false><img v-if="islook" style="height: 200px; width: 200px" alt="头像" class="avatar" :src="imageUrl"/><el-icon class="avatar-uploader-icon" v-else-if="!islook"></el-icon></el-upload></el-form-item><el-form-item label="用户名"><el-input v-model="user.username"/></el-form-item><el-form-item label="性别"><el-input v-model="user.sex"/></el-form-item><el-form-item label="年龄"><el-input v-model="user.age"/></el-form-item><el-form-item label="邮箱"><el-input v-model="user.mailbox"/></el-form-item><el-form-item label="个人简介"><el-input v-model="user.introduce" type="textarea"/></el-form-item></el-form></div>
</template>
<script>
import axios from 'axios';export default {name: 'MePage',data() {return {user: {userId: '',sex: '',age: '',mailbox: '',username: '',introduce: '',headPortrait: ''},uploadUrl: 'http://localhost:8081/upload/avatar' // 上传头像的接口地址,, islook: false,imgUrl: '../assets/logo.png',imageUrl: ''}},components: {},created() {this.getuser();},methods: {getuser() {axios.get('http://localhost:8081/user/getById?userId=123').then(response => {this.user = response.data;this.imageUrl = this.user.headPortrait ? `${this.user.headPortrait}` : '@/assets/logo.png';console.log('图片 URL:', this.imageUrl);this.islook = true;}).catch(error => {console.log(error);ElMessage.error('获取用户信息失败');});}}}</script>
<style>
.avatar-uploader .el-upload {border: 1px dashed var(--el-border-color);border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;transition: var(--el-transition-duration-fast);height: 200px;width: 200px
}.avatar-uploader .el-upload:hover {border-color: var(--el-color-primary);
}
</style>


实际上,这里不知道是代码才写到一半还是没给到我完整代码的原因。单从这部分代码看,功能也并不完善,达不到上传图片并展示图片的预期,但是点击上传后图片是没有任何变化的。所以我以这段代码为基础,进行了完善,来看看页面是否可以正常上传显示。
 

改善后的代码:

<template><div class="me"><el-form :model="user" label-width="auto" style="max-width: 600px" enctype="multipart/form-data"><el-form-item label="头像"><el-uploadclass="avatar-uploader":action="uploadUrl"accept="image/*"show-file-list="false":before-upload="beforeUpload":on-success="handleSuccess":on-error="handleError":on-change="handleFileChange"><img v-if="imageUrl" style="height: 200px; width: 200px" alt="头像" class="avatar" :src="imageUrl"/><el-icon class="avatar-uploader-icon" v-else-if="!islook"/></el-upload></el-form-item><el-form-item label="用户名"><el-input v-model="user.username"/></el-form-item><el-form-item label="性别"><el-input v-model="user.sex"/></el-form-item><el-form-item label="年龄"><el-input v-model="user.age"/></el-form-item><el-form-item label="邮箱"><el-input v-model="user.mailbox"/></el-form-item><el-form-item label="个人简介"><el-input v-model="user.introduce" type="textarea"/></el-form-item></el-form></div>
</template>
<script>
import axios from 'axios';
import { ElMessage } from 'element-plus';
export default {name: 'MePage',data() {return {user: {userId: '',sex: '',age: '',mailbox: '',username: '',introduce: '',headPortrait: ''},uploadUrl: 'http://localhost:8081/upload/avatar',islook: false,imgUrl: '../assets/logo.png',imageUrl: ''}},components: {},created() {this.getuser();},methods: {getuser() {axios.get('http://localhost:8081/user/getById?userId=123').then(response => {this.user = response.data;this.imageUrl = this.user.headPortrait ? `${this.user.headPortrait}` : '@/assets/logo.png';console.log('图片 URL:', this.imageUrl);this.islook = true;}).catch(error => {console.log(error);ElMessage.error('获取用户信息失败');});},beforeUpload(file) {const isImage = file.type.startsWith('image/');if (!isImage) {ElMessage.error('只能上传图片文件!');return false;}return isImage;},handleSuccess(response) {console.log('图片 上传成功响应:', response);if (response.code === 200) {ElMessage.success('上传成功');console.log('上传成功');// 重新调用 getuser 方法以获取最新的头像地址this.getuser();} else {ElMessage.error('上传失败: ' + response.msg);}},handleError(err) {console.error('图片上传失败:', err);ElMessage.error('上传失败: ' + (err.message || '未知错误'));},handleFileChange(file) {// 更新显示的图片const fileURL = file.raw;this.imageUrl = URL.createObjectURL(fileURL);}}
}
</script><style>
.avatar-uploader .el-upload {border: 1px dashed var(--el-border-color);border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;transition: var(--el-transition-duration-fast);height: 200px;width: 200px
}.avatar-uploader .el-upload:hover {border-color: var(--el-color-primary);
}
</style>



与一开始的代码相比:

  • 添加了 beforeUpload 方法,用于在上传文件之前进行校验,如果文件不是图片类型,我们直接显示错误信息并阻止继续上传。
  • 添加了 handleSuccess 方法,用于处理上传成功后的逻辑,如果上传成功,会显示成功信息,并重新调用getUser 方法以获取最新的头像地址。
  • 添加了 handleError 方法,用于处理上传失败后的逻辑。如果上传失败,会显示错误信息。
  • 添加了 handleFileChange 方法,用于在文件选择变化时更新显示的图片。通过 URL.createObjectURL  方法生成临时的 URL 来显示选择的图片。    

    
通过我们打印的日志可以看到,图片已经上传成功,且数据也已经入库,但是页面上的图片还是没有显示成功。
       
那么在确定前端已经没有问题了后,接下来我们就需要排查后端,看是什么原因导致的。

2.2 后端代码的剖析

后端主要是提供文件上传的接口,将个人信息(头像图片地址,用户名,性别,年龄等)进行编辑更新,同时还提供了用户信息查询接口,用来查询个人信息。我们来逐步分解并详细解释后端代码:

@CrossOrigin(origins = "http://localhost:8080")
@RestController
@RequestMapping("/upload")
public class Upload {@Autowiredprivate UploadService uploadService;/*** 头像上传*/@PostMapping("/avatar")public RestResult<Boolean> avatar(@RequestBody @RequestParam("file") MultipartFile file, HttpServletRequest request) throws Exception {if (!file.isEmpty()) {Boolean b = uploadService.avatarUpload(file, request);if (Boolean.TRUE.equals(b)) {return RestResult.build(true);}}throw new Exception("头像上传失败");}
}


这段代码的主要功能是处理头像上传的 HTTP POST 请求。它通过 UploadService  服务来处理文件上传逻辑,并返回上传结果。如果上传成功,这个控制器类还简单配置了跨域支持,允许来自  http://localhost:8080  的请求。

我们直接来看看具体实现的逻辑,逻辑很简单,就是从配置文件中获取头像上传的默认路径,处理上传文件信息并将上传的文件保存到指定路径。生成文件的访问URL,并调用  userService.updateAvatar  方法更新用户头像URL到数据库。看着也没啥问题。

@Service
public class UploadServiceImpo implements UploadService {@Autowiredprivate UserService userService;//头像上传路径@Value("${weibo.profile}")private String defaultBaseDir;@Overridepublic Boolean avatarUpload(MultipartFile file, HttpServletRequest request) {//获取当前头像上传路径String avatar = defaultBaseDir;//获取当前用户id并且修改用户的头像地址// 文件名称 用户id// 获取文件的名称String originalFilename = file.getOriginalFilename();// 文件后缀 例如:.pngassert originalFilename != null;String fileSuffix = originalFilename.substring(originalFilename.lastIndexOf("."));// uuid 生成文件名String uuid = String.valueOf(UUID.randomUUID());// 根路径,String basePath = avatar + uuid;// 新的文件名,使用uuid生成文件名String fileName = uuid + fileSuffix;// 创建新的文件File fileExist = new File(basePath);// 文件夹不存在,则新建if (!fileExist.exists()) {fileExist.mkdirs();}// 获取文件对象File newfile = new File(basePath, fileName);try {// 完成文件的上传file.transferTo(newfile);} catch (Exception e) {throw new RuntimeException("文件上传失败");}//判断文件是否上传成功并保存到数据库String url = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + "/image/" + uuid + "/" + fileName;return userService.updateAvatar(123L, url);}
}



接着我们来确认下配置文件,图片的存储路径也没啥问题。    

server:port: 8081
weibo:# 文件路径profile: G:/working-idea/vue3/vue3/src/assets/images/


    
2.3 问题剖析

现状
前端上传图片的功能已经成功上传图片并保存到数据库,但页面上的图片仍然无法显示。将后端保存的图片地址在尝试浏览器打开,可以发现访问不到。

原因分析

1.  路径问题,前端请求的图片路径可能有误。如果使用的是相对路径,路径相对于前端页面的位置可能不正确。后端生成的图片URL可能不正确,如URL中拼写错误或存在多余的空格。

2.  服务器配置问题,服务器可能没有配置正确的静态资源映射。例如,在Spring Boot中,需要配置静态资源路径,确保图片可以被正确访问。    
  
3.  浏览器缓存问题,有时候浏览器会缓存图片,导致显示的是旧的图片。尝试清除浏览器缓存或使用无痕模式查看图片是否能正常显示。

4.  权限问题,上传的图片文件没有正确的读取权限,导致Web服务器可能没有权限读取这些文件。


5.  前端代码问题,前端代码中使用的图片URL可能不正确,如没有拼写错误或路径错误。前端代码未正确加载了图片,例如使用`<img>`标签或通过JavaScript动态加载图片。
   
6. 图片格式问题,上传的图片格式可能不被浏览器支持。某些浏览器可能不支持某些图片格式。

基于上面的代码剖析,可以看出项目比较简单,也没有做权限控制,代码也验证过了,有问题我们也已经进行了优化,上传的图片格式也是jpeg,是浏览器支持的格式之一。

实际上,遇到这种问题,在确定前后端代码都没有问题但是图片又显示不出来的情况下,一般直接查看是否配置了正确的静态资源映射。 于是我仔细查看了后端代码,果然!没有发现到有配置静态资源路径,那么问题就很明朗了,在确保路径正确的情况下,由于后端没有加载静态资源,所以图片是无法被正确访问的。

找到问题点就好办了,我们先加上静态资源处理器,让所有以 `/image/` 开头的请求都会被映射到指定的文件系统路径。
    

@Configuration    
public class WebMvcConfig implements WebMvcConfigurer {@Value("${default.url}")private String defaultUrl;@Overridepublic void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {resourceHandlerRegistry.addResourceHandler("/image/**").addResourceLocations("file:" + defaultUrl);}
}



可以发现,现在是已经可以正常显示了,且进行图片上传时,也能够正常上传成功并更新显示的图片。

    
 

3. 复盘:如何排查问题并找到原因点

实际在开发过程中,我们总会遇到大大小小的各种问题,而遇到问题时,如何快速有效地排查问题并找到原因点是每个开发者所必备的技能。就拿这一次学弟遇到的问题,下面是我们在解决头像上传后页面无法显示图片问题时的复盘,希望能为大家提供一些参考和启示。

3.1. 确认问题现象

首先,我们需要明确问题的具体现象。在这个案例中,问题现象是:图片上传成功并保存到数据库后,页面上的图片无法显示。

3.2. 逐步排查

3.2.1 前端代码排查
  • 检查前端代码:确认前端代码中图片URL的获取和显示逻辑是否正确。我们检查了 <el-upload> 组件和 <img> 标签的 src 属性,确保它们正确地绑定了图片URL。
  • 调试前端代码:通过浏览器开发者工具查看网络请求和控制台输出,确认图片URL是否正确生成,并且没有404或其他错误。
3.2.2 后端代码排查
  • 检查后端代码:确认后端代码中图片上传和URL生成的逻辑是否正确。我们检查了 UploadService 中的 avatarUpload 方法,确保图片正确上传并生成正确的URL。
  • 调试后端代码:通过日志输出和断点调试,确认图片上传和URL生成的过程中没有异常或错误。
3.2.3 配置文件排查
  • 检查配置文件:确认配置文件中图片存储路径是否正确。我们检查了 application.yml 中的 weibo.profile 配置项,确保路径正确无误。

3.3. 分析可能原因

在排查过程中,我们列出了可能导致问题的原因:

  • 路径问题:图片URL路径可能不正确。
  • 服务器配置问题:服务器可能没有正确配置静态资源映射。
  • 浏览器缓存问题:浏览器可能缓存了旧的图片。
  • 权限问题:上传的图片可能没有正确的读取权限。
  • 前端代码问题:前端代码中可能存在拼写错误或路径错误。
  • 图片格式问题:上传的图片格式可能不被浏览器支持。

3.4. 逐一验证

我们逐一验证了上述可能原因,最终发现是由于服务器没有配置静态资源映射,导致图片无法被正确访问。

3.5. 解决问题

在确认问题原因后,我们通过添加静态资源处理器解决了问题。

4. 总结

通过这次排查问题的过程,我们不仅解决了学弟的问题,还深入探讨了前后端代码的实现和可能遇到的问题。可以总结出以下经验:

-   **明确问题现象**:首先明确问题的具体现象,有助于缩小排查范围。
-   **逐步排查**:从前端到后端,从代码到配置文件,逐步排查,确保每个环节都没有问题。
-   **列出可能原因**:列出所有可能导致问题的原因,逐一验证。
-   **确认问题原因**:通过验证,确认问题的根本原因。
-   **解决问题**:针对问题原因,采取相应的解决措施。

希望这些经验能帮助大家在未来的开发过程中,更快更有效地排查问题并找到原因点。
    
    
    

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

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

相关文章

三星Unpacked发布会即将举行:有新款折叠屏手机,还有智能戒指

随着7月的脚步渐近&#xff0c;科技界的目光再次聚焦于三星&#xff0c;它即将在法国巴黎举办今年的第二场Unpacked发布会。这不仅是一场新品的展示&#xff0c;更是三星对创新科技的一次深刻诠释。 从Galaxy Z Fold 6的全新设计&#xff0c;到Galaxy Z Flip 6的显著升级&…

CSS实现表格无限轮播

<div className{styles.tableTh}><div className{styles.thItem} style{{ width: 40% }}>报警名称</div><div className{styles.thItem} style{{ width: 35% }}>开始时间</div><div className{styles.thItem} style{{ width: 25% }}>状态&…

《后端程序猿 · @Value 注释说明》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

【Unity PC端打包exe封装一个并添加安装引导】

Unity PC端打包exe封装一个并添加安装引导 比特虫在线制作ico图标ico图标转换工具 选中打包出来的所有文件和ico图标 右键 使用RAR软件 添加到压缩文件 两个名称要相同 设置完点击确认等待压缩完成 然后就可以使用 Smart Install Maker制作引导安装程序了

解决Pycharm找不到conda可执行文件

解决&#xff1a; 在 ‘Conda 可执行文件’ 的输入框里面&#xff0c;找到并选中 anaconda\library\bin 路径下的“ conda.bat ” ,再点击‘ 加载环境 ’&#xff0c;即可出现 ‘ 使用现有环境 ’ 的输入框&#xff0c;如图所示。

【实现100个unity特效之8】使用ShaderGraph实现2d贴图中指定部分局部发光效果

最终效果 寒冰法师 火焰法师 文章目录 最终效果寒冰法师火焰法师 素材一、功能分析实现方法基本思路Unity的Bloom后处理为什么关键部位白色&#xff1f;最终结果 二、 新建URP项目三、合并图片四、使用PS制作黑白图片方法一 手动涂鸦方法二 魔棒工具1. 拖入图片进PS&#xff0…

Unity3d打包到Android

本文参考&#xff1a; Unity3D新手教程&#xff1a;如何打包发布到Android_哔哩哔哩_bilibili 一、Unity 打包Android的环境搭建 1、工具安装 Unity Hub已经集成了Android的环境搭建。 选择Add modules 然后安装Android Build Support下的所有工具。 如果各个工具都安装成功…

给Windows系统中注入服务,即windwos守护进程

最近总是在windwos环境下测试nginx&#xff0c;总是需要频繁重启nginx服务。于是考虑有没有可能把nginx加入到系统服务的操作。在网上找了一大堆资料&#xff0c;现在来总结一下&#xff01; 方法1&#xff1a;利用nssm工具实现 这是一个守护进程的软件&#xff0c;可以在win…

Optima: 一个用于 Tapestri 平台的单细胞多组学数据分析的开源 R 包

分子条形码技术的最新进展使得在单细胞水平进行下一代转录组测序成为可能&#xff0c;例如10 Genomics Chromium和DropSeq。此外&#xff0c;CITE-seq 的出现使得可以在对单个细胞进行转录组分析的基础上同时对表面蛋白进行分析。同时&#xff0c;为了表征 DNA 和蛋白质谱&…

ubuntu那些ppa源在哪

Ubuntu中的 PPA 终极指南 - UBUNTU粉丝之家 什么是PPA PPA 代表个人包存档。 PPA 允许应用程序开发人员和 Linux 用户创建自己的存储库来分发软件。 使用 PPA&#xff0c;您可以轻松获取较新的软件版本或官方 Ubuntu 存储库无法提供的软件。 为什么使用PPA&#xff1f; 正如…

添加动态云层

<template> <div class"topbox"> xx卫星管理 </div> <div class"selectbox"> <div class"title"> 卫星列表 </div> <el-table :data"tableData" style"width: 100%;height:230px;" …

亚信安慧AntDB-M负载均衡

负载均衡是分布式系统中常用的技术&#xff0c;主要是将工作任务均衡分布到系统的各个资源点上&#xff0c;可以充分利用系统资源。 AntDB-M分布式内存数据库节点角色可以分为管理节点(MN)、计算节点(CN)和数据节点(DN)三种。管理节点收到客户端连接请求后&#xff0c;会经由负…

IEEE官方列表会议 | 第三届能源与环境工程国际会议(CFEEE 2024)

会议简介 Brief Introduction 2024年第三届能源与环境工程国际会议(CFEEE 2024) 会议时间&#xff1a;2024年12月2日-4日 召开地点&#xff1a;澳大利亚凯恩斯 大会官网&#xff1a;CFEEE 2024-2024 International Conference on Frontiers of Energy and Environment Engineer…

【C#】visual studio打包winForm(依赖DLL)为安装程序exe

0.简介 IDE&#xff1a;VS2022 平台&#xff1a;C# .NetFramework4.7 WinForm界面 有GDAL、EEplus库的依赖&#xff0c;所以在其他未安装环境的电脑中功能无法使用。 1. 安装 1.1 运行文件 在VS扩展中选择管理扩展&#xff0c;Microsoft Visual Studio Installer Projects …

如何合规与安全地利用专业爬虫工具,构建企业数据竞争优势

摘要&#xff1a; 本文深入探讨了在当今大数据时代&#xff0c;企业如何通过合规且安全的方式运用专业爬虫工具&#xff0c;有效收集并分析海量信息&#xff0c;进而转化为企业独有的数据优势。我们不仅会介绍最佳实践&#xff0c;还会讨论关键技术和策略&#xff0c;帮助企业…

【网络】计算机网络基础——计算机网络背景和发展、认识网络协议、OSI七层模型、TCP/IP四层模型、网络的传输

文章目录 Linux网络1. 计算机网络背景和发展2. 认识网络协议3. OSI七层模型3.1 物理层3.2 数据链路层3.3 网络层3.4 传输层3.5 会话层3.6 表示层3.7 应用层 4. TCP/IP四层模型5. 网络的传输 Linux网络 1. 计算机网络背景和发展 开始的计算机都是独立模式&#xff08;计算机之间…

谷歌新的网站索引策略:将来可能不再为您的网站编制索引

10 年前&#xff0c;在 WordPress 上推出新博客的同时&#xff0c;Google 上的内容几乎是即时索引。 搜索引擎试图尽快向用户提供所有信息&#xff0c;这对内容创作者也有利。 然而&#xff0c;随着时间的推移&#xff0c;情况发生了变化&#xff0c;现在谷歌在索引方面变得极…

【解决】ubuntu20.04 root用户无法SSH登陆问题

Ubuntu root用户无法登录的问题通常可以通过修改‌SSH配置文件和系统登录配置来解决。 修改SSH配置文件 sudo vim /etc/ssh/sshd_config 找到 PermitRootLogin 设置&#xff0c;并将其值更改为 yes 以允许root用户通过SSH登录 保存并关闭文件之后&#xff0c;需要重启SSH服务…

用uniapp 及socket.io做一个简单聊天app 2

在这里只有群聊&#xff0c;二个好友聊天&#xff0c;可以认为是建了一个二人的群聊。 const express require(express); const http require(http); const socketIo require(socket.io); const cors require(cors); // 引入 cors 中间件const app express(); const serv…

Leetcode—426. 将二叉搜索树转化为排序的双向链表【中等】Plus

2024每日刷题&#xff08;148&#xff09; Leetcode—426. 将二叉搜索树转化为排序的双向链表 实现代码 /* // Definition for a Node. class Node { public:int val;Node* left;Node* right;Node() {}Node(int _val) {val _val;left NULL;right NULL;}Node(int _val, Nod…