1. 基于MEAN的技术栈,使用restful风格的接口
2. 在前端代码中放置文件上传按钮和处理表单数据
<div class="upload-file btn btn-sm btn-primary mb-2"> <span><i class="fa fa-upload">i> Uploadspan> <input type="file" class="btn btn-sm btn-primary mb-2" (change)="fileChange($event, topic)" placeholder="Upload file" accept=".csv,.xls"> div>
处理上传文件,生成表单数据
fileChange(event, topic) { this.topic = topic; const fileList: FileList = event.target.files; const file: File = fileList[0]; const formData: FormData = new FormData(); formData.append('_id', topic._id); formData.append('file', file, file.name); this.topicService.uploadMark(formData).subscribe((res) => { this.topic.marks = res; this.toast.setMessage('item import successfully.', 'success'); }, error => console.log(error)); }
uploadMark(fileData: FormData): Observable<any> { return this.http.post('/api/upload', fileData); }
3. 后端接收上传文件
文件上传的router
export default function setRoutes(app) { const router = express.Router();
// file uplaod router.route('/upload').post(uploadCtrl.uploadFile); // Apply the routes to our application with the prefix /api app.use('/api', router);
}
在路由中,req的file字段是获取不到上传文件的,或许可以通过设置bodyParser来处理,但我这里使用一个比较常见的库multer。
npm install multer --save
import * as path from 'path'; import * as multer from 'multer'; import TopicService from '../services/topic'; export default class UploadCtrl { uploadFile = (req, res) => { const topicService = new TopicService(); // 获取上传文件 const uploading = multer({ dest: path.join(__dirname, '../public/uploads'), }).single('file'); // 这里的file是formData.append('file', file, file.name)里的名称 uploading(req, res, (err) => { if (err) { return console.error(err); } const topicId = req.body._id; const uploadFile = req.file; // 保存数据 const save = async () => { const markList = await topicService.parseMark(uploadFile.path); const db = await topicService.saveDB(topicId, markList); return { markList: markList, db: db, }; }; save().then((result) => { res.status(200).json(result.markList); }, error => { console.error(error); }); }); } }
4. 处理上传文件的乱码
上传的文件是一个中文的csv,解析时出现了乱码,使用iconv-lite进行转换
npm install iconv-lite --save
import * as iconv from 'iconv-lite'; import * as Buffer from 'bufferhelper'; export default class IconvHelper { /** * 用于文件上传的转码 * @param fileStr * @returns {string} */ static iconv2utf8 = (fileStr) => { return iconv.decode(fileStr, 'gbk'); } /** * 用于文件下载的转码 * @param fileStr * @returns {NodeBuffer} */ static iconv2gbk = (fileStr) => { return iconv.encode(fileStr, 'gbk'); } }
bufferhelper是一个buffer的增强类,但这里使用后并不能正确赋值,所以这里暂且没有使用
对csv文件进行解析,生成数组,下一步可以保存到数据库
parseMark = (filePath) => { return new Promise((resolve, reject) => { // 读取文件内容 fs.readFile(filePath, (error, data) => { if (error) { return reject(error); } const text = IconvHelper.iconv2utf8(data); const markList = []; // 将文件按行拆成数组 text.split(/\r?\n/).forEach((line, index) => { const arr = line.split(','); if (index > 0 && arr[0]) { markList.push({ userId: arr[0], username: arr[1], donePageCount: arr[2], areaCount: arr[4], name: arr[6], }); } }); resolve(markList); }); }); }
5. 下载文件
res.setHeader('Content-disposition', `attachment; filename='${result.name}-member.csv'`); res.setHeader('Content-type', 'text/csv; charset=GBK'); res.end(IconvHelper.iconv2gbk(content));
6. 处理下载文件的乱码
由于node.js只支持'ascii', 'utf8', 'base64', 'binary'的编码方式,不支持MS的utf8 + BOM格式,网上有说增加BOM头,如下示:
const msExcelBuffer = Buffer.concat([ new Buffer('\xEF\xBB\xBF', 'binary'), new Buffer(IconvHelper.iconv2gbk(content)) ]);
但实际并没有起作用,最后只是简单的encode成gbk问题得到解决
res.end(IconvHelper.iconv2gbk(content));