>  기사  >  웹 프론트엔드  >  저수준 설계: 폴링 시스템 - Nodejs 사용

저수준 설계: 폴링 시스템 - Nodejs 사용

WBOY
WBOY원래의
2024-08-31 13:03:07380검색

Low-Level Design: Polling System - Using Nodejs

목차

  1. 데이터베이스 설정
    • MySQL 데이터베이스 스키마
    • 투표시스템 ERD
  2. 백엔드 설정
    • 1단계: 프로젝트 초기화
    • 2단계: 프로젝트 구조
  3. API 구현
    • 1단계: 데이터베이스 연결(db/db.js)
    • 2단계: 환경 변수(.env)
    • 3단계: 폴링 컨트롤러(controllers/pollController.js)
    • 4단계: 경로 폴링(routes/pollRoutes.js)
    • 5단계: 서버 진입점(index.js)
  4. 오류 처리
  5. 테스트
  6. 결론

폴링 시스템 기본 저수준 설계 - I 기사를 참조하세요

데이터베이스 설정, Express와 함께 Node.js를 사용한 API 구현, MySQL과의 상호 작용을 포함하여 전체 프로세스를 세부 단계로 나누어 보겠습니다. 우리가 다룰 내용은 다음과 같습니다.

데이터베이스 설정

먼저 MySQL 데이터베이스의 스키마를 정의하고 필요한 테이블을 생성하겠습니다.

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
);
  • 설문조사 테이블: 고유 식별자, 질문 및 생성 타임스탬프와 함께 설문조사 정보를 저장합니다.

  • 옵션 테이블: poll_id를 통해 연결된 설문조사와 관련된 옵션을 저장합니다.

  • 투표 테이블: 각 투표를 기록하고 설문조사, 옵션, 사용자에 연결합니다.

    폴링 시스템용 ERD

엔티티:

  1. 설문조사: poll_id 및 질문과 같은 속성을 사용하여 설문조사 자체를 나타냅니다.
  2. 옵션: option_id 및 option_text와 같은 속성을 사용하여 각 설문 조사에 사용할 수 있는 옵션을 나타냅니다.
  3. 투표: vote_id, user_id 및 타임스탬프와 같은 속성을 사용하여 사용자가 투표한 투표를 나타냅니다.

관계:

  1. 일대다 설문조사와 옵션 간: 각 설문조사에는 여러 옵션이 있을 수 있습니다.
  2. 다대일 투표와 옵션 간: 각 투표는 하나의 옵션과 연결됩니다.
  3. 다대일투표와 투표 간: 각 투표는 특정 투표와 연결됩니다.

ERD에 대한 설명은 다음과 같습니다.

  1. 여론조사표:

    • poll_id(기본 키)
    • 질문
    • created_at
  2. 옵션 표:

    • option_id(기본 키)
    • poll_id (polls.poll_id를 참조하는 외래 키)
    • 옵션_텍스트
  3. 투표 테이블:

    • vote_id(기본 키)
    • poll_id (polls.poll_id를 참조하는 외래 키)
    • option_id (options.option_id를 참조하는 외래 키)
    • 사용자_ID
    • created_at

관계는 항목 간의 선으로 표시됩니다.

  • 설문조사옵션: 하나의 설문조사에 여러 가지 옵션이 있을 수 있습니다.
  • 옵션투표: 하나의 옵션이 여러 표를 받을 수 있습니다.
  • 설문조사투표: 하나의 설문조사가 여러 표를 받을 수 있습니다.

백엔드 설정

Express와 MySQL을 사용하여 Node.js 프로젝트를 설정해 보겠습니다.

1단계: 프로젝트 초기화

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

  • express: Node.js용 웹 프레임워크
  • mysql2: Node.js용 MySQL 클라이언트
  • dotenv: 환경변수 관리용.

2단계: 프로젝트 구조

프로젝트의 기본 구조 만들기:

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

API 구현

1단계: 데이터베이스 연결

파일 - 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;

2단계: 환경 변수

파일 - .env

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

3단계: 폴링 컨트롤러

파일 - 컨트롤러/pollController.js

이 파일은 폴링 시스템에 필요한 모든 CRUD 작업을 구현합니다.

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

4단계: 경로 폴링

파일 - 경로/pollRoutes.js
각 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;

5단계: 서버 진입점

파일 - index.js
마지막으로 서버를 설정하세요.

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}`);
});

오류 처리

각 방법에는 잘못된 입력, 중복 투표, 설문조사 또는 옵션 누락, 서버 오류 등 일반적인 문제에 대한 오류 처리가 포함되어 있습니다.

  • 입력 유효성 검사: 필수 필드가 있고 형식이 올바른지 확인하는 등 입력이 유효한지 확인하는 검사가 수행됩니다.
  • 트랜잭션 관리: 여러 쿼리가 포함된 작업(예: 설문 조사 생성 또는 업데이트)의 경우 일관성을 보장하기 위해 트랜잭션이 사용됩니다.

테스트

Postman 또는 컬과 같은 도구를 사용하여 각 엔드포인트를 테스트합니다.

  • 설문조사 만들기: 질문과 옵션 배열이 포함된 JSON 본문이 포함된 POST /api/polls.
  • 설문조사 업데이트: 업데이트된 질문과 옵션이 포함된 PUT /api/polls/:pollId.
  • 설문조사 삭제: DELETE /api/polls/:pollId.
  • 설문조사 투표: userId 및 옵션을 사용하여 POST /api/polls/:pollId/vote.
  • 설문조사 결과 보기: GET /api/polls/:pollId/results.

결론

이것은 Node.js, Express 및 MySQL을 사용하는 온라인 폴링 시스템의 포괄적인 모듈식 구현입니다. 기본 CRUD 작업을 처리하고 트랜잭션과의 데이터 일관성을 보장합니다. 또한 API를 더욱 강력하고 사용자 친화적으로 만들기 위한 기본 오류 처리도 포함되어 있습니다.

폴링 시스템 기본 저수준 설계 - I 기사를 참조하세요

자세한 내용:

시스템 설계 관련 모든 기사 보기
Hastag: SystemDesignWithZeeshanAli

zeeshanali와 함께하는 시스템 디자인

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

위 내용은 저수준 설계: 폴링 시스템 - Nodejs 사용의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.