首页 web前端 js教程 底层设计:轮询系统 - 边缘情况

底层设计:轮询系统 - 边缘情况

Aug 31, 2024 pm 02:43 PM

Low-Level Design: Polling System - Edge Cases

目录

案例 1 - 处理更新的版本控制
情况 2 - PollID 作为 UUID 而不是主键
情况 3 - 选项为空或无效
案例 4 - 重复选项
案例 5 - 问题长度限制
案例 6 - 投票过期

请先参考以下文章:

  1. 底层设计:投票系统:基本

  2. 底层设计:轮询系统 - 使用 Node.js 和 SQL

边缘情况处理

案例1

要管理投票问题和选项的更新,同时保留与同一投票 ID 关联的先前详细信息,您可以实现版本控制系统。这种方法允许您跟踪每次民意调查的历史数据,确保即使在更新后也保留旧的详细信息。

第 1 步:数据库架构更改

  1. 更新投票表

    • 将 current_version_id 列添加到 polls 表中以跟踪投票的最新版本。
  2. 创建投票版本表

    • 创建一个新表来存储民意调查的历史版本。

更新的数据库架构

CREATE DATABASE polling_system;

USE polling_system;

CREATE TABLE polls (
    poll_id INT AUTO_INCREMENT PRIMARY KEY,
    current_version_id INT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (current_version_id) REFERENCES poll_versions(version_id) ON DELETE SET NULL
);

CREATE TABLE poll_versions (
    version_id INT AUTO_INCREMENT PRIMARY KEY,
    poll_id INT,
    question VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (poll_id) REFERENCES polls(poll_id) ON DELETE CASCADE
);

CREATE TABLE options (
    option_id INT AUTO_INCREMENT PRIMARY KEY,
    poll_id INT,
    option_text VARCHAR(255) NOT NULL,
    FOREIGN KEY (poll_id) REFERENCES polls(poll_id) ON DELETE CASCADE
);

CREATE TABLE votes (
    vote_id INT AUTO_INCREMENT PRIMARY KEY,
    poll_id INT,
    user_id VARCHAR(255) NOT NULL,
    option_id INT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (poll_id) REFERENCES polls(poll_id) ON DELETE CASCADE,
    FOREIGN KEY (option_id) REFERENCES options(option_id) ON DELETE CASCADE
);
登录后复制

第 2 步:API 实施变更

更新轮询控制器

修改 updatePoll 方法,在创建新版本之前检查问题是否发生变化。

文件:controllers/pollController.js
const pool = require('../db/db');

// Create Poll
exports.createPoll = async (req, res) => {
    const { question, options } = req.body;

    if (!question || !options || !Array.isArray(options) || options.length < 2) {
        return res.status(400).json({ message: "Invalid input data. Question and at least two options are required." });
    }

    try {
        const connection = await pool.getConnection();
        await connection.beginTransaction();

        const [result] = await connection.execute(
            'INSERT INTO polls (current_version_id) VALUES (NULL)'
        );

        const pollId = result.insertId;

        const [versionResult] = await connection.execute(
            'INSERT INTO poll_versions (poll_id, question) VALUES (?, ?)',
            [pollId, question]
        );

        const versionId = versionResult.insertId;

        // Update the current version in the polls table
        await connection.execute(
            'UPDATE polls SET current_version_id = ? WHERE poll_id = ?',
            [versionId, pollId]
        );

        const optionQueries = options.map(option => {
            return connection.execute(
                'INSERT INTO options (poll_id, option_text) VALUES (?, ?)',
                [pollId, option]
            );
        });

        await Promise.all(optionQueries);

        await connection.commit();
        connection.release();

        res.status(201).json({ pollId, message: "Poll created successfully." });

    } catch (error) {
        console.error("Error creating poll:", error.message);
        res.status(500).json({ message: "Error creating poll." });
    }
};

