Node.js链接MySql

发布于:2025-07-19 ⋅ 阅读:(18) ⋅ 点赞:(0)

前言:

        在现代 Web 开发和后端服务中,Node.js 因其高性能和异步特性被广泛使用。MySQL 作为流行的关系型数据库之一,提供了稳定高效的数据存储和管理能力。将 Node.js 与 MySQL 结合,可以构建强大的数据驱动型应用。

一、环境准备

安装必要依赖

npm install express mysql cors jsonwebtoken body-parser

数据库准备

        创建一个名为 demo 的数据库,并添加 user 表: 

CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `password` varchar(100) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

二、核心代码实现

数据库连接配置 (dbconfig.js)

const mysql = require('mysql');

// 建立连接池
const pool = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: 'root',
    database: 'demo',
})

/**
 * 执行SQL查询的Promise封装
 * @param {string} sql SQL语句
 * @param {array} values 参数值
 * @returns {Promise} 查询结果
 */
const query = (sql, values) => {
    return new Promise((resolve, reject) => {
        pool.getConnection((err, connection) => {
            if (err) {
                console.log('数据库连接错误', err)
                reject(err)
                return
            }
            connection.query(sql, values, (err, rows) => {
                if (err) {
                    console.log('SQL执行错误', err)
                    reject(err)
                    return
                }
                resolve(rows)
                connection.release() // 释放连接回连接池
            })
        })
    })
}

module.exports = query;

   关键点说明:

  • 使用链接池提高数据库性能,方便后期的统一管理。
  • Promise 封装使异步操作更易管理。 

Express 服务器配置 (server.js)

const express = require('express')
const query = require('./utils/dbconfig')
const cors = require('cors')
const jwt = require('jsonwebtoken')
const bodyParser = require('body-parser')

const app = express()
const PORT = 3000;

// 安全配置
const JWT_SECRET = 'your-secret-key-here'; // 生产环境应使用环境变量

// CORS跨域配置
const corsOptions = {
    origin: ['http://localhost:5173'], // 允许的前端地址
    methods: ['GET', 'POST', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization']
}

// 中间件
app.use(cors(corsOptions))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))

