el-upload上传文件,文件名乱码的解决方法

 一 、前端代码

主要组件

<el-upload
                ref="uploadRef"
                :action="UPLOAD.action"
                :data="UPLOAD.data"
                name="file"
                :auto-upload="false"
                :before-upload="beforeUpload"
                :on-change="onFileChange"
                :on-progress="handleProgress"
                :on-success="handleSuccess"
                multiple
                >
                <template #trigger>
                  <button ref="triggerBtn">select file</button>
                </template>
              </el-upload>

原本想在beforeUpload中对file.name进行编码,可这个属性没有setter,改不了。

换个思路,前端改不了,那看看后端

我用的node.js的express框架

二、乱码原因:

这个乱码的原因是因为multer模块不支持非Unicode字符,所以中文乱码。

file.originalname 是乱码的(如果 文件名原来是中文)

三、解决方案:

1. 方案一:使用 iconv-lite 模块

 const iconv = require('iconv-lite');

转成中文: iconv.decode(file.originalname, 'utf-8')

nodejs+express + multer+iconv   服务端文件上传主要代码

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, req.body.absdir);  // 这里使用请求参数中的 absDir 作为文件存储目录
  },
  filename: function (req, file, cb) {
    cb(null, iconv.decode(file.originalname, 'utf-8'));  // 使用原始文件名作为文件名
  }
});
// 创建 Multer 实例
const upload = multer({ storage: storage });

// 处理文件上传的路由
app.post('/upload', upload.array('file'), (req, res) => {
  if (req.files.length === 0) {
    res.status(400).send('没有选择要上传的文件');
    return;
  }
  res.status(200).send('文件上传成功');
});

2.方案二:编码处理

我问了gpt,根据它的提示:

multer的文件名编码格式并不支持直接指定为utf-8。multer内部使用的是默认的操作系统编码来保存文件名。如果你的操作系统编码不支持特定字符,那么multer保存的文件名可能会出现乱码。

但你可以尝试通过以下方式解决这个问题:

  1. 将multer保存的文件名进行编码处理:

Copy

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads'); // 指定上传文件的保存目录
  },
  filename: function (req, file, cb) {
    const originalname = Buffer.from(file.originalname, 'utf-8'); // 将原始文件名进行utf-8编码处理
    const extension = path.extname(originalname.toString()); // 将Buffer对象转换为字符串并获取扩展名
    const basename = path.basename(originalname.toString(), extension); // 获取文件名
    cb(null, basename + '-' + Date.now() + extension); // 生成保存的文件名
  }
});
  1. 在路由中获取文件名时进行解码处理:

app.post('/upload', upload.single('file'), function(req, res) {
  const originalname = Buffer.from(req.file.originalname, 'base64').toString('utf-8'); // 将文件名进行解码处理
});

通过以上方式,你可以尝试处理文件名的编码问题。如果这种方式还不能解决乱码问题,可能需要在操作系统级别设置正确的编码环境。

四、后端主要代码

upload 多文件上传   后端主要代码



const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, req.body.absdir);  // 这里使用请求参数中的 absDir 作为文件存储目录
  },
  filename: function (req, file, cb) {
    const originalname = Buffer.from(file.originalname, 'binary').toString("utf-8"); // 将原始文件名进行utf-8编码处理
    cb(null, originalname); // 生成保存的文件名
  }
});



const upload = multer({
  storage: storage,
  fileFilter: function (req, file, cb) {
    
    // 检查文件类型
    // if (!file.mimetype.startsWith('image/')) {
    //   cb(new Error('只允许上传图片文件'));
    // } else {
    //   cb(null, true);
    // }
    
   cb(null, true);
  },
  limits: {
    fileSize: 1 * 1024 * 1024 ,// 限制文件大小为 1MB
    fileSize: 1 * 1024 * 1024 * 1024 * 2 //限制文件大小为 2GB
  }
});

app.post('/upload', upload.single('file'), function(req, res) {
  const originalname = Buffer.from(req.file.originalname, 'binary').toString('utf-8'); // 将文件名进行解码处理
  console.log("upload==",originalname,"            "+Buffer.from("你好啊1234", 'utf-8').toString('binary'))
  res.send(originalname);
});

 

全部代码:

一、前端

