Node+Express+MySQL 后端生产环境部署,实现注册功能(三)
一、部署前准备
- 本地环境:MacOS(开发端)
- 服务器环境:阿里云 Ubuntu 22.04 轻量应用服务器
- 技术栈:Node.js + Express + MySQL
- 核心目标:将本地开发完成的 Express 后端项目部署到阿里云,实现公网访问接口
二、服务器环境配置(最终生效配置)
1. 登录服务器
通过 Mac 终端 SSH 连接服务器:
ssh root@你的服务器公网IP # 例如:ssh root@47.101.129.155
输入服务器登录密码即可进入。
2. 安装核心依赖软件
# 更新系统包
sudo apt update && sudo apt upgrade -y
# 安装Node.js(v16+)
curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt install -y nodejs
# 安装MySQL服务器
sudo apt install -y mysql-server
# 安装PM2(Node服务进程管理)
sudo npm install pm2 -g
# 安装Nginx(反向代理)
sudo apt install -y nginx
创建本地数据库
-
确保数据库已创建:用 MySQL Bench 连接本地 MySQL(root/admin123/3306),创建数据库
mydb(字符集utf8mb4,排序规则utf8mb4_unicode_ci)。
三、创建用户表(存储注册信息)
在 MySQL Bench 中,对 mydb 数据库执行以下 SQL,创建 users 表(用于存储注册用户):
USE mydb; -- 切换到 mydb 数据库
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY, -- 自增主键
email VARCHAR(100) NOT NULL UNIQUE, -- 邮箱(唯一,避免重复注册)
password VARCHAR(255) NOT NULL, -- 加密后的密码(bcrypt 加密后长度固定60)
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, -- 注册时间
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -- 更新时间
);
四 新建项目
创建文件夹node-api-test
-
安装依赖:需要
mysql2(数据库连接)、bcrypt(密码加密,不能明文存储)、express-validator(参数校验),dotenv 判断环境变量
npm init -y
npm install mysql2 bcrypt express-validator deotnev
2. 多环境配置文件(区分测试 / 正式)
在项目根目录创建 多个 .env 文件,分别对应不同环境:
project/
├── .env.development # 开发环境(本地调试)
├── .env.production # 生产环境(正式服务器)
├── .env.test # 测试环境(可选,测试服务器)
└── .gitignore # 忽略 .env* 文件,避免提交到 Git
文件内容示例:
NODE_ENV=development # 标识环境
DB_HOST=localhost # 本地数据库地址
DB_USER=root # 本地数据库账号
DB_PASSWORD=admin123 # 本地数据库密码
DB_NAME=mydb # 本地数据库名
API_PORT=3000 # 开发环境端口
.env.production(生产环境):
NODE_ENV=production
DB_HOST=10.0.0.1 # 服务器数据库地址(内网 IP)
DB_USER=prod_user # 服务器数据库账号(非 root,更安全)
DB_PASSWORD=Prod@123 # 服务器数据库密码(复杂密码)
DB_NAME=mydb_prod # 生产环境数据库名(可与开发环境不同)
API_PORT=3000 # 生产环境端口
3. 在代码中加载对应环境的配置
在db/mysql.js,根据 NODE_ENV 自动加载对应的 .env 文件:
// db/mysql.js(ESM 版)
import mysql from 'mysql2/promise';
import dotenv from 'dotenv';
import path from 'path';
import { fileURLToPath } from 'url';
// 解决 ESM 中 __dirname 问题
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 1. 确定当前环境(默认 development)
const env = process.env.NODE_ENV || 'development';
// 2. 加载对应环境的 .env 文件(如 .env.development 或 .env.production)
const envPath = path.resolve(__dirname, `../.env.${env}`);
dotenv.config({ path: envPath }); // 加载指定路径的 .env 文件
// 3. 从 process.env 中读取配置(环境变量全部是字符串类型)
const dbConfig = {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: Number(process.env.DB_PORT) || 3306, // 转换为数字
connectionLimit: 10,
};
// 创建连接池
const pool = mysql.createPool(dbConfig);
// 测试连接时打印当前环境
export async function testDbConnection() {
try {
await pool.getConnection();
console.log(`✅ 数据库连接成功(环境:${env},数据库:${dbConfig.database}`);
} catch (err) {
console.error(`❌ 数据库连接失败(环境:${env}):`, err.message);
throw err;
}
}
export { pool };
app.js如下:
import express from 'express'
import bodyParser from 'body-parser'
import userRouter from './routes/user.js'
import { testDbConnection } from './db/mysql.js'
import HttpError from './utils/HttpError.js' // 导入自定义错误类
const app = express()
// 从环境变量读取端口(对应.env中的API_PORT)
const port = process.env.API_PORT || 3000
// 解析JSON请求(必须,否则无法获取req.body)
app.use(bodyParser.json())
// 挂载用户模块路由
app.use('/api/user', userRouter)
// 全局错误处理中间件(必须放在所有路由和中间件之后)
app.use((err, req, res, next) => {
// 1. 处理自定义HttpError
if (err instanceof HttpError) {
return res.status(err.statusCode).json({
status: err.statusCode, // 业务错误状态码(如400)
message: err.message, // 错误提示信息
errors: err.errors, // 详细错误列表(如参数校验错误)
})
}
// 2. 处理系统错误(如数据库连接失败、代码bug等)
console.error('系统错误堆栈:', err.stack) // 打印堆栈,方便后端调试
res.status(500).json({
status: 500,
message:
process.env.NODE_ENV === 'production'
? '服务器内部错误,请稍后重试' // 生产环境隐藏具体错误
: `系统错误:${err.message}`, // 开发环境显示具体错误(便于调试)
errors: [],
})
})
// 启动服务(端口来自环境变量)
app.listen(port, () => {
console.log(
`服务启动成功(环境:${process.env.NODE_ENV}):http://localhost:${port}`
)
testDbConnection() // 启动时验证数据库连接
})
注册接口编写
在根目录下新建routes/user.js
import express from 'express'
import { body, validationResult } from 'express-validator'
import bcrypt from 'bcrypt'
import { pool } from '../db/mysql.js'
import HttpError from '../utils/HttpError.js'
const router = express.Router()
// 注册接口:POST /api/user/register
router.post(
'/register',
// 参数校验(字段名与前端传入、数据库字段一致)
[
body('email').isEmail().withMessage('邮箱格式错误'), // 对应数据库email字段
body('password').isLength({ min: 6 }).withMessage('密码至少6位'), // 对应password字段
body('nickname')
.optional()
.isLength({ max: 50 })
.withMessage('昵称最多50字'), // 对应nickname字段
],
async (req, res, next) => {
try {
// 校验参数
const errors = validationResult(req)
if (!errors.isEmpty()) {
throw new HttpError(400, '参数校验失败', errors.array())
}
// 解构前端传入的参数(字段名与数据库字段一致)
const { email, password, nickname } = req.body
// 1. 检查邮箱是否已注册(SQL中使用email字段,与数据库一致)
const [existingUsers] = await pool.query(
'SELECT id FROM users WHERE email = ?', // WHERE条件用email字段
[email]
)
if (existingUsers.length > 0) {
throw new HttpError(400, '该邮箱已被注册')
}
// 2. 密码加密
// const hashedPassword = await bcrypt.hash(password, 10)
// 3. 插入数据库(字段名与数据库表完全一致)
const [result] = await pool.query(
'INSERT INTO users (email, password, nickname) VALUES (?, ?, ?)', // 字段顺序:email, password, nickname
[email, password, nickname || null] // 对应字段的值
)
// 4. 返回结果(包含数据库自动生成的id和字段)
res.status(200).json({
code: 200,
message: '注册成功',
data: {
userId: result.insertId, // 数据库自增id
email: email, // 与数据库email字段一致
nickname: nickname || null, // 与数据库nickname字段一致
createdAt: new Date().toISOString(),
},
})
} catch (err) {
next(err)
}
}
)
// 获取所有用户
router.get('/allUsers', async (req, res, next) => {
try {
const [users] = await pool.query('SELECT * FROM users')
res.status(200).json({
code: 200,
message: '获取成功',
data: users,
})
} catch (err) {
next(err)
}
})
export default router
本地postman测试
数据库查看这条数据
下一篇学习如何把代码发布到服务器,通过域名来访问接口,实现注册,顺便把前端页面也发布上去