Node.js后端开发踩坑记录

Node.js后端开发踩坑记录

搞Node.js后端开发有一段时间了,从环境配置到加密、HTTP请求,记录一下踩过的坑和实战经验。

Node.js环境配置

CentOS安装指定版本

安装Node.js 14.x:

1
2
3
yum -y install curl
curl -sL https://rpm.nodesource.com/setup_14.x | sudo bash -
yum install -y nodejs

安装最新LTS:

1
2
3
4
5
curl -sL https://rpm.nodesource.com/setup_lts.x | sudo bash -
yum install -y nodejs

node --version
npm -v

Windows多版本管理

用nvm-windows管理多个版本:

  1. 下载:https://github.com/coreybutler/nvm-windows/releases
  2. 安装后验证:nvm version

配置淘宝镜像(setting.txt):

1
2
3
4
arch: 64
proxy: none
node_mirror: http://npm.taobao.org/mirrors/node/
npm_mirror: https://npm.taobao.org/mirrors/npm/

常用命令:

1
2
3
4
nvm ls                    # 查看已安装版本
nvm install 14.21.3 # 安装指定版本
nvm use 16.13.1 # 切换版本
node -v # 查看当前版本

npm镜像源

查看当前源:

1
npm config get registry

国内镜像:

1
2
3
4
5
6
7
8
# 淘宝镜像
npm config set registry https://registry.npmmirror.com

# 华为镜像
npm config set registry https://mirrors.huaweicloud.com/repository/npm/

# 腾讯镜像
npm config set registry http://mirrors.cloud.tencent.com/npm/

还原官方源:

1
npm config set registry https://registry.npmjs.org

Yarn换源

1
2
yarn config get registry
yarn config set registry https://registry.npmmirror.com

npm安装问题

错误:reify:fsevents sill reify mark deleted

解决:

1
2
3
4
npm config get registry
npm config set registry https://registry.npmjs.org/
npm cache clean --force
npm install

加密技术

HMAC SHA256

Node.js内置crypto模块,直接用就行。

基础用法:

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

const secret = 'your-secret-key';
const message = 'data-to-sign';

const hmac = crypto.createHmac('sha256', secret)
.update(message)
.digest('hex');

console.log('HMAC:', hmac);

Base64编码:

1
2
3
const hmacBase64 = crypto.createHmac('sha256', secret)
.update(message)
.digest('base64');

封装工具类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// utils/crypto.js
const crypto = require('crypto');

class CryptoUtil {
static hmacSha256(data, key, encoding = 'base64') {
return crypto
.createHmac('sha256', key)
.update(data)
.digest(encoding);
}

static hmacSha256Hex(data, key) {
return this.hmacSha256(data, key, 'hex');
}

static verifyHmac(data, key, signature, encoding = 'base64') {
const computed = this.hmacSha256(data, key, encoding);
return computed === signature;
}
}

module.exports = CryptoUtil;

API请求签名示例:

1
2
3
4
5
6
7
8
9
10
11
12
function signRequest(params, secretKey) {
// 1. 按键名排序
const sortedKeys = Object.keys(params).sort();

// 2. 构建参数字符串
const paramString = sortedKeys
.map(key => `${key}=${params[key]}`)
.join('&');

// 3. 生成签名
return CryptoUtil.hmacSha256Hex(paramString, secretKey);
}

MD5哈希

虽然MD5不推荐用于安全场景,但数据校验还是可以用。

1
npm install crypto --save

基础用法:

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

function md5(str) {
return crypto.createHash('md5')
.update(String(str))
.digest('hex');
}

// 加盐哈希
const password = 'userPassword123';
const salt = 'randomSalt';
const hashedPassword = md5(`${password}${salt}`);

封装工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// utils/md5.js
const crypto = require('crypto');

const MD5 = {
encrypt(data) {
const str = String(data);
return crypto.createHash('md5').update(str).digest('hex');
},

encryptWithSalt(data, salt) {
return this.encrypt(`${data}${salt}`);
},

verify(data, salt, hash) {
return this.encryptWithSalt(data, salt) === hash;
}
};

module.exports = MD5;

密码存储示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 注册
async function registerUser(username, password) {
const salt = crypto.randomBytes(16).toString('hex');
const hashedPassword = MD5.encryptWithSalt(password, salt);

await db.users.insert({
username,
password: hashedPassword,
salt,
createdAt: new Date()
});
}

// 登录验证
async function verifyUser(username, password) {
const user = await db.users.findOne({ username });
if (!user) return false;
return MD5.verify(password, user.salt, user.password);
}

提示:生产环境建议用bcrypt、scrypt或Argon2替代MD5。

HTTP请求处理

Axios基础

安装:

1
npm install axios

GET请求:

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