<template>
  <el-button @click="showdialog">
    <el-icon :size="20">
      <FolderAdd />
    </el-icon>
  </el-button>

  <el-dialog
    v-model="dialogVisible"
    title="操作"
    width="80%"
    custom-class="el-dialog2"
    destroy-on-close
    @close="handleCloseDialog"
  >
  <el-input v-if="!optionMode" v-model="creatFolderName" placeholder="输入文件夹名"></el-input>
  
  
  <!-- <span>This is a message</span> -->
  <div v-if="optionMode">
    <el-row :gutter="4" justify="space-around">
      <el-col :span="10">
        <div class="option_div add" @click="shouInput">
            <div>
              <el-icon :size="20">
                <FolderAdd />
              </el-icon>
            </div>
            <span>新建文件夹</span>
        </div>
      </el-col>
      <el-col :span="10">
        <!-- <div class="option_div upload" @click="doUploadFile" >
            <div>
              <el-icon :size="20">
                <Upload />
              </el-icon>
            </div>
            <span>上传文件</span>
        </div> -->


        <div class="option_div upload" @click="doUploadFile" >
            <div>
              <el-icon :size="20">
                <Upload />
              </el-icon>
            </div>
            <span>上传文件</span>
            <div style="width:0;height:0;margin: 0;padding:0;border: none;overflow: hidden;">
              <el-upload
                ref="uploadRef"
                :action="UPLOAD.action"
                :data="UPLOAD.data"
                name="file"
                :auto-upload="false"
                :before-upload="beforeUpload"
                :on-change="onFileChange"
                :on-progress="handleProgress"
                :on-success="handleSuccess"
                :on-error="handleError"
                multiple
                >
                <template #trigger>
                  <button ref="triggerBtn">select file</button>
                </template>
              </el-upload>
            </div>
        </div>
      </el-col>
    </el-row>
  </div>
  <template #footer>
    <span v-if="optionMode" class="dialog-footer" style="color:#1e82ee;">
      Author: QX
    </span>
    <el-button type="primary" v-if="!optionMode" @click="doCreateFolder" :loading="loading">创建</el-button>
      <el-row v-for="(item,index) in fileList" :key="item.name+index">
        <el-col>
          <div>
            <span style="color:gray;">{{ item.name +"  "+item.size }}</span>
          </div>
        </el-col>
      </el-row>
      <el-button @click="startSubmit">发送</el-button>
  </template>
  
  </el-dialog>
</template>
  
