Skip to content

taro框架,ios的qq浏览器上传失败问题

🕒 Published at:

看下面代码,ios的qq浏览器上传失败,但是别的浏览器都没有任何问题

问后端也是个小白,说不清二和三,就说是解析失败

tsx
import Taro from '@tarojs/taro'
import React from 'react'
import axios from 'axios'

export default function index() {
  const blobAsDataURL = (blob) => {
    return new Promise(resolve => {
      const fileReader = new FileReader();
      fileReader.onload = () => {
        resolve(fileReader.result);
      };
      fileReader.readAsDataURL(blob);
    });
  };
  const dataURLAsFile = (dataUrl, filename = 'file.jpg') => {
    const [type, data] = dataUrl.split(',');
    const mime = /:(.*?);/.exec(type)?.pop();
    const content = atob(data);
    let { length } = content;
    const u8arr = new Uint8Array(length);
    while (length--) {
      u8arr[length] = content.charCodeAt(length);
    }
    return new File([u8arr], filename, { type: mime });
  };

  const handleClick = () => {
    Taro.chooseImage({
      async success(res) {
        const tempFilePaths = res.tempFilePaths[0]
        const blob = await fetch(tempFilePaths).then(response => {
          return response.blob();
        });
        const dataUrl = await blobAsDataURL(blob);
        let file = await dataURLAsFile(dataUrl);
        Taro.uploadFile({
          url: 'https://example.weixin.qq.com/upload', //仅为示例,非真实的接口地址
          filePath: tempFilePaths,
          name: 'file',
          formData: {
            file
          },
          success(res) {
            const data = res.data
            //do something
          }
        })
      }
    })
  }
  return (
    <div>
      <div onClick={handleClick}>按钮</div>
    </div>
  )
}

那么怎么办,靠人不如靠己,自己手撸一个后端服务试试

测试发现,qq浏览器上传失败的原因是把文件后缀丢失,导致解析图片失败,估计是这款浏览器内部问题,其他浏览器都没有这个问题

解决方法是在上传文件时,把文件名也传递到后端,后端收到文件名后,再根据文件名保存文件

前端代码

tsx
import Taro from '@tarojs/taro'

export default function index() {
  const handleClick = () => {
    Taro.chooseImage({
      async success(res) {
        const tempFilePaths = res.tempFilePaths[0]
        const fileExtensionMatch = tempFilePaths.match(/\.([0-9a-z]+)(?:[\?#]|$)/i);
        const fileExtension = res.tempFiles[0] ? res.tempFiles[0].type.split('/')[1] : 'jpg'; // 默认为 'jpg'
        console.log('文件扩展名:', tempFilePaths, fileExtension, fileExtensionMatch);
        // 生成新的文件名(包括扩展名)
        const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
        const fileName = `file-${uniqueSuffix}.${fileExtension}`;
        console.log('生成的文件名:', fileName);
        Taro.uploadFile({
          url: 'http://xxx.xx.xx.xx:3000/api/data', //仅为示例,非真实的接口地址
          filePath: tempFilePaths,
          name: 'file',
          formData: {
            fileName: fileName
          },
          success(res) {
            const data = res.data
            //do something
          }
        })
      }
    })
  }
  return (
    <div>
      <div onClick={handleClick}>按钮</div>
    </div>
  )
}

后端代码

js
const express = require('express');
const cors = require('cors');
const multer = require('multer');
const fs = require('fs');
const path = require('path');
const app = express();
const port = 3000;

// 创建img目录(如果不存在)
const imgDir = path.join(__dirname, 'img');
if (!fs.existsSync(imgDir)) {
  fs.mkdirSync(imgDir, { recursive: true });
  console.log('已创建img目录');
}

// 配置CORS
app.use(cors({
  origin: 'http://xxx.xx.xx.xx:10086',
  methods: ['GET', 'POST', 'OPTIONS'],
  allowedHeaders: [
    'Content-Type',
    'Authorization',
    'X-Os-Version',
    'X-Device-Id',
    'X-Yzh-Language',
    'X-App-Type',
    'x-yzh-env',
    'request-id',
    'X-Access-Token',
    'X-Member-Id',
    'X-App-Id',
    'X-Device-Type',
    'X-Os'
  ],
  credentials: true
}));

// 处理预检请求
app.options('/api/data', cors());

// 配置multer存储图片到img目录
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, imgDir); // 保存到img目录
  },
  filename: function (req, file, cb) {
      // 使用前端传递的文件名
    const fileName = req.body.fileName || file.originalname; // 如果前端没有传递文件名,使用原文件名
    console.log(3333,fileName,file.originalname);

    // 清理文件名,确保不包含非法字符
    const cleanedFileName = fileName.replace(/[:/]/g, '-');
    cb(null, cleanedFileName);
  }
});

// 过滤只允许图片文件
const fileFilter = (req, file, cb) => {
  if (file.mimetype.startsWith('image/')) {
    cb(null, true); // 接受图片文件
  } else {
    cb(new Error('只允许上传图片文件'), false); // 拒绝非图片文件
  }
};

const upload = multer({
  storage: storage,
  fileFilter: fileFilter,
  limits: { fileSize: 10 * 1024 * 1024 } // 限制10MB
});

// 处理图片上传并保存
app.post('/api/data', upload.array('file'), (req, res) => {
  console.log('\n===== 接收到的表单数据 =====');
  console.log('文本字段:', req.body);

  if (req.files && req.files.length > 0) {
    console.log('\n文件保存信息:');
    const savedFiles = req.files.map(file => {
      console.log(11,file);

      const filePath = path.join(imgDir, req.body.fileName);
      console.log(`- 已保存: ${filePath}`);
      return {
        filename: req.body.fileName,
        path: filePath,
        size: Math.round(file.size / 1024) + ' KB'
      };
    });

    res.json({
      code: 200,
      message: `成功保存 ${req.files.length} 张图片到img目录`,
      savedFiles: savedFiles
    });
  } else {
    res.status(400).json({
      code: 400,
      message: '未接收到图片文件'
    });
  }
});

// 错误处理
app.use((err, req, res, next) => {
  console.error('错误:', err.message);
  res.status(400).json({
    code: 400,
    message: err.message
  });
});

// 启动服务器
app.listen(port, '0.0.0.0', () => {
  console.log(`服务器运行在 http://xxx.xx.xx.xx:${port}`);
  console.log(`图片将保存到: ${imgDir}`);
});