当前位置:   article > 正文

用户头像的上传以及前端回显_前端上传头像

前端上传头像

在开发中,头像上传是一个非常基本的需求,那么如何去完成这个需求呢?本文将从前端到后端完整的讲述设计过程。

前端(使用 react 18)

既然要上传头像,那么我们就需要一个上传文件的标签

<input type="file" id="headImg" />

但是这样放上去后并不美观,看网上的各种点击上传头像都是点击一个区域就可以弹出文件上传的对话框,那么我们要实现这个效果其实也非常简单,用一个盒子来将这个盒子做成自己想要的样式,并添加上 <label> 标签扩大文件上传按钮的受控范围

  1. <label htmlFor="headImg">
  2. <div className={classes.headImg} id="headImg" style={{backgroundImage: `url(${headImg})`}} >
  3. <span>+</span>
  4. </div>
  5. </label>

在完成这一步后,原本的文件上传按钮依旧存在,这样看上去还是不美观,所以我们选择将它给隐藏掉,并添加上一个事件,事件触发后就像后端发送请求,将图片文件传递给后端

<input type="file" id="headImg" hidden onChange={uploadImg} />

那么通过以上几步骤后,显示的效果如下图

此时,点击灰色的部分就可以打开选择文件的对话框,并选择你想上传的头像,但选择后头像并不会回显,因为你只是选择了一个文件,而想要将头像回显就需要请求后端传回图片的路径,那么怎么才能实现这样的效果呢?

在发送请求方面,我使用的是 axios,还记得在文件上传标签中放入的事件吗,现在就要来使用了

  1. // 进行头像上传以及回显
  2. const uploadImg = (e) => {
  3. const formdata = new FormData();
  4. formdata.append("headImg", e.target.files[0]);
  5. axios({
  6. method: "post",
  7. url: "http://localhost:8080/scs/system/uploadImg",
  8. headers: {
  9. "Content-Type": "multipart/form-data",
  10. },
  11. data: {
  12. multipartFile: formdata.get("headImg"),
  13. }
  14. }).then(res => {
  15. setHeadImg(prevState => res.data.data);
  16. dispatch(setFlush(1));
  17. console.log(res);
  18. }).catch(err => {
  19. console.log(err);
  20. })
  21. }

在使用 axios 发送请求时,可以看到加上了请求头 "Content-Type": "multipart/form-data" ,只有加上这个请求头,后端在解析请求时才能知道这是个文件请求,至于后端为何知道,这个可以查看 springboot 源码,在 springboot 使用 doDispatch 方法的过程中就会完成对请求的解析。在识别到请求头有上述的描述后,就会对请求做一遍封装。而在上述代码中可以看到一个 setFlush 函数,这个函数是我自定义用来刷新状态的,不然当你发送请求后如果不刷新状态,那么你的图片就不会显示,你还需要点击浏览器的刷新按钮来手动刷新页面,至于 setHeadImg 方法就是用来接收图片路径的,还记得我们在盒子中设置的背景图片样式吗,其中使用了模板字符串backgroundImage: `url(${headImg})`,因此可以动态获取你返回的图片路径,到这一步,那么前端的工作就算是完成了。

后端(使用 springboot 3.1.2 版本 )

在收到前端发来的请求后,并通过解析发现是文件请求后,那么我们在接收传递过来的参数时就需要使用注解 @RequestPart 并且形参类型必须是 MultipartFile 类型,完整的方法签名如下

  1. @PostMapping("/uploadImg")
  2. public Result uploadImg(@RequestPart("multipartFile") MultipartFile multipartFile)

那么在书写后面的代码前,我们先思考下要如何处理呢?一定要先想,再写。

首先获取文件后,我们可以将文件存储到我们指定的位置,并将该路径返回给前端,但是如果前端发送的文件名相同岂不是会冲突,所以 UUID 势必要使用,它可以生成你看不懂且不会重复的字符串,那现在名称解决了,那就直接返回图片路径给前端!但是如果这样操作你可以想象未来会有什么后果,是不是只要前端发送请求你的后端就会将图片存储到指定目录下而且每个图片名称均不相同,也就是说一个图片会被反复存储,当用户操作的次数越来越多,你的没用的图片文件也会不断增加,甚至你不好清理更不好管理,你不可能将这种不确定是否用户一定使用的路径存储到你数据库中,而你的最终头像显示正是读取你数据库中存储的路径!那么既然预见了这个情况,怎么解决?

可以这样做,将图片放在两个不同的文件夹中,一个用来存储临时的图片,另一个用来存储你真正确认存储的图片,而只有你真正确认存储的图片的路径才存入数据库中,临时存储的图片路径只做一次性返回,不存储。这样,你前端用户在选择一个头像后,后端返回临时存储的地址,你也可以显示出来,而只有当你点击确认后发送请求给后端,后端再将临时的头像文件通过字节流的方式写入确认存储的文件夹中,并将路径放入数据库,以后只要你的用户读取头像,后端就会返回该路径来让前端显示。可是此时新的问题又出现了,一个用户绑定一个图片路径,那当这个用户更换头像后,那以前存储的图片岂不是路径就丢失被荒废在了文件夹中,那这样日积月累,一样会产生非常多的垃圾文件,而且你不好清理,应为这个文件夹放了各个用户的头像。所以怎么解决呢?

