You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android向Node.js服务器发送PDF文件无响应问题求助

问题排查与修复:Android文件上传至Node.js服务器无响应

核心问题

你的Android端代码直接将文件流写入HTTP请求体,但Node.js的Multer中间件要求请求采用multipart/form-data格式来解析文件。原请求未按该格式封装,导致Multer无法识别文件,因此服务器没有输出预期日志。

Android端代码修复

修改sendPDF方法,手动构建符合要求的multipart/form-data请求,确保和Node.js端的Multer配置匹配:

public static void sendPDF(Context context, Uri fileUri) {
    new AsyncTask<Uri, Void, Void>() {
        @Override
        protected Void doInBackground(Uri... uris) {
            Uri uri = uris[0];
            try {
                // 替换为你的实际服务器IP和端口(模拟器用10.0.2.2映射本地localhost)
                URL url = new URL("http://你的服务器IP:3001/upload");
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("POST");
                conn.setDoInput(true);
                conn.setDoOutput(true);

                // 设置multipart/form-data请求头
                String boundary = "----WebKitFormBoundary" + System.currentTimeMillis();
                conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

                conn.setReadTimeout(35000);
                conn.setConnectTimeout(35000);

                OutputStream os = conn.getOutputStream();
                PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, "UTF-8"), true);

                // 从Uri解析真实文件名
                String fileName = getFileName(context, uri);

                // 写入文件表单字段(字段名myFile需和Node.js端一致)
                writer.append("--" + boundary).append("\r\n");
                writer.append("Content-Disposition: form-data; name=\"myFile\"; filename=\"" + fileName + "\"").append("\r\n");
                writer.append("Content-Type: application/octet-stream").append("\r\n");
                writer.append("\r\n").flush();

                // 写入文件内容
                InputStream inputStream = context.getContentResolver().openInputStream(uri);
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    os.write(buffer, 0, bytesRead);
                }
                os.flush();
                inputStream.close();

                // 结束表单
                writer.append("\r\n").flush();
                writer.append("--" + boundary + "--").append("\r\n").flush();
                writer.close();
                os.close();

                // 打印响应信息用于调试
                int responseCode = conn.getResponseCode();
                Log.d("UploadDebug", "响应码: " + responseCode);
                BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                StringBuilder response = new StringBuilder();
                String line;
                while ((line = br.readLine()) != null) {
                    response.append(line);
                }
                br.close();
                Log.d("UploadDebug", "服务器响应: " + response.toString());

                conn.disconnect();
            } catch (IOException e) {
                e.printStackTrace();
                Log.e("UploadError", "上传失败: " + e.getMessage());
            }
            return null;
        }
    }.execute(fileUri);
}

// 辅助方法:从Uri获取真实文件名
private static String getFileName(Context context, Uri uri) {
    String result = null;
    if (uri.getScheme().equals("content")) {
        Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
        try {
            if (cursor != null && cursor.moveToFirst()) {
                result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
            }
        } finally {
            if (cursor != null) cursor.close();
        }
    }
    if (result == null) {
        result = uri.getPath();
        int cut = result.lastIndexOf('/');
        if (cut != -1) result = result.substring(cut + 1);
    }
    return result;
}

关键修改点

  • 添加Content-Type: multipart/form-data; boundary=xxx请求头,这是Multer解析文件的必要条件
  • 表单字段名myFile和Node.js端upload.single('myFile')严格保持一致
  • 补充文件名解析逻辑,避免使用uri.getPath()返回的虚拟路径
  • 添加调试日志,便于确认请求是否到达服务器

Node.js端验证

确保代码已正确引入依赖模块,同时添加错误捕获排查问题:

const express = require('express');
const multer = require('multer');
const fs = require('fs');
const app = express();

const uploadDirectory = 'uploads';
if (!fs.existsSync(uploadDirectory)) {
    fs.mkdirSync(uploadDirectory);
}

const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        cb(null, 'uploads/');
    },
    filename: (req, file, cb) => {
        cb(null, file.originalname);
    }
});

const upload = multer({ storage });

// 添加错误捕获的上传路由
app.post('/upload', (req, res) => {
    upload.single('myFile')(req, res, (err) => {
        if (err) {
            console.error('上传错误:', err);
            return res.status(500).send('上传失败');
        }
        console.log('文件上传成功:', req.file);
        res.status(200).send('文件上传成功');
    });
});

app.listen(3001, () => {
    console.log('服务器运行在端口3001');
});

额外注意事项

  1. 确保AndroidManifest.xml中添加网络权限:
<uses-permission android:name="android.permission.INTERNET" />
  1. 若服务器在本地,Android模拟器需用10.0.2.2代替本地IP(模拟器环回地址映射主机localhost)
  2. AsyncTask已被标记为过时,可考虑改用Coroutines或OkHttp等现代网络库实现异步请求

内容的提问来源于stack exchange,提问作者abcd1211231

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 12:08:10