<script>
import { FolderAdd, Upload } from "@element-plus/icons-vue";
import { reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
import axios from 'axios';
import api from '@/api';
export default {
  name: "FolderComponent",
  components: {
    FolderAdd,
    Upload
  },
  props:["updateDir","getjoinPath"],
  setup(props){
    let { updateDir, getjoinPath } = props;
    let dialogVisible = ref(false);
    let optionMode = ref(true);
    let creatFolderName = ref("");
    const uploadRef = ref(null);
    const triggerBtn = ref(null);
    let fileList = reactive([]);
    let UPLOAD = reactive({
      action:api.upload,
      data:{
        absdir:''
      }
    })
    const startSubmit = ()=>{
      UPLOAD.data.absdir = getjoinPath();
      console.log("上传的参数:");
      console.log(UPLOAD.data);
      uploadRef.value.submit();
    }
    

    const beforeUpload = (file)=> {
      
      console.log("beforeUpload:"+file);
      return true;
    }


    const onFileChange = (file,files) =>{
      file;
      fileList.splice(0,fileList.length,...files);
      let t=[];
      files.forEach(file =>{
        t.push({
          name:file.name,
          uid:file.uid
        })
      })
      
      console.log("onfile changed",file);
    }
    const handleProgress = (event, file, fileList)=>{
      file,fileList;
      console.log("event.percent",event.percent)
    }
    const handleSuccess = (response, file, fileList) => {
      console.log("handleSuccess",file.name);
      ElMessage.warning("清除数据")
      uploadRef.value.clearFiles();

    }
    const handleError = () => {
      ElMessage.warning("清除数据")
      console.error("handleError")
      uploadRef.value.clearFiles();
    }

    let loading = ref(false);
    const showdialog = () => {
      dialogVisible.value = true;
    }
    const doCreateFolder = () => {
      if(creatFolderName.value=="" || creatFolderName.value.length==0){
        return ElMessage({
          type: 'warning',
          message: "文件夹名称不能为空"
        })
      }
      loading.value=true;
      // console.log(creatFolderName.value);
      //判断文件夹名称是否合法,检测是否有非法字符
      let reg = /[`~!@#$%^&*()+<>?:"{},.\/;'[\]]/im;
      if(reg.test(creatFolderName.value)){
        loading.value=false;
        return ElMessage({
          type: 'warning',
          message: "文件夹名称不能包含非法字符",
        });
      }else{
        axios.post(api.createFolder,{
          path:getjoinPath(),
          name: creatFolderName.value
        }).then(res => {
          loading.value=false;
          creatFolderName.value = "";
          if(res.data.code == 200){
            ElMessage.success(res.data.msg);
            updateDir();
            optionMode.value = true;
            dialogVisible.value = false;
          }else{
            ElMessage.error(res.data.msg);
            updateDir();
            optionMode.value = false;
          }
        })
        .catch(err => {
          loading.value=false;
          creatFolderName.value = "";
          console.log(err);
        })
      }
    }
    const handleCloseDialog = ()=>{
      dialogVisible.value=false;
      optionMode.value=true;
    }
    
    const doUploadFile = () => {
      // 模拟点击上传组件
      triggerBtn.value.click();
      uploadRef.value.handleStart=(rawFile)=>{
        console.log("uploadRef");
        console.log(rawFile);
      }
      
    }
    
    const shouInput = () => {
      setTimeout(() => {
        optionMode.value = false;
      }, 200);
    }
    return {
      dialogVisible,
      creatFolderName,
      showdialog,
      shouInput,
      optionMode,
      doCreateFolder,
      loading,
      props,
      doUploadFile,
      handleCloseDialog,
      uploadRef,
      triggerBtn,
      fileList,

      beforeUpload,
      onFileChange,
      handleProgress,
      handleSuccess,
      UPLOAD,
      startSubmit,
      handleError
    }
  }
};
</script>

<style>
.el-dialog.el-dialog2 {
  background-color: #2f2f35 !important;
}
.option_div {
  border: 1px solid rgb(144, 144, 144);
  border-radius: 9px;
  padding: 10px;
}
.option_div.add {
  border: 1px solid rgb(11, 89, 173);
}
.option_div.upload {
  border: 1px solid rgb(171, 119, 8);
}
.option_div:hover {
  background-color: rgb(0, 0, 0);
}
</style>

二、后端全部代码

const express = require('express');
const os = require('os');
const fs = require('fs');
const path = require('path');
const multer = require('multer');
// const iconv = require('iconv-lite');
// const multiparty = require('multiparty');

const app = express();


const bodyParser = require('body-parser'); // 引入 body-parser 模块
const qrcode = require('./qrcode');
app.use(bodyParser.json()); // 解析 JSON 格式的请求体
app.use(bodyParser.urlencoded({ extended: true })); // 解析 URL-encoded 格式的请求体
// 部署
// 静态资源目录,指向刚刚打包生成的 dist 目录
// app.use(express.static(path.join(__dirname, 'dist')));


app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  next();
});


app.get('/ostype', (req, res) => {
  return res.end(os.type());
});
app.get('/getipaddress',(req, res) => {
    return res.end("http://"+getWLAN_IP()+":3000");
})
app.get('/dir',(req, res) => {
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
    const dir = req.query.dir;
    fs.readdir(dir, (err, files) => {
        if (err) {
            console.error("获取路径",dir,"出错",err);
            res.statusCode = 400;
            res.end('路径不存在:'+dir);
            return;
        }
        // 创建一个对象用于存储文件夹和文件
        const fileObj = {
            folders: [],
            files: [],
            permission:[]//需要权限才能访问
        };
        files.forEach(file => {
            try{
                // 使用 fs.statSync() 同步获取文件或文件夹的状态信息
                const stats = fs.statSync(dir + '/' + file);
                const e={
                    name:file,
                    size:stats.size,
                    mtime:stats.mtime
                }
                if (stats.isDirectory()) {
                fileObj.folders.push(e);
                } else {
                fileObj.files.push(e);
                }
            }catch(e){
                fileObj.permission.push(file);
            }
          });
        res.end(JSON.stringify({
            code: 200,
            data: fileObj
        }));
    });
});

app.get('/getdownload',(req, res) => {
  const absPath = req.query.fullpath;
  if (fs.existsSync(absPath)) {
    // 设置 Content-Disposition header,指定文件名
    // 对文件名进行 URL 编码
    const filename = encodeURIComponent(path.basename(absPath));
    const stat = fs.statSync(absPath);
    const fileSize = stat.size;

    console.log("download ",absPath,fileSize+"byte");
    res.setHeader('Content-Length', fileSize);

    // 设置 Content-Disposition header,指定文件名
    res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${filename}`);

    // 以流的方式将文件发送给客户端
    const fileStream = fs.createReadStream(absPath);
    fileStream.pipe(res);
} else {
    res.status(404).send('文件不存在');
}
});


app.post('/download', (req, res) => {
    // console.log(req.body);
  
    const fullpath = req.body.fullpath; // 获取请求中的 fullpath 参数
    // 根据 fullpath 参数进行文件读取操作
    const absPath = fullpath;
    
    // 读取文件内容
    fs.readFile(absPath, (err, data) => {
      if (err) {
        console.error('文件读取失败:', err);
        res.status(500).send('文件读取失败');
      } else {
        // 将文件内容作为响应返回给前端
        res.send(data);
      }
    });
  });
app.post('/downloadStream', (req, res) => {
    console.log(req.body);
    const fullpath = req.body.fullpath; // 获取请求中的 fullpath 参数
    // 根据 fullpath 参数进行文件读取操作
    const absPath = fullpath;
    

    /**
     * 在这段代码中,我们使用 createReadStream 
     * 方法创建了一个文件读取的可读流 fileStream,
     * 然后使用 pipe 方法将其直接导向响应的可写流 res。
     * 这样就实现了边读取文件边传输给前端进行下载的效果。
     */
    // 读取文件内容
    // 检查文件是否存在
    // 检查文件是否存在
    if (fs.existsSync(absPath)) {
        // 设置 Content-Disposition header,指定文件名
        // 对文件名进行 URL 编码
        const filename = encodeURIComponent(path.basename(absPath));
        const stat = fs.statSync(absPath);
        const fileSize = stat.size;

        res.setHeader('Content-Length', fileSize);
        console.log("fileSize=",fileSize);

        // 设置 Content-Disposition header,指定文件名
        res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${filename}`);

        // 以流的方式将文件发送给客户端
        const fileStream = fs.createReadStream(absPath);
        fileStream.pipe(res);
    } else {
        res.status(404).send('文件不存在');
    }
  });


app.post('/music/stream', (req, res) => {
  const fullpath = req.body.fullpath; // 从请求中获取音频文件的完整路径

  // 使用 fs 模块读取音频文件,并将数据以流的形式传输给客户端
  const stream = fs.createReadStream(fullpath); // 创建可读流

  // 设置响应头,指定音频格式和分块传输方式
  res.setHeader('Content-Type', 'audio/flac');
  res.setHeader('Transfer-Encoding', 'chunked');

  // 将音频数据流通过响应发送给客户端
  stream.pipe(res);
});
app.get('/getplay',(req, res) => {
  const absPath = req.query.fullpath;
  if (fs.existsSync(absPath)) {
    // 设置 Content-Disposition header,指定文件名
    // 对文件名进行 URL 编码
    const filename = encodeURIComponent(path.basename(absPath));

    res.setHeader('Content-Type', 'audio/flac');
    res.setHeader('Transfer-Encoding', 'chunked');

    // 设置 Content-Disposition header,指定文件名
    res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${filename}`);

    // 以流的方式将文件发送给客户端
    const fileStream = fs.createReadStream(absPath);
    fileStream.pipe(res);
} else {
    console.log(absPath);
    res.status(404).send('文件不存在');
}
});

