问题现象

当用户上传包含中文字符的文件时,在服务器端获取到的文件名可能变成类似 æµ‹è¯•文件.txt 这样的乱码,而不是预期的中文文件名。

为什么只有Node会乱码?

  • 很多后端框架(如 Java Spring Boot、Python Django、PHP Laravel)为了简化开发,在底层已经处理了 “编码不匹配” 问题,开发者感知不到。
  • Node.js 的核心特点是 “轻量、原生模块仅提供基础能力,不做过度封装”,这导致它没有默认解决编码问题,需要开发者手动处理

问题根源

首先了解 HTTP 协议和 Node.js 处理请求的方式:

  1. HTTP 协议的历史遗留问题:早期的 HTTP 协议主要设计用于传输英文内容,默认采用 latin1(ISO-8859-1)编码。

  2. 表单提交的编码方式:当通过 multipart/form-data 格式上传文件时,浏览器会使用 latin1 编码来传输文件名等元数据,即使其中包含非拉丁字符。

  3. Node.js 的默认处理:Node.js 在解析请求时,默认会将这些 latin1 编码的数据直接转换为字符串,而 latin1 无法正确表示中文字符,从而导致乱码

简单来说,中文文件名被浏览器以 latin1 编码传输,但 Node.js 没有正确解码,导致了乱码现象。

latin1 是一种适合西欧语言的单字节编码,因历史原因成为早期互联网的默认编码,也因此导致了中文等多字节字符在传输中的乱码问题


解决方案

一、接收前端传递:解决这个问题的关键在于正确地解码文件名。我们可以使用 Node.js 的 Buffer 类来实现这一转换:

// 将乱码的文件名转换为正确的中文
const correctFilename = Buffer.from(originalname, "latin1").toString("utf8");
第一步:Buffer.from (originalname, "latin1")
  • originalname 是从请求中获取的原始文件名(已被错误解码为乱码)
  • 第二个参数 "latin1" 表示:把乱码的字符串按照 latin1 编码重新转换为字节序列
  • 这一步的作用是还原浏览器发送时的原始字节数据
第二步:.toString ("utf8")
  • 将上一步得到的原始字节序列,用正确的编码(utf8)重新解码为字符串
  • 这一步会把之前被拆分为单字节的中文字符重新组合为正确的多字节表示

为什么这样有效?

  • latin1 编码的特性是:每个字符都直接对应一个字节(0-255),不会丢失信息
  • 即使原始字符是 UTF-8 编码,用 latin1 解码成乱码后,依然可以通过反向操作还原
  • 这是一种 "-lossless"(无损失)的转换方式,专门用于修复此类编码不匹配问题

二、返回前端响应:同时,为了确保服务器返回的响应中中文能正确显示,我们需要设置响应头的字符集

告诉浏览器:“我(服务器)成功处理了你的请求,接下来会返回一段 HTML 格式的内容,并且这段内容是用 UTF-8 编码的,请你用 HTML 规则渲染、用 UTF-8 解码,确保界面正常显示且中文不乱码”。

// 设置响应头,确保中文正常显示
res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'});

建议放置到全局中间件

// 全局编码处理中间件
app.use((req, res, next) => {// 设置响应头确保UTF-8编码res.setHeader('Content-Type', 'application/json; charset=utf-8');// 处理请求中的文件名编码问题if (req.headers['content-type'] && req.headers['content-type'].includes('multipart/form-data')) {// 对于multipart/form-data请求,确保正确处理文件名编码req.setEncoding = 'utf8';}next();
});

完整示例(原生Nodejs示列)

const http = require('http');
const fs = require('fs');
const path = require('path');// 创建服务器
const server = http.createServer((req, res) => {// 处理 GET 请求 - 显示上传表单if (req.method === 'GET') {res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' });res.end(`<form method="POST" enctype="multipart/form-data"><input type="file" name="file" /><button type="submit">上传文件</button></form>`);return;}// 处理 POST 请求 - 处理文件上传if (req.method === 'POST' && req.headers['content-type'].startsWith('multipart/form-data')) {// 获取分隔符const boundary = req.headers['content-type'].split('; ')[1].split('=')[1];let fileName = '';let fileData = [];let isFilePart = false;// 接收数据req.on('data', (chunk) => {// 转换为字符串用于解析文件名(临时用 latin1)const chunkStr = chunk.toString('latin1');// 提取并处理文件名(核心解决方案)if (!fileName && chunkStr.includes('filename="')) {const match = chunkStr.match(/filename="(.*?)"/);if (match && match[1]) {// 关键步骤:修复中文文件名乱码// 将 latin1 编码的文件名重新解码为 utf8fileName = Buffer.from(match[1], 'latin1').toString('utf8');}}// 收集文件内容if (fileName && !isFilePart && chunkStr.includes('\r\n\r\n')) {isFilePart = true;const start = chunkStr.indexOf('\r\n\r\n') + 4;fileData.push(chunk.slice(start - chunk.length));} else if (isFilePart && !chunkStr.includes(`--${boundary}--`)) {fileData.push(chunk);}});// 数据接收完成,保存文件req.on('end', () => {if (!fileName) {res.writeHead(400, { 'Content-Type': 'text/html;charset=utf-8' });return res.end('未找到文件');}// 合并并清理文件内容const fileBuffer = Buffer.concat(fileData);const endIndex = fileBuffer.lastIndexOf(Buffer.from(`--${boundary}--`));const cleanData = endIndex > 0 ? fileBuffer.slice(0, endIndex - 2) : fileBuffer;// 保存文件const savePath = path.join(__dirname, 'uploads', fileName);fs.writeFile(savePath, cleanData, (err) => {// 关键:设置响应编码为 utf8,确保返回中文正常显示res.writeHead(err ? 500 : 200, { 'Content-Type': 'text/html;charset=utf-8' });res.end(err ? '上传失败' : `文件上传成功: ${fileName}`);});});}
});// 启动服务器
const PORT = 3000;
server.listen(PORT, () => {console.log(`服务器运行在 http://localhost:${PORT}`);// 创建上传目录if (!fs.existsSync('./uploads')) fs.mkdirSync('./uploads');
});

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/97995.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/97995.shtml
英文地址,请注明出处:http://en.pswp.cn/web/97995.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习英语音标 (从汉语角度看英语音标发音差异)

