Maison >interface Web >js tutoriel >Conception de bas niveau : système d'interrogation - Utilisation de Nodejs

Conception de bas niveau : système d'interrogation - Utilisation de Nodejs

WBOY
WBOYoriginal
2024-08-31 13:03:07385parcourir

Low-Level Design: Polling System - Using Nodejs

Table des matières

  1. Configuration de la base de données
    • Schéma de base de données MySQL
    • ERD pour le système de vote
  2. Configuration du back-end
    • Étape 1 : Initialiser le projet
    • Étape 2 : Structure du projet
  3. Implémentation de l'API
    • Étape 1 : Connexion à la base de données (db/db.js)
    • Étape 2 : Variables d'environnement (.env)
    • Étape 3 : Contrôleur de sondage (controllers/pollController.js)
    • Étape 4 : interroger les itinéraires (routes/pollRoutes.js)
    • Étape 5 : Point d'entrée du serveur (index.js)
  4. Gestion des erreurs
  5. Tests
  6. Conclusion

Veuillez vous référer à l'article Conception de base de bas niveau du système d'interrogation - I

Décomposons l'ensemble du processus en étapes détaillées, y compris la configuration de la base de données, la mise en œuvre de l'API à l'aide de Node.js avec Express et l'interaction avec MySQL. Nous couvrirons :

Configuration de la base de données

Tout d'abord, nous allons définir le schéma de la base de données MySQL et créer les tables nécessaires.

Schéma de base de données MySQL

CREATE DATABASE polling_system;

USE polling_system;

CREATE TABLE polls (
    poll_id INT AUTO_INCREMENT PRIMARY KEY,
    question VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

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
);
  • tableau des sondages : stocke les informations du sondage avec un identifiant unique, une question et un horodatage de création.

  • table d'options : stocke les options associées à un sondage, liées via poll_id.

  • tableau des votes : enregistre chaque vote, avec un lien vers le sondage, l'option et l'utilisateur.

    ERD pour le système de vote

Entités :

  1. Polls : représente le sondage lui-même, avec des attributs tels que poll_id et question.
  2. Options : représente les options disponibles pour chaque sondage, avec des attributs tels que option_id et option_text.
  3. Votes : représente les votes exprimés par les utilisateurs, avec des attributs tels que vote_id, user_id et timestamps.

Relations :

  1. Un à plusieurs entre sondages et options : chaque sondage peut avoir plusieurs options.
  2. Plusieurs à un entre votes et options : chaque vote est associé à une option.
  3. Many-to-One entre Votes et Sondages : Chaque vote est lié à un sondage spécifique.

Voici une description de l’ERD :

  1. Tableau des sondages :

    • poll_id (Clé primaire)
    • question
    • created_at
  2. Tableau des options :

    • option_id (Clé primaire)
    • poll_id (clé étrangère faisant référence à polls.poll_id)
    • option_text
  3. Tableau des votes :

    • vote_id (Clé primaire)
    • poll_id (clé étrangère faisant référence à polls.poll_id)
    • option_id (Clé étrangère faisant référence à options.option_id)
    • user_id
    • created_at

Les relations seraient représentées par des lignes entre les entités :

  • SondagesOptions : Un sondage peut avoir plusieurs options.
  • OptionsVotes : Une option peut avoir plusieurs votes.
  • SondagesVotes : Un sondage peut avoir plusieurs votes.

Configuration du back-end

Configurons un projet Node.js en utilisant Express et MySQL.

Étape 1 : initialiser le projet

mkdir polling-system
cd polling-system
npm init -y
npm install express mysql2 dotenv

  • express : Un framework web pour Node.js.
  • mysql2 : Un client MySQL pour Node.js.
  • dotenv : Pour gérer les variables d'environnement.

Étape 2 : Structure du projet

Créer une structure de base pour le projet :

polling-system/
│
├── .env
├── index.js
├── db/
│   └── db.js
├── routes/
│   └── pollRoutes.js
└── controllers/
    └── pollController.js

Implémentation de l'API

Étape 1 : connexion à la base de données

Fichier - db/db.js

const mysql = require('mysql2/promise');
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,
    waitForConnections: true,
    connectionLimit: 10,
    queueLimit: 0
});

module.exports = pool;

Étape 2 : Variables d'environnement

Fichier - .env

DB_HOST=localhost
DB_USER=root
DB_PASSWORD=yourpassword
DB_NAME=polling_system
PORT=3000

Étape 3 : Contrôleur de sondage

Fichier - contrôleurs/pollController.js

Ce fichier implémentera toutes les opérations CRUD nécessaires au système de sondage.

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 (question) VALUES (?)',
            [question]
        );

        const pollId = result.insertId;

        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 || !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 [pollResult] = await connection.execute(
            'UPDATE polls SET question = ? WHERE poll_id = ?',
            [question, pollId]
        );

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

        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);
        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." });
    }
};

Étape 4 : Itinéraires de sondage

Fichier - routes/pollRoutes.js
Définissez les routes pour chaque point de terminaison de l'API :

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;

Étape 5 : Point d'entrée du serveur

Fichier - index.js
Enfin, configurez le serveur :

const express = require('express');
const pollRoutes = require('./routes/pollRoutes');
require('dotenv').config();

const app = express();
app.use(express.json());

// Routes
app.use('/api', pollRoutes);

// Error Handling Middleware
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).json({ message: "Internal server error" });
});

// Start Server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

Gestion des erreurs

Chaque méthode inclut la gestion des erreurs pour les problèmes courants tels que les entrées invalides, les votes en double, les sondages ou options manquants et les erreurs de serveur.

  • Validation des entrées : des vérifications sont effectuées pour garantir que les entrées sont valides, par exemple en vérifiant si les champs obligatoires sont présents et correctement formatés.
  • Gestion des transactions : pour les opérations impliquant plusieurs requêtes (par exemple, création ou mise à jour de sondages), les transactions sont utilisées pour garantir la cohérence.

Essai

Testez chaque point de terminaison à l'aide d'outils comme Postman ou curl.

  • Créer un sondage : POST /api/polls avec un corps JSON contenant une question et un tableau d'options.
  • Mettre à jour le sondage : PUT /api/polls/:pollId avec la question et les options mises à jour.
  • Supprimer le sondage : DELETE /api/polls/:pollId.
  • Votez dans le sondage : POST /api/polls/:pollId/vote avec userId et option.
  • Afficher les résultats du sondage : GET /api/polls/:pollId/results.

Conclusion

Il s'agit d'une implémentation modulaire complète d'un système de sondage en ligne utilisant Node.js, Express et MySQL. Il gère les opérations CRUD de base et garantit la cohérence des données avec les transactions. Il inclut également une gestion des erreurs de base pour rendre l'API plus robuste et plus conviviale.

Veuillez vous référer à l'article Conception de base de bas niveau du système d'interrogation - I

Plus de détails :

Obtenez tous les articles liés à la conception de systèmes
Hastag : SystemDesignWithZeeshanAli

conception du système aveczeeshanali

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn