林安随笔 - node
type: channel
link: https://www.yuaq.cn/index.php/category/node/
items: 使用node+react+sse搭建AI聊天工具
items: nodejs 读取excel，查找缺少数据的时间
items: 监控直播流实现
items: nodejs搭建服务器+连接数据库+接口开发
items: txt文件报文解析写入excel
items: nvm node版本控制工具
items: node.js文件读取与写入

使用node+react+sse搭建AI聊天工具
type: item
date: 2025-02-15T14:07:27+08:00
description: 思路：使用nodejs搭建服务端，接收到客户端发送的问题后返回加载响应，然后调用ai接口，等待ai接口响应后将响应结果通过sse推送给客户端；使用react搭建页面，主要由问题输入框、发送按钮和问答信息展示模块步骤：创建node项目，安装相关依赖项npm init -y npm install express openai body-parser cors创建server.js文件，配置服务const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const OpenAI = require("openai"); const app = express(); const port = 3001; app.use(cors()); app.use(bodyParser.json()); // deepseek 配置 // const openai = new OpenAI({ // baseURL: 'https://api.deepseek.com/v1', // apiKey: '你的key' // }); // 存储 SSE 客户端连接 const clients = new Map(); // SSE 端点 app.get('/stream', (req, res) => { const clientId = Date.now().toString(); res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); // 存储连接并发送客户端 ID clients.set(clientId, res); res.write(`event: clientId\ndata: ${clientId}\n\n`); // 心跳保持连接 const heartbeat = setInterval(() => res.write(': heartbeat\n\n'), 30000); req.on('close', () => { clearInterval(heartbeat); clients.delete(clientId); }); }); // 消息处理端点 app.post('/chat', async (req, res) => { const { clientId, message } = req.body; // 立即返回等待提示 res.status(200).send('AI 正在思考中...'); // 模拟 AI 接口调用 setTimeout(async () => { try { const aiResponse = `AI回复：${Math.random() * 100}`; // 替换为真实 AI 响应 const clientRes = clients.get(clientId); if (clientRes) { clientRes.write(`event: response\n`); clientRes.write(`data: ${JSON.stringify({ text: aiResponse })}\n\n`); } } catch (error) { const clientRes = clients.get(clientId); clientRes?.write(`event: error\ndata: ${error.message}\n\n`); } }, 2000); }); app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); }); 搭建前端项目npx create-react-app ai-chat cd ai-chat npm install axios在src文件下，创建Chat.js文件，搭建简单聊天页面import React, { useState, useEffect } from 'react'; import axios from 'axios'; function Chat() { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [clientId, setClientI…

nodejs 读取excel，查找缺少数据的时间
type: item
date: 2024-07-08T17:22:00+08:00
description: 写在前面：因为每隔一段时间需要查看历史数据，检查每天缺少数据几条数据和具体缺少数据的时间（每天24条数据，00-23），所以写一个工具减少工作时间（增加摸鱼时间），数据是从系统上导出的excel，如下图（数据为假数据）：实现思路：读取excel数据，获取时间，放入数组写一个方法，生成这个时间段的完整小时数据数组遍历完整的时间数组，查找出缺少的时间，统计缺少数量和具体时间段写入excel代码const XLSX = require('xlsx'); let fileDataList = [[ '离线时间', '离线时长', '离线时间段' ]] const fileName = 'gaoxinqu' // 将JSON对象写入Excel function writeExcel(arrayData, fileName) { console.log('开始写入Excel文件', fileName); // 创建一个工作簿 let WorkSheet = XLSX.utils.aoa_to_sheet(arrayData); // 创建一个工作表 let new_workbook = XLSX.utils.book_new(); // 将数组数据写入工作表 XLSX.utils.book_append_sheet(new_workbook, WorkSheet, '第一页'); // 将工作簿写入文件 XLSX.writeFile(new_workbook, fileName); console.log('写入Excel文件成功!'); } // 读取excel文件 const readExcel = () => { // 读取Excel文件 const workbook = XLSX.readFile(fileName + '.xlsx'); // 获取工作表的名字 const sheetNames = workbook.SheetNames; // 获取第一个工作表 const sheet = workbook.Sheets[sheetNames[0]]; // 将工作表转换为JSON对象 const data = XLSX.utils.sheet_to_json(sheet); // 读取到的数据 return data } // 读取excel文件 const excelData = readExcel(); // 转换excel的数据格式 const oldTimeList = excelData.map(item => { return item['采样时间'] }) // 生成的日期时间字符串数组 const generateYearlyHourlyDates = (startTime,endDate) => { // 创建一个空数组来存储日期时间字符串 let dateTimeArray = []; // 设定开始日期和结束日期 let startDate = new Date(startTime); let endDate = new Date(endDate); // 循环生成每小时的日期时间字符串 for (let currentDate = new Date(startDate); currentDate <= endDate; currentDate.setHours(currentDate.getHours() + 1)) { // 格式化日期时间字符串为"YYYY-MM-DD hh:00" let year = currentDate.getFullYear(); let month = String(currentDate.getMonth() + 1).padStart(2, '0'); let day = String(currentDate.getDate()).padStart(2, '0'); let hour = String(currentDate.getHours()).padStart(2, '0'); let formattedDateTime = `${year}-${month}-${day} ${hour}:00`; dateTimeArray.push(formattedDateTime); } // 生成的日期时间字符串数组 return dateTimeArray; } const yearlyHourlyDates = generateYearlyHourlyDates('2024-06-01T00:00:00','2024-06-30T23:00:00'); // 获取日期数组 // 找到每天缺少的小时数和具体的时间 const findMissingHours = (…

