|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
在现代Web开发中,Vue3作为一款流行的前端框架,通常与后端数据库配合使用以构建全栈应用。MySQL作为最流行的关系型数据库之一,经常被选为数据存储解决方案。然而,由于Vue3是一个前端框架,它不能直接连接MySQL数据库,而是需要通过后端服务作为中间层。本文将详细介绍在Vue3项目中连接MySQL数据库的最佳实践,以及解决常见问题的方案。
Vue3连接MySQL的基本架构
在前后端分离的架构中,Vue3作为前端框架运行在浏览器中,而MySQL数据库运行在服务器上。它们之间的通信必须通过后端服务进行。基本架构如下:
- Vue3前端应用 -> HTTP请求 -> 后端API服务 -> MySQL数据库
复制代码
这种架构有以下几个优势:
• 安全性:数据库不会直接暴露给客户端
• 解耦:前后端可以独立开发和部署
• 可扩展性:后端可以服务多个前端应用
最佳实践
1. 使用后端API作为中间层
在Vue3项目中,最佳实践是使用后端服务作为API中间层来处理数据库操作。常用的后端技术栈包括:
• Node.js + Express/Koa/NestJS
• PHP + Laravel
• Java + Spring Boot
• Python + Django/FastAPI
下面以Node.js + Express为例,介绍如何搭建后端服务:
- // server.js
- const express = require('express');
- const mysql = require('mysql2/promise');
- const cors = require('cors');
- const app = express();
- const port = 3000;
- // 中间件
- app.use(cors());
- app.use(express.json());
- // MySQL连接池配置
- const pool = mysql.createPool({
- host: 'localhost',
- user: 'root',
- password: 'password',
- database: 'vue3_mysql_demo',
- waitForConnections: true,
- connectionLimit: 10,
- queueLimit: 0
- });
- // 测试数据库连接
- app.get('/api/test-connection', async (req, res) => {
- try {
- const connection = await pool.getConnection();
- await connection.ping();
- connection.release();
- res.json({ success: true, message: '数据库连接成功' });
- } catch (error) {
- console.error('数据库连接失败:', error);
- res.status(500).json({ success: false, message: '数据库连接失败', error: error.message });
- }
- });
- app.listen(port, () => {
- console.log(`服务器运行在 http://localhost:${port}`);
- });
复制代码
2. RESTful API设计
设计良好的RESTful API可以使前后端交互更加清晰和高效。以下是一些设计原则:
• 使用合适的HTTP方法(GET、POST、PUT、DELETE等)
• 使用名词复数形式表示资源集合
• 使用清晰的URL结构
• 返回适当的HTTP状态码
例如,对于一个用户资源,可以设计如下API:
- // 用户相关API
- // 获取所有用户
- app.get('/api/users', async (req, res) => {
- try {
- const [rows] = await pool.query('SELECT * FROM users');
- res.json({ success: true, data: rows });
- } catch (error) {
- res.status(500).json({ success: false, message: '获取用户列表失败', error: error.message });
- }
- });
- // 获取单个用户
- app.get('/api/users/:id', async (req, res) => {
- try {
- const { id } = req.params;
- const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [id]);
-
- if (rows.length === 0) {
- return res.status(404).json({ success: false, message: '用户不存在' });
- }
-
- res.json({ success: true, data: rows[0] });
- } catch (error) {
- res.status(500).json({ success: false, message: '获取用户信息失败', error: error.message });
- }
- });
- // 创建用户
- app.post('/api/users', async (req, res) => {
- try {
- const { name, email, age } = req.body;
-
- // 验证请求数据
- if (!name || !email) {
- return res.status(400).json({ success: false, message: '姓名和邮箱是必填项' });
- }
-
- const [result] = await pool.query(
- 'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
- [name, email, age || null]
- );
-
- res.status(201).json({
- success: true,
- message: '用户创建成功',
- data: { id: result.insertId, name, email, age }
- });
- } catch (error) {
- // 处理唯一键冲突等错误
- if (error.code === 'ER_DUP_ENTRY') {
- return res.status(409).json({ success: false, message: '邮箱已存在' });
- }
-
- res.status(500).json({ success: false, message: '创建用户失败', error: error.message });
- }
- });
- // 更新用户
- app.put('/api/users/:id', async (req, res) => {
- try {
- const { id } = req.params;
- const { name, email, age } = req.body;
-
- // 检查用户是否存在
- const [existingUsers] = await pool.query('SELECT * FROM users WHERE id = ?', [id]);
- if (existingUsers.length === 0) {
- return res.status(404).json({ success: false, message: '用户不存在' });
- }
-
- // 构建更新语句
- const updateFields = [];
- const updateValues = [];
-
- if (name !== undefined) {
- updateFields.push('name = ?');
- updateValues.push(name);
- }
-
- if (email !== undefined) {
- updateFields.push('email = ?');
- updateValues.push(email);
- }
-
- if (age !== undefined) {
- updateFields.push('age = ?');
- updateValues.push(age);
- }
-
- if (updateFields.length === 0) {
- return res.status(400).json({ success: false, message: '没有提供要更新的字段' });
- }
-
- updateValues.push(id);
-
- await pool.query(
- `UPDATE users SET ${updateFields.join(', ')} WHERE id = ?`,
- updateValues
- );
-
- // 获取更新后的用户信息
- const [updatedUsers] = await pool.query('SELECT * FROM users WHERE id = ?', [id]);
-
- res.json({ success: true, message: '用户更新成功', data: updatedUsers[0] });
- } catch (error) {
- if (error.code === 'ER_DUP_ENTRY') {
- return res.status(409).json({ success: false, message: '邮箱已存在' });
- }
-
- res.status(500).json({ success: false, message: '更新用户失败', error: error.message });
- }
- });
- // 删除用户
- app.delete('/api/users/:id', async (req, res) => {
- try {
- const { id } = req.params;
-
- // 检查用户是否存在
- const [existingUsers] = await pool.query('SELECT * FROM users WHERE id = ?', [id]);
- if (existingUsers.length === 0) {
- return res.status(404).json({ success: false, message: '用户不存在' });
- }
-
- await pool.query('DELETE FROM users WHERE id = ?', [id]);
-
- res.json({ success: true, message: '用户删除成功' });
- } catch (error) {
- res.status(500).json({ success: false, message: '删除用户失败', error: error.message });
- }
- });
复制代码
3. GraphQL作为替代方案
除了RESTful API,GraphQL也是一种现代的API设计方式,它允许客户端精确地请求需要的数据。下面是使用Express和Apollo Server实现GraphQL的示例:
- // 安装依赖: npm install apollo-server-express graphql
- const { ApolloServer, gql } = require('apollo-server-express');
- // GraphQL schema定义
- const typeDefs = gql`
- type User {
- id: ID!
- name: String!
- email: String!
- age: Int
- }
- type Query {
- users: [User]
- user(id: ID!): User
- }
- type Mutation {
- createUser(name: String!, email: String!, age: Int): User
- updateUser(id: ID!, name: String, email: String, age: Int): User
- deleteUser(id: ID!): Boolean
- }
- `;
- // Resolver实现
- const resolvers = {
- Query: {
- users: async () => {
- const [rows] = await pool.query('SELECT * FROM users');
- return rows;
- },
- user: async (_, { id }) => {
- const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [id]);
- return rows[0];
- }
- },
- Mutation: {
- createUser: async (_, { name, email, age }) => {
- const [result] = await pool.query(
- 'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
- [name, email, age]
- );
- return { id: result.insertId, name, email, age };
- },
- updateUser: async (_, { id, name, email, age }) => {
- // 检查用户是否存在
- const [existingUsers] = await pool.query('SELECT * FROM users WHERE id = ?', [id]);
- if (existingUsers.length === 0) {
- throw new Error('用户不存在');
- }
-
- // 构建更新语句
- const updateFields = [];
- const updateValues = [];
-
- if (name !== undefined) {
- updateFields.push('name = ?');
- updateValues.push(name);
- }
-
- if (email !== undefined) {
- updateFields.push('email = ?');
- updateValues.push(email);
- }
-
- if (age !== undefined) {
- updateFields.push('age = ?');
- updateValues.push(age);
- }
-
- if (updateFields.length === 0) {
- throw new Error('没有提供要更新的字段');
- }
-
- updateValues.push(id);
-
- await pool.query(
- `UPDATE users SET ${updateFields.join(', ')} WHERE id = ?`,
- updateValues
- );
-
- // 获取更新后的用户信息
- const [updatedUsers] = await pool.query('SELECT * FROM users WHERE id = ?', [id]);
- return updatedUsers[0];
- },
- deleteUser: async (_, { id }) => {
- // 检查用户是否存在
- const [existingUsers] = await pool.query('SELECT * FROM users WHERE id = ?', [id]);
- if (existingUsers.length === 0) {
- throw new Error('用户不存在');
- }
-
- await pool.query('DELETE FROM users WHERE id = ?', [id]);
- return true;
- }
- }
- };
- // 创建Apollo Server
- const server = new ApolloServer({ typeDefs, resolvers });
- // 应用中间件
- server.applyMiddleware({ app, path: '/graphql' });
复制代码
4. 数据库连接池配置
使用数据库连接池可以显著提高应用性能,避免频繁创建和销毁连接的开销。以下是MySQL连接池的配置示例:
- const pool = mysql.createPool({
- host: 'localhost',
- user: 'root',
- password: 'password',
- database: 'vue3_mysql_demo',
- waitForConnections: true, // 当连接池没有可用连接时,是否等待(而不是抛出错误)
- connectionLimit: 10, // 连接池大小
- queueLimit: 0, // 排队等待的最大连接数(0表示不限制)
- namedPlaceholders: true, // 启用命名占位符
- dateStrings: true, // 返回日期作为字符串而不是Date对象
- supportBigNumbers: true, // 支持大数字(BIGINT和DECIMAL列)
- bigNumberStrings: true // 将大数字作为字符串返回
- });
- // 使用连接池执行查询
- async function query(sql, params) {
- let connection;
- try {
- connection = await pool.getConnection();
- const [rows] = await connection.query(sql, params);
- return rows;
- } finally {
- if (connection) connection.release();
- }
- }
- // 使用示例
- app.get('/api/users', async (req, res) => {
- try {
- const users = await query('SELECT * FROM users');
- res.json({ success: true, data: users });
- } catch (error) {
- res.status(500).json({ success: false, message: '获取用户列表失败', error: error.message });
- }
- });
复制代码
5. 安全性考虑
在连接MySQL数据库时,安全性是一个重要考虑因素。以下是一些最佳实践:
- // 安装依赖: npm install dotenv
- // 创建.env文件
- DB_HOST=localhost
- DB_USER=root
- DB_PASSWORD=your_password
- DB_NAME=vue3_mysql_demo
- // 在服务器代码中加载环境变量
- require('dotenv').config();
- const pool = mysql.createPool({
- host: process.env.DB_HOST,
- user: process.env.DB_USER,
- password: process.env.DB_PASSWORD,
- database: process.env.DB_NAME,
- // 其他配置...
- });
复制代码
使用参数化查询或预处理语句来防止SQL注入攻击:
- // 不安全的方式 - 容易受到SQL注入攻击
- app.get('/api/users/unsafe', async (req, res) => {
- const { name } = req.query;
- const [rows] = await pool.query(`SELECT * FROM users WHERE name = '${name}'`); // 危险!
- res.json(rows);
- });
- // 安全的方式 - 使用参数化查询
- app.get('/api/users/safe', async (req, res) => {
- const { name } = req.query;
- const [rows] = await pool.query('SELECT * FROM users WHERE name = ?', [name]); // 安全
- res.json(rows);
- });
- // 使用命名占位符(需要配置namedPlaceholders: true)
- app.get('/api/users/named', async (req, res) => {
- const { name, email } = req.query;
- const [rows] = await pool.query(
- 'SELECT * FROM users WHERE name = :name AND email = :email',
- { name, email }
- );
- res.json(rows);
- });
复制代码- // 安装依赖: npm install jsonwebtoken bcryptjs
- const jwt = require('jsonwebtoken');
- const bcrypt = require('bcryptjs');
- // 用户登录
- app.post('/api/login', async (req, res) => {
- try {
- const { email, password } = req.body;
-
- // 查找用户
- const [users] = await pool.query('SELECT * FROM users WHERE email = ?', [email]);
- if (users.length === 0) {
- return res.status(401).json({ success: false, message: '邮箱或密码错误' });
- }
-
- const user = users[0];
-
- // 验证密码
- const isPasswordValid = await bcrypt.compare(password, user.password);
- if (!isPasswordValid) {
- return res.status(401).json({ success: false, message: '邮箱或密码错误' });
- }
-
- // 生成JWT令牌
- const token = jwt.sign(
- { id: user.id, email: user.email },
- process.env.JWT_SECRET || 'your-secret-key',
- { expiresIn: '24h' }
- );
-
- res.json({
- success: true,
- message: '登录成功',
- data: {
- token,
- user: {
- id: user.id,
- name: user.name,
- email: user.email
- }
- }
- });
- } catch (error) {
- res.status(500).json({ success: false, message: '登录失败', error: error.message });
- }
- });
- // 认证中间件
- function authenticateToken(req, res, next) {
- const authHeader = req.headers['authorization'];
- const token = authHeader && authHeader.split(' ')[1];
-
- if (!token) {
- return res.status(401).json({ success: false, message: '访问令牌缺失' });
- }
-
- jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key', (err, user) => {
- if (err) {
- return res.status(403).json({ success: false, message: '无效的访问令牌' });
- }
- req.user = user;
- next();
- });
- }
- // 受保护的路由
- app.get('/api/profile', authenticateToken, async (req, res) => {
- try {
- const [users] = await pool.query('SELECT id, name, email, age FROM users WHERE id = ?', [req.user.id]);
- if (users.length === 0) {
- return res.status(404).json({ success: false, message: '用户不存在' });
- }
-
- res.json({ success: true, data: users[0] });
- } catch (error) {
- res.status(500).json({ success: false, message: '获取用户信息失败', error: error.message });
- }
- });
复制代码
Vue3前端调用API
在Vue3项目中,我们可以使用Axios或Fetch API来调用后端API。以下是使用Axios的示例:
1. 安装和配置Axios
- // src/utils/api.js
- import axios from 'axios';
- // 创建axios实例
- const api = axios.create({
- baseURL: 'http://localhost:3000/api',
- timeout: 10000,
- headers: {
- 'Content-Type': 'application/json'
- }
- });
- // 请求拦截器 - 添加认证令牌
- api.interceptors.request.use(
- config => {
- const token = localStorage.getItem('token');
- if (token) {
- config.headers.Authorization = `Bearer ${token}`;
- }
- return config;
- },
- error => {
- return Promise.reject(error);
- }
- );
- // 响应拦截器 - 处理错误
- api.interceptors.response.use(
- response => {
- return response.data;
- },
- error => {
- if (error.response) {
- // 服务器返回了错误状态码
- const { status, data } = error.response;
-
- if (status === 401) {
- // 未授权,清除令牌并跳转到登录页
- localStorage.removeItem('token');
- window.location.href = '/login';
- }
-
- return Promise.reject({
- success: false,
- message: data.message || '请求失败',
- status
- });
- } else if (error.request) {
- // 请求已发出但没有收到响应
- return Promise.reject({
- success: false,
- message: '服务器无响应,请检查网络连接'
- });
- } else {
- // 请求设置出错
- return Promise.reject({
- success: false,
- message: '请求设置错误: ' + error.message
- });
- }
- }
- );
- export default api;
复制代码
2. 创建API服务
- // src/services/userService.js
- import api from '@/utils/api';
- export default {
- // 获取所有用户
- getUsers() {
- return api.get('/users');
- },
-
- // 获取单个用户
- getUser(id) {
- return api.get(`/users/${id}`);
- },
-
- // 创建用户
- createUser(userData) {
- return api.post('/users', userData);
- },
-
- // 更新用户
- updateUser(id, userData) {
- return api.put(`/users/${id}`, userData);
- },
-
- // 删除用户
- deleteUser(id) {
- return api.delete(`/users/${id}`);
- },
-
- // 用户登录
- login(credentials) {
- return api.post('/login', credentials);
- },
-
- // 获取用户资料
- getProfile() {
- return api.get('/profile');
- }
- };
复制代码
3. 在Vue组件中使用API
- <!-- src/views/UserList.vue -->
- <template>
- <div class="user-list">
- <h1>用户列表</h1>
-
- <div v-if="loading" class="loading">加载中...</div>
-
- <div v-else-if="error" class="error">{{ error }}</div>
-
- <div v-else>
- <table class="user-table">
- <thead>
- <tr>
- <th>ID</th>
- <th>姓名</th>
- <th>邮箱</th>
- <th>年龄</th>
- <th>操作</th>
- </tr>
- </thead>
- <tbody>
- <tr v-for="user in users" :key="user.id">
- <td>{{ user.id }}</td>
- <td>{{ user.name }}</td>
- <td>{{ user.email }}</td>
- <td>{{ user.age || '未设置' }}</td>
- <td>
- <button @click="editUser(user)" class="btn-edit">编辑</button>
- <button @click="deleteUser(user.id)" class="btn-delete">删除</button>
- </td>
- </tr>
- </tbody>
- </table>
-
- <button @click="showAddForm = true" class="btn-add">添加用户</button>
- </div>
-
- <!-- 添加/编辑用户表单 -->
- <div v-if="showAddForm || editingUser" class="user-form">
- <h2>{{ editingUser ? '编辑用户' : '添加用户' }}</h2>
-
- <div class="form-group">
- <label for="name">姓名:</label>
- <input
- type="text"
- id="name"
- v-model="formData.name"
- required
- />
- </div>
-
- <div class="form-group">
- <label for="email">邮箱:</label>
- <input
- type="email"
- id="email"
- v-model="formData.email"
- required
- />
- </div>
-
- <div class="form-group">
- <label for="age">年龄:</label>
- <input
- type="number"
- id="age"
- v-model="formData.age"
- min="1"
- />
- </div>
-
- <div class="form-actions">
- <button @click="saveUser" class="btn-save">保存</button>
- <button @click="cancelForm" class="btn-cancel">取消</button>
- </div>
- </div>
- </div>
- </template>
- <script>
- import { ref, reactive, onMounted } from 'vue';
- import userService from '@/services/userService';
- export default {
- name: 'UserList',
- setup() {
- const users = ref([]);
- const loading = ref(false);
- const error = ref(null);
- const showAddForm = ref(false);
- const editingUser = ref(null);
- const formData = reactive({
- name: '',
- email: '',
- age: null
- });
- // 获取用户列表
- const fetchUsers = async () => {
- loading.value = true;
- error.value = null;
-
- try {
- const response = await userService.getUsers();
- if (response.success) {
- users.value = response.data;
- } else {
- error.value = response.message || '获取用户列表失败';
- }
- } catch (err) {
- error.value = err.message || '获取用户列表失败';
- console.error('获取用户列表错误:', err);
- } finally {
- loading.value = false;
- }
- };
- // 编辑用户
- const editUser = (user) => {
- editingUser.value = user;
- formData.name = user.name;
- formData.email = user.email;
- formData.age = user.age;
- showAddForm.value = true;
- };
- // 删除用户
- const deleteUser = async (id) => {
- if (!confirm('确定要删除这个用户吗?')) {
- return;
- }
-
- try {
- const response = await userService.deleteUser(id);
- if (response.success) {
- // 从列表中移除已删除的用户
- users.value = users.value.filter(user => user.id !== id);
- alert('用户删除成功');
- } else {
- alert(response.message || '删除用户失败');
- }
- } catch (err) {
- alert(err.message || '删除用户失败');
- console.error('删除用户错误:', err);
- }
- };
- // 保存用户
- const saveUser = async () => {
- // 验证表单
- if (!formData.name || !formData.email) {
- alert('姓名和邮箱是必填项');
- return;
- }
-
- try {
- let response;
-
- if (editingUser.value) {
- // 更新用户
- response = await userService.updateUser(editingUser.value.id, {
- name: formData.name,
- email: formData.email,
- age: formData.age
- });
-
- if (response.success) {
- // 更新列表中的用户数据
- const index = users.value.findIndex(u => u.id === editingUser.value.id);
- if (index !== -1) {
- users.value[index] = response.data;
- }
- alert('用户更新成功');
- }
- } else {
- // 创建新用户
- response = await userService.createUser({
- name: formData.name,
- email: formData.email,
- age: formData.age
- });
-
- if (response.success) {
- // 添加新用户到列表
- users.value.push(response.data);
- alert('用户创建成功');
- }
- }
-
- if (!response.success) {
- alert(response.message || '保存用户失败');
- } else {
- cancelForm();
- }
- } catch (err) {
- alert(err.message || '保存用户失败');
- console.error('保存用户错误:', err);
- }
- };
- // 取消表单
- const cancelForm = () => {
- showAddForm.value = false;
- editingUser.value = null;
- formData.name = '';
- formData.email = '';
- formData.age = null;
- };
- // 组件挂载时获取用户列表
- onMounted(() => {
- fetchUsers();
- });
- return {
- users,
- loading,
- error,
- showAddForm,
- editingUser,
- formData,
- editUser,
- deleteUser,
- saveUser,
- cancelForm
- };
- }
- };
- </script>
- <style scoped>
- .user-list {
- max-width: 1000px;
- margin: 0 auto;
- padding: 20px;
- }
- .loading, .error {
- padding: 20px;
- text-align: center;
- background-color: #f5f5f5;
- border-radius: 4px;
- margin: 20px 0;
- }
- .error {
- color: #f44336;
- background-color: #ffebee;
- }
- .user-table {
- width: 100%;
- border-collapse: collapse;
- margin: 20px 0;
- }
- .user-table th, .user-table td {
- padding: 12px 15px;
- text-align: left;
- border-bottom: 1px solid #ddd;
- }
- .user-table th {
- background-color: #f5f5f5;
- font-weight: bold;
- }
- .user-table tr:hover {
- background-color: #f9f9f9;
- }
- .btn-edit, .btn-delete, .btn-add, .btn-save, .btn-cancel {
- padding: 8px 12px;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- margin-right: 5px;
- }
- .btn-edit {
- background-color: #2196f3;
- color: white;
- }
- .btn-delete {
- background-color: #f44336;
- color: white;
- }
- .btn-add {
- background-color: #4caf50;
- color: white;
- margin-top: 20px;
- }
- .btn-save {
- background-color: #4caf50;
- color: white;
- }
- .btn-cancel {
- background-color: #9e9e9e;
- color: white;
- }
- .user-form {
- margin-top: 30px;
- padding: 20px;
- border: 1px solid #ddd;
- border-radius: 4px;
- background-color: #f9f9f9;
- }
- .form-group {
- margin-bottom: 15px;
- }
- .form-group label {
- display: block;
- margin-bottom: 5px;
- font-weight: bold;
- }
- .form-group input {
- width: 100%;
- padding: 8px;
- border: 1px solid #ddd;
- border-radius: 4px;
- }
- .form-actions {
- margin-top: 20px;
- }
- </style>
复制代码
常见问题及解决方案
1. CORS问题
当Vue3前端应用和后端API服务运行在不同的域或端口时,会遇到跨域资源共享(CORS)问题。以下是解决方案:
- // 使用cors中间件
- const cors = require('cors');
- app.use(cors());
- // 或者自定义CORS配置
- app.use(cors({
- origin: 'http://localhost:8080', // 允许的前端域名
- methods: ['GET', 'POST', 'PUT', 'DELETE'],
- allowedHeaders: ['Content-Type', 'Authorization'],
- credentials: true // 允许发送cookie
- }));
复制代码
在Vue项目的vue.config.js文件中配置代理:
- // vue.config.js
- module.exports = {
- devServer: {
- proxy: {
- '/api': {
- target: 'http://localhost:3000',
- changeOrigin: true,
- pathRewrite: {
- '^/api': '/api' // 不重写路径
- }
- }
- }
- }
- };
复制代码
然后修改API实例的baseURL:
- // src/utils/api.js
- const api = axios.create({
- baseURL: '/api', // 使用相对路径,通过代理转发
- timeout: 10000,
- headers: {
- 'Content-Type': 'application/json'
- }
- });
复制代码
2. 数据库连接泄露
数据库连接泄露是指应用程序没有正确释放数据库连接,导致连接池中的连接被耗尽。以下是解决方案:
- // 不安全的做法 - 可能导致连接泄露
- app.get('/api/users/unsafe', async (req, res) => {
- const connection = await pool.getConnection();
- const [rows] = await connection.query('SELECT * FROM users');
- // 如果在查询和释放连接之间发生错误,连接可能不会被释放
- connection.release();
- res.json(rows);
- });
- // 安全的做法 - 使用try-finally确保连接释放
- app.get('/api/users/safe', async (req, res) => {
- let connection;
- try {
- connection = await pool.getConnection();
- const [rows] = await connection.query('SELECT * FROM users');
- res.json(rows);
- } catch (error) {
- console.error('获取用户列表失败:', error);
- res.status(500).json({ success: false, message: '获取用户列表失败' });
- } finally {
- if (connection) connection.release(); // 确保连接总是被释放
- }
- });
复制代码- // 使用连接池的query方法,它会自动获取和释放连接
- app.get('/api/users/auto', async (req, res) => {
- try {
- const [rows] = await pool.query('SELECT * FROM users');
- res.json(rows);
- } catch (error) {
- console.error('获取用户列表失败:', error);
- res.status(500).json({ success: false, message: '获取用户列表失败' });
- }
- });
复制代码- // 定期记录连接池状态
- setInterval(() => {
- console.log('连接池状态:', {
- totalConnections: pool.pool.allConnections.length,
- activeConnections: pool.pool.allConnections.length - pool.pool.freeConnections.length,
- freeConnections: pool.pool.freeConnections.length,
- queuedRequests: pool.pool.acquiringConnections.length
- });
- }, 30000); // 每30秒记录一次
复制代码
3. SQL注入防护
SQL注入是一种常见的安全漏洞,攻击者可以通过恶意输入来执行未授权的SQL命令。以下是防护措施:
- // 不安全的做法 - 容易受到SQL注入攻击
- app.get('/api/users/search/unsafe', async (req, res) => {
- const { term } = req.query;
- // 直接拼接SQL字符串,容易受到SQL注入攻击
- const sql = `SELECT * FROM users WHERE name LIKE '%${term}%'`;
- const [rows] = await pool.query(sql);
- res.json(rows);
- });
- // 安全的做法 - 使用参数化查询
- app.get('/api/users/search/safe', async (req, res) => {
- const { term } = req.query;
- // 使用参数化查询,防止SQL注入
- const [rows] = await pool.query(
- 'SELECT * FROM users WHERE name LIKE ?',
- [`%${term}%`]
- );
- res.json(rows);
- });
复制代码
使用ORM(对象关系映射)工具如Sequelize、TypeORM等可以进一步减少SQL注入的风险:
- // 安装Sequelize: npm install sequelize mysql2
- const { Sequelize, DataTypes } = require('sequelize');
- // 初始化Sequelize
- const sequelize = new Sequelize(
- process.env.DB_NAME,
- process.env.DB_USER,
- process.env.DB_PASSWORD,
- {
- host: process.env.DB_HOST,
- dialect: 'mysql'
- }
- );
- // 定义用户模型
- const User = sequelize.define('User', {
- name: {
- type: DataTypes.STRING,
- allowNull: false
- },
- email: {
- type: DataTypes.STRING,
- allowNull: false,
- unique: true
- },
- age: {
- type: DataTypes.INTEGER,
- allowNull: true
- }
- });
- // 使用ORM进行查询
- app.get('/api/users/orm', async (req, res) => {
- try {
- const users = await User.findAll();
- res.json({ success: true, data: users });
- } catch (error) {
- res.status(500).json({ success: false, message: '获取用户列表失败', error: error.message });
- }
- });
- // 使用ORM进行条件查询
- app.get('/api/users/search/orm', async (req, res) => {
- try {
- const { term } = req.query;
- const users = await User.findAll({
- where: {
- name: {
- [Sequelize.Op.like]: `%${term}%`
- }
- }
- });
- res.json({ success: true, data: users });
- } catch (error) {
- res.status(500).json({ success: false, message: '搜索用户失败', error: error.message });
- }
- });
复制代码
4. 性能优化
在Vue3项目连接MySQL数据库时,性能优化是一个重要考虑因素。以下是一些优化策略:
- // 使用索引优化查询
- // 确保在常用查询条件的列上创建索引
- // 例如:CREATE INDEX idx_users_email ON users(email);
- // 避免N+1查询问题
- // 不好的做法 - 可能导致N+1查询问题
- app.get('/api/users-with-posts/inefficient', async (req, res) => {
- try {
- const [users] = await pool.query('SELECT * FROM users');
-
- // 对每个用户执行额外的查询获取帖子
- for (const user of users) {
- const [posts] = await pool.query('SELECT * FROM posts WHERE userId = ?', [user.id]);
- user.posts = posts;
- }
-
- res.json({ success: true, data: users });
- } catch (error) {
- res.status(500).json({ success: false, message: '获取用户和帖子失败', error: error.message });
- }
- });
- // 好的做法 - 使用JOIN或子查询一次性获取所有数据
- app.get('/api/users-with-posts/efficient', async (req, res) => {
- try {
- // 使用JOIN一次性获取所有数据
- const [rows] = await pool.query(`
- SELECT u.*, p.id as postId, p.title, p.content
- FROM users u
- LEFT JOIN posts p ON u.id = p.userId
- ORDER BY u.id, p.id
- `);
-
- // 处理结果,将帖子分组到用户对象中
- const users = [];
- let currentUser = null;
-
- for (const row of rows) {
- if (!currentUser || currentUser.id !== row.id) {
- currentUser = {
- id: row.id,
- name: row.name,
- email: row.email,
- age: row.age,
- posts: []
- };
- users.push(currentUser);
- }
-
- if (row.postId) {
- currentUser.posts.push({
- id: row.postId,
- title: row.title,
- content: row.content
- });
- }
- }
-
- res.json({ success: true, data: users });
- } catch (error) {
- res.status(500).json({ success: false, message: '获取用户和帖子失败', error: error.message });
- }
- });
复制代码- // 实现分页查询
- app.get('/api/users/paginated', async (req, res) => {
- try {
- // 获取分页参数
- const page = parseInt(req.query.page) || 1;
- const limit = parseInt(req.query.limit) || 10;
- const offset = (page - 1) * limit;
-
- // 获取总记录数
- const [countResult] = await pool.query('SELECT COUNT(*) as total FROM users');
- const total = countResult[0].total;
-
- // 获取分页数据
- const [users] = await pool.query(
- 'SELECT id, name, email, age FROM users LIMIT ? OFFSET ?',
- [limit, offset]
- );
-
- // 计算总页数
- const totalPages = Math.ceil(total / limit);
-
- res.json({
- success: true,
- data: {
- users,
- pagination: {
- page,
- limit,
- total,
- totalPages
- }
- }
- });
- } catch (error) {
- res.status(500).json({ success: false, message: '获取分页用户列表失败', error: error.message });
- }
- });
复制代码- // 安装内存缓存: npm install memory-cache
- const cache = require('memory-cache');
- // 实现简单的缓存中间件
- function cacheMiddleware(duration) {
- return (req, res, next) => {
- const key = '__express__' + req.originalUrl || req.url;
- const cachedBody = cache.get(key);
-
- if (cachedBody) {
- res.send(cachedBody);
- return;
- }
-
- res.sendResponse = res.send;
- res.send = (body) => {
- cache.put(key, body, duration * 1000);
- res.sendResponse(body);
- };
-
- next();
- };
- }
- // 使用缓存中间件
- app.get('/api/users/cached', cacheMiddleware(60), async (req, res) => {
- try {
- const [users] = await pool.query('SELECT * FROM users');
- res.json({ success: true, data: users });
- } catch (error) {
- res.status(500).json({ success: false, message: '获取用户列表失败', error: error.message });
- }
- });
复制代码
5. 错误处理
良好的错误处理可以提高应用的健壮性和用户体验。以下是一些错误处理的最佳实践:
- // 全局错误处理中间件
- app.use((err, req, res, next) => {
- console.error('全局错误:', err);
-
- // 根据错误类型返回不同的状态码
- if (err.code === 'ER_DUP_ENTRY') {
- return res.status(409).json({ success: false, message: '数据已存在' });
- }
-
- if (err.name === 'ValidationError') {
- return res.status(400).json({ success: false, message: '数据验证失败', details: err.message });
- }
-
- if (err.name === 'UnauthorizedError') {
- return res.status(401).json({ success: false, message: '未授权访问' });
- }
-
- // 默认服务器错误
- res.status(500).json({ success: false, message: '服务器内部错误' });
- });
- // 在路由中使用try-catch捕获错误
- app.get('/api/users/error', async (req, res, next) => {
- try {
- // 可能出错的代码
- const [users] = await pool.query('SELECT * FROM non_existent_table');
- res.json({ success: true, data: users });
- } catch (error) {
- // 将错误传递给全局错误处理中间件
- next(error);
- }
- });
复制代码- // 处理需要多个数据库操作的事务
- app.post('/api/transfer', async (req, res) => {
- const { fromUserId, toUserId, amount } = req.body;
-
- if (!fromUserId || !toUserId || !amount) {
- return res.status(400).json({ success: false, message: '缺少必要参数' });
- }
-
- if (fromUserId === toUserId) {
- return res.status(400).json({ success: false, message: '转出和转入用户不能相同' });
- }
-
- let connection;
-
- try {
- // 获取连接并开始事务
- connection = await pool.getConnection();
- await connection.beginTransaction();
-
- // 检查转出用户余额
- const [fromUsers] = await connection.query(
- 'SELECT * FROM accounts WHERE userId = ? FOR UPDATE',
- [fromUserId]
- );
-
- if (fromUsers.length === 0) {
- throw new Error('转出用户不存在');
- }
-
- const fromUser = fromUsers[0];
-
- if (fromUser.balance < amount) {
- throw new Error('余额不足');
- }
-
- // 检查转入用户
- const [toUsers] = await connection.query(
- 'SELECT * FROM accounts WHERE userId = ? FOR UPDATE',
- [toUserId]
- );
-
- if (toUsers.length === 0) {
- throw new Error('转入用户不存在');
- }
-
- const toUser = toUsers[0];
-
- // 更新转出用户余额
- await connection.query(
- 'UPDATE accounts SET balance = balance - ? WHERE userId = ?',
- [amount, fromUserId]
- );
-
- // 更新转入用户余额
- await connection.query(
- 'UPDATE accounts SET balance = balance + ? WHERE userId = ?',
- [amount, toUserId]
- );
-
- // 记录交易
- await connection.query(
- 'INSERT INTO transactions (fromUserId, toUserId, amount, createdAt) VALUES (?, ?, ?, NOW())',
- [fromUserId, toUserId, amount]
- );
-
- // 提交事务
- await connection.commit();
-
- res.json({ success: true, message: '转账成功' });
- } catch (error) {
- // 发生错误,回滚事务
- if (connection) {
- await connection.rollback();
- }
-
- console.error('转账失败:', error);
- res.status(500).json({ success: false, message: error.message || '转账失败' });
- } finally {
- if (connection) {
- connection.release();
- }
- }
- });
复制代码
完整示例:一个简单的CRUD应用
下面是一个完整的Vue3项目连接MySQL数据库的CRUD(创建、读取、更新、删除)应用示例。
1. 数据库准备
首先,创建MySQL数据库和表:
- -- 创建数据库
- CREATE DATABASE vue3_mysql_demo;
- -- 使用数据库
- USE vue3_mysql_demo;
- -- 创建用户表
- CREATE TABLE users (
- id INT AUTO_INCREMENT PRIMARY KEY,
- name VARCHAR(100) NOT NULL,
- email VARCHAR(100) NOT NULL UNIQUE,
- age INT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
- );
- -- 插入一些示例数据
- INSERT INTO users (name, email, age) VALUES
- ('张三', 'zhangsan@example.com', 25),
- ('李四', 'lisi@example.com', 30),
- ('王五', 'wangwu@example.com', 28);
复制代码
2. 后端服务
创建一个Node.js/Express后端服务:
- // server.js
- require('dotenv').config();
- const express = require('express');
- const mysql = require('mysql2/promise');
- const cors = require('cors');
- const bcrypt = require('bcryptjs');
- const jwt = require('jsonwebtoken');
- const app = express();
- const port = process.env.PORT || 3000;
- // 中间件
- app.use(cors());
- app.use(express.json());
- // MySQL连接池配置
- const pool = mysql.createPool({
- host: process.env.DB_HOST || 'localhost',
- user: process.env.DB_USER || 'root',
- password: process.env.DB_PASSWORD || '',
- database: process.env.DB_NAME || 'vue3_mysql_demo',
- waitForConnections: true,
- connectionLimit: 10,
- queueLimit: 0,
- namedPlaceholders: true
- });
- // 测试数据库连接
- app.get('/api/test-connection', async (req, res) => {
- try {
- const connection = await pool.getConnection();
- await connection.ping();
- connection.release();
- res.json({ success: true, message: '数据库连接成功' });
- } catch (error) {
- console.error('数据库连接失败:', error);
- res.status(500).json({ success: false, message: '数据库连接失败', error: error.message });
- }
- });
- // 用户相关API
- // 获取所有用户
- app.get('/api/users', async (req, res) => {
- try {
- const [rows] = await pool.query('SELECT id, name, email, age FROM users');
- res.json({ success: true, data: rows });
- } catch (error) {
- res.status(500).json({ success: false, message: '获取用户列表失败', error: error.message });
- }
- });
- // 获取单个用户
- app.get('/api/users/:id', async (req, res) => {
- try {
- const { id } = req.params;
- const [rows] = await pool.query('SELECT id, name, email, age FROM users WHERE id = ?', [id]);
-
- if (rows.length === 0) {
- return res.status(404).json({ success: false, message: '用户不存在' });
- }
-
- res.json({ success: true, data: rows[0] });
- } catch (error) {
- res.status(500).json({ success: false, message: '获取用户信息失败', error: error.message });
- }
- });
- // 创建用户
- app.post('/api/users', async (req, res) => {
- try {
- const { name, email, age } = req.body;
-
- // 验证请求数据
- if (!name || !email) {
- return res.status(400).json({ success: false, message: '姓名和邮箱是必填项' });
- }
-
- const [result] = await pool.query(
- 'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
- [name, email, age || null]
- );
-
- res.status(201).json({
- success: true,
- message: '用户创建成功',
- data: { id: result.insertId, name, email, age }
- });
- } catch (error) {
- // 处理唯一键冲突等错误
- if (error.code === 'ER_DUP_ENTRY') {
- return res.status(409).json({ success: false, message: '邮箱已存在' });
- }
-
- res.status(500).json({ success: false, message: '创建用户失败', error: error.message });
- }
- });
- // 更新用户
- app.put('/api/users/:id', async (req, res) => {
- try {
- const { id } = req.params;
- const { name, email, age } = req.body;
-
- // 检查用户是否存在
- const [existingUsers] = await pool.query('SELECT * FROM users WHERE id = ?', [id]);
- if (existingUsers.length === 0) {
- return res.status(404).json({ success: false, message: '用户不存在' });
- }
-
- // 构建更新语句
- const updateFields = [];
- const updateValues = [];
-
- if (name !== undefined) {
- updateFields.push('name = ?');
- updateValues.push(name);
- }
-
- if (email !== undefined) {
- updateFields.push('email = ?');
- updateValues.push(email);
- }
-
- if (age !== undefined) {
- updateFields.push('age = ?');
- updateValues.push(age);
- }
-
- if (updateFields.length === 0) {
- return res.status(400).json({ success: false, message: '没有提供要更新的字段' });
- }
-
- updateValues.push(id);
-
- await pool.query(
- `UPDATE users SET ${updateFields.join(', ')} WHERE id = ?`,
- updateValues
- );
-
- // 获取更新后的用户信息
- const [updatedUsers] = await pool.query('SELECT id, name, email, age FROM users WHERE id = ?', [id]);
-
- res.json({ success: true, message: '用户更新成功', data: updatedUsers[0] });
- } catch (error) {
- if (error.code === 'ER_DUP_ENTRY') {
- return res.status(409).json({ success: false, message: '邮箱已存在' });
- }
-
- res.status(500).json({ success: false, message: '更新用户失败', error: error.message });
- }
- });
- // 删除用户
- app.delete('/api/users/:id', async (req, res) => {
- try {
- const { id } = req.params;
-
- // 检查用户是否存在
- const [existingUsers] = await pool.query('SELECT * FROM users WHERE id = ?', [id]);
- if (existingUsers.length === 0) {
- return res.status(404).json({ success: false, message: '用户不存在' });
- }
-
- await pool.query('DELETE FROM users WHERE id = ?', [id]);
-
- res.json({ success: true, message: '用户删除成功' });
- } catch (error) {
- res.status(500).json({ success: false, message: '删除用户失败', error: error.message });
- }
- });
- // 全局错误处理中间件
- app.use((err, req, res, next) => {
- console.error('全局错误:', err);
-
- if (err.code === 'ER_DUP_ENTRY') {
- return res.status(409).json({ success: false, message: '数据已存在' });
- }
-
- res.status(500).json({ success: false, message: '服务器内部错误' });
- });
- // 启动服务器
- app.listen(port, () => {
- console.log(`服务器运行在 http://localhost:${port}`);
- });
复制代码
3. Vue3前端应用
创建Vue3前端应用,实现用户管理功能:
- <!-- src/App.vue -->
- <template>
- <div id="app">
- <nav class="navbar">
- <div class="nav-brand">Vue3 & MySQL Demo</div>
- <div class="nav-links">
- <router-link to="/" class="nav-link">首页</router-link>
- <router-link to="/users" class="nav-link">用户管理</router-link>
- </div>
- </nav>
-
- <div class="container">
- <router-view />
- </div>
-
- <footer class="footer">
- <p>© 2023 Vue3 & MySQL Demo. All rights reserved.</p>
- </footer>
- </div>
- </template>
- <script>
- export default {
- name: 'App'
- }
- </script>
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
- body {
- font-family: 'Arial', sans-serif;
- line-height: 1.6;
- color: #333;
- background-color: #f5f5f5;
- }
- #app {
- display: flex;
- flex-direction: column;
- min-height: 100vh;
- }
- .navbar {
- display: flex;
- justify-content: space-between;
- align-items: center;
- background-color: #333;
- color: white;
- padding: 1rem 2rem;
- }
- .nav-brand {
- font-size: 1.5rem;
- font-weight: bold;
- }
- .nav-links {
- display: flex;
- }
- .nav-link {
- color: white;
- text-decoration: none;
- margin-left: 1rem;
- padding: 0.5rem;
- border-radius: 4px;
- transition: background-color 0.3s;
- }
- .nav-link:hover {
- background-color: #555;
- }
- .router-link-active {
- background-color: #555;
- }
- .container {
- flex: 1;
- max-width: 1200px;
- margin: 2rem auto;
- padding: 0 1rem;
- width: 100%;
- }
- .footer {
- background-color: #333;
- color: white;
- text-align: center;
- padding: 1rem;
- margin-top: auto;
- }
- </style>
复制代码- <!-- src/views/UserList.vue -->
- <template>
- <div class="user-list">
- <h1>用户管理</h1>
-
- <div class="actions">
- <button @click="showAddForm = true" class="btn-add">添加用户</button>
- <div class="search">
- <input
- type="text"
- v-model="searchTerm"
- placeholder="搜索用户..."
- @input="searchUsers"
- />
- </div>
- </div>
-
- <div v-if="loading" class="loading">加载中...</div>
-
- <div v-else-if="error" class="error">{{ error }}</div>
-
- <div v-else-if="filteredUsers.length === 0" class="no-data">
- {{ searchTerm ? '没有找到匹配的用户' : '暂无用户数据' }}
- </div>
-
- <div v-else>
- <div class="user-grid">
- <div v-for="user in filteredUsers" :key="user.id" class="user-card">
- <div class="user-avatar">
- {{ user.name.charAt(0).toUpperCase() }}
- </div>
- <div class="user-info">
- <h3>{{ user.name }}</h3>
- <p>{{ user.email }}</p>
- <p v-if="user.age">年龄: {{ user.age }}</p>
- </div>
- <div class="user-actions">
- <button @click="editUser(user)" class="btn-edit">编辑</button>
- <button @click="deleteUser(user.id)" class="btn-delete">删除</button>
- </div>
- </div>
- </div>
- </div>
-
- <!-- 添加/编辑用户表单 -->
- <div v-if="showAddForm || editingUser" class="modal">
- <div class="modal-content">
- <div class="modal-header">
- <h2>{{ editingUser ? '编辑用户' : '添加用户' }}</h2>
- <button @click="cancelForm" class="close-btn">×</button>
- </div>
-
- <div class="modal-body">
- <div class="form-group">
- <label for="name">姓名:</label>
- <input
- type="text"
- id="name"
- v-model="formData.name"
- required
- />
- </div>
-
- <div class="form-group">
- <label for="email">邮箱:</label>
- <input
- type="email"
- id="email"
- v-model="formData.email"
- required
- />
- </div>
-
- <div class="form-group">
- <label for="age">年龄:</label>
- <input
- type="number"
- id="age"
- v-model="formData.age"
- min="1"
- />
- </div>
- </div>
-
- <div class="modal-footer">
- <button @click="saveUser" class="btn-save">保存</button>
- <button @click="cancelForm" class="btn-cancel">取消</button>
- </div>
- </div>
- </div>
-
- <!-- 删除确认对话框 -->
- <div v-if="showDeleteConfirm" class="modal">
- <div class="modal-content confirm-dialog">
- <div class="modal-header">
- <h2>确认删除</h2>
- <button @click="showDeleteConfirm = false" class="close-btn">×</button>
- </div>
-
- <div class="modal-body">
- <p>确定要删除用户 "{{ userToDelete?.name }}" 吗?此操作不可撤销。</p>
- </div>
-
- <div class="modal-footer">
- <button @click="confirmDelete" class="btn-delete">确认删除</button>
- <button @click="showDeleteConfirm = false" class="btn-cancel">取消</button>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script>
- import { ref, reactive, computed, onMounted } from 'vue';
- import userService from '@/services/userService';
- export default {
- name: 'UserList',
- setup() {
- const users = ref([]);
- const loading = ref(false);
- const error = ref(null);
- const searchTerm = ref('');
- const showAddForm = ref(false);
- const editingUser = ref(null);
- const showDeleteConfirm = ref(false);
- const userToDelete = ref(null);
- const formData = reactive({
- name: '',
- email: '',
- age: null
- });
- // 过滤用户列表
- const filteredUsers = computed(() => {
- if (!searchTerm.value) return users.value;
-
- const term = searchTerm.value.toLowerCase();
- return users.value.filter(user =>
- user.name.toLowerCase().includes(term) ||
- user.email.toLowerCase().includes(term)
- );
- });
- // 获取用户列表
- const fetchUsers = async () => {
- loading.value = true;
- error.value = null;
-
- try {
- const response = await userService.getUsers();
- if (response.success) {
- users.value = response.data;
- } else {
- error.value = response.message || '获取用户列表失败';
- }
- } catch (err) {
- error.value = err.message || '获取用户列表失败';
- console.error('获取用户列表错误:', err);
- } finally {
- loading.value = false;
- }
- };
- // 搜索用户
- const searchUsers = () => {
- // 使用计算属性filteredUsers实现搜索
- };
- // 编辑用户
- const editUser = (user) => {
- editingUser.value = user;
- formData.name = user.name;
- formData.email = user.email;
- formData.age = user.age;
- showAddForm.value = true;
- };
- // 删除用户
- const deleteUser = (id) => {
- const user = users.value.find(u => u.id === id);
- if (user) {
- userToDelete.value = user;
- showDeleteConfirm.value = true;
- }
- };
- // 确认删除
- const confirmDelete = async () => {
- if (!userToDelete.value) return;
-
- try {
- const response = await userService.deleteUser(userToDelete.value.id);
- if (response.success) {
- // 从列表中移除已删除的用户
- users.value = users.value.filter(user => user.id !== userToDelete.value.id);
- showDeleteConfirm.value = false;
- userToDelete.value = null;
- } else {
- alert(response.message || '删除用户失败');
- }
- } catch (err) {
- alert(err.message || '删除用户失败');
- console.error('删除用户错误:', err);
- }
- };
- // 保存用户
- const saveUser = async () => {
- // 验证表单
- if (!formData.name || !formData.email) {
- alert('姓名和邮箱是必填项');
- return;
- }
-
- try {
- let response;
-
- if (editingUser.value) {
- // 更新用户
- response = await userService.updateUser(editingUser.value.id, {
- name: formData.name,
- email: formData.email,
- age: formData.age
- });
-
- if (response.success) {
- // 更新列表中的用户数据
- const index = users.value.findIndex(u => u.id === editingUser.value.id);
- if (index !== -1) {
- users.value[index] = response.data;
- }
- alert('用户更新成功');
- }
- } else {
- // 创建新用户
- response = await userService.createUser({
- name: formData.name,
- email: formData.email,
- age: formData.age
- });
-
- if (response.success) {
- // 添加新用户到列表
- users.value.push(response.data);
- alert('用户创建成功');
- }
- }
-
- if (!response.success) {
- alert(response.message || '保存用户失败');
- } else {
- cancelForm();
- }
- } catch (err) {
- alert(err.message || '保存用户失败');
- console.error('保存用户错误:', err);
- }
- };
- // 取消表单
- const cancelForm = () => {
- showAddForm.value = false;
- editingUser.value = null;
- formData.name = '';
- formData.email = '';
- formData.age = null;
- };
- // 组件挂载时获取用户列表
- onMounted(() => {
- fetchUsers();
- });
- return {
- users,
- loading,
- error,
- searchTerm,
- filteredUsers,
- showAddForm,
- editingUser,
- showDeleteConfirm,
- userToDelete,
- formData,
- searchUsers,
- editUser,
- deleteUser,
- confirmDelete,
- saveUser,
- cancelForm
- };
- }
- };
- </script>
- <style scoped>
- .user-list {
- max-width: 1200px;
- margin: 0 auto;
- }
- h1 {
- color: #2c3e50;
- margin-bottom: 1.5rem;
- text-align: center;
- }
- .actions {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 1.5rem;
- }
- .search input {
- padding: 0.5rem;
- border: 1px solid #ddd;
- border-radius: 4px;
- width: 250px;
- }
- .loading, .error, .no-data {
- padding: 2rem;
- text-align: center;
- background-color: #f5f5f5;
- border-radius: 8px;
- margin: 1rem 0;
- }
- .error {
- color: #f44336;
- background-color: #ffebee;
- }
- .user-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
- gap: 1.5rem;
- }
- .user-card {
- background-color: white;
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- padding: 1.5rem;
- display: flex;
- align-items: center;
- transition: transform 0.3s, box-shadow 0.3s;
- }
- .user-card:hover {
- transform: translateY(-5px);
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
- }
- .user-avatar {
- width: 60px;
- height: 60px;
- border-radius: 50%;
- background-color: #42b983;
- color: white;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 1.5rem;
- font-weight: bold;
- margin-right: 1rem;
- flex-shrink: 0;
- }
- .user-info {
- flex: 1;
- }
- .user-info h3 {
- margin-bottom: 0.5rem;
- color: #2c3e50;
- }
- .user-info p {
- margin: 0.25rem 0;
- color: #666;
- }
- .user-actions {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- }
- .btn-add, .btn-edit, .btn-delete, .btn-save, .btn-cancel {
- padding: 0.5rem 1rem;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-weight: bold;
- transition: background-color 0.3s;
- }
- .btn-add {
- background-color: #42b983;
- color: white;
- }
- .btn-edit {
- background-color: #2196f3;
- color: white;
- }
- .btn-delete {
- background-color: #f44336;
- color: white;
- }
- .btn-save {
- background-color: #42b983;
- color: white;
- }
- .btn-cancel {
- background-color: #9e9e9e;
- color: white;
- }
- .modal {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, 0.5);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1000;
- }
- .modal-content {
- background-color: white;
- border-radius: 8px;
- width: 90%;
- max-width: 500px;
- max-height: 90vh;
- overflow-y: auto;
- }
- .modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 1rem;
- border-bottom: 1px solid #eee;
- }
- .modal-header h2 {
- margin: 0;
- color: #2c3e50;
- }
- .close-btn {
- background: none;
- border: none;
- font-size: 1.5rem;
- cursor: pointer;
- color: #999;
- }
- .close-btn:hover {
- color: #333;
- }
- .modal-body {
- padding: 1rem;
- }
- .form-group {
- margin-bottom: 1rem;
- }
- .form-group label {
- display: block;
- margin-bottom: 0.5rem;
- font-weight: bold;
- color: #333;
- }
- .form-group input {
- width: 100%;
- padding: 0.75rem;
- border: 1px solid #ddd;
- border-radius: 4px;
- font-size: 1rem;
- }
- .modal-footer {
- display: flex;
- justify-content: flex-end;
- gap: 0.5rem;
- padding: 1rem;
- border-top: 1px solid #eee;
- }
- .confirm-dialog {
- max-width: 400px;
- }
- .confirm-dialog .modal-body {
- padding: 1.5rem;
- }
- .confirm-dialog p {
- margin: 0;
- line-height: 1.5;
- }
- </style>
复制代码
总结与最佳实践回顾
在Vue3项目中连接MySQL数据库时,遵循以下最佳实践可以确保应用的安全性、性能和可维护性:
1. 架构设计
• 采用前后端分离架构,Vue3作为前端,后端服务作为中间层连接MySQL
• 使用RESTful API或GraphQL设计清晰的接口
• 考虑使用微服务架构处理复杂业务逻辑
2. 数据库连接
• 使用连接池管理数据库连接,避免频繁创建和销毁连接
• 确保正确释放数据库连接,防止连接泄露
• 监控连接池状态,及时调整连接池大小
3. 安全性
• 使用环境变量存储敏感信息,如数据库凭据
• 实现参数化查询或使用ORM工具防止SQL注入
• 实现认证和授权机制,保护敏感API
• 使用HTTPS加密传输数据
• 实现请求限流和防攻击措施
4. 性能优化
• 优化数据库查询,使用索引提高查询效率
• 避免N+1查询问题,使用JOIN或子查询一次性获取相关数据
• 实现分页查询,减少单次请求的数据量
• 使用缓存策略减少数据库访问
• 压缩响应数据,减少网络传输量
5. 错误处理
• 实现全局错误处理中间件,统一处理错误
• 提供有意义的错误信息,便于调试
• 记录错误日志,便于问题追踪
• 实现事务处理,确保数据一致性
6. 代码组织
• 分离业务逻辑和数据访问层
• 使用服务类封装API调用
• 实现可重用的组件和工具函数
• 编写单元测试和集成测试,确保代码质量
通过遵循这些最佳实践,你可以构建一个安全、高效、可维护的Vue3与MySQL集成应用。记住,技术选择应该根据项目需求和团队技能进行调整,没有一种方案适用于所有情况。
版权声明
1、转载或引用本网站内容(Vue3项目中连接MySQL数据库的最佳实践与常见问题解决方案)须注明原网址及作者(威震华夏关云长),并标明本网站网址(https://pixtech.org/)。
2、对于不当转载或引用本网站内容而引起的民事纷争、行政处理或其他损失,本网站不承担责任。
3、对不遵守本声明或其他违法、恶意使用本网站内容者,本网站保留追究其法律责任的权利。
本文地址: https://pixtech.org/thread-39744-1-1.html
|
|