uniApp上传文件踩坑日记

发布于:2024-12-21 ⋅ 阅读:(15) ⋅ 点赞:(0)

       最近在做移动端app,开始接触uniapp。想着直接用PC端的前后端API去做文件上传,但是uniapp的底层把请求拆成了普通请求和文件上传请求,所以不能用一个axios去做所有请求的处理,拆成uni.request和uni.uploadFile去分别处理两种情况。

H5

       所以要封装两个请求接口,一个request.js普通请求封装和PC大致相同。另一个是文件请求封装 ,用upload.js去封装uni.uploadFile做请求拦截器

import store from "@/store";
import config from "@/config";
import { getToken } from "@/utils/auth";
import errorCode from "@/utils/errorCode";
import { toast, showConfirm, tansParams } from "@/utils/common";

let timeout = 10000;
const baseUrl = config.baseUrl;

const upload = (config) => {
  // 是否需要设置 token
  const isToken = (config.headers || {}).isToken === false;
  config.header = config.header || {};
  if (getToken() && !isToken) {
    config.header["Authorization"] = getToken();
  }
  // get请求映射params参数
  if (config.params) {
    let url = config.url + "?" + tansParams(config.params);
    url = url.slice(0, -1);
    config.url = url;
  }
  return new Promise((resolve, reject) => {
    uni.uploadFile({
      timeout: config.timeout || timeout,
      url: baseUrl + config.url,
      filePath: config.filePath,
      name: config.name || "file",
      header: config.header,
      formData: config.formData,
      success: (res) => {
        let result = JSON.parse(res.data);
        const code = result.code || 200;
        const msg = errorCode[code] || result.msg || errorCode["default"];
        if (code === 200) {
          resolve(result);
        } else if (code == 401) {
          showConfirm(
            "登录状态已过期,您可以继续留在该页面,或者重新登录?"
          ).then((res) => {
            if (res.confirm) {
              store.dispatch("LogOut").then((res) => {
                uni.reLaunch({ url: "/pages/login/login" });
              });
            }
          });
          reject("无效的会话,或者会话已过期,请重新登录。");
        } else if (code === 500) {
          toast(msg);
          reject("500");
        } else if (code !== 200) {
          toast(msg);
          reject(code);
        }
      },
      fail: (error) => {
        let { message } = error;
        if (message == "Network Error") {
          message = "后端接口连接异常";
        } else if (message.includes("timeout")) {
          message = "系统接口请求超时";
        } else if (message.includes("Request failed with status code")) {
          message = "系统接口" + message.substr(message.length - 3) + "异常";
        }
        toast(message);
        reject(error);
      },
    });
  });
};

export default upload;

然后修改之前的前端API为

import upload from "@/utils/upload";

export function fileUpload(data) {
  return upload({
    url: "/common/app/upload/" + data.fileType,
    name: data.name,
    filePath: data.filePath,
  });
}

        这里api解释一下,因为现在uniapp上传文件接口的差别,是创建一个临时url,我们去上传这个临时文件url到后端去保存的。

        所以前后端的接口都需要做调整,后端是拿到前端的文件然后转存到指定位置,再传回文件的路径。

后端API

	@PostMapping("/app/upload/{fileType}")
	public R appUpload(MultipartFile file,@PathVariable String fileType) {
		// 检查文件是否存在
		if (file.isEmpty()) {
			log.info("文件不存在,请检查路径:");
			throw new CustomException("文件上传失败");
		}
		String fileFolder = basePath + File.separator + "file";
		if (File.separator.equals("\\")) {
			// 本地windows测试
			fileFolder = "C:\\Users\\A\\Desktop\\rm-mes\\data\\file";
		}
		File imgFolder = new File(fileFolder);
		if (!imgFolder.exists()) {
			imgFolder.mkdirs();
			log.info("创建指定目录文件夹:" + fileFolder);
		}

		// 获取文件大小(字节)
		long fileSize = file.getSize();
		// 转换为MB
		double fileSizeInMB = fileSize / (1024.0 * 1024.0); // 使用1024.0确保结果为浮点数

		// UUID重新生成文件,防止重复覆盖
		String fileName = UUID.randomUUID() + "." + fileType;// xxx.jpg  xxx.mp4
		String filePath = fileFolder + File.separator + fileName;
		try {
			// 文件转存位置
			file.transferTo(new File(filePath));
		} catch (IOException e) {
			e.printStackTrace();
		}
		return R.ok().data("filePath", filePath).data("fileType", fileType).data("fileSize", fileSizeInMB);
	}

       文件的后缀是通过前端传过来的类型去保存的,按照之前PC端动态获取文件后缀的方式会失败,导致

