【node.js】核心进阶
个人主页:Guiat
归属专栏:node.js
文章目录
- 1. Node.js高级异步编程
- 1.1 Promise深入理解
- 1.1.1 创建和使用Promise
- 1.1.2 Promise组合模式
- 1.2 Async/Await高级模式
- 1.2.1 基本使用
- 1.2.2 并行执行
- 1.2.3 顺序执行与错误处理
- 1.3 事件循环高级概念
- 1.3.1 事件循环阶段详解
- 1.3.2 微任务与宏任务
- 1.3.3 控制事件循环
- 2. Node.js流和缓冲区高级应用
- 2.1 高级流操作
- 2.1.1 自定义流实现
- 2.1.2 流的背压处理
- 2.1.3 对象模式流
- 2.2 Buffer高级操作
- 2.2.1 Buffer创建和操作
- 2.2.2 Buffer池和性能优化
- 2.2.3 二进制数据处理
- 3. Node.js性能优化与调试
- 3.1 性能分析工具
- 3.1.1 内置性能分析
- 3.1.2 使用Node.js内置性能钩子
- 3.1.3 使用Clinic.js进行性能诊断
- 3.2 内存泄漏检测与修复
- 3.2.1 常见内存泄漏场景
- 3.2.2 使用堆快照分析内存
- 3.3 CPU性能优化
- 3.3.1 避免阻塞主线程
- 3.3.2 代码优化技巧
- 3.3.3 使用V8优化提示
- 3.4 调试技巧与工具
- 3.4.1 使用内置调试器
- 3.4.2 高级日志技巧
- 3.4.3 使用APM工具
- 4. Node.js安全最佳实践
- 4.1 常见安全威胁与防护
- 4.1.1 输入验证与消毒
- 4.1.2 防止跨站脚本(XSS)攻击
- 4.1.3 防止跨站请求伪造(CSRF)
- 4.2 认证与授权
- 4.2.1 安全的密码处理
- 4.2.2 JWT认证实现
- 4.2.3 OAuth 2.0集成
正文
1. Node.js高级异步编程
1.1 Promise深入理解
Promise是JavaScript中处理异步操作的标准方式,Node.js广泛使用Promise来管理异步流程。
1.1.1 创建和使用Promise
// 创建一个Promise
function readFilePromise(filePath) {return new Promise((resolve, reject) => {const fs = require('fs');fs.readFile(filePath, 'utf8', (err, data) => {if (err) {reject(err); // 失败时调用} else {resolve(data); // 成功时调用}});});
}// 使用Promise
readFilePromise('config.json').then(data => {console.log('文件内容:', data);return JSON.parse(data); // 返回新的Promise}).then(config => {console.log('解析后的配置:', config);return config.databaseUrl; // 链式处理}).catch(err => {console.error('处理过程中出错:', err);}).finally(() => {console.log('无论成功或失败都会执行');});
1.1.2 Promise组合模式
// 并行执行多个Promise
const promises = [fetch('https://api.example.com/users'),fetch('https://api.example.com/posts'),fetch('https://api.example.com/comments')
];Promise.all(promises).then(responses => Promise.all(responses.map(res => res.json()))).then(data => {const [users, posts, comments] = data;console.log('所有数据加载完成');console.log(`用户数: ${users.length}`);console.log(`文章数: ${posts.length}`);console.log(`评论数: ${comments.length}`);}).catch(error => {console.error('至少一个请求失败:', error);});// 获取最先完成的Promise
Promise.race([fetchWithTimeout('https://api1.example.com/data', 2000),fetchWithTimeout('https://api2.example.com/data', 2000),fetchWithTimeout('https://api3.example.com/data', 2000)
]).then(result => {console.log('最快的API返回结果:', result);}).catch(error => {console.error('所有API请求失败:', error);});// 带超时的fetch
function fetchWithTimeout(url, timeout) {return Promise.race([fetch(url).then(response => response.json()),new Promise((_, reject) => setTimeout(() => reject(new Error('请求超时')), timeout))]);
}// Promise.allSettled - 等待所有Promise完成,无论成功或失败
Promise.allSettled([Promise.resolve(1),Promise.reject(new Error('失败')),Promise.resolve(3)
]).then(results => {results.forEach((result, index) => {if (result.status === 'fulfilled') {console.log(`Promise ${index} 成功:`, result.value);} else {console.log(`Promise ${index} 失败:`, result.reason);}});});
1.2 Async/Await高级模式
Async/Await是基于Promise的语法糖,使异步代码更易读和维护。
1.2.1 基本使用
const fs = require('fs').promises;async function readConfigFile() {try {// 等待异步操作完成const data = await fs.readFile('config.json', 'utf8');const config = JSON.parse(data);return config;} catch (error) {console.error('读取配置文件失败:', error);throw error; // 重新抛出错误}
}// 调用异步函数
readConfigFile().then(config => {console.log('配置加载成功:', config);}).catch(err => {console.error('配置加载失败:', err);});
1.2.2 并行执行
const fs = require('fs').promises;async function loadMultipleFiles() {try {// 并行读取多个文件const [config, users, logs] = await Promise.all([fs.readFile('config.json', 'utf8'),fs.readFile('users.json', 'utf8'),fs.readFile('logs.txt', 'utf8')]);return {config: JSON.parse(config),users: JSON.parse(users),logs: logs.split('\n')};} catch (error) {console.error('加载文件失败:', error);throw error;}
}
1.2.3 顺序执行与错误处理
async function processUserData(userIds) {const results = [];// 顺序处理每个用户for (const id of userIds) {try {const userData = await fetchUserData(id);const processedData = await processData(userData);results.push({id,data: processedData,status: 'success'});} catch (error) {console.error(`处理用户 ${id} 失败:`, error);results.push({id,error: error.message,status: 'error'});// 继续处理下一个用户}}return results;
}// 自定义异步迭代器
async function* asyncGenerator() {let i = 0;while (i < 5) {await new Promise(resolve => setTimeout(resolve, 1000));yield i++;}
}// 使用异步迭代器
async function useAsyncGenerator() {for await (const num of asyncGenerator()) {console.log(`生成数字: ${num}`);}
}
1.3 事件循环高级概念
深入理解Node.js事件循环对于编写高性能应用至关重要。
1.3.1 事件循环阶段详解
const fs = require('fs');// 展示事件循环各阶段执行顺序
console.log('1. 脚本开始');// 定时器阶段
setTimeout(() => {console.log('2. setTimeout 回调');// 嵌套的定时器setTimeout(() => {console.log('6. 嵌套的 setTimeout');}, 0);// 立即执行setImmediate(() => {console.log('7. 内部 setImmediate');});// 添加到下一个事件循环的微任务队列Promise.resolve().then(() => {console.log('8. 内部 Promise.then');});
}, 0);// 检查阶段
setImmediate(() => {console.log('4. setImmediate 回调');
});// I/O回调阶段
fs.readFile(__filename, () => {console.log('5. I/O 回调');// 检查阶段setImmediate(() => {console.log('9. I/O内部 setImmediate');});// 定时器阶段setTimeout(() => {console.log('10. I/O内部 setTimeout');}, 0);
});// 微任务(在每个阶段之间执行)
Promise.resolve().then(() => {console.log('3. Promise.then 微任务');
});console.log('0. 脚本结束');// 输出顺序可能是:
// 1. 脚本开始
// 0. 脚本结束
// 3. Promise.then 微任务
// 2. setTimeout 回调
// 8. 内部 Promise.then
// 4. setImmediate 回调
// 5. I/O 回调
// 9. I/O内部 setImmediate
// 6. 嵌套的 setTimeout
// 7. 内部 setImmediate
// 10. I/O内部 setTimeout
1.3.2 微任务与宏任务
console.log('1. 开始');// 宏任务
setTimeout(() => {console.log('2. setTimeout 宏任务');// 微任务Promise.resolve().then(() => {console.log('3. setTimeout内的微任务');});// 微任务process.nextTick(() => {console.log('4. setTimeout内的nextTick');});
}, 0);// 微任务
Promise.resolve().then(() => {console.log('5. Promise.then 微任务');// 微任务process.nextTick(() => {console.log('6. Promise内的nextTick');});
});// 微任务(优先级高于Promise)
process.nextTick(() => {console.log('7. process.nextTick 微任务');
});console.log('8. 结束');// 输出顺序:
// 1. 开始
// 8. 结束
// 7. process.nextTick 微任务
// 5. Promise.then 微任务
// 6. Promise内的nextTick
// 2. setTimeout 宏任务
// 4. setTimeout内的nextTick
// 3. setTimeout内的微任务
1.3.3 控制事件循环
// 使用setImmediate延迟执行
function delayOperation() {// 将耗时操作放入检查阶段setImmediate(() => {// 执行耗时操作const result = performHeavyCalculation();console.log('计算结果:', result);});
}// 使用process.nextTick优先执行
function priorityOperation(callback) {process.nextTick(() => {const result = performQuickOperation();callback(result);});
}// 使用queueMicrotask添加微任务
function scheduleMicrotask() {queueMicrotask(() => {console.log('这是一个微任务');});
}
2. Node.js流和缓冲区高级应用
2.1 高级流操作
2.1.1 自定义流实现
const { Readable, Writable, Transform, Duplex } = require('stream');// 自定义可读流
class CounterStream extends Readable {constructor(max) {super();this.max = max;this.counter = 0;}_read() {this.counter++;if (this.counter <= this.max) {// 推送数据到流const data = `计数: ${this.counter}\n`;this.push(data);} else {// 结束流this.push(null);}}
}// 自定义可写流
class LoggerStream extends Writable {constructor(options) {super(options);this.count = 0;}_write(chunk, encoding, callback) {this.count++;console.log(`[写入 #${this.count}]: ${chunk.toString().trim()}`);// 表示写入完成callback();}
}// 自定义转换流
class UppercaseTransform extends Transform {_transform(chunk, encoding, callback) {// 转换数据const upperChunk = chunk.toString().toUpperCase();// 推送转换后的数据this.push(upperChunk);// 表示转换完成callback();}
}// 使用自定义流
const counter = new CounterStream(5);
const logger = new LoggerStream();
const upperCase = new UppercaseTransform();counter.pipe(upperCase) // 转换为大写.pipe(logger); // 输出结果
2.1.2 流的背压处理
const fs = require('fs');
const { pipeline } = require('stream');// 创建大文件读取流
const readStream = fs.createReadStream('large-file.txt', {highWaterMark: 64 * 1024 // 64KB缓冲区
});// 创建写入流,较小的缓冲区
const writeStream = fs.createWriteStream('output-file.txt', {highWaterMark: 16 * 1024 // 16KB缓冲区
});// 监控背压情况
readStream.on('data', (chunk) => {// 尝试写入数据const canContinue = writeStream.write(chunk);if (!canContinue) {console.log('背压发生,暂停读取');// 暂停读取流,等待写入流排空readStream.pause();}
});// 当写入流准备接收更多数据时
writeStream.on('drain', () => {console.log('写入流已排空,继续读取');// 恢复读取流readStream.resume();
});// 使用pipeline API处理错误和清理
pipeline(fs.createReadStream('source.txt'),new UppercaseTransform(),fs.createWriteStream('destination.txt'),(err) => {if (err) {console.error('Pipeline失败', err);} else {console.log('Pipeline成功完成');}}
);
2.1.3 对象模式流
const { Transform } = require('stream');// 创建对象模式转换流
class ObjectTransformer extends Transform {constructor(options) {// 启用对象模式super({ objectMode: true, ...options });}_transform(chunk, encoding, callback) {// 处理对象数据if (typeof chunk === 'object') {// 转换对象const transformedObject = {...chunk,timestamp: Date.now(),transformed: true};// 推送转换后的对象this.push(transformedObject);}callback();}
}// 使用对象模式流
const objectStream = new ObjectTransformer();objectStream.on('data', (data) => {console.log('转换后的对象:', data);
});// 写入对象到流
objectStream.write({ name: 'John', age: 30 });
objectStream.write({ name: 'Alice', age: 25 });
objectStream.end();
2.2 Buffer高级操作
Buffer是Node.js中处理二进制数据的核心类。
2.2.1 Buffer创建和操作
// 创建Buffer的不同方式
const buffer1 = Buffer.alloc(10); // 创建10字节的空Buffer
const buffer2 = Buffer.from('Hello, Node.js', 'utf8'); // 从字符串创建
const buffer3 = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // 从字节数组创建// Buffer操作
console.log(buffer2.toString()); // 转换为字符串
console.log(buffer2.toString('hex')); // 转换为十六进制字符串
console.log(buffer2.length); // Buffer长度// 写入Buffer
buffer1.write('Hi', 0, 2);
console.log(buffer1.toString()); // 'Hi' + 8个空字节// 复制Buffer
const bufferSource = Buffer.from('ABC');
const bufferTarget = Buffer.alloc(3);
bufferSource.copy(bufferTarget);
console.log(bufferTarget.toString()); // 'ABC'// 切片Buffer (共享内存)
const originalBuffer = Buffer.from('Hello, World!');
const slicedBuffer = originalBuffer.slice(0, 5);
slicedBuffer[0] = 0x4A; // 修改为'J'
console.log(slicedBuffer.toString()); // 'Jello'
console.log(originalBuffer.toString()); // 'Jello, World!'
2.2.2 Buffer池和性能优化
// 使用Buffer池
const pooledBuffers = [];function createPooledBuffer(size) {// 对于小Buffer使用Buffer.allocUnsafe(),它可能使用池if (size < 4096) {return Buffer.allocUnsafe(size);} else {// 大Buffer直接分配return Buffer.alloc(size);}
}// 创建多个小Buffer
for (let i = 0; i < 1000; i++) {const buf = createPooledBuffer(100);buf.fill(0); // 确保没有敏感数据pooledBuffers.push(buf);
}// 内存使用分析
const memoryUsage = process.memoryUsage();
console.log(`堆总大小: ${memoryUsage.heapTotal / 1024 / 1024} MB`);
console.log(`堆已使用: ${memoryUsage.heapUsed / 1024 / 1024} MB`);
console.log(`外部内存: ${memoryUsage.external / 1024 / 1024} MB`);
2.2.3 二进制数据处理
// 处理二进制协议
function parseBinaryProtocol(buffer) {let offset = 0;// 读取头部(4字节)const header = buffer.readUInt32BE(offset);offset += 4;// 读取消息类型(1字节)const messageType = buffer.readUInt8(offset);offset += 1;// 读取数据长度(2字节)const dataLength = buffer.readUInt16BE(offset);offset += 2;// 读取数据const data = buffer.slice(offset, offset + dataLength);offset += dataLength;// 读取校验和(4字节)const checksum = buffer.readUInt32BE(offset);return {header,messageType,dataLength,data,checksum};
}// 创建二进制消息
function createBinaryMessage(messageType, data) {const dataBuffer = Buffer.from(data);const dataLength = dataBuffer.length;// 计算总长度: 头部(4) + 消息类型(1) + 数据长度(2) + 数据 + 校验和(4)const totalLength = 4 + 1 + 2 + dataLength + 4;// 创建消息bufferconst message = Buffer.alloc(totalLength);let offset = 0;// 写入头部(固定值)message.writeUInt32BE(0x01020304, offset);offset += 4;// 写入消息类型message.writeUInt8(messageType, offset);offset += 1;// 写入数据长度message.writeUInt16BE(dataLength, offset);offset += 2;// 写入数据dataBuffer.copy(message, offset);offset += dataLength;// 简单校验和(实际应用中应使用更复杂的算法)const checksum = calculateChecksum(message, 0, offset);message.writeUInt32BE(checksum, offset);return message;
}// 简单校验和计算
function calculateChecksum(buffer, start, end) {let sum = 0;for (let i = start; i < end; i++) {sum += buffer[i];}return sum & 0xFFFFFFFF; // 保持为32位
}
3. Node.js性能优化与调试
3.1 性能分析工具
3.1.1 内置性能分析
// 使用console.time测量执行时间
console.time('操作');
// 执行耗时操作
for (let i = 0; i < 1000000; i++) {// 一些计算
}
console.timeEnd('操作'); // 输出: 操作: 10.123ms// 使用process.hrtime进行高精度计时
const start = process.hrtime();
// 执行操作
const diff = process.hrtime(start);
console.log(`执行时间: ${diff[0] * 1e9 + diff[1]} 纳秒`);// 使用process.memoryUsage监控内存
function monitorMemory() {const memUsage = process.memoryUsage();console.log('内存使用:');console.log(` RSS: ${Math.round(memUsage.rss / 1024 / 1024)} MB`);console.log(` 堆总大小: ${Math.round(memUsage.heapTotal / 1024 / 1024)} MB`);console.log(` 堆已用: ${Math.round(memUsage.heapUsed / 1024 / 1024)} MB`);console.log(` 外部: ${Math.round(memUsage.external / 1024 / 1024)} MB`);
}// 定期检查内存使用
const memoryInterval = setInterval(monitorMemory, 5000);
3.1.2 使用Node.js内置性能钩子
const { PerformanceObserver, performance } = require('perf_hooks');// 创建性能观察器
const obs = new PerformanceObserver((items) => {const entries = items.getEntries();entries.forEach((entry) => {console.log(`${entry.name}: ${entry.duration}ms`);});
});// 订阅所有性能事件
obs.observe({ entryTypes: ['measure', 'function'] });// 标记开始
performance.mark('操作-开始');// 执行一些操作
someFunction();// 标记结束
performance.mark('操作-结束');// 测量两个标记之间的持续时间
performance.measure('操作持续时间', '操作-开始', '操作-结束');// 使用timerify包装函数进行性能测量
const timerify = performance.timerify;function someFunction() {// 耗时操作const arr = new Array(1000000);for (let i = 0; i < 1000000; i++) {arr[i] = Math.random();}arr.sort();
}// 包装函数
const timerifiedFunction = timerify(someFunction);// 调用函数并测量性能
timerifiedFunction();
3.1.3 使用Clinic.js进行性能诊断
# 安装Clinic.js
npm install -g clinic# 使用Doctor分析整体性能
clinic doctor -- node app.js# 使用Bubbleprof分析异步操作
clinic bubbleprof -- node app.js# 使用Flame生成火焰图
clinic flame -- node app.js
3.2 内存泄漏检测与修复
3.2.1 常见内存泄漏场景
// 1. 闭包导致的内存泄漏
function createLeak() {const largeData = new Array(1000000).fill('x');return function leakingFunction() {// 引用了外部的largeData,导致无法垃圾回收console.log(largeData.length);};
}// 修复方法: 不保留对大数据的引用
function createFixed() {const largeData = new Array(1000000).fill('x');const length = largeData.length;return function fixedFunction() {// 只保留需要的数据console.log(length);};
}// 2. 事件监听器未移除
function setupEventHandlers() {const element = { addEventListener: (event, handler) => {this.handler = handler;}};element.addEventListener('data', function dataHandler() {console.log('Data received');});// 未移除事件处理器,可能导致泄漏return element;
}// 修复方法: 保存引用并移除
function setupFixedEventHandlers() {const element = { addEventListener: (event, handler) => {this.handler = handler;},removeEventListener: () => {this.handler = null;}};const handler = function dataHandler() {console.log('Data received');};element.addEventListener('data', handler);// 提供移除方法return {element,cleanup: () => element.removeEventListener('data', handler)};
}// 3. 缓存未限制大小
const cache = {};function addToCache(key, value) {// 无限制添加到缓存,可能导致内存泄漏cache[key] = value;
}// 修复方法: 限制缓存大小
const LRUCache = require('lru-cache');
const limitedCache = new LRUCache({max: 500, // 最多存储项目数maxAge: 1000 * 60 * 60 // 项目最长存活时间(1小时)
});function addToLimitedCache(key, value) {limitedCache.set(key, value);
}
3.2.2 使用堆快照分析内存
// 生成堆快照
const heapdump = require('heapdump');// 生成堆快照文件
function generateHeapSnapshot() {const filename = `${Date.now()}.heapsnapshot`;heapdump.writeSnapshot(filename, (err) => {if (err) console.error('堆快照生成失败:', err);else console.log(`堆快照已保存到 ${filename}`);});
}// 在特定条件下生成堆快照
let memoryThreshold = 500; // MBfunction checkMemoryUsage() {const memUsage = process.memoryUsage();const usedMemoryMB = memUsage.heapUsed / 1024 / 1024;if (usedMemoryMB > memoryThreshold) {console.log(`内存使用超过阈值: ${usedMemoryMB.toFixed(2)} MB`);generateHeapSnapshot();memoryThreshold += 100; // 提高阈值,避免生成太多快照}
}// 定期检查内存使用
setInterval(checkMemoryUsage, 30000);
3.3 CPU性能优化
3.3.1 避免阻塞主线程
// 不良实践: 阻塞主线程
function blockingOperation() {// 耗时计算let sum = 0;for (let i = 0; i < 1e9; i++) {sum += i;}return sum;
}// 良好实践: 使用setImmediate拆分任务
function nonBlockingOperation(callback) {let sum = 0;let i = 0;const iterations = 1e9;const chunk = 1e6;function processChunk() {// 处理一小块数据const end = Math.min(i + chunk, iterations);for (; i < end; i++) {sum += i;}// 检查是否完成if (i < iterations) {setImmediate(processChunk);} else {callback(sum);}}// 开始处理processChunk();
}// 使用Worker Threads处理CPU密集型任务
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');// 使用Worker Threads处理CPU密集型任务
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');if (isMainThread) {// 主线程代码function runWorker(data) {return new Promise((resolve, reject) => {// 创建新的工作线程const worker = new Worker(__filename, {workerData: data});// 接收工作线程的消息worker.on('message', resolve);worker.on('error', reject);worker.on('exit', (code) => {if (code !== 0) {reject(new Error(`Worker stopped with exit code ${code}`));}});});}// 使用工作线程处理任务async function main() {try {const result = await runWorker({ numbers: Array.from({length: 10000000}, (_, i) => i) });console.log('计算结果:', result);} catch (err) {console.error('工作线程错误:', err);}}main();
} else {// 工作线程代码const { numbers } = workerData;// 执行CPU密集型计算const sum = numbers.reduce((acc, val) => acc + val, 0);// 将结果发送回主线程parentPort.postMessage(sum);
}
3.3.2 代码优化技巧
// 1. 避免频繁创建对象
// 不良实践
function createObjects() {const results = [];for (let i = 0; i < 1000000; i++) {// 每次迭代创建新对象results.push({ index: i, value: i * 2 });}return results;
}// 良好实践: 对象复用
function reuseObjects() {const results = [];const obj = {};for (let i = 0; i < 1000000; i++) {// 重用同一个对象obj.index = i;obj.value = i * 2;// 存储对象的拷贝results.push({...obj});}return results;
}// 2. 使用适当的数据结构
// 不良实践: 数组查找
function findInArray(id) {const items = Array.from({length: 10000}, (_, i) => ({ id: `id-${i}`, value: i }));// O(n)复杂度的查找return items.find(item => item.id === id);
}// 良好实践: 使用Map
function findInMap(id) {const items = new Map();// 填充Mapfor (let i = 0; i < 10000; i++) {items.set(`id-${i}`, { id: `id-${i}`, value: i });}// O(1)复杂度的查找return items.get(id);
}// 3. 避免深层嵌套循环
// 不良实践: 嵌套循环
function nestedLoops(matrix) {let sum = 0;for (let i = 0; i < matrix.length; i++) {for (let j = 0; j < matrix[i].length; j++) {for (let k = 0; k < matrix[i][j].length; k++) {sum += matrix[i][j][k];}}}return sum;
}// 良好实践: 使用flat和reduce
function flattenAndReduce(matrix) {// 扁平化多维数组并求和return matrix.flat(2).reduce((sum, val) => sum + val, 0);
}
3.3.3 使用V8优化提示
// 1. 使用类型一致的数组
// 不良实践: 混合类型数组
const mixedArray = [1, 'string', true, {}, 3.14];// 良好实践: 类型一致的数组
const numbersArray = new Float64Array(1000);
for (let i = 0; i < 1000; i++) {numbersArray[i] = i * 1.1;
}// 2. 避免动态修改对象结构
// 不良实践: 动态添加属性
function createDynamicObject() {const obj = {};obj.name = 'John';obj.age = 30;// 后续添加属性会导致隐藏类变化obj.address = 'Some Street';return obj;
}// 良好实践: 一次性创建完整对象
function createCompleteObject() {// 一次性定义所有属性return {name: 'John',age: 30,address: 'Some Street'};
}// 3. 使用函数内联
// V8可以自动内联小函数,保持函数简洁
function add(a, b) {return a + b;
}function calculate() {let sum = 0;for (let i = 0; i < 1000; i++) {// V8可能会内联这个函数调用sum = add(sum, i);}return sum;
}
3.4 调试技巧与工具
3.4.1 使用内置调试器
// 启动调试器: node --inspect app.js
// 或在代码中添加调试器断点
function debugMe() {let a = 1;let b = 2;debugger; // 代码会在这里暂停执行let c = a + b;return c;
}debugMe();
3.4.2 高级日志技巧
// 创建结构化日志
const util = require('util');// 自定义日志格式化
function structuredLog(level, message, context = {}) {const timestamp = new Date().toISOString();const logEntry = {timestamp,level,message,...context};// 格式化输出console.log(JSON.stringify(logEntry));
}// 使用不同日志级别
function logger() {return {debug: (message, context) => structuredLog('DEBUG', message, context),info: (message, context) => structuredLog('INFO', message, context),warn: (message, context) => structuredLog('WARN', message, context),error: (message, context) => structuredLog('ERROR', message, context)};
}const log = logger();
log.info('服务器启动', { port: 3000 });
log.debug('数据库查询', { query: 'SELECT * FROM users', duration: 15 });
log.error('请求失败', { url: '/api/users', statusCode: 500, error: 'Database connection failed' });// 使用util.inspect进行深度对象检查
function inspectObject(obj) {return util.inspect(obj, {showHidden: true,depth: null,colors: true});
}const complexObject = {user: {name: 'John',profile: {address: {street: '123 Main St',city: 'Anytown'}}},permissions: new Set(['read', 'write']),token: Buffer.from('secret-token')
};console.log(inspectObject(complexObject));
3.4.3 使用APM工具
// 使用Elastic APM监控应用
const apm = require('elastic-apm-node').start({serviceName: 'my-nodejs-app',serverUrl: 'http://localhost:8200'
});// 跟踪自定义事务
function handleRequest(req, res) {// 开始一个事务const transaction = apm.startTransaction('GET /api/users', 'request');try {// 开始一个跨度(span)const span = transaction.startSpan('database query');// 执行数据库查询const users = fetchUsersFromDatabase();if (span) span.end();// 发送响应res.send(users);} catch (error) {// 捕获并报告错误apm.captureError(error);res.status(500).send('Internal Server Error');} finally {// 结束事务if (transaction) transaction.end();}
}
4. Node.js安全最佳实践
4.1 常见安全威胁与防护
4.1.1 输入验证与消毒
const validator = require('validator');
const sanitizeHtml = require('sanitize-html');// 验证用户输入
function validateUserInput(input) {const validationResults = {isValid: true,errors: []};// 检查必填字段if (!input.email) {validationResults.isValid = false;validationResults.errors.push('Email is required');} else if (!validator.isEmail(input.email)) {validationResults.isValid = false;validationResults.errors.push('Invalid email format');}// 验证密码强度if (!input.password) {validationResults.isValid = false;validationResults.errors.push('Password is required');} else if (!validator.isStrongPassword(input.password, {minLength: 8,minLowercase: 1,minUppercase: 1,minNumbers: 1,minSymbols: 1})) {validationResults.isValid = false;validationResults.errors.push('Password is not strong enough');}// 验证URLif (input.website && !validator.isURL(input.website)) {validationResults.isValid = false;validationResults.errors.push('Invalid website URL');}return validationResults;
}// 清理HTML内容
function sanitizeUserContent(html) {return sanitizeHtml(html, {allowedTags: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],allowedAttributes: {'a': ['href', 'target', 'rel']},// 强制所有链接在新窗口打开并添加noopenertransformTags: {'a': (tagName, attribs) => {return {tagName,attribs: {...attribs,target: '_blank',rel: 'noopener noreferrer'}};}}});
}// 防止SQL注入
function buildSafeQuery(knex, table, filters) {let query = knex(table);// 安全地添加WHERE条件if (filters.id) {query = query.where('id', filters.id);}if (filters.status) {query = query.where('status', filters.status);}// 安全地处理LIKE查询if (filters.search) {query = query.where('name', 'like', `%${filters.search}%`);}return query;
}
4.1.2 防止跨站脚本(XSS)攻击
const express = require('express');
const helmet = require('helmet');
const xss = require('xss');
const app = express();// 使用Helmet设置安全相关的HTTP头
app.use(helmet());// 设置CSP (内容安全策略)
app.use(helmet.contentSecurityPolicy({directives: {defaultSrc: ["'self'"],scriptSrc: ["'self'", "'unsafe-inline'", 'trusted-cdn.com'],styleSrc: ["'self'", "'unsafe-inline'", 'trusted-cdn.com'],imgSrc: ["'self'", 'data:', 'trusted-cdn.com'],connectSrc: ["'self'", 'api.example.com'],fontSrc: ["'self'", 'trusted-cdn.com'],objectSrc: ["'none'"],mediaSrc: ["'self'"],frameSrc: ["'none'"]}
}));// XSS防护中间件
function xssProtection(req, res, next) {// 清理请求体if (req.body) {Object.keys(req.body).forEach(key => {if (typeof req.body[key] === 'string') {req.body[key] = xss(req.body[key]);}});}// 清理查询参数if (req.query) {Object.keys(req.query).forEach(key => {if (typeof req.query[key] === 'string') {req.query[key] = xss(req.query[key]);}});}next();
}app.use(xssProtection);// 安全地渲染用户提供的内容
app.get('/profile', (req, res) => {const userBio = getUserBio(req.user.id);// 在发送到客户端前清理内容const sanitizedBio = xss(userBio);res.render('profile', {user: req.user,bio: sanitizedBio});
});
4.1.3 防止跨站请求伪造(CSRF)
const express = require('express');
const csrf = require('csurf');
const cookieParser = require('cookie-parser');const app = express();// 解析Cookie
app.use(cookieParser());// 解析表单数据
app.use(express.urlencoded({ extended: false }));// 设置CSRF保护
const csrfProtection = csrf({ cookie: true });// 应用CSRF保护到所有需要的路由
app.get('/form', csrfProtection, (req, res) => {// 传递CSRF令牌到视图res.render('form', { csrfToken: req.csrfToken() });
});app.post('/process', csrfProtection, (req, res) => {// CSRF验证已通过(中间件会自动验证)res.send('表单处理成功!');
});// 处理CSRF错误
app.use((err, req, res, next) => {if (err.code === 'EBADCSRFTOKEN') {// 处理CSRF令牌错误return res.status(403).send('表单已过期或无效。请重新提交。');}next(err);
});// 在前端视图中使用CSRF令牌
/*
<form action="/process" method="post"><input type="hidden" name="_csrf" value="<%= csrfToken %>"><input type="text" name="username"><button type="submit">提交</button>
</form>
*/
4.2 认证与授权
4.2.1 安全的密码处理
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const argon2 = require('argon2');// 使用bcrypt哈希密码
async function hashPasswordBcrypt(password) {// 生成盐值(推荐值为10+)const saltRounds = 12;try {// 生成哈希const hash = await bcrypt.hash(password, saltRounds);return hash;} catch (error) {console.error('密码哈希失败:', error);throw error;}
}// 使用bcrypt验证密码
async function verifyPasswordBcrypt(password, hash) {try {// 比较密码和哈希const match = await bcrypt.compare(password, hash);return match;} catch (error) {console.error('密码验证失败:', error);return false;}
}// 使用Argon2哈希密码(更安全但更消耗资源)
async function hashPasswordArgon2(password) {try {// 使用Argon2id变体(推荐)const hash = await argon2.hash(password, {type: argon2.argon2id,memoryCost: 2**16, // 64MBtimeCost: 3, // 3次迭代parallelism: 2 // 2个并行线程});return hash;} catch (error) {console.error('Argon2密码哈希失败:', error);throw error;}
}// 使用Argon2验证密码
async function verifyPasswordArgon2(password, hash) {try {return await argon2.verify(hash, password);} catch (error) {console.error('Argon2密码验证失败:', error);return false;}
}// 生成安全的随机令牌
function generateSecureToken(byteLength = 32) {return new Promise((resolve, reject) => {crypto.randomBytes(byteLength, (err, buffer) => {if (err) {reject(err);} else {resolve(buffer.toString('hex'));}});});
}
4.2.2 JWT认证实现
const jwt = require('jsonwebtoken');
const crypto = require('crypto');// 生成安全的JWT密钥
const JWT_SECRET = crypto.randomBytes(64).toString('hex');
// 在生产环境中应从环境变量或配置服务获取// 创建JWT令牌
function generateToken(user) {// 设置令牌有效期(例如24小时)const expiresIn = '24h';// 创建有效载荷(不要包含敏感信息)const payload = {sub: user.id, // 主题(用户ID)name: user.name, // 用户名role: user.role, // 用户角色iat: Date.now() // 签发时间};// 签署令牌return jwt.sign(payload, JWT_SECRET, { expiresIn });
}// 验证JWT令牌
function verifyToken(token) {try {// 验证并解码令牌const decoded = jwt.verify(token, JWT_SECRET);return { valid: true, expired: false, payload: decoded };} catch (error) {// 处理不同类型的错误return {valid: false,expired: error.name === 'TokenExpiredError',payload: null};}
}// JWT认证中间件
function authenticateJWT(req, res, next) {// 从请求头获取令牌const authHeader = req.headers.authorization;if (authHeader) {// 提取令牌(格式: "Bearer TOKEN")const token = authHeader.split(' ')[1];// 验证令牌const result = verifyToken(token);if (result.valid) {// 将用户信息附加到请求对象req.user = result.payload;next();} else if (result.expired) {res.status(401).json({ error: 'Token expired' });} else {res.status(403).json({ error: 'Invalid token' });}} else {res.status(401).json({ error: 'Authorization header required' });}
}// 基于角色的授权中间件
function authorizeRole(requiredRole) {return (req, res, next) => {// 检查用户是否已通过认证if (!req.user) {return res.status(401).json({ error: 'Authentication required' });}// 检查用户角色if (req.user.role !== requiredRole) {return res.status(403).json({ error: 'Access denied',required: requiredRole,current: req.user.role});}// 用户有所需角色next();};
}// 使用示例
/*
app.post('/login', async (req, res) => {// 验证用户凭据const user = await authenticateUser(req.body.username, req.body.password);if (user) {// 生成令牌const token = generateToken(user);res.json({ token });} else {res.status(401).json({ error: 'Invalid credentials' });}
});// 受保护的路由
app.get('/api/profile', authenticateJWT, (req, res) => {res.json({ user: req.user });
});// 仅管理员可访问的路由
app.get('/api/admin', authenticateJWT, authorizeRole('admin'), (req, res) => {res.json({ message: 'Admin dashboard' });
});
*/
4.2.3 OAuth 2.0集成
const express = require('express');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const session = require('express-session');const app = express();// 配置会话
app.use(session({secret: 'your-secret-key',resave: false,saveUninitialized: false,cookie: {secure: process.env.NODE_ENV === 'production', // 在生产环境中使用HTTPShttpOnly: true,maxAge: 24 * 60 * 60 * 1000 // 24小时}
}));// 初始化Passport
app.use(passport.initialize());
app.use(passport.session());// 配置Google OAuth策略
passport.use(new GoogleStrategy({clientID: process.env.GOOGLE_CLIENT_ID,clientSecret: process.env.GOOGLE_CLIENT_SECRET,callbackURL: "http://localhost:3000/auth/google/callback",scope: ['profile', 'email']},async function(accessToken, refreshToken, profile, done) {try {// 查找或创建用户let user = await findUserByGoogleId(profile.id);if (!user) {// 创建新用户user = await createUser({googleId: profile.id,email: profile.emails[0].value,name: profile.displayName,picture: profile.photos[0].value});}// 保存令牌(可选)user.accessToken = accessToken;user.refreshToken = refreshToken;await updateUser(user);return done(null, user);} catch (error) {return done(error);}}
));// 序列化和反序列化用户
passport.serializeUser((user, done) => {done(null, user.id);
});passport.deserializeUser(async (id, done) => {try {const user = await findUserById(id);done(null, user);} catch (error) {done(error);}
});// 登录路由
app.get('/auth/google',passport.authenticate('google', { scope: ['profile', 'email'] })
);// 回调路由
app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/login',successRedirect: '/dashboard'})
);// 登出路由
app.get('/logout', (req, res) => {req.logout();res.redirect('/');
});// 检查认证状态的中间件
function ensureAuthenticated(req, res, next) {if (req.isAuthenticated()) {return next();}res.redirect('/login');
}// 受保护的路由
app.get('/dashboard', ensureAuthenticated, (req, res) => {res.render('dashboard', { user: req.user });
});// 模拟数据库函数
async function findUserByGoogleId(googleId) {// 实际应用中应查询数据库return null; // 假设用户不存在
}async function createUser(userData) {// 实际应用中应创建数据库记录return { id: 'user-123', ...userData };
}async function updateUser(user) {// 实际应用中应更新数据库记录return user;
}async function findUserById(id) {// 实际应用中应查询数据库return { id, name: 'Test User', email: 'test@example.com' };
}
结语
感谢您的阅读!期待您的一键三连!欢迎指正!
相关文章:
【node.js】核心进阶
个人主页:Guiat 归属专栏:node.js 文章目录 1. Node.js高级异步编程1.1 Promise深入理解1.1.1 创建和使用Promise1.1.2 Promise组合模式 1.2 Async/Await高级模式1.2.1 基本使用1.2.2 并行执行1.2.3 顺序执行与错误处理 1.3 事件循环高级概念1.3.1 事件循…...
高频Java面试题深度拆解:String/StringBuilder/StringBuffer三剑客对决(万字长文预警)
文章目录 一、这道题的隐藏考点你Get到了吗?二、内存模型里的暗战(图解警告)2.1 String的不可变性之谜2.2 可变双雄的内存游戏 三、线程安全背后的修罗场3.1 StringBuffer的同步真相3.2 StringBuilder的裸奔哲学 四、性能对决:用数…...
量子计算的曙光:从理论奇点到 IT 世界的颠覆力量
在信息技术(IT)的飞速发展中,一项前沿技术正以耀眼的光芒照亮未来——量子计算(Quantum Computing)。2025 年,随着量子硬件的突破、算法的优化以及企业对超算能力的渴求,量子计算从科幻梦想逐步…...
c++使用protocol buffers
在 C 里使用 Protocol Buffer,要先定义消息结构,接着生成 C 代码,最后在程序里使用这些生成的代码。 定义消息结构 首先要创建一个.proto文件,在其中定义消息类型和字段。 // person.proto syntax "proto3"; // 指…...
AI驱动发展——高能受邀参加华为2025广东新质生产力创新峰会
当AI浪潮席卷全球产业版图,一场以"智变"驱动"质变"的变革正在发生。5月15日,华为中国行2025广东新质生产力创新峰会璀璨启幕,作为华为生态战略合作伙伴,高能计算机与行业领军者同台论道,共同解码A…...
怎样解决photoshop闪退问题
检查系统资源:在启动 Photoshop 之前,打开任务管理器检查 CPU 和内存的使用情况。如果发现资源占用过高,尝试关闭不必要的程序或重启计算机以释放资源。更新 Photoshop 版本:确保 Photoshop 是最新版本。Adobe 经常发布更新以修复…...
AWS CodePipeline+ Elastic Beanstalk(AWS中国云CI/CD)
问题 最近需要利用AWS云上面的CI/CD部署Spring应用。 一图胜千言 步骤 打开CodePipeline网页,开始管道创建,如下图: 管道设置,如下图: 这里主要设置管道名称,至于服务角色,直接让codepipel…...
人工智能核心知识:AI Agent 的四种关键设计模式
人工智能核心知识:AI Agent 的四种关键设计模式 一、引言 在人工智能领域,AI Agent(人工智能代理)是实现智能行为和决策的核心实体。它能够感知环境、做出决策并采取行动以完成特定任务。为了设计高效、灵活且适应性强的 AI Age…...
Electron+vite+vue3 从0到1搭建项目,开发Win、Mac客户端
随着前端技术的发展,出现了所谓的大前端。 大前端则是指基于前端技术延伸出来的各种终端平台及应用场景,包括APP、桌面端、手表终端、服务端等。 本篇文章主要是和大家一起学习一下使用Electron 如何打包出 Windows 和 Mac 所使用的客户端APPÿ…...
GitLab部署
学git Git最新最新详细教程、安装(从入门到精通!!!!企业级实战!!!工作必备!!!结合IDEA、Github、Gitee实战!!!…...
基于R语言地理加权回归、主成份分析、判别分析等空间异质性数据分析技术
在自然和社会科学领域,存在大量与地理或空间相关的数据,这些数据通常具有显著的空间异质性。传统的统计学方法在处理这类数据时往往力不从心。基于R语言的一系列空间异质性数据分析方法,如地理加权回归(GWR)、地理加权…...
指针深入理解(二)
volatile关键字 防止优化指向内存地址, typedef 指针可以指向C语言所有资源 typedef 就是起一个外号。 指针运算符加减标签操作 指针加的是地址,并且增加的是该指针类型的一个单位,指针变量的步长可以用sizeof(p[0]) 这两个的p1是不一样…...
django回忆录(Python的一些基本概念, pycharm和Anaconda的配置, 以及配合MySQL实现基础功能, 适合初学者了解)
django 说实在的, 如果是有些Python基础或者编程基础, 使用django开发本地网站的速度还是很快的, 特别是配合ai进行使用. 本人使用该框架作业的一个主要原因就是因为要做数据库大作业, 哥们想速通, 结果由于我一开始没有接触过这些方面的知识, 其实也不算快, 而且现在我也没有…...
leetcode hot100刷题日记——5.无重复字符的最长字串
解答:滑动窗口思想(见官方题解) //方法1 class Solution { public:int lengthOfLongestSubstring(string s) {//哈希表记录是否有重复字符unordered_set<char>c;int maxlength0;int ns.size();//右指针初始化为-1,可以假设…...
一文讲清python、anaconda的安装以及pycharm创建工程
软件下载 Pycharm下载地址: https://download-cdn.jetbrains.com.cn/python/pycharm-community-2024.1.1.exe?_gl1*1xfh3l8*_gcl_au*MTg1NjU2NjA0OC4xNzQ3MTg3Mzg1*FPAU*MTg1NjU2NjA0OC4xNzQ3MTg3Mzg1*_ga*MTA2NzE5ODc1NS4xNzI1MzM0Mjc2*_ga_9J976DJZ68*czE3NDczMD…...
[每日一题] 3355. 零数组变换 i
文章目录 1. 题目链接2. 题目描述3. 题目示例4. 解题思路5. 题解代码6. 复杂度分析 1. 题目链接 3355. 零数组变换 I - 力扣(LeetCode) 2. 题目描述 给定一个长度为 n 的整数数组 nums 和一个二维数组 queries,其中 queries[i] [li, ri]。…...
【笔记】与PyCharm官方沟通解决开发环境问题
#工作记录 2025年5月20日 星期二 背景 在此前的笔记中,我们提到了向PyCharm官方反馈了几个关于Conda环境自动激活、远程解释器在社区版中的同步问题以及Shell脚本执行时遇到的问题。这些问题对日常开发流程产生了一定影响,因此决定联系官方支持寻求解…...
mariadb-cenots8安装
更新系统:安装完成 CentOS 8 后,连接到互联网,打开终端并运行以下命令来更新系统,以获取最新的软件包和安全补丁。 bash sudo yum update -y安装 MariaDB:运行以下命令来安装 MariaDB。 bash sudo yum install mariadb…...
Python实现VTK - 自学笔记(4):用Widgets实现三维交互控制
核心知识点 交互器样式(vtkInteractorStyle):自定义鼠标/键盘交互逻辑三维控件(3D Widgets):使用预制控件实现复杂交互回调机制:实现动态数据更新参数化控制:通过控件调整算法参数import vtk# 1. 创建圆锥体数据源 cone = vtk.vtkConeSour…...
在tp6模版中加减法
实际项目中,我们经常需要标签变量加减运算的操作。但是,在ThinkPHP中,并不支持模板变量直接运算的操作。幸运的是,它提供了自定义函数的方法,我们可以利用自定义函数解决:ThinkPHP模板自定义函数语法如下&a…...
Linux:库与链接
库是预先编译好、可执⾏的⼆进制码,可以被操作系统加载到内存中执⾏。 库有两种: 静态库:.a(Linux)、.lib(Windows) 动态库:.so(Linux)、.dil(Windows) 静态库 1.程序在链接时把库的代码链接到可执⾏⽂件中,运⾏时…...
T008-网络管理常用命令:ping,ipconfig,nslookup,route,netstat
ipconfig:网络诊断命令,显示 IP 地址、掩码、网关信息,清除/显示 DNS 缓存信息; route:主要用于管理路由表,确定数据包如何从源主机通过网络到达目的主机 nslookup:用于查询域名到IP地址&…...
Qt文件:XML文件
XML文件 1. XML文件结构1.1 基本结构1.2 XML 格式规则1.3 XML vs HTML 2. XML文件操作2.1 DOM 方式(QDomDocument)读取 XML写入XML 2.2 SAX 方式(QXmlStreamReader/QXmlStreamWriter)读取XML写入XML 2.3 对比分析 3. 使用场景3.1 …...
MySQL 8.0 OCP 英文题库解析(六)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题41~50 试题4…...
微软开放代理网络愿景
🌐 Microsoft的开放式智能代理网络愿景 2025年05月20日 | AI日报  欢迎各位人工智能爱好者 微软刚刚在Build 2025大会上开启了备受期待的AI周活动,通过发布大…...
阿尔泰科技助力电厂——520为爱发电!
当城市的霓虹在暮色中亮起,当千万个家庭在温暖中共享天伦,总有一群默默的 "光明守护者" 在幕后坚守 —— 它们是为城市输送能量的电厂,更是以科技赋能电力行业的阿尔泰科技。值此 520 爱意满满的日子,阿尔泰科技用硬核技…...
微软账户无密码化的取证影响
五月初,微软正式宣布,新创建的微软账户现在将默认为无密码,以实现“更简单、更安全的登录”。这一变化延续了Windows 11所设定的方向,即逐步淘汰传统密码,转而采用更安全、更方便用户的身份验证方法,如PIN码…...
idea部署本地仓库和连接放送远程仓库
1.下载git,安装好后任意地方又键会出现两个带git的东西 2.点击bash here的那个,召唤出git的小黑窗,输入 git config --global user.name "你自己取名" git config --global user.email "你自己输入你的邮箱" 3.打开id…...
4大AI智能体平台,你更适合哪一个呐?
好记忆不如烂笔头,能记下点东西,就记下点,有时间拿出来看看,也会发觉不一样的感受. AI的火热程度,应该说是今年IT行业内最热的话题了,以下是根据我对各个智能体平台的了解和熟悉,按照 平台特点、…...
Pandas:Series和DataFrame的概念、常用属性和方法
本文目录: 一、Series和Dataframe的概念二、创建Series对象三、创建Dataframe对象(一)Series1.Series的常用属性总结如下:2.Series的常用方法总结如下: (二)Dataframe1.Dataframe的常用属性2.Da…...
Index-AniSora论文速读:探索Sora时代动画视频生成的前沿
AniSora: Exploring the Frontiers of Animation Video Generation in the Sora Era 一、引言 论文开篇指出动画产业近年来的显著增长,动画内容的需求不断攀升,但传统动画制作流程存在劳动密集和耗时的问题,如故事板创建、关键帧生成和中间…...
扫盲笔记之NPM
简介 npm,全名 node package manger。 NPM(Node Package Manager)是一个 JavaScript 包管理工具,也是 Node.js 的默认包管理器。 NPM 允许开发者轻松地下载、安装、共享、管理项目的依赖库和工具。网址:https://www…...
【Go-2】基本语法与数据类型
基本语法与数据类型 Go语言作为一种静态类型、编译型语言,拥有简洁且高效的语法结构。本章将深入介绍Go的基本语法和数据类型,帮助你建立扎实的编程基础。 2.1 第一个 Go 程序 编写第一个Go程序是学习任何编程语言的传统步骤。通过一个简单的“Hello,…...
Varlet UI-Material Design风格Vue 3框架移动端组件库
#Varlet UI是什么 在现代Web开发中,Vue 3以其强大的组件系统特性,成为了构建可复用、模块化应用界面的首选框架。而在Vue 3的生态系统中,Varlet UI开源组件库以其高效、一致和可维护的设计,为开发者提供了丰富的工具和资源。本文将…...
Golang的文件上传与下载
## Golang的文件上传与下载 文件上传 在Golang中,我们可以使用 net/http 包来实现文件上传功能。文件上传的一般流程包括创建一个接收上传请求的处理器,解析表单数据,然后获取文件并保存到服务器指定的位置。 创建文件上传接口 首先ÿ…...
信奥赛-刷题笔记-栈篇-T3-P4387验证栈序列0520
总题单 本部分总题单如下 【腾讯文档】副本-CSP-JSNOI 题单 (未完待续) https://docs.qq.com/sheet/DSmJuVXR4RUNVWWhW?tabBB08J2 栈篇题单 P4387 【深基15.习9】验证栈序列 题目描述 给出两个序列 pushed 和 poped 两个序列,其取值从 1 到 n ( n ≤ 10…...
jenkins授权管理.
使用背景: 在企业中可能多个开发组织共用同一个Jenkins服务器, 不会让用户具有管理员权限的, 需要给用户分配对应的Group组织权限。例如: 张三, 属于devops1这个组织, 仅允许张三对devops1组织相关的jenkins作业进行构…...
Ubuntu24.04安装Dify
1、win10上安装docker不顺利 参考:Dify的安装_dify安装-CSDN博客等资料,Dify依赖Docker运行,在Win10上安装Docker,先安装wsl。在PowerShell(管理员)中输入: wsl --install 或显示“找不到指定文件”,或显示…...
Spring Boot 集成 Elasticsearch【实战】
前言: 上一篇我们简单分享了 Elasticsearch 的一些概念性的知识,本篇我们来分享 Elasticsearch 的实际运用,也就是在 Spring Booot 项目中使用 Elasticsearch。 Elasticsearch 系列文章传送门 Elasticsearch 基础篇【ES】 Elasticsearch …...
Spark离线数据处理实例
工具:Jupyter notebook # 一、需求分析 (1)分析美妆商品信息,找出每个“商品小类”中价格最高的前5个商品。 (2)每月订购情况,统计每个月订单的订购数量情况和消费金额。 (3&#x…...
window 安装 wsl + cuda + Docker
WSL 部分参考这里安装: Windows安装WSL2 Ubuntu环境 - 知乎 如果出现错误: WslRegisterDistribution failed with error: 0x800701bc 需要运行:https://crayon-shin-chan.blog.csdn.net/article/details/122994190 wsl --update wsl --shu…...
多通道振弦式数据采集仪MCU安装指南
设备介绍 数据采集仪 MCU集传统数据采集器与5G/4G,LoRa/RS485两种通信功能与一体的智能数据采集仪。该产品提供振弦、RS-485等的物理接口,能自动采集并存储多种自然资源、建筑、桥梁、城市管廊、大坝、隧道、水利、气象传感器的实时数据,利用现场采集的数…...
Linux:进程信号---信号的概念与产生
文章目录 1. 信号的概念1.1 信号1.2 认识信号1.3 signal函数1.4 信号的识别(硬件角度) 2. 信号的产生2.1 键盘组合键2.2 kill命令2.3 系统调用2.4 异常2.5 软件条件 3. core dump 序:在我们的生活中,有很多信号,比如红…...
开放鸿蒙OpenHarmony 5.0.0 Release 兼容性测试实战经验分享
OpenHarmony 5.0版本的发布时间是2024年12月20日至21日。这个版本带来了许多新特性和改进。现在5.0出了两个release 版本,分别是5.0.0和5.0.1。 就在5.0版本发布不到2周的时间内,2025年01月01日起,不支持新产品基于老分支(OpenHa…...
Nvidia - NVLink Fusion
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
C#处理印尼地区的数字分隔符方法
1.在印尼 数字中的 小数点 和 千分位分隔符 的用法与欧美习惯相反 逗号(,) 用作 小数点(如 1,23 表示 1.23)。点(.) 用作 千分位分隔符(如 1.000 表示 1000)。 查阅资料后发现&#…...
Python爬虫(30)Python爬虫高阶:Selenium+Scrapy+Playwright融合架构,攻克动态页面与高反爬场景
目录 一、背景:动态页面与反爬技术的崛起二、技术融合架构设计1. 核心组件分工2. 架构图示3. 关键技术点 三、代码实现:分步详解1. 环境配置2. 核心代码结构3. Scrapy项目集成4. Playwright增强功能示例 四、总结:技术融合的优势与挑战1. 优势…...
PHP、JAVA、Shiro反序列化
目录 一、PHP反序列化 二、JAVA反序列化 三、Shiro反序列化 Shiro-550 反序列化漏洞原理 Shiro-721 反序列化漏洞原理 Padding Oracle 漏洞补充: 防御措施: 一、PHP反序列化 主要是分为有类和无类: 1、有类:就有相关的魔术…...
FreeRTOS全攻略:从入门到精通
目录 一、FreeRTOS 基础概念1.1 FreeRTOS 是什么1.2 为什么选择 FreeRTOS 二、与裸机开发的区别2.1 任务管理2.2 中断处理2.3 资源管理 三、FreeRTOS 入门篇3.1 内存管理3.2 任务创建3.3 任务状态3.4 任务优先级3.5 空闲任务和钩子函数3.6 同步与互斥3.7 队列3.8 信号量3…...
机器学习 决策树-分类
决策树-分类 1 概念2 基于信息增益决策树的建立(1) 信息熵(2) 信息增益(3) 信息增益决策树建立步骤 3 基于基尼指数决策树的建立(了解)4 sklearn API5 示例 1 概念 1、决策节点 通过条件判断而进行分支选择的节点。如:将某个样本中的属性值(特征值)与决策节点上的值…...