监控直播流实现
type: item
date: 2023-12-12T16:30:00+08:00
description: 直播流播放实现技术栈：后端：node + ffmpeg + WebSocket前端：vue + fiv.js实现逻辑：后端启动WebSocket服务，根据ffmpeg工具实现实时转流，对实时流进行格式转换输出flv格式，前端使用flv.js插件播放flv格式的视频一、后端安装依赖包，fluent-ffmpeg是基于ffmpeg工具命令的依赖npm install fluent-ffmpeg ws websocket-stream --save安装ffmpeg二进制，不安装服务端会出现报错：An error occurred: Cannot find ffmpegnpm i @ffmpeg-installer/ffmpeg完整代码const WebSocket = require('ws') const webSocketStream = require('websocket-stream/stream') const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path const ffmpeg = require('fluent-ffmpeg') ffmpeg.setFfmpegPath(ffmpegPath) // 建立WebSocket服务 const wss = new WebSocket.Server({ port: 8881, perMessageDeflate: false }) // 监听连接 wss.on('connection', handleConnection) // 连接时触发事件 function handleConnection(ws, req) { // 获取前端请求的流地址（前端websocket连接时后面带上流地址） const url = req.url.slice(1) // 传入连接的ws客户端 实例化一个流 const stream = webSocketStream(ws, { binary: true }) // 通过ffmpeg命令 对实时流进行格式转换 输出flv格式 const ffmpegCommand = ffmpeg(url) .addInputOption('-analyzeduration', '100000', '-max_delay', '1000000') .on('start', function () { console.log('Stream started.') }) .on('codecData', function () { console.log('Stream codecData.') }) .on('error', function (err) { console.log('An error occured: ', err.message) stream.end() }) .on('end', function () { console.log('Stream end!') stream.end() }) .outputFormat('flv').videoCodec('copy') // .outputFormat('flv').videoCodec('copy').noAudio() // 取消音频输出 stream.on('close', function () { ffmpegCommand.kill('SIGKILL') }) try { // 执行命令 传输到实例流中返回给客户端 ffmpegCommand.pipe(stream) } catch (error) { console.log(error) } }二、前端安装依赖包npm install flv.js --save完整代码 播放 三、测试url公网 rtmp 测试地址：邓紫棋 多美丽mv (非常流畅秒开级别，强烈推荐) 地址：rtmp://liteavapp.qcloud.com/live/liteavdemoplayerstreamid (可用) 伊拉克 Al Sharqiya 电视台 (有卡顿延迟) 地址：rtmp://ns8.indexforce.com/home/mystream (可用) 韩国GOOD TV (不推荐太卡，很慢) 地址：rtmp://mobliestream.c3tv.com:554/live/goodtv.sdp (可用)公网 m3u8 测试地址：http格式地址计时器 m3u8 (可用) http://devimages.apple.com/iphone/samples/bipbop/gear1/prog_index.m3u8 计时器 m3u8 (可用) http://devima…

