基于 Node.js + Express + MySQL + Redis 的个人博客后端服务。
- Node.js - 运行环境
- Express - Web 框架
- MySQL - 数据库
- Redis - 缓存
- JWT - 身份认证
- bcryptjs - 密码加密
- 用户认证与授权(JWT)
- 文章管理(CRUD)
- 分类与标签管理
- 菜单管理
- 主题配置
- 页面管理
- 文件上传
- 数据统计
- 字典管理
- Node.js >= 16.0.0
- MySQL >= 5.7
- Redis >= 5.0
blog-backend/
├── src/
│ ├── config/ # 配置文件
│ │ ├── database.js # MySQL 配置
│ │ └── redis.js # Redis 配置
│ ├── middleware/ # 中间件
│ │ ├── auth.js # 认证中间件
│ │ └── validator.js # 参数校验
│ ├── routes/ # 路由
│ │ ├── article/ # 文章相关
│ │ ├── auth/ # 认证相关
│ │ ├── system/ # 系统管理
│ │ ├── upload/ # 文件上传
│ │ └── user/ # 用户管理
│ ├── scripts/ # 脚本
│ │ └── init-db.js # 数据库初始化
│ ├── utils/ # 工具函数
│ │ ├── jwt.js # JWT 工具
│ │ └── response.js # 响应封装
│ └── app.js # 应用入口
├── .env.example # 环境变量示例
├── package.json
└── README.md
npm install
# 或使用 pnpm
pnpm install复制环境变量示例文件:
cp .env.example .env编辑 .env 文件,配置数据库和 Redis 连接信息:
# 服务器配置
PORT=3001
NODE_ENV=development
# 数据库配置
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=personal_blog
# Redis配置
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
# JWT配置
JWT_SECRET=your-super-secret-jwt-key-change-in-production
JWT_EXPIRES_IN=7d
# 上传配置
UPLOAD_PATH=uploads
MAX_FILE_SIZE=5242880确保 MySQL 服务已启动,然后执行:
npm run init-db此命令会:
- 创建数据库(如果不存在)
- 创建所有数据表
- 插入默认管理员账号和示例数据
默认管理员账号:
- 用户名:
admin - 密码:
admin123
开发模式(热重载):
npm run dev生产模式:
npm start服务启动后,访问 http://localhost:3001/api/health 检查运行状态。
安装 Node.js 16+、MySQL 5.7+、Redis 5.0+。
将项目代码上传到服务器,例如 /opt/blog-backend。
cd /opt/blog-backend
npm install --production创建 .env 文件,配置生产环境参数:
PORT=3001
NODE_ENV=production
# 数据库配置
DB_HOST=localhost
DB_PORT=3306
DB_USER=blog_user
DB_PASSWORD=your_strong_password
DB_NAME=personal_blog
# Redis配置
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=your_redis_password
# JWT配置(生产环境务必修改)
JWT_SECRET=your-production-jwt-secret-key
JWT_EXPIRES_IN=7d
# 上传配置
UPLOAD_PATH=uploads
MAX_FILE_SIZE=5242880npm run init-db创建服务文件:
sudo nano /etc/systemd/system/blog-backend.service写入以下内容:
[Unit]
Description=Blog Backend API Service
After=network.target mysql.service redis.service
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/blog-backend
ExecStart=/usr/bin/node src/app.js
Restart=always
RestartSec=10
Environment=NODE_ENV=production
Environment=PORT=3001
Environment=DB_HOST=localhost
Environment=DB_PORT=3306
Environment=DB_USER=blog_user
Environment=DB_PASSWORD=your_password
Environment=DB_NAME=personal_blog
Environment=REDIS_HOST=localhost
Environment=REDIS_PORT=6379
Environment=JWT_SECRET=your-jwt-secret
[Install]
WantedBy=multi-user.target启动并启用服务:
# 重新加载 systemd
sudo systemctl daemon-reload
# 启动服务
sudo systemctl start blog-backend
# 设置开机自启
sudo systemctl enable blog-backend
# 查看状态
sudo systemctl status blog-backend
# 查看日志
sudo journalctl -u blog-backend -f常用命令:
sudo systemctl start blog-backend # 启动
sudo systemctl stop blog-backend # 停止
sudo systemctl restart blog-backend # 重启
sudo systemctl status blog-backend # 查看状态安装 PM2:
npm install -g pm2创建 PM2 配置文件 ecosystem.config.js:
module.exports = {
apps: [{
name: 'blog-backend',
script: './src/app.js',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
NODE_ENV: 'production'
},
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log',
time: true
}]
};启动服务:
pm2 start ecosystem.config.js
pm2 save
pm2 startupNODE_ENV=production node src/app.js
⚠️ 此方式关闭终端后服务会停止,仅用于临时测试。
server {
listen 80;
server_name api.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
location /uploads {
alias /opt/blog-backend/uploads;
expires 30d;
add_header Cache-Control "public, immutable";
}
}在项目根目录创建 Dockerfile:
FROM node:18-alpine
# 设置工作目录
WORKDIR /app
# 安装依赖
COPY package*.json ./
RUN npm install --production
# 复制项目代码
COPY . .
# 创建上传目录
RUN mkdir -p uploads
# 暴露端口
EXPOSE 3001
# 启动命令
CMD ["node", "src/app.js"]在项目根目录创建 docker-compose.yml:
version: '3.8'
services:
# Node.js 应用服务
app:
build: .
container_name: blog-backend
ports:
- "3001:3001"
environment:
- NODE_ENV=production
- PORT=3001
- DB_HOST=mysql
- DB_PORT=3306
- DB_USER=blog
- DB_PASSWORD=blog_password
- DB_NAME=personal_blog
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=
- JWT_SECRET=your-super-secret-jwt-key
- JWT_EXPIRES_IN=7d
- UPLOAD_PATH=uploads
- MAX_FILE_SIZE=5242880
volumes:
- ./uploads:/app/uploads
- ./logs:/app/logs
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
restart: always
networks:
- blog-network
# MySQL 数据库服务
mysql:
image: mysql:8.0
container_name: blog-mysql
environment:
- MYSQL_ROOT_PASSWORD=root_password
- MYSQL_DATABASE=personal_blog
- MYSQL_USER=blog
- MYSQL_PASSWORD=blog_password
volumes:
- mysql_data:/var/lib/mysql
- ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
restart: always
networks:
- blog-network
# Redis 缓存服务
redis:
image: redis:7-alpine
container_name: blog-redis
volumes:
- redis_data:/data
ports:
- "6379:6379"
restart: always
networks:
- blog-network
# 数据卷
volumes:
mysql_data:
redis_data:
# 网络
networks:
blog-network:
driver: bridge创建 mysql/my.cnf:
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
default-authentication-plugin=mysql_native_password
# 性能优化
innodb_buffer_pool_size=256M
max_connections=200
# 日志配置
slow_query_log=1
slow_query_log_file=/var/lib/mysql/slow.log
long_query_time=2 - "6379:6379"
restart: always
volumes: mysql_data: redis_data:
#### 4. 启动服务
```bash
# 构建并启动所有服务
docker-compose up -d
# 查看服务状态
docker-compose ps
# 查看日志
docker-compose logs -f app
等待 MySQL 完全启动后,执行:
docker-compose exec app npm run init-db# 停止服务
docker-compose down
# 停止并删除数据卷(谨慎使用)
docker-compose down -v
# 重启服务
docker-compose restart
# 查看容器日志
docker-compose logs -f [service_name]
# 进入容器
docker-compose exec app sh
# 更新部署
docker-compose pull
docker-compose up -d创建 .env 文件:
# 数据库
MYSQL_ROOT_PASSWORD=your_strong_root_password
MYSQL_DATABASE=personal_blog
MYSQL_USER=blog
MYSQL_PASSWORD=your_strong_password
# JWT
JWT_SECRET=your-super-secret-jwt-key-change-this
# 其他配置
NODE_ENV=production修改 docker-compose.yml 使用环境变量:
services:
app:
environment:
- JWT_SECRET=${JWT_SECRET}
# ... 其他配置
mysql:
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}当使用 Docker 部署时,Nginx 配置:
server {
listen 80;
server_name api.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
location /uploads {
proxy_pass http://127.0.0.1:3001/uploads;
expires 30d;
add_header Cache-Control "public, immutable";
}
}创建备份脚本 backup.sh:
#!/bin/bash
BACKUP_DIR="/opt/backups"
DATE=$(date +%Y%m%d_%H%M%S)
# 备份 MySQL
docker-compose exec -T mysql mysqldump -u root -p${MYSQL_ROOT_PASSWORD} personal_blog > ${BACKUP_DIR}/blog_${DATE}.sql
# 备份上传文件
tar -czf ${BACKUP_DIR}/uploads_${DATE}.tar.gz ./uploads
# 保留最近 7 天的备份
find ${BACKUP_DIR} -name "*.sql" -mtime +7 -delete
find ${BACKUP_DIR} -name "*.tar.gz" -mtime +7 -delete添加定时任务:
crontab -e
# 每天凌晨 2 点备份
0 2 * * * /opt/blog-backend/backup.shPOST /api/auth/login- 用户登录POST /api/auth/register- 用户注册GET /api/auth/profile- 获取用户信息PUT /api/auth/profile- 更新用户信息
GET /api/articles- 获取文章列表GET /api/articles/:id- 获取文章详情POST /api/articles- 创建文章(需认证)PUT /api/articles/:id- 更新文章(需认证)DELETE /api/articles/:id- 删除文章(需认证)
GET /api/categories- 获取分类列表POST /api/categories- 创建分类(需认证)PUT /api/categories/:id- 更新分类(需认证)DELETE /api/categories/:id- 删除分类(需认证)
GET /api/tags- 获取标签列表POST /api/tags- 创建标签(需认证)PUT /api/tags/:id- 更新标签(需认证)DELETE /api/tags/:id- 删除标签(需认证)
GET /api/menus- 获取菜单列表GET /api/themes- 获取主题配置GET /api/pages- 获取页面列表GET /api/dict- 获取字典数据GET /api/stats- 获取统计数据
POST /api/upload- 上传文件
| 特性 | systemd | PM2 | Docker |
|---|---|---|---|
| 安装难度 | ⭐ 简单 | ⭐⭐ 中等 | ⭐⭐ 中等 |
| 自动重启 | ✅ | ✅ | ✅ |
| 开机自启 | ✅ | ✅ | ✅ |
| 日志管理 | 系统日志 | 自带日志 | 容器日志 |
| 资源占用 | 低 | 中 | 较高 |
| 环境隔离 | ❌ | ❌ | ✅ |
| 一键部署 | ❌ | ❌ | ✅ |
| 适合场景 | 单服务器 | 单服务器 | 多环境/集群 |
选择建议:
- 新手/单服务器:使用 systemd(无需额外安装,Linux 自带)
- 需要监控面板:使用 PM2
- 需要环境隔离/快速迁移:使用 Docker
# 开发模式启动(热重载)
npm run dev
# 生产模式启动
npm start
# 初始化数据库
npm run init-db# 启动服务
sudo systemctl start blog-backend
# 停止服务
sudo systemctl stop blog-backend
# 重启服务
sudo systemctl restart blog-backend
# 查看状态
sudo systemctl status blog-backend
# 查看日志
sudo journalctl -u blog-backend -f
# 开机自启
sudo systemctl enable blog-backend# 查看 PM2 状态
pm2 status
# 查看日志
pm2 logs blog-backend
# 重启服务
pm2 restart blog-backend
# 停止服务
pm2 stop blog-backend
# 保存配置
pm2 save
# 开机自启
pm2 startup# 启动服务
docker-compose up -d
# 停止服务
docker-compose down
# 查看日志
docker-compose logs -f app
# 重启服务
docker-compose restart
# 进入容器
docker-compose exec app sh-
生产环境安全
- 务必修改 JWT_SECRET
- 使用强密码
- 配置防火墙,只开放必要端口
- 启用 HTTPS
-
数据库备份
- 定期备份 MySQL 数据
- 可以使用
mysqldump命令备份
-
文件上传
- 上传的文件保存在
uploads目录 - 生产环境建议配置 CDN 或对象存储
- 上传的文件保存在
-
日志管理
- 日志文件保存在
logs目录 - 建议配置日志轮转,防止磁盘占满
- 日志文件保存在
Copyright (c) 2026 wenjie1388
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.