nodejs 实现Excel数据导入数据库,以及数据库数据导出excel接口(核心使用了multer和node-xlsx库)

项目地址:https://gitee.com/LiangDouJun/nodejsExcel

一、实现效果

1、数据库数据导出

2、excel导入

二、代码实现

// 根据环境加载对应的配置文件
const env = process.env.NODE_ENV || 'development';
require('dotenv').config({ path: `.env.${env}` });const express = require('express');
let multer = require('multer');
let moment = require('moment');
const xlsx = require("node-xlsx");
const fs = require('fs').promises; // 使用异步版本
const path = require('path');
const router = express.Router()
const connection = require('../db/db')// 常量定义 - 从环境变量获取,如果没有则使用默认值
const ALLOWED_FILE_TYPES = ['.xlsx', '.xls'];
const MAX_FILE_SIZE = parseInt(process.env.MAX_FILE_SIZE) || 10 * 1024 * 1024; // 10MB
const MAX_FILES = parseInt(process.env.MAX_FILES) || 5;
const FILE_DELETE_DELAY = parseInt(process.env.FILE_DELETE_DELAY) || 60 * 1000; // 1分钟
const BATCH_SIZE = 1000; // 批量插入大小// 文件类型验证函数
const validateFileType = (file) => {const ext = path.extname(file.originalname).toLowerCase();return ALLOWED_FILE_TYPES.includes(ext);
};let Storage = multer.diskStorage({destination: (req, file, callback) => {// 指定当前这个文件存放的目录callback(null, 'files'); },filename: (req, file, callback) => {console.log('fieldname', file.originalname);// 文件命名:当前时间戳 + "_" + 源文件名称callback(null, new Date().getTime() + '_' + file.originalname); }
});// 文件上传配置
let upload = multer({ storage: Storage,limits: {fileSize: MAX_FILE_SIZE,files: MAX_FILES},fileFilter: (req, file, cb) => {if (!validateFileType(file)) {return cb(new Error('不支持的文件类型,只支持 .xlsx 和 .xls 文件'), false);}cb(null, true);}
}).array('file', MAX_FILES);// 批量插入数据到数据库
const batchInsertData = async (data) => {if (data.length === 0) return;const values = data.map(item => [item.id, item.userId, item.latitude, item.longitude, item.createTime]);const placeholders = values.map(() => '(?,?,?,?,?)').join(',');const sql = `INSERT INTO \`position\` (id, userId, latitude, longitude, createTime) VALUES ${placeholders}`;const flatValues = values.flat();try {const conn = await connection.promise();const [result] = await conn.execute(sql, flatValues);return result;} catch (error) {throw error;}
};// 解析Excel文件数据
const parseExcelData = (fileUrl) => {try {const sheets = xlsx.parse(fileUrl, { cellDates: true });const arr = [];sheets.forEach((sheet) => {for (let i = 1; i < sheet.data.length; i++) {const row = sheet.data[i];if (row && row.length >= 3) { // 确保至少有3列数据arr.push({id: Math.random().toString(36).substring(2, 15),userId: row[0] || '',latitude: row[1] || 0,longitude: row[2] || 0,createTime: moment().utc('+8:00').format('YYYY-MM-DD HH:mm:ss'),});}}});return arr;} catch (error) {throw new Error(`解析Excel文件失败: ${error.message}`);}
};// 安全删除文件
const safeDeleteFile = async (fileUrl) => {try {await fs.unlink(fileUrl);console.log(`文件已删除: ${fileUrl}`);} catch (error) {console.error(`删除文件失败: ${fileUrl}`, error);}
};// 导入Excel
router.post('/loadExcel', function (req, res) {upload(req, res, async (err) => {if (err) {console.error('文件上传错误:', err);return res.status(400).send({status: 1,message: err.message || '导入失败',data: null,});}if (!req.files || req.files.length === 0) {return res.status(400).send({status: 1,message: '请选择要上传的文件',data: null,});}const fileUrl = req.files[0].path;try {// 解析Excel数据const arr = parseExcelData(fileUrl);if (arr.length === 0) {await safeDeleteFile(fileUrl);return res.send({ status: 0, message: '文件解析成功,但没有有效数据', data: { list: [], total: 0 } });}// 批量插入数据const batches = [];for (let i = 0; i < arr.length; i += BATCH_SIZE) {batches.push(arr.slice(i, i + BATCH_SIZE));}for (const batch of batches) {await batchInsertData(batch);}// 延迟删除文件setTimeout(() => {safeDeleteFile(fileUrl);}, FILE_DELETE_DELAY);res.send({ status: 0, message: '导入成功', data: { list: arr, total: arr.length } });} catch (error) {console.error('导入处理错误:', error);// 清理文件await safeDeleteFile(fileUrl);res.status(500).send({status: 1,message: `导入失败: ${error.message}`,data: null,});}});
});// 导出Excel
router.get('/export', async function (req, res) {const sqlStr = 'SELECT userId, latitude, longitude FROM position ORDER BY createTime DESC';try {const conn = await connection.promise();const [data] = await conn.execute(sqlStr);const info = [["用户", "经度", "纬度"],...data.map(({userId, latitude, longitude}) => [userId, latitude, longitude])];const sheetOptions = {'!cols': [{wch: 30}, {wch: 30}, {wch: 30}]};const buffer = xlsx.build([{ name: '位置数据', data: info }], { sheetOptions }); res.setHeader('Content-Type','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');// 使用英文文件名,确保兼容性const filename = `position_data_${moment().format('YYYY-MM-DD_HH-mm-ss')}.xlsx`;res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);res.end(buffer, 'binary');} catch (error) {console.error('导出错误:', error);res.status(500).send({status: 1,message: '导出失败',data: null,});}
});module.exports = router;

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

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

相关文章

VUE2 学习笔记8 v-text/html/cloak/once/pre/自定义

除了之前已经介绍过的v-on v-bind v-for v-if v-show&#xff0c;vue还有很多其他的指令。v-textv-text是Vue内置指令。内置指令&#xff0c;是Vue内部定义好的&#xff0c;开发的时候直接拿来用就行了。v-text用于向其所在的标签添加文本。<body><div id"root&q…

vue 使用postcss-pxtorem 实现适老化

1. 安装依赖 npm install postcss-pxtorem -D2. 配置 Vite (vite.config.js) import { defineConfig } from vite import vue from vitejs/plugin-vue import postcsspxtorem from postcss-pxtoremexport default defineConfig({plugins: [vue()],css: {postcss: {plugins: [po…

Rust:高效错误处理工具 anyhow

Rust 的 anyhow 库是一个专注于简化错误处理的工具&#xff0c;特别适合应用程序开发场景。它通过统一的错误类型和便捷的 API&#xff0c;减少模板代码&#xff0c;提升错误信息的可读性。以下是其核心用法及示例&#xff1a;1. 安装与基础用法 在 Cargo.toml 中添加依赖&…

Solidity基础(教程①-简单数字存储)

我们来尝试一个超级简单的智能合约&#xff0c;它只会做一件事情&#xff1a;存储一个数字&#xff0c;并且让我们能修改这个数字。最简单的 Solidity 代码// SPDX-License-Identifier: MIT pragma solidity ^0.8.0;// 定义一个智能合约&#xff0c;名字叫做 SimpleStorage (简…

在 Web3 时代通过自我主权合规重塑 KYC/AML

1. 引言 前序博客有&#xff1a; Ligero 和 Ligetron 中的 MPC 和 ZKLigetron&#xff1a;Nim Network开发的针对AI的zkVMLigetron&#xff1a;基于MPC-In-The-Head范式的zkVM简介谷歌采用 Ligero 构建其 ZK 技术栈 KYC&#xff08;了解你的客户&#xff0c;Know Your Custo…

Linux kernel pinctrl子系统简介

pinctrl(Pin Control)子系统是 Linux 内核中用于统一管理 SoC 引脚(Pin)功能配置的核心子系统,主要解决传统引脚管理方式中存在的配置分散、驱动冲突、资源管理混乱等问题。尤其在嵌入式系统中,SoC 引脚通常支持多种复用功能(如 GPIO、UART、SPI、I2C、视频接口等),pi…

web开发常见问题解决方案大全:502/503 Bad Gateway/Connection reset/504 timed out/400 Bad Request/401 Unauthorized

web开发常见问题解决方案大全&#xff1a;502/503 Bad Gateway&#xff0f;Connection reset&#xff0f;504 timed out&#xff0f;400 Bad Request&#xff0f;401 Unauthorized&#xff0f;403 Forbidden 在使用反向代理&#xff08;如 Nginx、HAProxy&#xff09;或正向代…

Vue 3 拖拽排序功能优化实现:从原理到实战应用

一、引言&#xff1a;为什么需要拖拽排序&#xff1f;在现代Web应用中&#xff0c;交互体验越来越受到重视。拖拽排序(Drag and Drop)作为一种直观的用户交互方式&#xff0c;被广泛应用于&#xff1a;任务管理工具&#xff08;如Trello的任务卡片排序&#xff09;内容管理系统…

git 使用 rebase 删除某次 提交

git删除某次commit记录 在Git中&#xff0c;要删除某次commit记录有几种不同的实现方法&#xff1a; 方法一&#xff1a;使用git rebase命令和~标记 该方法适用于删除最近的几次commit记录。 首先&#xff0c;使用以下命令查看你需要删除的commit的记录 git log找到你要删除的c…

第2章 cmd命令基础:常用基础命令(2)

Hi~ 我是李小咖&#xff0c;主要从事网络安全技术开发和研究。 本文取自《李小咖网安技术库》&#xff0c;欢迎一起交流学习&#x1fae1;&#xff1a;https://imbyter.com 本节介绍的命令有时间与日期&#xff08;time/date&#xff09;、显示目录&#xff08;dir&#xff09;…

我从农村来到了大城市

从田埂到霓虹初到城市那天&#xff0c;行李箱的滚轮碾过柏油路的震动&#xff0c;和老家泥地上的拖沓感完全不同。站在天桥上往下看&#xff0c;车流像被打翻的调色盘&#xff0c;红的黄的光在柏油画布上流淌&#xff0c;我数了三遍才认清那是出租车和公交车的尾灯。第一个月总…

代码随想录算法训练营第三十六天

LeetCode.1049 最后一块石头的重量 II 题目链接 最后一块石头的重量II 题解 class Solution {public int lastStoneWeightII(int[] stones) {int len stones.length;int sum 0;for(int i 0;i<len;i) sum stones[i];int target sum / 2;int[] dp new int[target 1…

Apache Ignite 的监控与指标(Monitoring and Metrics)

这段文档是关于 Apache Ignite 的监控与指标&#xff08;Monitoring and Metrics&#xff09; 的介绍&#xff0c;内容非常关键&#xff0c;尤其在生产环境中保障系统稳定性和性能时至关重要。 我们来一步步深入解析这段文字&#xff0c;帮助你彻底理解其含义和实际意义。&…

【ssh】ubuntu服务器+本地windows主机,使用密钥对进行ssh链接

目录1、服务器配置ssh2、本地主机秘钥对3、上传公钥至服务器4、配置服务器的公钥信息5、测试连接1、服务器配置ssh 使用的服务器系统为 ubuntu系统20.04 首先确认服务器是否已安装SSH&#xff0c;已安装的话会返回openssh 的相关信息&#xff0c;返回为空表示未安装 dpkg -l …

Linux文件fd

文件理解 文件属性内容 打开文件&#xff1a;本质是进程打开文件&#xff0c;文件没被打开时候再磁盘上。 操作文件&#xff1a;本质是进程操作文件。 在操作系统内部&#xff0c;一定存在大量被打开的文件&#xff0c;会对其进行管理&#xff0c;每一个被打开的文件&#…

北京-4年功能测试2年空窗-报培训班学测开-第六十四天-准备面试项目(焦虑)-同学开始面试

今日产出&#xff0c;整理自我介绍&#xff0c;继续整理第一个项目&#xff0c;学习linux命令很焦虑啊很焦虑&#xff0c;很着急今天本打算结束第一个项目的&#xff0c;但是没能够&#xff0c;越说感觉越乱&#xff0c;让同学听我讲&#xff0c;同学说&#xff0c;要听睡着了于…

网络是如何运转的?——常见网络协议与网络分层模型

目录 基本网络协议 TCP&#xff08;传输控制协议&#xff09; 可靠传输&#xff1a;序列号确认应答重传机制 序列号&#xff08;seq&#xff09; 确认应答&#xff08;ACK&#xff09; 超时重传 三次握手与四次挥手 三次握手&#xff08;建立连接&#xff09; 四次挥手…

OpenAI放大招:ChatGPT学习模式上线,免费AI智能家教

目录一、背景介绍二、学习模式是什么国内直接使用AI主流模型GPT-5也会第一时间同步更新。三、主要功能特点1、互动式提示2、分层次响应3、个性化支持4、知识检查5、灵活切换四、学生如何使用学习模式1、访问方式2、适用场景3、交互过程4、使用示例五、局限性1、依赖学生自觉性2…

设计模式:享元模式 Flyweight

目录前言问题解决方案享元工厂结构代码前言 享元是一种结构型设计模式&#xff0c;它摒弃了在每个对象中保存所有数据的方式&#xff0c;通过共享多个对象所共有的相同状态&#xff0c;让你能在有限的内存容量中载入更多对象。 问题 假如你希望在长时间工作后放松一下&#x…

Spring Boot容器化实战:用官方OpenJDK镜像极速启动你的应用

前言 用 Docker 打包 Java 应用,尤其是 Spring Boot,简直是开发者的超级利器。想象一下,你的程序就像勤快的外卖小哥,随时待命,跑遍任何一台机器,马上为你服务。不论是开发环境还是生产环境,Docker 都能让部署变得轻松又高效,彻底告别“环境不一致”的烦恼。 本篇文章…