有很多方式,你可以为每个用户建一个文件夹,这个用户的头像都放在这个文件夹中,这样用户还可以浏览历史头像。或者你不为每个用户单独建文件夹,你就要所有用户头像都存在一个文件夹中,那么你可以这么操作,每次存入图片时,先将该用户之前存储的图片从路径中删除掉,再将新头像写入。那么本文采用第二种方式。

现在思路有了,延伸的问题也基本解决,接下来就开始写代码吧

头像上传

  1. @PostMapping("/uploadImg")
  2. public Result uploadImg(@RequestPart("multipartFile") MultipartFile multipartFile) {
  3. if (!multipartFile.isEmpty()) {
  4. String path = null;
  5. try {
  6. String uuid = UUID.randomUUID().toString().replace("-", "").toLowerCase();
  7. String originalFilename = multipartFile.getOriginalFilename();
  8. assert originalFilename != null;
  9. int indexOf = originalFilename.lastIndexOf(".");
  10. String substring = originalFilename.substring(indexOf);
  11. String filename = uuid.concat(substring);
  12. multipartFile.transferTo(new File("D:\\code\\project\\SmartCampus\\front\\public\\img\\temporary\\"+filename));
  13. // 响应给前端的文件路径
  14. path = "/img/temporary/".concat(filename);
  15. } catch (IOException e) {
  16. throw new RuntimeException(e);
  17. }
  18. return Result.ok(path).message("上传成功");
  19. }
  20. return Result.fail().message("上传失败");
  21. }

代码执行后前端就会获得这个临时存储图片的路径,并显示出来,如下图

在点击确认按钮进行提交后(此处使用的是添加用户的功能做的演示),后端的代码如下

  1. @Override
  2. public boolean insertStuInfo(Student student) {
  3. if (student.getName() != null && student.getName() != "" && !"null".equals(student.getName()) &&
  4. student.getGender() != null && student.getGender() != "" && !"null".equals(student.getGender()) &&
  5. student.getPassword() != null && student.getPassword() != "" && !"null".equals(student.getPassword())) {
  6. if (student.getEmail() != null && student.getEmail() != "" && !"null".equals(student.getEmail()) &&
  7. student.getTelephone() != null && student.getTelephone() != "" && !"null".equals(student.getTelephone()) &&
  8. student.getAddress() != null && student.getAddress() != "" && !"null".equals(student.getAddress())) {
  9. if (student.getIntroducation() != null && student.getIntroducation() != "" && !"null".equals(student.getIntroducation()) &&
  10. student.getPortraitPath() != null && student.getPortraitPath() != "" && !"null".equals(student.getPortraitPath()) &&
  11. student.getClazzName() != null && student.getClazzName() != "" && !"null".equals(student.getClazzName())) {
  12. // 临时保存的地址
  13. String path = student.getPortraitPath();
  14. String relPath = "D:/code/project/SmartCampus/front/public".concat(path);
  15. // 永久保存的地址
  16. String remPath = path.substring(path.lastIndexOf("/"));
  17. String remRelPath = "D:/code/project/SmartCampus/front/public/img".concat(remPath);
  18. BufferedInputStream bufferedInputStream = null;
  19. BufferedOutputStream bufferedOutputStream = null;
  20. try {
  21. bufferedInputStream = new BufferedInputStream(new FileInputStream(new File(relPath)));
  22. bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(new File(remRelPath)));
  23. byte[] bytes = new byte[1024];
  24. while (bufferedInputStream.read(bytes) != -1) {
  25. bufferedOutputStream.write(bytes);
  26. }
  27. bufferedOutputStream.flush();
  28. } catch (IOException e) {
  29. throw new RuntimeException(e);
  30. } finally {
  31. try {
  32. bufferedOutputStream.close();
  33. bufferedInputStream.close();
  34. } catch (IOException e) {
  35. throw new RuntimeException(e);
  36. }
  37. }
  38. // 存入数据库的路径名称
  39. student.setPortraitPath("/img".concat(student.getPortraitPath().substring(student.getPortraitPath().lastIndexOf("/"))));
  40. String password = MD5.encrypt(student.getPassword());
  41. student.setPassword(password);
  42. adminMapper.insertStuInfo(student);
  43. if (student.getId() != 0 && student.getId() != null) {
  44. return true;
  45. }
  46. }
  47. }
  48. }
  49. return false;
  50. }

首先我们拿到临时存储的文件,并通过 lastIndex 方法取得图片名称开始的那个 / 在字符串中的下标,通过该下标,我们就可以使用 subString 方法来截取图片的名称。并通过输入流读取当前文件,再通过输出流将文件存储到你真正存储文件的目录下,使用了 IO 流一定要要记得关闭。在存入完成后,只需要将真正存储用户头像的路径存入数据库即可。

至此,后端的工作也完成了,一起看一下最后的效果吧

再来看一下图片存储的临时文件夹里面什么情况

可以看到多且没用,但是现在你可以都删掉,也不会有任何影响啦

再看看真正存储的文件夹里面什么情况

我只给六个用户上传了头像,所以看得见只有六个(最后那个是测试用图片)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/624837
推荐阅读
相关标签
  

闽ICP备14008679号