// Update Poll
exports.updatePoll = async (req, res) => {
    const { pollId } = req.params;
    const { question, options } = req.body;

    if (!pollId || !options || !Array.isArray(options) || options.length < 2) {
        return res.status(400).json({ message: "Invalid input data. At least two options are required." });
    }

    try {
        const connection = await pool.getConnection();
        await connection.beginTransaction();

        // Fetch the existing poll
        const [existingPoll] = await connection.execute(
            'SELECT question FROM poll_versions WHERE poll_id = (SELECT current_version_id FROM polls WHERE poll_id = ?)',
            [pollId]
        );

        if (existingPoll.length === 0) {
            await connection.rollback();
            connection.release();
            return res.status(404).json({ message: "Poll not found." });
        }

        const currentQuestion = existingPoll[0].question;

        // Check if the question has changed
        if (currentQuestion !== question) {
            // Create a new version since the question has changed
            const [versionResult] = await connection.execute(
                'INSERT INTO poll_versions (poll_id, question) VALUES (?, ?)',
                [pollId, question]
            );

            const versionId = versionResult.insertId;

            // Update the current version in the polls table
            await connection.execute(
                'UPDATE polls SET current_version_id = ? WHERE poll_id = ?',
                [versionId, pollId]
            );
        }

        // Remove old options and insert new ones
        await connection.execute('DELETE FROM options WHERE poll_id = ?', [pollId]);

        const optionQueries = options.map(option => {
            return connection.execute(
                'INSERT INTO options (poll_id, option_text) VALUES (?, ?)',
                [pollId, option]
            );
        });

        await Promise.all(optionQueries);

        await connection.commit();
        connection.release();

        res.status(200).json({ message: "Poll updated successfully." });

    } catch (error) {
        console.error("Error updating poll:", error.message);
        await connection.rollback();
        res.status(500).json({ message: "Error updating poll." });
    }
};

// Delete Poll
exports.deletePoll = async (req, res) => {
    const { pollId } = req.params;

    try {
        const connection = await pool.getConnection();

        const [result] = await connection.execute(
            'DELETE FROM polls WHERE poll_id = ?',
            [pollId]
        );

        connection.release();

        if (result.affectedRows === 0) {
            return res.status(404).json({ message: "Poll not found." });
        }

        res.status(200).json({ message: "Poll deleted successfully." });

    } catch (error) {
        console.error("Error deleting poll:", error.message);
        res.status(500).json({ message: "Error deleting poll." });
    }
};

// Vote in Poll
exports.voteInPoll = async (req, res) => {
    const { pollId } = req.params;
    const { userId, option } = req.body;

    if (!userId || !option) {
        return res.status(400).json({ message: "User ID and option are required." });
    }

    try {
        const connection = await pool.getConnection();

        const [userVote] = await connection.execute(
            'SELECT * FROM votes WHERE poll_id = ? AND user_id = ?',
            [pollId, userId]
        );

        if (userVote.length > 0) {
            connection.release();
            return res.status(400).json({ message: "User has already voted." });
        }

        const [optionResult] = await connection.execute(
            'SELECT option_id FROM options WHERE poll_id = ? AND option_text = ?',
            [pollId, option]
        );

        if (optionResult.length === 0) {
            connection.release();
            return res.status(404).json({ message: "Option not found." });
        }

        const optionId = optionResult[0].option_id;

        await connection.execute(
            'INSERT INTO votes (poll_id, user_id, option_id) VALUES (?, ?, ?)',
            [pollId, userId, optionId]
        );

        connection.release();

        res.status(200).json({ message: "Vote cast successfully." });

    } catch (error) {
        console.error("Error casting vote:", error.message);
        res.status(500).json({ message: "Error casting vote." });
    }
};

// View Poll Results
exports.viewPollResults = async (req, res) => {
    const { pollId } = req.params;

    try {
        const connection = await pool.getConnection();

        const [poll] = await connection.execute(
            'SELECT * FROM polls WHERE poll_id = ?',
            [pollId]
        );

        if (poll.length === 0) {
            connection.release();
            return res.status(404).json({ message: "Poll not found." });
        }

        const [options] = await connection.execute(
            'SELECT option_text, COUNT(votes.option_id) as vote_count FROM options ' +
            'LEFT JOIN votes ON options.option_id = votes.option_id ' +
            'WHERE options.poll_id = ? GROUP BY options.option_id',
            [pollId]
        );

        connection.release();

        res.status(200).json({
            pollId: poll[0].poll_id,
            question: poll[0].question,
            results: options.reduce((acc, option) => {
                acc[option.option_text] = option.vote_count;
                return acc;
            }, {})
        });

    } catch (error) {
        console.error("Error viewing poll results:", error.message);
        res.status(500).json({ message: "Error viewing poll results." });
    }
};
登录后复制