// 简单GET
axios.get('https://api.example.com/data')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});

// 带参数GET
axios.get('https://api.example.com/search', {
params: {
keyword: 'nodejs',
page: 1
}
});

POST请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// JSON提交
const postData = {
app_id: 'your-app-id',
open_id: 'user-open-id'
};

axios.post('https://api.example.com/submit', postData)
.then(response => {
console.log(response.status);
console.log(response.data);
});

// FormData提交
const formData = {
file: fs.createReadStream('/path/to/file'),
name: 'upload'
};

const header = {
'content-type': 'multipart/form-data'
};

axios.post('https://api.example.com/upload', formData, { headers: header });

创建实例:

1
2
3
4
5
6
7
8
9
10
11
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token'
}
});

apiClient.get('/users')
.then(response => console.log(response.data));

请求拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 请求拦截器
apiClient.interceptors.request.use(
config => {
console.log('Request:', config.url);
return config;
},
error => {
return Promise.reject(error);
}
);

// 响应拦截器
apiClient.interceptors.response.use(
response => {
return response.data;
},
error => {
if (error.response) {
console.error('Error status:', error.response.status);
}
return Promise.reject(error);
}
);

错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async function fetchData() {
try {
const response = await axios.get('https://api.example.com/data');
return response.data;
} catch (error) {
if (error.response) {
// 服务器返回错误状态码
console.error('Server error:', error.response.status);
console.error('Error data:', error.response.data);
} else if (error.request) {
// 请求发送但没收到响应
console.error('No response:', error.request);
} else {
// 请求配置出错
console.error('Error:', error.message);
}
}
}

VSCode调试配置

Node.js调试

.vscode/launch.json

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/src/main.js",
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
}
]
}

NestJS调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch NestJS",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/src/main.ts",
"preLaunchTask": "nest-build",
"outFiles": [
"${workspaceFolder}/dist/**/*.js",
"!**/node_modules/**"
]
}
]
}

配套tasks.json

1
2
3
4
5
6
7
8
9
10
11
12
{
"version": "2.0.0",
"tasks": [
{
"label": "nest-build",
"type": "shell",
"command": "nest build",
"problemMatcher": [],
"group": "build"
}
]
}

EJS模板引擎升级

EJS 2.7.4到3.0.1语法变化:

1
2
3
4
5
<!-- 2.7.4 旧语法 -->
<% include ./header.ejs %>

<!-- 3.0.1 新语法 -->
<%- include('./header.ejs') %>

迁移检查:

旧语法 新语法
<% include file %> <%- include('file') %>
<% include file.ejs %> <%- include('file.ejs') %>

JavaScript值判断

判断undefined

1
2
3
4
5
6
7
8
9
// typeof
if (typeof value === "undefined") {
console.log("is undefined");
}

// 严格相等
if (value === undefined) {
console.log("is undefined");
}

判断null

1
2
3
4
5
6
7
8
9
// 严格相等(推荐)
if (value === null) {
console.log("is null");
}

// 非空判断
if (!value && typeof value !== "undefined") {
console.log("is null");
}

判断NaN

1
2
3
4
// 唯一方法
if (isNaN(value)) {
console.log("is NaN");
}

判断空值

1
2
3
4
5
6
7
8
9
10
11
12
function isEmpty(value) {
return value === null ||
value === undefined ||
(typeof value === 'string' && value.trim() === '');
}

isEmpty(null); // true
isEmpty(undefined); // true
isEmpty(""); // true
isEmpty(" "); // true
isEmpty(0); // false
isEmpty(false); // false

判断假值

1
2
3
4
5
6
7
// 判断null、undefined、0、NaN、false、空字符串
if (!value) {
console.log("falsy value");
}

// 更精确
const isFalsy = !value && value !== 0 && value !== false;

Object.assign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 基础用法
const target = { name: '张三' };
const source = { age: 18 };
const result = Object.assign(target, source);
console.log(result); // { name: '张三', age: 18 }
console.log(target === result); // true(修改了原对象)

// 创建新对象(推荐)
const newObj = Object.assign({}, target, source);

// 多个源对象
const obj = Object.assign({}, obj1, obj2, obj3);

// 属性覆盖(后面的覆盖前面的)
const obj1 = { name: '张三', age: 16 };
const obj2 = { age: 18 };
const merged = Object.assign({}, obj1, obj2);
console.log(merged.age); // 18

Node.js后端开发整体来说上手快,但坑也不少。npm依赖地狱、异步处理、内存泄漏是老问题了。建议:

  1. 用nvm管理Node版本
  2. npm换国内镜像
  3. 加密用内置crypto模块
  4. HTTP请求用Axios,拦截器很好用
  5. VSCode调试配置好能省很多时间

有问题的欢迎留言讨论。