前言:
????????在現代 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 => {// 在請求頭中添加tokenconst 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('請檢查賬號密碼')breakcase 403:// 處理禁止訪問console.log('禁止訪問')breakcase 404:// 處理未找到console.log('未找到')breakcase 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存入localStoragelocalStorage.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 Tokenif (!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 => {// 在請求頭中添加tokenconst token = localStorage.getItem('token');if (token) {config.headers.Authorization = `Bearer ${token}`;}return config;
}, err => {Promise.reject(error)
})
?模塊化:
export function getUsers() {return request({url: '/users',})
}
三、安全最佳實踐
- 密碼存儲:實際項目中應使用bcrypt等庫進行哈希處理
- ?環境變量:敏感信息(如數據庫密碼、JWT密鑰)應存儲在環境變量中
- HTTPS:生產環境必須啟用HTTPS
- SQL注入防護:始終使用參數化查詢
- Token安全:設置合理的過期時間,考慮實現刷新Token機制