第 3 步:更新投票路线

确保在 pollRoutes.js 中正确定义路由。

文件:routes/pollRoutes.js
const express = require('express');
const router = express.Router();
const pollController = require('../controllers/pollController');

// Routes
router.post('/polls', pollController.createPoll);
router.put('/polls/:pollId', pollController.updatePoll);
router.delete('/polls/:pollId', pollController.deletePoll);
router.post('/polls/:pollId/vote', pollController.voteInPoll);
router.get('/polls/:pollId/results', pollController.viewPollResults);

module.exports = router;
登录后复制

变更摘要

  1. 数据库:

    • 更新了投票表以包含 current_version_id。
    • 创建了 poll_versions 表来跟踪问题版本。
    • 选项和投票表保持不变。
  2. API:

    • 创建了一个新的 createPoll 方法来初始化民意调查和版本。
    • 更新了 updatePoll 方法以在创建新版本之前检查问题更改。
    • 添加了投票和查看投票结果的方法。
  3. 路线:

    • 确保定义所有必要的路由来处理民意调查创建、更新、投票和结果。

案例2

处理 pollId 需要是 UUID(通用唯一标识符)的场景。

以下是在轮询系统中为 thepollId 实现 UUID 的步骤,无需提供代码:

为投票 ID 实施 UUID 的步骤

  1. ** 数据库架构更新:**

    • 修改 polls、poll_versions、options 和 votes 表,以使用 CHAR(36) 作为 poll_id 而不是整数。
    • 创建一个新的 poll_versions 表来存储由 UUID 链接的投票问题和选项的历史版本。
  2. ** UUID 生成:**

    • 决定生成UUID的方法。您可以使用应用程序环境中的库或内置函数来创建UUID。
  3. ** 创建投票逻辑:**

    • 创建新投票时,生成一个 UUID 并将其用作 poll_id。
    • 将新的投票记录插入投票表中。
    • 将初始问题插入 poll_versions 表并将其与生成的 UUID 链接。
  4. ** 更新投票逻辑:**

    • 更新投票时:
  5. 检查问题是否已更改。

    • 如果问题已更改,请在poll_versions 表中创建一个新版本条目来存储旧问题和选项。
    • 根据需要使用新问题和选项更新投票表。
  6. ** 投票逻辑:**

    • 更新投票机制,确保使用UUID作为poll_id。
  7. 验证投票请求中提供的 UUID 是否存在于 polls 表中。

  8. ** API 更新:**

    • 修改 API 端点以接受并返回 poll_id 的 UUID。
    • 确保所有 API 操作(创建、更新、删除、投票)一致引用 UUID 格式。
  9. ** 测试:**

    • 彻底测试应用程序,确保在所有场景(创建、更新、投票和检索投票结果)中正确处理 UUID。
  10. ** 文档:**

    • 更新您的 API 文档以反映 poll_id 格式的更改以及与版本控制和 UUID 使用相关的任何新行为。

按照以下步骤,您可以在轮询系统中成功实现 pollId 的 UUID,同时确保数据完整性和历史跟踪。


案例3

空或无效选项

验证方法:

  • API 输入验证: 在 API 端点中实施检查,以验证请求正文中提供的选项不为空并满足特定条件(例如,如果不允许,则不得使用特殊字符)。
  • 反馈机制:如果选项无效或为空,向用户提供清晰的错误消息,指导他们纠正输入。

案例4

重复选项

唯一性检查:

  • 预插入验证: 在将选项添加到投票之前,检查数据库中现有选项是否有重复项。这可以通过使用轮询 ID 查询选项表并将其与新选项进行比较来完成。
  • 用户反馈:如果检测到重复选项,则返回一条有意义的错误消息,通知用户哪些选项是重复的,从而允许他们相应地修改其输入。