然后到前端的签名图片上传

 async uploadSignature() {
      const blob = this.base64ToBlob(this.signature);
      const filePath = URL.createObjectURL(blob); // 创建一个临时的文件路径

      let data = { name: "file", filePath: filePath, fileType: "png" };
      const res = await fileUpload(data);
      this.editForm.responsibleSign = res.data.filePath;
    },
    base64ToBlob(b64) {
      const byteCharacters = atob(b64.replace("data:image/png;base64,", "")); // 把前缀去掉
      const byteNumbers = new Array(byteCharacters.length);

      for (let i = 0; i < byteCharacters.length; i++) {
        byteNumbers[i] = byteCharacters.charCodeAt(i);
      }
      const byteArray = new Uint8Array(byteNumbers);
      const blob = new Blob([byteArray], { type: "image/jpeg" });

      return blob;
    },

       在我们签完名后,一般会拿到签名的base64编码,现在要根据接口的需求,创建一个临时URL放到请求中,所以第一步先调用base64ToBlob转成Blob后,再创建临时地址。最后创建请求体的数据,把文件的路径和文件类型即可,最后调用接口保存到服务器中~

uniApp

        如果不是H5的环境下上传文件会比较麻烦,需要考虑是app还是哪个第三方的小程序。因为在app中创建临时文件需要获取当前运行环境,还要获取一些设备权限。

         所以想要避免这种前端差异和麻烦,我们把后端文件上传接口,改成接收base64编码,在后端去转码成文件保存到服务器中。那么现在我们在前端需要写一个base64编码转换的方法。如果能直接拿到则跳过,直接调用请求api即可。

        现在不在H5环境下,签名会拿到一个图片的临时地址,但是这个地址不能通过第一种创建Blob方式临时http的形式传到后端。因为app不支持http的创建。下面写个方法去转码

export function pathToBase64(path) {
  return new Promise(function (resolve, reject) {
    // app
    if (typeof plus === "object") {
      plus.io.resolveLocalFileSystemURL(
        path,
        function (entry) {
          entry.file(
            function (file) {
              var fileReader = new plus.io.FileReader();
              fileReader.onload = function (evt) {
                resolve(evt.target.result);
              };
              fileReader.onerror = function (error) {
                reject(error);
              };
              fileReader.readAsDataURL(file);
            },
            function (error) {
              reject(error);
            }
          );
        },
        function (error) {
          reject(error);
        }
      );

      return;
    }

    // 微信小程序
    if (typeof wx === "object" && wx.canIUse("getFileSystemManager")) {
      wx.getFileSystemManager().readFile({
        filePath: path,
        encoding: "base64",
        success: function (res) {
          resolve("data:image/png;base64," + res.data);
        },
        fail: function (error) {
          reject(error);
        },
      });

      return;
    }

    reject(new Error("not support"));
  });
}

       现在我们拿到base64编码,那么其实就是一个字符串,我们不需要使用uni.uploadFile(),直接用uni.request()去上传,当做普通请求去处理,放到post请求的请求体中即可。

修改前端api

import upload from "@/utils/upload";
import request from "@/utils/request";

export function fileUpload(data) {
  return request({
    url: "/common/app/upload",
    method: "post",
    data: data,
  });
}

修改后端api(用png图片做示例)

	@PostMapping("/app/upload")
	public R uploadBase64(@RequestBody FlierInfo flierInfo) {
		String base64 = flierInfo.getBase64();
		// 确定文件类型
		String fileType = "png";

		// 去掉前缀
		String base64Image = base64.replace("data:image/png;base64,", "");

		String fileFolder = basePath + File.separator + "file";
		if (File.separator.equals("\\")) {
			// 本地windows测试
			fileFolder = "C:\\Users\\A\\Desktop\\rm-mes\\data\\file";
		}
		File imgFolder = new File(fileFolder);
		if (!imgFolder.exists()) {
			imgFolder.mkdirs();
			log.info("创建指定目录文件夹:" + fileFolder);
		}

		// UUID重新生成文件,防止重复覆盖
		String fileName = UUID.randomUUID() + "." + fileType;// xxx.jpg  xxx.mp4
		String filePath = fileFolder + File.separator + fileName;
		Base64Util.GenerateImage(base64Image, filePath); // 转码成图片保存到指定路径

		return R.ok().data("filePath", filePath).data("fileType", fileType).data("fileSize", "");
	}

前端调用

import { fileUpload } from "@/api/mes/system/common";
import { pathToBase64 } from "@/utils/pathToBase64";

async submit(ref) {
      // await this.uploadSignature();
      pathToBase64(this.signature)
        .then(async (base64) => {
          // 保存base64图片
          await this.uploadSign(base64);
        })
        .catch((error) => {
          console.error(error);
        });

      
    },
async uploadSign(base64) {
      let data = {
        base64: base64,
      };
      const res = await fileUpload(data);
      this.editForm.responsibleSign = res.data.filePath;
    },

       H5和app的处理文件上传的记录到此,第一种方法在H5环境下拿到的base64编码其实可以直接发送到第二种情况的后端接口去做保存,万变不离其宗。


网站公告

今日签到

点亮在社区的每一天
去签到