app.post("/createfolder", (req, res) => {
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
  const absDir = req.body.path;
  const folderName = req.body.name;

  // 判断路径是否存在
  if (!fs.existsSync(absDir)) {
    // 路径不存在的处理逻辑
    res.end(JSON.stringify({
      code: 404,
      msg: "路径"+absDir+"不存在"
    }));
    return;
  }
  
  // 判断文件夹是否存在
  const folderPath = path.join(absDir, folderName);
  if (fs.existsSync(folderPath)) {
    // 文件夹已存在的处理逻辑
    // res.status(409).send("");
    res.end(JSON.stringify({
      code: 405,
      msg: "文件夹"+folderName+"已存在"
    }));
    return;
  }
  
  // 创建文件夹
  fs.mkdirSync(folderPath);
  
  // 返回成功响应
  // res.status(200).send("文件夹创建成功");
  res.end(JSON.stringify({
    code: 200,
    msg: "文件夹创建成功,"+folderPath
  }));
});


/*
测试一:
需要引入模块iconv
   // console.log("seq",seq,"utf-8",iconv.decode(file.originalname, 'utf-8'),"fileName",JSON.parse(req.body.fileInfoList)[seq-1].name);
// multer模块不支持中文这种非Unicode字符,需要别的模块转换iconv
var seq=0;
// 设置文件存储目录和文件名
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, req.body.absdir);  // 这里使用请求参数中的 absDir 作为文件存储目录
  },
  filename: function (req, file, cb) {
    cb(null, iconv.decode(file.originalname, 'utf-8'));  // 使用原始文件名作为文件名
  }
});
// 创建 Multer 实例
const upload = multer({ storage: storage });

// 处理文件上传的路由
app.post('/upload', upload.array('file'), (req, res) => {
  if (req.files.length === 0) {
    res.status(400).send('没有选择要上传的文件');
    return;
  }
  res.status(200).send('文件上传成功');
});

*/