nodejs搭建服务器+连接数据库+接口开发
type: item
date: 2023-12-02T14:15:00+08:00
description: 1. 创建项目文件，然后执行初始化命令（会生成package.json项目描述文件，记录项目的信息，安装依赖包信息、相关命令等）node init -y2. 安装需要的依赖包/* * express: 快速搭建服务器 对于nodejs中http模块的进一步封装 * nodemon: 热重载 * cors: 处理跨域 * @hapi/joi: 请求数据进行验证 * @escook/express-joi: 表单数据进行验证 * express-jwt：解析 Token * mysql：数据库 * bcryptjs：非对称加密 * jsonwebtoken：生成 Token 字符串 */ npm add express nodemon cors @hapi/joi@17.1.0 @escook/express-joi express-jwt jsonwebtoken mysql3. 项目结构│ config.js │ index.js │ package-lock.json │ package.json │ ├─db │ index.js │ ├─routerHandle │ user.js │ userinfo.js │ ├─routers │ user.js │ userinfo.js │ └─schema user.js4. 执行文件 index.js// 导入模块 const express = require('express'); const cors = require('cors'); // 导入定义验证规则的包 const joi = require('joi'); const app = express(); app.use(cors()); app.use(express.json()) // 解析表单中json数据，不配置的情况下，req.body默认为undefined app.use(express.urlencoded({ extended: false })) // 解析表单中url-encoded数据，不配置的情况下，req.body默认为 {} // 失败响应方法中间件 app.use((req, res, next) => { res.cc = (status, err) => { res.send({ status, message: err instanceof Error ? err.message : err }) } next() }) // 路由之前声明中间件 // token 解析中间件 const expressJWT = require('express-jwt') const config = require('./config') app.use(expressJWT({ secret: config.jwtSecretKey }).unless({ path: [/^\/api/] })) // 导入用户登录注册路由模块 const userRouter = require('./routers/user') app.use('/api', userRouter) // 导入用户信息路由模块 const userInfoRouter = require('./routers/userinfo') app.use('/my', userInfoRouter) // 错误级别中间件，需放在所有路由最后 app.use((err, req, res, next) => { // 表单验证失败 if (err instanceof joi.ValidationError) return res.cc(400, err) // 身份认证失败 if (err.name === 'UnauthorizedError') return res.cc(401, '身份认证失败') res.cc(400, err) }) // 启动服务 app.listen(8081, () => { console.log('Server is running..., http://127.0.0.1'); })5. 数据库文件 db/index.jsconst mysql = require('mysql') const db = mysql.createPool({ host: '127.0.0.1', user: 'root', password: '123456', database: 'text' }) module.exports = db6. 登录注册路由模块 routers/user// 导入模块 const express = require('express'); const rou…

txt文件报文解析写入excel
type: item
date: 2023-12-01T10:50:00+08:00
description: 实现思路：文件夹（datalog）下面有多个文件夹（20231123），下面是log.txt，循环遍历获取到log.txt，用split切割为数组，然后使用正则匹配代码对应的参数值，存放在数组中，最后写入excel│ TxtToExcel.js │ ├─dataLog │ │ HJ 212-2017 污染物在线监控（监测）系统数据传输标准.pdf │ │ │ ├─20231128 │ │ log.txt │ │ │ └─20231129 │ log.txt │代码：// 引入模块 const fs = require('fs'); const path = require('path'); const XLSX = require('xlsx') // 正则匹配 let typeHregex = /CN=(2061);/; // 小时数据 let type5Mregex = /CN=(2051);/; // 5分钟数据 let type1Mregex = /CN=(2011);/; // 1分钟数据 let Tregex = /DataTime=(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/; // 时间 let MN = /MN=(.*?);/; // 设备MN号 let PM10regex = /a34002-Avg=(.*?),/; let PM10regex1 = /a34002-Rtd=(.*?),/; let PM25regex = /a34004-Avg=(.*?),/; let PM25regex1 = /a34004-Rtd=(.*?),/; let Tspregex = /a34001-Avg=(.*?),/; // 悬浮颗粒物 let Wdregex = /a01001-Avg=(.*?),/; // 温度 let Sdregex = /a01002-Avg=(.*?),/; // 湿度 let Fsregex = /a01007-Avg=(.*?),/; // 风速 let Fxregex = /a01008-Avg=(.*?),/; // 风向 let Qyregex = /a01006-Avg=(.*?),/; // 气压 let SO2regex = /a21026-Avg=(.*?),/; // 二氧化硫 let SO2regex1 = /a21026-Rtd=(.*?),/; // 二氧化硫 let Coregex = /a21005-Avg=(.*?),/; // 一氧化碳 let Coregex1 = /a21005-Rtd=(.*?),/; // 一氧化碳 let NO2regex = /a21004-Avg=(.*?),/; // 二氧化氮 let NO2regex1 = /a21004-Rtd=(.*?),/; // 二氧化氮 let O3regex = /a05024-Avg=(.*?),/; // 臭氧 let O3regex1 = /o31104-Avg=(.*?),/; // 臭氧 let O3regex2 = /a05024-Rtd=(.*?),/; // 臭氧 let O3regex3 = /ac0001-Rtd=(.*?),/; // 臭氧 // 将JSON对象写入Excel function writeArrayToExcel(arrayData, fileName) { // 创建一个工作簿 let WorkSheet = XLSX.utils.aoa_to_sheet(arrayData); // 创建一个工作表 let new_workbook = XLSX.utils.book_new(); // 将数组数据写入工作表 XLSX.utils.book_append_sheet(new_workbook, WorkSheet, '第一页'); // 将工作簿写入文件 try { XLSX.writeFile(new_workbook, fileName) console.log('写入文件成功!') } catch (err) { console.log('写入文件失败!' + err) } } // 解析结果表头 let fileDataList = [[ '采样时间', '设备mn号', 'PM10', 'PM25', '二氧化硫SO2', '一氧化碳CO', '二氧化氮NO2', '臭氧O3', '悬浮颗粒物', '温度', '湿度', '风速', '风向', '气压', ]] // 正则解析 const jxFun = (item, typeRegex, mnValue) => { let result = Tre…

