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

news2024/12/23 8:46:21

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-upload
            class="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 {
    @Autowired
    private 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 {

    @Autowired
    private UserService userService;
    //头像上传路径
    @Value("${weibo.profile}")
    private String defaultBaseDir;

    @Override
    public Boolean avatarUpload(MultipartFile file, HttpServletRequest request) {
        //获取当前头像上传路径
        String avatar = defaultBaseDir;
        //获取当前用户id并且修改用户的头像地址

        // 文件名称 用户id
        // 获取文件的名称
        String originalFilename = file.getOriginalFilename();
        // 文件后缀 例如:.png
        assert 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;

    @Override
    public 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.coloradmin.cn/o/1940812.html

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

相关文章

力扣高频SQL 50 题(基础版)第一题

文章目录 力扣高频SQL 50 题&#xff08;基础版&#xff09;第一题1757.可回收且低脂的产品题目说明思路分析实现过程准备数据&#xff1a;实现方式&#xff1a;结果截图&#xff1a; 力扣高频SQL 50 题&#xff08;基础版&#xff09;第一题 1757.可回收且低脂的产品 题目说…

昇思25天学习打卡营第14天 | SSD目标检测

探索SSD目标检测算法 在深入学习SSD&#xff08;Single Shot MultiBox Detector&#xff09;目标检测算法的过程中&#xff0c;我对现代计算机视觉中的目标检测技术有了更加深入的理解。SSD作为一种有效的单阶段目标检测算法&#xff0c;它在准确性和检测速度之间取得了良好的…

HAL库源码移植与使用之RTC时钟

实时时钟(Real Time Clock&#xff0c;RTC)&#xff0c;本质是一个计数器&#xff0c;计数频率常为秒&#xff0c;专门用来记录时间。 普通定时器无法掉电运行&#xff01;但RTC可由VBAT备用电源供电&#xff0c;断电不断时 这里讲F1系列的RTC 可以产生三个中断信号&#xff…

分类损失函数 (一) torch.nn.CrossEntropyLoss()

1、交叉熵 是一种用于衡量两个概率分布之间的距离或相似性的度量方法。机器学习中&#xff0c;交叉熵常用于损失函数&#xff0c;用于评估模型的预测结果和实际标签的差异。公式&#xff1a; y&#xff1a;真是标签的概率分布&#xff0c;y&#xff1a;模型预测的概率分布 …

FPGA实验3:D触发器设计

一、实验目的及要求 熟悉Quartus II 的 VHDL 文本设计简单时序电路的方法&#xff1b; 掌握时序电路的描述方法、波形仿真和测试&#xff0c;特别是时钟信号的特性。 二、实验原理 运用Quartus II 集成环境下的VHDL文本设计方法设计简单时序电路——D触发器&#xff0c;依据…

【Godot4.2】GodotXML插件 - 解析和生成XML

概述 近期在研究基于Godot的XML和SVG解析&#xff0c;并且在昨天&#xff08;2024年7月20日&#xff09;编写了一个简易的SVG文件解析器。 在群友的提示下&#xff0c;知道早就存在GodotXML这样的解析器。所以今天就来测试使用并准备研究学习源代码了。和以往一样&#xff0c…

康康近期的慢SQL(oracle vs 达梦)

近期执行的sql&#xff0c;哪些比较慢&#xff1f; 或者健康检查时搂一眼状态 oracle&#xff1a; --最近3天内的慢sql set lines 200 pages 100 col txt for a65 col sql_id for a13 select a.sql_id,a.cnt,a.pctload,b.sql_text txt from (select * from (select sql_id,co…

MySQL0.MSI方式安装

本机运行环境&#xff1a;Windows10 1.下载 进入MySQL官方下载页面&#xff1a;https://downloads.mysql.com/archives/installer/ 红色箭头&#xff1a;点击选择下载的版本 黄色箭头&#xff1a;点击下载MSI安装包 此次下载选择MySQL8.0.37的MSI安装包 2.安装 下载完毕后…

微信小程序开发:基础架构与配置文件

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

SecureCRT (mac or windows)解决中文显示乱码

中文乱码问题的方法主要包括设置SecureCRT的编码为UTF-8以及设置LANG环境变量为zh_CN.UTF-8。‌ 1.设置SecureCRT的编码为UTF-8&#xff1a;‌ 打开SecureCRT&#xff0c;‌进入Options -> Global Options -> Default Session -> Edit Default Settings-> Appear…

数据结构day3

一、思维导图 二、顺序表实现学生管理系统 //头文件 #ifndef TEST_H #define TEST_H #define MAX_SIZE 100//定义学生类型 typedef struct {char name[20]; //姓名int age; //年龄double score; //分数 }datatype;//定义班级类型 typedef struct {datatype student[MAX…

Linux fork、进程的退出和等待详解

初识fork函数 它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程。 #include <unistd.h> pid_t fork(void); 返回值&#xff1a;子进程中返回0&#xff0c;父进程返回子进程id&#xff0c;出错返回-1 最简单的fork使用示例 #include<stdi…

初阶数据结构的实现2 双向链表

1.双向链表 1.1 概念与结构 1.2实现双向链表 1.2.1定义程序目标 #define _CRT_SECURE_NO_WARNINGS 1 #pragma once #include<stdio.h> #include<assert.h> #include<stdlib.h> #include<stdbool.h> typedef int LTDateType; //定义双向链表结构 typ…

list(链表)容器的规则及list的高级排序案例

1.list的基本概念&#xff1a; 功能&#xff1a;将数据进行链式存储 list&#xff08;链表&#xff09;是一种物理存储单元上非连续的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接实现的 链表是由一系列节点组成&#xff0c;节点的组成包含存储数据元素的…

秋招提前批:抢占求职先机的绝佳机遇(25届提前批名单公布)

秋招&#xff0c;对于即将毕业的大学生来说&#xff0c;是一场至关重要的求职盛宴。每年的秋季&#xff0c;各大企业纷纷抛出橄榄枝&#xff0c;为应届毕业生提供了众多宝贵的就业机会。然而&#xff0c;在求职的漫漫征途中&#xff0c;秋招是一场关键的战役。而秋招提前批&…

二百四十五、海豚调度器——用DolphinScheduler调度执行复杂的HiveSQL(HQL包含多种海豚无法正确识别的符号)

一、目的 在Hive中完成复杂JSON&#xff0c;既有对象还有数组而且数组中包含数组的解析后&#xff0c;原本以为没啥问题了&#xff0c;结果在DolphinScheduler中调度又出现了大问题&#xff0c;搞了一天、试了很多种方法、死了无数脑细胞&#xff0c;才解决了这个问题&#xf…

fastjson1.2.24 反序列化漏洞复现

fastjson简介 Fastjson 是一个 Java 库&#xff0c;可以将 Java 对象转换为 JSON 格式&#xff0c;当然它也可以将 JSON 字符串转换为 Java 对象。 Fastjson 可以操作任何 Java 对象&#xff0c;即使是一些预先存在的没有源码的对象。 这里json与java对象之间的转换&#xff0…

【GaussDB关键技术原理|高可用】DCF双集群容灾

GaussDB关键技术原理&#xff1a;高性能篇&#xff0c;从GaussDB数据库性能优化系统概述、查询处理综述、高性能关键技术等方面为大家进行了解读&#xff0c;并对高斯数据库性能优化做了总结。本篇将分享GaussDB高可用方面的相关知识&#xff0c;详细介绍GaussDB的DCF与双集群容…

双目相机立体匹配算法概述

这里写目录标题 双目相机立体匹配算法概述1.算法分类2.传统算法2.1 局部算法2.2 全局算法2.3 半全局算法 3.深度学习算法3.1 基于CNN的方法3.2 基于GAN的方法3.3 基于transformer的方法 4.总结5.参考文献 双目相机立体匹配算法概述 双目立体匹配是计算机视觉中的一个重要研究方…