/*
测试二
上传太直接了,没有限制,不灵活,不能判断
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, req.body.absdir);  // 这里使用请求参数中的 absDir 作为文件存储目录
  },
  filename: function (req, file, cb) {
   console.log("save file ",file.originalname);

    cb(null, file.originalname + "");  // 使用原始文件名作为文件名
  }
});



const upload = multer({
  storage: storage,
  fileFilter: function (req, file, cb) {
    
    // 检查文件类型
    // if (!file.mimetype.startsWith('image/')) {
    //   cb(new Error('只允许上传图片文件'));
    // } else {
    //   cb(null, true);
    // }
    
   cb(null, true);
  },
  limits: {
    fileSize: 1 * 1024 * 1024 ,// 限制文件大小为 1MB
    fileSize: 1 * 1024 * 1024 * 1024 * 2 //限制文件大小为 2GB
  }
});

app.post('/upload', upload.array('file'), (req, res) => {
  if (req.files.length === 0) {
    res.status(400).send('没有选择要上传的文件');
    return;
  }

  // 处理每个上传的文件
  req.files.forEach(file => {
    // 检查文件名称
    if (file.originalname) {
      // res.status(400).send('文件名必须以 prefix_ 开头');
      res.status(200).send(req.body.absdir+"收到"+file.originalname);
      return;
    }

    // 保存文件到指定目录
    // ...
  });

  // res.status(200).send('文件上传成功');
});
*/


/**
 * 测试三
 * 不用额外引入模块,又能过滤文件,设置个数、大小、类型
 */

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, req.body.absdir);  // 这里使用请求参数中的 absDir 作为文件存储目录
  },
  filename: function (req, file, cb) {
    const originalname = Buffer.from(file.originalname, 'binary').toString("utf-8"); // 将原始文件名进行utf-8编码处理
    cb(null, originalname); // 生成保存的文件名
  }
});



const upload = multer({
  storage: storage,
  fileFilter: function (req, file, cb) {
    
    // 检查文件类型
    // if (!file.mimetype.startsWith('image/')) {
    //   cb(new Error('只允许上传图片文件'));
    // } else {
    //   cb(null, true);
    // }
    
   cb(null, true);
  },
  limits: {
    fileSize: 1 * 1024 * 1024 ,// 限制文件大小为 1MB
    fileSize: 1 * 1024 * 1024 * 1024 * 2 //限制文件大小为 2GB
  }
});

app.post('/upload', upload.single('file'), function(req, res) {
  const originalname = Buffer.from(req.file.originalname, 'binary').toString('utf-8'); // 将文件名进行解码处理
  console.log("upload==",originalname,"            "+Buffer.from("你好啊1234", 'utf-8').toString('binary'))
  res.send(originalname);
});














app.listen(3000, () => {
  console.log('Server is running on port 3000...');
  let homeUrl = "http://"+getWLAN_IP()+":3000";
  console.log(homeUrl);
  printQRCODE(homeUrl);
});
function printQRCODE(text){
  // 创建二维码生成器实例
  var qr = new qrcode(0, 'M');

  // 设置要生成二维码的文本

  // 使用二维码生成器生成二维码
  qr.addData(text);
  qr.make();

  // 获取生成的二维码的字符表示形式
  var qrData = qr.createASCII();

  // 打印二维码
  console.log(qrData);
}
function getWLAN_IP() {
    const interfaces = os.networkInterfaces();
    const addresses = [];
    for (let k in interfaces) {
        for (let k2 in interfaces[k]) {
            let address = interfaces[k][k2];
            if (address.family.toLowerCase().match('ipv4')!=null) {
                addresses.push({
                    interface: k,
                    ip: address.address
                });
                if (k.toLowerCase().match("wlan")!=null) {
                        return address.address;
                }
            }
        }
    }
  }

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qx09

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值