nvm node版本控制工具
type: item
date: 2022-07-13T11:40:00+08:00
description: Windows安装nvm：node版本控制工具1. nvm下载地址：https://github.com/coreybutler/nvm-windows/releases下载压缩包：nvm-setup.zip：安装版，推荐使用，下一步式傻瓜安装（注意：安装路径最好不要出现中文和空格，如果电脑上之前已经单独安装了node，先卸载）2. 安装nvm的路径自己可以更改安装路径，建议更改路径，不要选择Program Files或 Program Files (x86)，因为为它们命名有空格和()符号,这会导致node安装失败第一个选择的路径为nvm安装路径，第二个为nodejs安装路径最好将nvm安装在根目录下（D:\nvm D:\nvm\nodejs）安装完毕后输入 nvm -v 查看版本3. 使用，常用命令列出所有已经安装的Node版本nvm ls列出所有可以安装的Node版本号nvm list available安装指定版本号的Nodenvm install 14.20.0使用特定版本的Nodenvm use 14.20.0卸载版本号Nodenvm uninstall 14.20.04. 问题一. nvm切换node版本出现乱码用管理员运行控制台1不行的话直接卸载重新安装二. nvm命令正常，node -v 和npm -v 提示不是内部命令用管理员运行控制台正常的路径是 nvm下面有指定的node版本目录。node版本目录下会有npm。如果发现node包里没有下面的这些文件，就说明npm没有安装成功需要去官网查找对应版本，下载完整的包，把下载后的包，全部替换到D:\soft\nvm\v14.20.0 下面 即可node所有版本下载地址:https://nodejs.org/en/download/releases/三. 终端里运行nvm、node和npm命令正常，vscode报错重启vscode5. yarn切换node版本，执行安装命令即可nvm user 14.20.0 npm install -g yarn yarn -v

node.js文件读取与写入
type: item
date: 2022-03-15T11:31:00+08:00
description: node.js文件操作node.js文件读取// 导入fs文件系统模块 const fs = require('fs'); // 调用 fs.readFile() 方法，读取文件内容 fs.readFile('./files/1.txt', 'utf8', function(err, dataStr) { // 判断文件是否读取成功 if (err) { return console.log('读取文件失败', err.message); } console.log('读取文件成功', dataStr); })node.js文件写入// 导入fs文件系统模块 const fs = require('fs'); // 调用 fs.writeFile() 方法，写入文件内容 /* 参数1：表示文件的存放路径 参数2：表示要写入的内容 参数3：读取文件的时候采用的编码格式一般默认为utf8 参数4：回调函数 */ fs.writeFile('./files/2.txt', 'Hello World Node.js', 'utf8', function(err) { // 如果文件写入成功，则err的值为null // 如果文件写入失败，则err的值是一个错误对象 if (err) { return console.log('文件写入失败', err.message); } console.log('文件写入成功'); })