Node.js开发踩坑记录

Node.js开发踩坑记录

Node.js项目做多了,从项目初始化到部署上线,踩了不少坑。记录一下常见问题和解决方案。

项目初始化

React项目创建

1
2
3
4
5
# 创建React应用
npx create-react-app my-web-project
cd my-web-project
code .
npm start

环境变量配置

用dotenv管理环境变量:

1
npm install dotenv --save
1
2
require('dotenv').config();
console.log(process.env.API_URL);

.env文件示例:

1
2
3
4
NODE_ENV=development
API_URL=https://api.example.com
DB_HOST=localhost
DB_PORT=3306

npm镜像加速

使用nrm管理镜像源

国内访问npm官方源慢,用nrm快速切换:

1
2
3
4
5
6
7
8
9
10
11
# 安装nrm
npm install -g nrm

# 列出所有源
nrm ls

# 切换到淘宝镜像
nrm use taobao

# 验证配置
npm config get registry

国内镜像对比

镜像源 地址 特点
淘宝npm https://registry.npmmirror.com 速度快
腾讯云 https://mirrors.cloud.tencent.com/npm 稳定
华为云 https://mirrors.huaweicloud.com/repository/npm 企业级

HTTP请求处理

needle轻量请求

1
2
3
4
5
6
7
8
9
10
11
12
const needle = require('needle');

async function fetchUrl(url) {
try {
const response = await needle('get', url);
console.log('状态:', response.statusCode);
return response.body;
} catch (err) {
console.error('请求失败:', err.message);
throw err;
}
}

request下载文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const fs = require('fs');
const request = require('request');
const path = require('path');

function downloadFile(url, outputDir, callback) {
const filename = path.basename(url);
const outputPath = path.join(outputDir, filename);

if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}

const writeStream = fs.createWriteStream(outputPath);

request(url)
.pipe(writeStream)
.on('finish', () => {
console.log(`下载完成: ${filename}`);
callback(true, outputPath);
})
.on('error', (err) => {
console.error(`下载失败: ${err.message}`);
callback(false, null);
});
}

日志管理系统

morgan + winston组合

生产环境推荐用这个组合:

1
npm install morgan winston winston-daily-rotate-file

完整配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const express = require('express');
const morgan = require('morgan');
const path = require('path');
const winston = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');

const app = express();
const logDirectory = path.join(__dirname, 'logs');

const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.json()
),
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}),
new DailyRotateFile({
filename: path.join(logDirectory, 'application-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d'
}),
new winston.transports.File({
filename: path.join(logDirectory, 'error.log'),
level: 'error'
})
]
});

app.use(morgan('combined', {
stream: { write: (message) => logger.info(message.trim()) }
}));

app.use((err, req, res, next) => {
logger.error({
url: req.url,
method: req.method,
error: err.message,
stack: err.stack
});
res.status(500).json({ code: 500, message: 'Internal Server Error' });
});

日志级别

级别 用途
error 错误信息,需立即处理
warn 警告信息,需关注
info 常规信息
debug 调试信息

TypeScript调试配置

VSCode调试TS

tsconfig.json:

1
2
3
4
5
6
7
8
9
10
11
12
{
"compilerOptions": {
"module": "commonjs",
"target": "es2020",
"outDir": "./dist",
"sourceMap": true,
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

launch.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug TS File",
"type": "node",
"request": "launch",
"args": ["${workspaceRoot}/src/index.ts"],
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
"sourceMaps": true,
"cwd": "${workspaceRoot}",
"protocol": "inspector"
}
]
}

坑1:chalk 5.x兼容问题

chalk 5.x用ES Module,CommonJS项目不兼容。降级到4.x:

1
2
yarn remove chalk
yarn add chalk@^4.1.2

日期处理

moment.js计算日期差

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const moment = require('moment');

function calculateDateDiff(startDate, endDate) {
const start = moment(startDate);
const end = moment(endDate);

const years = end.diff(start, 'years');
end.subtract(years, 'years');
const days = end.diff(start, 'days');

return { years, days };
}

const result = calculateDateDiff('2022-01-01', '2025-06-30');
console.log(`相差 ${result.years}${result.days} 天`);

部署相关

ngrok内网穿透

1
2
3
4
5
6
7
8
# 安装ngrok
npm install -g ngrok

# 配置token
ngrok config add-authtoken YOUR_AUTHTOKEN

# 启动穿透
ngrok http http://localhost:8080

适用场景:

  • 微信/支付宝回调调试
  • 第三方支付接口联调
  • 移动端真机测试

Telegram Bot示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const Bot = require('node-telegram-bot-api');

const bot = new Bot(process.env.TELEGRAM_TOKEN, { polling: true });

bot.onText(/\/start/, async (msg) => {
const chatId = msg.chat.id;
const userId = msg.from.id;

try {
const chatMember = await bot.getChatMember(chatId, userId);
const userInfo = `
ID: ${chatMember.user.id}
名字: ${chatMember.user.first_name}
用户名: ${chatMember.user.username || '未设置'}
状态: ${chatMember.status}
`;
await bot.sendMessage(chatId, userInfo);
} catch (error) {
console.error('获取用户信息失败:', error);
await bot.sendMessage(chatId, '获取信息失败');
}
});

bot.startPolling();

总结

Node.js开发常见问题:

  1. npm慢就用nrm切淘宝镜像
  2. 日志系统用winston+morgan组合
  3. TypeScript调试要配好sourceMap
  4. chalk 5.x有兼容问题,降级到4.x
  5. 回调调试用ngrok穿透

建议项目初期就配好日志和调试环境,省得后期踩坑。