NodeJS Express 重叠路由器

NodeJS Express overlapping routers

我在构建 NodeJS Express API 时遇到了一些错误 (?)。显然有些端点相互重叠,这意味着我无法到达它们,请求直到超时才会结束。

例如:

const load_dirs = (dirs, filename) => {
    const router_files = readdirSync(`./${dirs}`).filter(file => file.endsWith(`${filename}.js`));
    router_files.forEach(file => {
        const { 
            getUsers, getUser, addUser, editUser, removeUser, loginUser, updateUserStatus
        } = require(`../${dirs}/${file}`);

        router
            .route('/')
            .get(cors(corsOptions), getUsers)
            .post(cors(corsOptions), addUser);

        router
            .route('/login')
            .post(cors(corsOptions), loginUser);
        
        router
            .route('/:userId')
            .get(cors(corsOptions), getUser)
            .post(cors(corsOptions), editUser)
            .put(cors(corsOptions), removeUser);

        router
            .route('/:userId/status')
            .post(cors(corsOptions), updateUserStatus);
    });
}

['methods'].forEach(e => load_dirs(e, 'users'));

module.exports = (router);

如果登录方法高于通过 Id 获取用户,我无法通过 Id 访问用户,反之亦然。 由于这是使用节点的一种有点独特的方式(还没有找到其他人使用它),我无法弄清楚...

我错过了什么? 提前致谢!

编辑 1:

根据评论中的要求,我在这里留下 getUsers 方法:

exports.getUsers = async (req, res, next) => {
    try  {
        await new Promise(async (resolved, rejected) => {
            let conn;

            try {
                conn = await db.pool.getConnection();
                const result = await conn.query(`
                    SELECT * FROM users
                `)

                .then((resp) => {                    
                    if (!resp.length) {
                        res
                            .status(200)
                            .json({ success: true, message: general.noResults });
                        resolved(resp);
                        return;
                    }

                    const data = resp;

                    res
                        .status(200)
                        .json({ success: true, utilizadores: data });
                    resolved(resp);
                })

                .catch ((err) => {
                    if (mode() === 'development' || mode() === 'test') console.log(`Error: ${ err }.`);
                    res
                        .status(400)
                        .json({ success: true, message: general.badRequest });
                    rejected(err);
                })
            } catch (err) {
                if (mode() === 'development' || mode() === 'test') console.log(`Error: ${ err }`);
                res
                    .status(400)
                    .json({ success: false, message: general.badRequest });
                rejected(err);
            } finally {
                if (conn) {
                    conn.end();
                }
            }
        })

        .then((message) => {
            if (mode() === 'development' || mode() === 'test') console.log(message);
        })

        .catch((err) => {
            if (mode() === 'development' || mode() === 'test') console.log(err);
        });
    } catch (err) {
        if (mode() === 'development' || mode() === 'test') console.log(`Error: ${ err }.`);
        res
            .status(400)
            .json({ success: false, message: general.badRequest });
    }
}

这个结构:

      router
        .route('/:userId')
        .get(cors(corsOptions), getUser)
        .post(cors(corsOptions), editUser)
        .put(cors(corsOptions), removeUser);

创建一个通配符路由,匹配任何具有顶级路径的 http 请求,例如:

/abc
/xxx
/123

因此,当您完成循环的第一次迭代并创建这些顶级通配符路由中的第一个时,它将优先于在它之后添加到路由器的任何其他顶级路由。因此,它将接受尝试为 /login.

定义处理程序的循环的未来迭代的请求。

但是,整个模型都被破坏了。即使您删除通配符路由,这也不是唯一的问题。由于循环的每次迭代只是一遍又一遍地定义完全相同的路由,因此在循环的第一次迭代中定义的路由将优先于循环后续迭代中的所有其他路由。

循环的后续迭代需要定义不同的路由。如果我们删除您在此处的所有并发症并仅显示:

app.get("/login", someHandler1);
app.get("/login", someHandler2);

然后,第二个定义永远不会有机会看到 /login 路由,除非 someHandler1 碰巧决定不发送响应,而是调用 next() 将控制传递到链下。

因此,在循环中一遍又一遍地定义相同路由的整个模型似乎有缺陷。此外,我倾向于避免使用顶级通配符路由,因为它会在您扩展功能时与其他顶级路由发生冲突。相反,要么执行 /id/:userId,要么定义一个仅匹配顶级 userId 的正则表达式,但为不匹配该正则表达式的其他顶级路由留出空间。


要提出解决方案,我必须更好地理解当您有多个文件具有所有相同的导出请求处理程序和所有相同的路由路径时,您到底想做什么。从表面上看,这对我来说没有意义。您不会多次处理完全相同的传入请求。您只处理一次,因此需要一段代码(而不是 N 段代码)来处理任何给定的路由。


仅供参考,这是 getUsers() 函数的简化版本。您不需要承诺中的包装,也不需要那么多重复的错误处理:

exports.getUsers = async (req, res, next) => {
    let conn;
    try {
        conn = await db.pool.getConnection();
        const result = await conn.query(`SELECT * FROM users`);
        if (!result.length) {
            res.json({ success: true, message: general.noResults });
        } else {
            res.json({ success: true, utilizadores: results });
        }
    } catch(err) {
        if (mode() === 'development' || mode() === 'test') console.log(`Error:`, err);
        res.status(400).json({ success: true, message: general.badRequest });
    } finally {
        if (conn) {
            conn.end();
        }
    }
});

更改摘要:

  1. 删除 await new Promise() 包装器,因为那不是必需的
  2. 删除 .then().catch() 所以我们只使用 awaittry/catch
  3. 将所有错误处理放在一个 catch 块中
  4. 将所有错误处理程序合并到一个地方
  5. 修复错误对象的日志记录
  6. 删除 .status(200),因为 200 已经是默认值。

仅供参考,我认为您确实想要记录错误,即使是在生产中,而不仅仅是在开发中。如果您开始在已部署的生产服务器中遇到问题,您将需要日志信息来告诉您实际的内部错误是什么。因此,我建议您删除仅记录开发或测试错误的 if

循环是问题所在。我猜你正在尝试为每个文件设置一个路由。您不需要这样做,因为您可以使用路由参数来替换文件访问代码中的 userId。

import express from 'express';

const app = express();
const router = express.Router();
app.use(router);

const port = 3000;

const render1 = (req, res) => {
  res.send(`Render 1 says: hello world`);
}

const render2 = (req, res) => {
  res.send(`Render 2 says: logging in user...`);
}

const render3 = (req, res) => {
  res.send(`Render 3 says: reading user data from file ./userdata/${req.params.userId}.json`);
}

const render4 = (req, res) => {
  res.send(`Render 4 says: User ${req.params.userId} is logged in.`);
}

router.route('/').get(render1);

router.route('/login').get(render2);

router.route('/:userId').get(render3);

router.route('/:userId/status').get(render4);


app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})