You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Express.js与Passport.js,如何设计数据库实现用户仅操作自身帖子?

嘿,针对你的问题,我来梳理一套兼顾简洁性和可维护性的解决方案,完全适配你的Express + Passport + PostgreSQL技术栈:

第一步:先搞定数据库表关联(基础中的基础)

既然你提到两张表(应该是users和posts吧?)没关联,那首先得给posts表加个外键字段,关联到users的主键,这样才能追踪每个帖子的所有者。执行下面的SQL语句:

ALTER TABLE posts ADD COLUMN user_id INTEGER REFERENCES users(id) ON DELETE CASCADE;

ON DELETE CASCADE的作用是:如果用户被删除,他的所有帖子也会跟着自动删除,避免数据库里出现无主的“孤儿帖子”,符合业务逻辑。

第二步:确保Passport认证的基础配置到位

假设你已经通过Passport实现了用户登录(比如用Local策略或者JWT策略),要保证登录成功后,用户信息会被挂载到req.user上——这是Passport的默认行为,也是后续权限控制的核心依据。如果还没完成这一步,优先把Passport的登录流程跑通,确保req.user能正确获取当前登录用户的ID和其他信息。

第三步:核心权限控制策略(简洁可维护的关键)

这里的核心思路是把权限校验逻辑抽成可复用的中间件,让路由只专注于业务逻辑,代码结构更清晰。

1. 封装帖子所有者校验中间件

创建一个单独的中间件文件middleware/isPostOwner.js,把“验证当前登录用户是否是帖子所有者”的逻辑封装进去:

// middleware/isPostOwner.js
const db = require('../db'); // 替换成你的PostgreSQL连接模块

module.exports = async (req, res, next) => {
  try {
    // 根据帖子ID查询对应的user_id
    const postResult = await db.query('SELECT user_id FROM posts WHERE id = $1', [req.params.id]);
    
    if (postResult.rows.length === 0) {
      return res.status(404).json({ message: '帖子不存在' });
    }
    
    // 对比当前登录用户ID和帖子的user_id
    if (postResult.rows[0].user_id !== req.user.id) {
      return res.status(403).json({ message: '你没有权限修改/删除这个帖子' });
    }
    
    // 校验通过,继续执行后续路由处理函数
    next();
  } catch (err) {
    console.error(err.message);
    res.status(500).send('服务器内部错误');
  }
};

2. 用中间件保护路由

在你的帖子路由文件里,给需要权限控制的接口加上Passport的认证中间件和我们刚写的所有者校验中间件:

// routes/posts.js
const express = require('express');
const router = express.Router();
const passport = require('passport');
const isPostOwner = require('../middleware/isPostOwner');
const db = require('../db');

// 只有登录用户才能创建帖子(创建时自动关联当前用户ID)
router.post('/', passport.authenticate('jwt', { session: false }), async (req, res) => {
  try {
    const newPost = await db.query(
      'INSERT INTO posts (title, content, user_id) VALUES ($1, $2, $3) RETURNING *',
      [req.body.title, req.body.content, req.user.id]
    );
    res.json(newPost.rows[0]);
  } catch (err) {
    console.error(err.message);
    res.status(500).send('服务器错误');
  }
});

// 只有帖子所有者才能更新帖子
router.put('/:id', passport.authenticate('jwt', { session: false }), isPostOwner, async (req, res) => {
  try {
    const updatedPost = await db.query(
      'UPDATE posts SET title = $1, content = $2 WHERE id = $3 RETURNING *',
      [req.body.title, req.body.content, req.params.id]
    );
    res.json(updatedPost.rows[0]);
  } catch (err) {
    console.error(err.message);
    res.status(500).send('服务器错误');
  }
});

// 只有帖子所有者才能删除帖子
router.delete('/:id', passport.authenticate('jwt', { session: false }), isPostOwner, async (req, res) => {
  try {
    await db.query('DELETE FROM posts WHERE id = $1', [req.params.id]);
    res.json({ message: '帖子已成功删除' });
  } catch (err) {
    console.error(err.message);
    res.status(500).send('服务器错误');
  }
});

// 所有人都可以查看帖子(可选:关联用户信息返回)
router.get('/:id', async (req, res) => {
  try {
    const post = await db.query(
      'SELECT p.*, u.username FROM posts p JOIN users u ON p.user_id = u.id WHERE p.id = $1',
      [req.params.id]
    );
    res.json(post.rows[0]);
  } catch (err) {
    console.error(err.message);
    res.status(500).send('服务器错误');
  }
});

module.exports = router;

这里的passport.authenticate('jwt', { session: false })是假设你用JWT策略——如果用的是Session认证,去掉{ session: false }就行。

第四步:提升可维护性的最佳实践

1. 分层架构:把数据库操作抽成Service层

把数据库CRUD逻辑从路由里抽出来,放到单独的services/postService.js里,让路由只处理请求响应,代码更清晰:

// services/postService.js
const db = require('../db');

exports.createPost = async (title, content, userId) => {
  const result = await db.query(
    'INSERT INTO posts (title, content, user_id) VALUES ($1, $2, $3) RETURNING *',
    [title, content, userId]
  );
  return result.rows[0];
};

exports.getPostById = async (postId) => {
  const result = await db.query(
    'SELECT p.*, u.username FROM posts p JOIN users u ON p.user_id = u.id WHERE p.id = $1',
    [postId]
  );
  return result.rows[0];
};

// 其他更新、删除方法同理

然后路由里就可以简化成:

router.post('/', passport.authenticate('jwt', { session: false }), async (req, res) => {
  try {
    const newPost = await postService.createPost(req.body.title, req.body.content, req.user.id);
    res.json(newPost);
  } catch (err) {
    console.error(err.message);
    res.status(500).send('服务器错误');
  }
});

2. 统一错误处理

创建全局错误处理中间件,避免每个路由里重复写try-catch(或者用express-async-errors包自动捕获异步错误):

// middleware/errorHandler.js
module.exports = (err, req, res, next) => {
  console.error(err.stack);
  const statusCode = err.statusCode || 500;
  const message = err.message || '服务器内部错误';
  res.status(statusCode).json({ message });
};

然后在app.js里注册:

const errorHandler = require('./middleware/errorHandler');
// ...其他中间件注册
app.use(errorHandler);

3. 输入数据验证

用express-validator对请求参数进行验证,比如创建帖子时检查title和content是否为空,避免无效数据存入数据库:

// 在路由里添加验证规则
const { body, validationResult } = require('express-validator');

router.post(
  '/',
  passport.authenticate('jwt', { session: false }),
  body('title').notEmpty().withMessage('标题不能为空'),
  body('content').notEmpty().withMessage('内容不能为空'),
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    // 后续业务逻辑...
  }
);

4. 测试权限逻辑

写单元测试或集成测试,验证以下场景:

  • 未登录用户无法访问增删改接口
  • 登录用户无法修改/删除他人的帖子
  • 帖子所有者可以正常修改/删除自己的帖子
    这样能确保权限控制逻辑没有漏洞。

内容的提问来源于stack exchange,提问作者craftdeer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 07:04:59