仅供参考, 跟着教学视频看不懂时再来看以下引导 以下只写容易出错的音标 发音视频: https://www.jiwake.com/yinbiaofayin/ 音标规则单词ɜː类似汉语e, 饿~urgeə类似汉语e, 饿goɔː类似汉语o, 哦~walkɒ类似汉语o, 哦washɪ/iː/的短语, 不止发声短,舌头不用隆起itʃ类似汉…

论文笔记(九十一)GWM: Towards Scalable Gaussian World Models for Robotic Manipulation

GWM: Towards Scalable Gaussian World Models for Robotic Manipulation文章概括摘要1. 引言2. 相关工作3. 高斯世界模型&#xff08;Gaussian World Model&#xff09;3.1. 世界状态编码&#xff08;World State Encoding&#xff09;3.2. 基于扩散的动态建模&#xff08;Dif…

apache phoenix sql 命令大全详解

这是一份非常详细的 Apache Phoenix SQL 命令大全和详解。Phoenix 作为 HBase 上的 SQL 层&#xff0c;其语法大部分与标准 SQL 兼容&#xff0c;但也有许多针对 HBase 的特性扩展。核心概念 在开始之前&#xff0c;请记住 Phoenix 的两个核心概念&#xff1a; 主键&#xff08…

【代码讲解】SO-ARM100 双场景演示:手柄驱动 Mujoco 仿真 + 实机控制

视频讲解&#xff1a; 【代码讲解】SO-ARM100 双场景演示&#xff1a;手柄驱动 Mujoco 仿真 实机控制今天介绍下使用使用北通手柄通过控制 Mujoco 中的 SO-ARM100 机械臂&#xff0c;然后将关节数据通过 zmq 通信转发控制实际机械臂。 本期中会涉及如下点&#xff0c;需要注意…

「数据获取」《中国教育经费统计年鉴》(1997-2024)

01、数据简介《中国教育经费统计年鉴》作为我国教育经费领域的核心统计典籍&#xff0c;全面系统地呈现了全国各级各类教育经费的来源构成、分配流向与使用成效。其统计范围覆盖学前教育、基础教育、中等职业教育、高等教育及特殊教育等全学段&#xff0c;数据维度涵盖财政性教…

使用 Logspout 收集所有容器的

1.将所有容器的输出路由到远程 rsyslog 服务器1.修改 rsyslog 配置文件/etc/rsyslog.conf, 从中找到 “# Provides UDP sysilog recepion"语句。并将该处的以下两行配置代码行首的“#”字符删除&#xff08;取消注释&#xff09;[roothost1 ~]# vi /etc/rsyslog.conf [roo…

【智能化解决方案】基于多目标优化检索增强生成的智能行程规划方案

&#x1f4dd; 基于多目标优化的智能行程规划方案 1 用户需求分析与矩阵构建 1.1 核心用户信息提取 根据用户提供的年龄、出发地、目的地、出行时间等基本信息&#xff0c;我们首先构建一个用户特征向量&#xff1a; U {Age, Origin, Destination, TravelDate, Duration, Budg…

软件研发的演变

软件研发从一门手工作坊式的艺术&#xff0c;逐步演进为一门系统化、工程化、智能化的现代学科。其发展历程不仅体现了技术的飞跃&#xff0c;更反映了方法论、协作模式和思维方式的深刻变革。一、发展演变历程软件研发的演变可以大致划分为以下几个阶段&#xff1a;1. 软件作坊…

「日拱一码」091 机器学习——集成学习

目录 集成学习介绍 1. 核心思想 2. 为什么有效&#xff1f; 3. 主要流派与方法 A. 并行方法&#xff1a;Bagging (Bootstrap Aggregating) B. 串行方法&#xff1a;Boosting C. 堆叠法&#xff1a;Stacking 代码示例 Bagging 的代表 —— 随机森林 (Random Forest) 集成…

