Springboot+Vue实现头像的上传和访问

1.效果展示
 

20240218_162533

2.前端代码

前端使用的vue框架, 要求会vue2和elementui的基本使用,步骤如下

1. 准备一个表单
     注意:表单数据提交类型必须是multipart/form-data,这样上传文件才没问题

        给表单绑定一个提交事件

      <form
        enctype="multipart/form-data"
        class="myAvatar"
        method="post"
        @submit="handlerSubmit"
      >
        <input
          type="file"
          name="image"
          accept="image/*"
          required
          @change="validateFile"
          ref="file"
          style="margin-bottom: 30px"
        />
        <div class="buttom">
          <input v-model="avatar.name" name="name" style="display: none" />
          <el-button type="info" @click="resetFile" icon="el-icon-refresh"
            >重置文件</el-button
          >
          <el-input
            type="submit"
            style="width: auto"
            prefix-icon="el-icon-position"
            value="提 交"
          ></el-input>
        </div>
      </form>

2.判断文件的大小是否合法

    // 判断文件大小合法
    validateFile(event) {
      const file = event.target.files[0];
      this.avatar.img = file;
      console.log(this.avatar.img);
      const maxSizeInBytes = 1024 * 1024; // 设置最大文件大小为 1MB

      if (file && file.size > maxSizeInBytes) {
        // 文件大小超过限制,进行相应的处理
        this.$message.error("文件大小超过1MB");
        event.target.value = null; // 清空文件输入框的值
        this.avatar.img = null;
      } else {
        // 打开
        this.dialogVisible = true;
      }
    },


3.el-dialog+cropperjs完成图片的裁剪
3.1 先安装cropperjs,执行指令`npm i cropperjs`
3.2 使用elementui的组件
先初始化

    // 初始化
    initCropper() {
      const createCropper = () => {
        return new Promise((resolve, reject) => {
          this.cropper = new Cropper(this.$refs.image, {
            // 在这里设置Cropper.js的选项
            aspectRatio: 1, // 裁剪框的宽高比
            viewMode: 1, // 显示模式:0 - 图像自由裁剪,1 - 图像按比例缩放裁剪,2 - 图像按比例缩放填充裁剪区域,3 - 图像完全填充裁剪区域
            dragMode: "move", // 拖动模式:'crop' - 创建裁剪框,'move' - 移动图像,'none' - 禁用拖动
            cropBoxResizable: false, // 是否允许修改裁剪框大小
            minContainerWidth: 200, // 最小容器宽度
            minContainerHeight: 200, // 最小容器高度
            minCropBoxWidth: 100, // 最小裁剪框宽度
            minCropBoxHeight: 100, // 最小裁剪框高度
            zoomable: true, // 是否允许缩放图像
            guides: true, // 是否显示裁剪框辅助线
            background: false, // 是否显示裁剪框外的背景遮罩
            autoCrop: true, // 是否自动创建裁剪框
            autoCropArea: 0.6, // 自动创建裁剪框时的默认裁剪区域占比
            toggleDragModeOnDblclick: false, // 是否允许双击切换拖动模式
            responsive: true, // 是否启用响应式布局
          });
          resolve("ok");
        });
      };

      createCropper();
    },




截取时候调用,getCroppedCanvas方法执行截取拿到截取后的图片的base64编码。
 

    cropImage() {
      const croppedCanvas = this.cropper.getCroppedCanvas({
        imageSmoothingQuality: "high",
      });
      // 在这里你可以将裁剪后的canvas转换为DataURL或Blob,并发送到后端进行保存或进一步处理
      const croppedDataUrl = croppedCanvas.toDataURL("image/jpg");

      const prefixIndex = croppedDataUrl.indexOf(",") + 1;
      this.afterImg = croppedDataUrl.slice(prefixIndex);
      this.dialogVisible = false;
    },

重置文件,这里先关闭对话框,然后移除Blob生成的缓存url,然后再清空数据模型

    // 重置文件
    resetFile() {
      this.dialogVisible = false;
      URL.revokeObjectURL(this.selectFileURL);
      const file = this.$refs.file;
      file.value = null;
      this.avatar.img = null;
      this.afterImg = null;
    },

base64编码转file的工具类

export default function base64ToFile(base64Data, fileName) {
    const extendName = fileName.split(".").at(-1);

    const byteCharacters = atob(base64Data); // 解码Base64字符串
    const byteArrays = [];
    

    for (let offset = 0; offset < byteCharacters.length; offset += 512) {
        const slice = byteCharacters.slice(offset, offset + 512); // 每次处理512个字符
        const byteNumbers = new Array(slice.length);

        for (let i = 0; i < slice.length; i++) {
            byteNumbers[i] = slice.charCodeAt(i);
        }

        const byteArray = new Uint8Array(byteNumbers);
        byteArrays.push(byteArray);
    }

    const blob = new Blob(byteArrays, { type: `image/${extendName}` }); // 创建Blob对象
    const file = new File([blob], fileName, { type: `image/${extendName}` }); // 创建File对象

    return file;
}



3. 提交事件的处理