案例5

问题长度限制

字符限制:

  • API 验证: 设置 API 中投票问题和选项的最大字符限制。这可以通过在创建和更新过程中检查问题的长度和每个选项来完成。
  • 用户界面反馈:实施客户端验证,以便在用户输入时超出字符限制时向用户提供即时反馈,增强用户体验。

案例6

投票过期

过期机制:

  • 时间戳管理: 将时间戳字段添加到民意调查表中,以记录每个民意调查的创建时间,还可以选择另一个字段来记录到期日期。
  • 计划检查: 实施后台作业或 cron 任务,定期检查过期的轮询并将其在数据库中标记为非活动状态。这还可以包括阻止对过期民意调查进行投票。
  • 用户通知:(可选)通知投票创建者和参与者即将到期的日期,以便他们在投票变为非活动状态之前参与投票。

请先参考以下文章:

  1. 底层设计:投票系统:基本

  2. 底层设计:轮询系统 - 使用 Node.js 和 SQL

更多详情:

获取所有与系统设计相关的文章
标签:SystemDesignWithZeeshanAli

系统设计与zeeshanali

Git:https://github.com/ZeeshanAli-0704/SystemDesignWithZeeshanAli

以上是底层设计:轮询系统 - 边缘情况的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1653
14
CakePHP 教程
1413
52
Laravel 教程
1306
25
PHP教程
1251
29
C# 教程
1224
24
前端热敏纸小票打印遇到乱码问题怎么办? 前端热敏纸小票打印遇到乱码问题怎么办? Apr 04, 2025 pm 02:42 PM

前端热敏纸小票打印的常见问题与解决方案在前端开发中,小票打印是一个常见的需求。然而,很多开发者在实...

神秘的JavaScript:它的作用以及为什么重要 神秘的JavaScript:它的作用以及为什么重要 Apr 09, 2025 am 12:07 AM

JavaScript是现代Web开发的基石,它的主要功能包括事件驱动编程、动态内容生成和异步编程。1)事件驱动编程允许网页根据用户操作动态变化。2)动态内容生成使得页面内容可以根据条件调整。3)异步编程确保用户界面不被阻塞。JavaScript广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

谁得到更多的Python或JavaScript? 谁得到更多的Python或JavaScript? Apr 04, 2025 am 12:09 AM

Python和JavaScript开发者的薪资没有绝对的高低,具体取决于技能和行业需求。1.Python在数据科学和机器学习领域可能薪资更高。2.JavaScript在前端和全栈开发中需求大,薪资也可观。3.影响因素包括经验、地理位置、公司规模和特定技能。

如何实现视差滚动和元素动画效果,像资生堂官网那样?
或者:
怎样才能像资生堂官网一样,实现页面滚动伴随的动画效果? 如何实现视差滚动和元素动画效果,像资生堂官网那样? 或者: 怎样才能像资生堂官网一样,实现页面滚动伴随的动画效果? Apr 04, 2025 pm 05:36 PM

实现视差滚动和元素动画效果的探讨本文将探讨如何实现类似资生堂官网(https://www.shiseido.co.jp/sb/wonderland/)中�...

JavaScript的演变:当前的趋势和未来前景 JavaScript的演变:当前的趋势和未来前景 Apr 10, 2025 am 09:33 AM

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

如何使用JavaScript将具有相同ID的数组元素合并到一个对象中? 如何使用JavaScript将具有相同ID的数组元素合并到一个对象中? Apr 04, 2025 pm 05:09 PM

如何在JavaScript中将具有相同ID的数组元素合并到一个对象中?在处理数据时,我们常常会遇到需要将具有相同ID�...

前端开发中如何实现类似 VSCode 的面板拖拽调整功能? 前端开发中如何实现类似 VSCode 的面板拖拽调整功能? Apr 04, 2025 pm 02:06 PM

探索前端中类似VSCode的面板拖拽调整功能的实现在前端开发中,如何实现类似于VSCode...

JavaScript引擎:比较实施 JavaScript引擎:比较实施 Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

See all articles