vscode实现第三方包的使用,cmake结合vcpkg(跨平台)

要使用cmake和vcpkg组织一个完整的现代cpp项目&#xff0c;一般来说需要三个文件vcpkg.json描述第三方依赖项//vcpkg.json {"dependencies": ["fmt"] }//安装,在vcpkg.json目录执行 vcpkg installCMakePresets.json定义项目的本质属性&#xff08;What&…

DevExpress中Word Processing Document API学习记录

文章目录1 文档结构划分2 文档操作基础2.1 Positions and Ranges2.2 Secitions2.3 Paragraphs2.4 Tables2.5 Lists2.6 Hyperlinks and Bookmarks2.7 Comments2.8 Headers and Footers2.9 Shapes and Pictures2.10 Watermarks2.11 Charts2.12 OLE Objects2.13 ActiveX Controls2…

Roo Code 的差异_快速编辑功能

什么是差异编辑&#xff1f; 简单来说&#xff0c;差异编辑就像是一位细心的装修师傅&#xff1a;他不会把整个房子拆掉重盖&#xff0c;而是精准地只修补需要改动的部分。Roo Code 的这项功能默认开启&#xff0c;它通过比对代码差异&#xff08;diff&#xff09;来实施修改&a…

【Axure高保真原型】标签树分类查询案例

今天和大家分享标签树分类查询案例的原型模版&#xff0c;效果包括&#xff1a; 树形分类——点击左侧树形里的箭头&#xff0c;可以展开或收起子级选项&#xff1b; 查询表格——点击标签树里的选项&#xff0c;如果是末级选项&#xff0c;可以筛选右侧表格用户标签&#xf…

容器化部署项目05

一、工作原理 镜像&#xff1a;容器的模板&#xff0c;包括容器运行时所需的数据 容器&#xff1a;运行中的进程&#xff0c;依赖镜像运行&#xff0c;镜像的具现化 镜像你可以把它看成Python中的类&#xff0c;而容器可以看做是类的实例化对象。 一个类可以有多个对象&#xf…

微信小程序 工作日历 周计划日报 修改等提报和状态展示功能,支持h5,Android ,ios,基于uniapp,适配vue2和vue3

Work-calendar 介绍 &#xff08;底部附链接&#xff09; 基于uni-calendar做的定制化开发&#xff0c;主要功能为工作日历展示和提报组件 ​ 1.支持周计划日报状态展示且可配置 ​ 2.支持农历展示配置&#xff0c;回到当日&#xff0c;月份切换 ​ 3.日历&#xff0c;周报…

openharmony 鸿蒙 下 利用蓝牙API(a2dp模块-高级音频,ble模块-低功耗蓝牙等)完成对蓝牙音响的控制(蓝牙广播)

1.首先是登录页面&#xff08;利用webapi 和本地数据存储完成登陆操作&#xff09; 2.添加设备&#xff08;利用ble.startBLEScan 和 ble.on("BLEDeviceFind", onReceiveEvent);完成蓝牙扫描与显示&#xff09; 3.蓝牙ble连接&#xff08;利用ble.createGattClientDe…

17、逻辑回归与分类评估 - 从连续到离散的智能判断

学习目标:理解分类问题的本质和评估方法,掌握逻辑回归的数学原理和概率解释,学会二分类和多分类问题的处理方法,熟练使用分类评估指标,理解过拟合和正则化的基本概念。 > 从第16章到第17章:从预测数值到判断类别 在第16章中,我们学习了线性回归,解决的是预测连续数…

自动化脚本的核心引擎

自动化脚本作为现代软件开发与运维的重要工具&#xff0c;其核心引擎承担着解析指令、调度任务和执行逻辑的关键职能。这种引擎本质上是一个轻量级的运行时环境&#xff0c;通过预定义的规则集将人类可读的脚本语言转化为机器可执行的原子操作。在持续集成/持续交付&#xff08…

【Vue2 ✨】Vue2 入门之旅 · 进阶篇(九):Vue2 性能优化

在前几篇文章中&#xff0c;我们学习了 Vuex 的内部机制以及 Vue Router 的工作原理。本篇将深入探讨 Vue2 性能优化&#xff0c;帮助你掌握在开发中提升 Vue 应用性能的方法和技巧。 目录 性能优化的意义响应式系统优化虚拟 DOM 与渲染优化组件懒加载与按需渲染事件与计算属性…

【题解】B2600 【深基1.例2】简单的分苹果

题目描述 这里有 101010 个苹果&#xff0c;小 A 拿走了 222 个&#xff0c;Uim 拿走了 444 个&#xff0c;八尾勇拿走剩下的所有的苹果。我们想知道&#xff1a; 小A 和 Uim 两个人一共拿走多少苹果&#xff1f;八尾勇能拿走多少苹果&#xff1f; 现在需要编写一个程序&#x…