先阻止表单的默认提交,因为表单默认提交会跳转url
我们这里发送ajax请求

    // 提交头像
    handlerSubmit(event) {
      event.preventDefault();
        
      // 如果头像为空
      if (this.avatar.img == "" || this.avatar.img == null) {
        this.$message.warning("请先选择文件");
      } else {
        const { name } = this.avatar.img;
        const extendName = name.split(".").at(-1);
        this.avatar.img = base64ToFile(this.afterImg, "file." + extendName);
        this.$store
          .dispatch("changeAvatar", this.avatar)
          .then(() => {
            this.resetFile();
            this.$message.success("提交成功!");
            // 请求一下头像路径,更新仓库里的url
            this.$store.dispatch("getAvatarImg", "Kingah");
          })
          .catch((error) => {
            this.$message.error("添加失败");
          });
      }
    },

3.后端代码


1.编写Controller

    @PostMapping("/upload")
    public Result uploadAvatar(String name, MultipartFile image) {
        log.info("{}头像上传,头像大小为{}MB", name, String.format("%.2f", 1.0 * image.getSize() / 1024 / 1024));
        FileUpLoadUtil.transferTo(name, image);
        return Result.success();
    }


2.图片上传的工具类
 


/**
 * @Time : 2024/2/12 16:18
 * @Author : kinGah
 * @File : FileUpLoadUtil.java
 * @Weekday : 星期一
 * @Description : 文件上传,一般是头像上传
 **/

@Slf4j
@Component
public class FileUpLoadUtil {

    private static String location;
    private static EmpService empService;

    @Value("${employee.avatar.location}")
    public void setLocation(String location) {
        FileUpLoadUtil.location = location;
        FileUpLoadUtil.location = FileUpLoadUtil.location.substring(5);
    }

    public String getLocation() {
        return FileUpLoadUtil.location;
    }
//
//    @Autowired
    public void setResourceLoader(ResourceLoader resourceLoader) {
        FileUpLoadUtil.resourceLoader = resourceLoader;
    }

    @Autowired
    public void setEmployeeDao(EmpService empService) {
        FileUpLoadUtil.empService = empService;
    }

    /**
     * 删除文件
     *
     * @param name
     * @return
     */
    private static Boolean removeOriginImage(String name) {
        Employee emp = empService.lambdaQuery().eq(Employee::getName, name).one();
        // 如果文件存在
        if (ObjUtil.isNotEmpty(emp)) {
            try {
                String imgUrl = emp.getImgUrl();
                String[] arr = imgUrl.split("/");
                String fileName = arr[arr.length - 1];
                FileUtil.del(location + fileName);
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }

        return true;
    }

    public static void transferTo(String name, MultipartFile image) {
        log.info("文件上传...");
        // 获取原始文件名
        String originalFilename = image.getOriginalFilename();
        String extName = FileNameUtil.extName(originalFilename);

        if (removeOriginImage(name)) {
            // 生成新的文件名
            String newFilename = UUID.randomUUID().toString() + "." + extName;

            try {
                // 存储在本地
                image.transferTo(new File(location + newFilename));
                // 更新数据库imgUrl
                LambdaUpdateWrapper<Employee> wrapper = new LambdaUpdateWrapper<>();
                wrapper.eq(Employee::getName, name)
                        .set(Employee::getImgUrl, "/images/" + newFilename);
                empService.update(wrapper);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}


3.图片访问路径
 

    public String concatAvatarUrl(String name) throws MalformedURLException, doBusinessException {
        Employee emp = query().eq("name", name).one();
        if (ObjectUtil.isEmpty(emp))
            throw new doBusinessException(Code.AVATAR_NOT_EXIST);

        String uri = emp.getImgUrl();
        return "/api/" + uri;
    }

4.开放静态资源访问路径
 

/**
 * @Time : 2024/2/11 21:35
 * @Author : kinGah
 * @File : SpringMvcConfig.java
 * @Weekday : 星期日
 * @Descripton : 添加MP拦截器、静态资源映射
 **/

@RequiredArgsConstructor
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {

    @Value("${employee.avatar.location}")
    private String location;

    private  LoginInterceptor loginInterceptor;

    /**
     * token
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
//        registry.addInterceptor(loginInterceptor)
//                .addPathPatterns("/**")
//                .excludePathPatterns("/login/**");
    }

    /**
     * 添加静态资源访问路径
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 访问路径为/images/*映射到classpath:/static/images/下
        registry.addResourceHandler("/images/*")
                .addResourceLocations(location);
    }
}

4.总体思路

前端设置提交文件的按钮,选中文件后进行裁剪,然后把裁剪后的文件替换为选中的文件,最后提交表单。
后端接收请求,MultipartFile类型的文件有一个transferTo(new File(...))方法将缓存的文件保存到本地,然后把地址存到数据库里,查询的时候再发送请求拿。




5.仓库地址

kingah_sph_admin: 后台管理系统    前端vue,具体代码在views/person/adminview

/kgh_backgroud202402icon-default.png?t=N7T8https://gitee.com/Kingah/kgh_backgroud202402.git

  • 19
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值