// 启动服务器
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`)
})

        到这里基本的配置就完成了,现在就可以去完成接口的实现了。

接口实现

登录接口:

app.post('/login', async (req, res) => {
    try {
        const { username, password } = req.body;
        
        // 参数验证 对请求体中的username和password进行非空检查
        if (!username || !password) {
            return res.status(400).json({
                success: false,
                message: '用户名和密码不能为空'
            });
        }

        // 数据库查询验证 使用预处理SQL语句查询匹配的用户
        const sql = 'SELECT * FROM user WHERE username = ? AND password = ?';
        const users = await query(sql, [username, password]);
        
        if (users.length === 0) {
            return res.status(401).json({
                success: false,
                message: '用户名或密码错误'
            });
        }

        const user = users[0];

        // 使用jsonwebtoken生成访问令牌
        const token = jwt.sign(
            { userId: user.id, username: user.username },
            JWT_SECRET,
            { expiresIn: '1h' } // Token有效期1小时
        );

        // 返回成功响应
        res.json({
            success: true,
            message: '登录成功',
            token: token,
            user: {
                id: user.id,
                username: user.username
            }
        });
    } catch (err) {
        console.error('登录错误:', err);
        res.status(500).json({
            success: false,
            message: '服务器错误'
        });
    }
});

         使用Express框架处理POST请求。主要功能包括参数验证、数据库查询、JWT生成和错误处理。

        这边其实在实际项目中像密码这类敏感数据为了安全应该使用哈希存储而非明文查询。

const sql = 'SELECT * FROM user WHERE username = ?';
const users = await query(sql, [username]);
const isValid = await bcrypt.compare(password, users[0].password_hash);
验证:

        到这里就可以来验证我们完成的接口了,这里可以给大家推荐一个VScode中的插件:

        可以看到接口是可以正常运行的,并返回了一个token给我们,这边我写了一个vue的例子,包含了 Vue Router 和  axios的二次封装,这些我在之前的博客都详细的讲过,可以点击对应的文章查看我之前的博客。

        我做了一个简单的登录页面:

<template>
    <div class="login">
        <h1>Login</h1>
        <form>
            <label for="username">Username:</label>
            <input type="text" id="username" v-model="username">
            <label for="password">Password:</label>
            <input type="password" id="password" v-model="password">
            <button type="button" @click="login">Login</button>
        </form>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import router from '../router';

const username = ref('');
const password = ref('');

function login() {
}
</script>

axios封装:

// 1.引入axios
import axios from "axios";

// 2.创建axios对象
const service = axios.create({
    baseURL: 'http://localhost:3000'
});

// 3.设置请求拦截器 请求前进行一些操作 比如添加请求头、设置loading动画等
service.interceptors.request.use(config => {
    // 在请求头中添加token
    const token = localStorage.getItem('token');
    if (token) {
        config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
}, err => {
    Promise.reject(error)
})

// 4.设置响应拦截器 后端给前端返回数据 可以处理http状态码
service.interceptors.response.use(
    (response) => {
        if (response.status === 200) {
            return response.data
        }
    },
    error => {
        if (error.response) {
            switch (error.response.status) {
                case 401:
                    // 处理未授权
                    console.log('请检查账号密码')
                    break
                case 403:
                    // 处理禁止访问
                    console.log('禁止访问')
                    break
                case 404:
                    // 处理未找到
                    console.log('未找到')
                    break
                case 500:
                    // 处理服务器错误
                    console.log('服务器错误')
                    break
            }
        }
        throw error
    }
)

export default service

 模块化:

import request from '../request';

// 登录
export function getLogin(userName,password){
    return request({
        url: '/login',
        method: 'post',
        data: {
            username: userName,
            password: password
        }
    })
}

        这样我们直接调用这个函数就能发送请求了。 

        在登录页面中导入对应的函数并使用:

import { getLogin } from '../utils/api/users';

function login() {
    getLogin(username.value, password.value).then(res => {
        if(res.success){
            // 将token存入localStorage
            localStorage.setItem('token', res.token);
            // 跳转到首页
            router.push('/');
        }
    }).catch(err => {
        throw err;
    });
}

        在路由中配置路由守卫来防止url跳转:

// 配置路由守卫
router.beforeEach((to, from, next) => {
    const token = localStorage.getItem('token')
    const isAuthenticated = !!token
    
    // 如果路由需要认证但用户未登录
    if (to.meta.requiresAuth && !isAuthenticated) {
        return next('/login')
    }
    
    // 如果路由要求未登录(如登录页)但用户已登录
    if (to.meta.requiresGuest && isAuthenticated) {
        return next('/home') // 重定向到首页
    }
    
    // 其他情况正常放行
    next()
})

        这样基本的登录功能就完成了

Token 验证中间件

        在一些请求中需要携带token,否则无法请求到数据,就可以封装一个token验证中间件。

function authenticateToken(req, res, next) {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1]; // 提取Bearer Token
    
    if (!token) {
        return res.status(401).json({
            success: false,
            message: '未提供认证Token'
        });
    }

    // 验证Token有效性
    jwt.verify(token, JWT_SECRET, (err, user) => {
        if (err) {
            return res.status(403).json({
                success: false,
                message: '无效的Token'
            });
        }
        req.user = user; // 将用户信息附加到请求对象
        next(); // 继续后续处理
    });
}

获取用户列表

app.get('/users', authenticateToken, async (req, res) => {
    try {
        // 只返回必要字段,不包含密码
        const sql = 'SELECT id, username FROM user';
        const users = await query(sql)
        res.json({
            success: true,
            data: users
        })
    } catch (err) {
        console.error('Database error:', err);
        res.status(500).json({
            success: false,
            message: '获取用户列表失败'
        });
    }
})

        获取用户列表时就需要在请求头中添加token,在axios的二次封装中,在请求拦截器中就可以在请求头中统一添加token。

service.interceptors.request.use(config => {
    // 在请求头中添加token
    const token = localStorage.getItem('token');
    if (token) {
        config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
}, err => {
    Promise.reject(error)
})

 模块化:

export function getUsers() {
    return request({
        url: '/users',
    })
}

三、安全最佳实践

  1. 密码存储:实际项目中应使用bcrypt等库进行哈希处理
  2.  环境变量:敏感信息(如数据库密码、JWT密钥)应存储在环境变量中
  3. HTTPS:生产环境必须启用HTTPS
  4. SQL注入防护:始终使用参数化查询
  5. Token安全:设置合理的过期时间,考虑实现刷新Token机制

网站公告

今日签到

点亮在社区的每一天
去签到