如何在Node.js Sequelize中关联交易模型与用户表实现数据隔离?
解决方案
1. 修正关联关系:一对多才是正确选择
你考虑的一对一关系不合适,一个用户必然会有多个交易记录,所以User(用户)和 Transaction(交易)是一对多的关联:
- 在Transaction模型中添加
userId字段(作为关联User表id的外键) - 通过Sequelize定义两者的关联关系:
User模型代码示例:
const { Model, DataTypes } = require('sequelize'); const sequelize = require('../config/database'); class User extends Model {} User.init({ id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, username: { type: DataTypes.STRING, unique: true, allowNull: false }, password: { type: DataTypes.STRING, allowNull: false } }, { sequelize, modelName: 'user' }); // 定义关联:一个用户拥有多个交易 User.hasMany(require('./Transaction'), { foreignKey: 'userId' }); module.exports = User;
Transaction模型代码示例:
const { Model, DataTypes } = require('sequelize'); const sequelize = require('../config/database'); class Transaction extends Model {} Transaction.init({ id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, concept: { type: DataTypes.STRING, allowNull: false }, amount: { type: DataTypes.FLOAT, allowNull: false }, category: { type: DataTypes.STRING, allowNull: false }, userId: { // 添加外键,关联用户表 type: DataTypes.INTEGER, references: { model: 'users', // 对应User模型生成的表名(Sequelize默认复数形式) key: 'id' }, allowNull: false } }, { sequelize, modelName: 'transaction' }); // 定义关联:每个交易属于一个用户 Transaction.belongsTo(require('./User'), { foreignKey: 'userId' }); module.exports = Transaction;
2. 注册/登录核心实现(JWT + bcrypt)
注册时需要加密存储密码,登录时验证密码并返回JWT令牌,步骤如下:
安装依赖
npm install bcryptjs jsonwebtoken
注册路由示例
const express = require('express'); const router = express.Router(); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const User = require('../models/User'); router.post('/register', async (req, res) => { try { const { username, password } = req.body; // 检查用户名是否已存在 const existingUser = await User.findOne({ where: { username } }); if (existingUser) { return res.status(400).json({ message: '用户名已存在' }); } // 加密密码 const hashedPassword = await bcrypt.hash(password, 10); // 创建用户 const user = await User.create({ username, password: hashedPassword }); // 返回JWT令牌 const token = jwt.sign({ userId: user.id }, '你的密钥', { expiresIn: '7d' }); res.status(201).json({ token, userId: user.id }); } catch (err) { res.status(500).json({ message: '服务器错误' }); } });
登录路由示例
router.post('/login', async (req, res) => { try { const { username, password } = req.body; const user = await User.findOne({ where: { username } }); if (!user) { return res.status(400).json({ message: '用户名或密码错误' }); } // 验证密码 const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { return res.status(400).json({ message: '用户名或密码错误' }); } // 返回JWT令牌 const token = jwt.sign({ userId: user.id }, '你的密钥', { expiresIn: '7d' }); res.json({ token, userId: user.id }); } catch (err) { res.status(500).json({ message: '服务器错误' }); } });
3. 路由保护:确保用户仅能访问自己的交易
创建JWT验证中间件,解析令牌并获取当前用户ID:
const jwt = require('jsonwebtoken'); const authMiddleware = (req, res, next) => { const token = req.header('Authorization')?.replace('Bearer ', ''); if (!token) { return res.status(401).json({ message: '未授权,请先登录' }); } try { const decoded = jwt.verify(token, '你的密钥'); req.userId = decoded.userId; // 将用户ID挂载到请求对象上 next(); } catch (err) { res.status(401).json({ message: '无效的令牌' }); } }; module.exports = authMiddleware;
在交易相关路由中使用该中间件,并过滤当前用户的交易数据:
const express = require('express'); const router = express.Router(); const Transaction = require('../models/Transaction'); const authMiddleware = require('../middleware/auth'); // 获取当前用户的所有交易 router.get('/transactions', authMiddleware, async (req, res) => { try { const transactions = await Transaction.findAll({ where: { userId: req.userId } }); res.json(transactions); } catch (err) { res.status(500).json({ message: '服务器错误' }); } }); // 添加交易(自动关联当前用户) router.post('/transactions', authMiddleware, async (req, res) => { try { const { concept, amount, category } = req.body; const transaction = await Transaction.create({ concept, amount, category, userId: req.userId }); res.status(201).json(transaction); } catch (err) { res.status(500).json({ message: '服务器错误' }); } }); // 修改/删除交易同理,需先验证交易的userId与当前用户ID一致,防止越权操作
注意事项
- JWT密钥不要硬编码,建议使用环境变量(如
process.env.JWT_SECRET) - 密码加密的rounds设为10即可,过高会影响性能
- 修改或删除交易时,需额外校验目标交易的userId是否等于当前用户ID,避免越权操作
内容的提问来源于stack exchange,提问作者Gabriel Moyano
相关产品推荐
相关产品推荐

