问题描述

在使用 uni-app 开发跨平台应用时,发现一个奇怪的现象:

  • 安卓端:图片上传接口正常工作,请求能发出,后端能接收并处理,响应也能正常返回
  • 鸿蒙端:请求能正常发出,后端也能正常接收并处理,但响应始终收不到,最终导致请求超时

经过排查,发现响应内容其实很小(只有几个字段),并非大数据返回,但就是无法接收到。

问题原因

经过深入分析,发现根本原因是请求体大小导致的:

安卓端

Android 的网络框架(OkHttp)在发送请求时,会自动对请求体进行压缩。即使你发送的是一张几 MB 的原图,Android 也会自动压缩后再发送,实际发送的请求体可能只有几百 KB。(安卓端超过1MB也会出现)

鸿蒙端

鸿蒙端的网络请求不会自动压缩,会直接发送原始数据。当图片较大(超过 1MB)时:

  1. 请求能正常发出
  2. 后端能正常接收并处理
  3. 后端也正常返回响应
  4. 客户端无法接收到响应

这是因为鸿蒙端在处理较大的请求体时,底层网络栈可能存在某些限制或 bug,导致响应无法正常返回。

问题复现

// 这段代码在安卓端正常,鸿蒙端会超时
uni.uploadFile({
  url: "https://example.com/api/upload",
  filePath: largeImagePath, // 一张 2MB+ 的图片
  name: "file",
  success: (res) => {
    // 安卓端能进入这里
    // 鸿蒙端永远不会进入,最终超时
    console.log("上传成功", res);
  },
  fail: (err) => {
    console.log("上传失败", err);
  }
});

解决方案

方案一:压缩图片(推荐)

在上传前先压缩图片,减小请求体大小:

/**
 * 压缩图片
 * @param {string} filePath 原始图片路径
 * @returns {Promise<string>} 压缩后的图片路径
 */
const compressImage = (filePath) => {
  return new Promise((resolve, reject) => {
    uni.compressImage({
      src: filePath,
      quality: 80, // 压缩质量 0-100
      width: 1024, // 缩放宽度
      success: (res) => {
        console.log("压缩成功:", res.tempFilePath);
        resolve(res.tempFilePath);
      },
      fail: (err) => {
        console.error("压缩失败:", err);
        // 压缩失败时返回原始路径
        resolve(filePath);
      }
    });
  });
};

/**
 * 上传图片(先压缩再上传)
 */
const uploadImage = async (filePath) => {
  uni.showLoading({ title: "上传中..." });

  try {
    // 先压缩图片
    const compressedPath = await compressImage(filePath);

    // 再上传
    const res = await new Promise((resolve, reject) => {
      uni.uploadFile({
        url: "https://example.com/api/upload",
        filePath: compressedPath,
        name: "file",
        success: (res) => resolve(res),
        fail: (err) => reject(err)
      });
    });

    console.log("上传成功:", res);
  } catch (err) {
    console.error("上传失败:", err);
  } finally {
    uni.hideLoading();
  }
};

压缩参数建议:

参数 推荐值 说明
quality 60-80 压缩质量,越低文件越小,但清晰度下降
width 1024-1920 缩放宽度,身份证 OCR 1024 足够

方案二:分片上传

对于特别大的文件,可以考虑分片上传:

/**
 * 分片上传大文件
 */
const uploadLargeFile = async (filePath) => {
  const chunkSize = 512 * 1024; // 512KB 每片
  const fs = uni.getFileSystemManager();
  
  // 读取文件信息
  const fileInfo = await new Promise((resolve, reject) => {
    fs.getFileInfo({
      filePath: filePath,
      success: (res) => resolve(res),
      fail: (err) => reject(err)
    });
  });

  const totalSize = fileInfo.size;
  const totalChunks = Math.ceil(totalSize / chunkSize);

  for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    const end = Math.min(start + chunkSize, totalSize);
    
    // 读取分片
    const chunkData = await new Promise((resolve, reject) => {
      fs.read_file({
        filePath: filePath,
        position: start,
        length: end - start,
        encoding: "base64",
        success: (res) => resolve(res.data),
        fail: (err) => reject(err)
      });
    });

    // 上传分片
    await new Promise((resolve, reject) => {
      uni.request({
        url: "https://example.com/api/uploadChunk",
        method: "POST",
        data: {
          chunkIndex: i,
          totalChunks: totalChunks,
          data: chunkData
        },
        success: () => resolve(),
        fail: (err) => reject(err)
      });
    });

    console.log(`分片 ${i + 1}/${totalChunks} 上传完成`);
  }

  // 通知后端合并分片
  await new Promise((resolve, reject) => {
    uni.request({
      url: "https://example.com/api/mergeChunks",
      method: "POST",
      data: { totalChunks },
      success: () => resolve(),
      fail: (err) => reject(err)
    });
  });
};

最佳实践

1. 封装统一的上传方法

// utils/upload.js
import { compressImage } from "./image";

/**
 * 统一上传方法
 * @param {string} url 接口地址
 * @param {string} filePath 文件路径
 * @param {object} options 配置项
 * @returns {Promise<object>} 上传结果
 */
export const uploadFile = async (url, filePath, options = {}) => {
  const { 
    name = "file", 
    formData = {}, 
    needCompress = true,
    compressQuality = 80 
  } = options;

  // 显示加载
  uni.showLoading({ title: "上传中..." });

  try {
    let uploadPath = filePath;

    // 是否需要压缩
    if (needCompress) {
      uploadPath = await compressImage(filePath, compressQuality);
    }

    // 上传
    const res = await new Promise((resolve, reject) => {
      uni.uploadFile({
        url,
        filePath: uploadPath,
        name,
        formData,
        header: {
          "X-Access-Token": uni.getStorageSync("token")
        },
        success: (res) => {
          if (res.statusCode === 200) {
            resolve(JSON.parse(res.data));
          } else {
            reject(new Error(`请求失败: ${res.statusCode}`));
          }
        },
        fail: (err) => reject(err)
      });
    });

    return res;
  } finally {
    uni.hideLoading();
  }
};

2. 使用示例

import { uploadFile } from "@/utils/upload";

// 上传身份证照片
const uploadIdCard = async (imagePath) => {
  try {
    const res = await uploadFile(
      "/api/ocr/recognize",
      imagePath,
      {
        name: "file",
        needCompress: true,
        compressQuality: 80
      }
    );

    if (res.success) {
      console.log("识别结果:", res.result);
    }
  } catch (err) {
    console.error("上传失败:", err);
  }
};

总结

问题 原因 解决方案
鸿蒙端上传大图后无响应 请求体超过 1MB,鸿蒙网络栈无法正常处理响应 上传前压缩图片
安卓端正常 Android 自动压缩请求体 -
H5 端正常 浏览器有完善的网络处理 -

核心要点:

  1. 鸿蒙端不会自动压缩请求体
  2. 请求体超过 1MB 时会导致响应丢失
  3. 解决方案:上传前使用 uni.compressImage 压缩图片
  4. 建议封装统一的上传方法,自动处理压缩逻辑

怀疑是uniapp框架的问题,但是并没有证据

作者:煊承岳

日期:2026-06-04

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