Для эффективного управления данными веб-сайта, предоставляющего товары и услуги, необходимо разработать структуру базы данных, включающую следующие таблицы:
users
(Пользователи)Хранит информацию о зарегистрированных пользователях системы.
Поле | Тип | Описание |
---|---|---|
id | SERIAL PRIMARY KEY | Уникальный идентификатор пользователя |
username | VARCHAR(50) UNIQUE NOT NULL | Уникальное имя пользователя |
VARCHAR(100) UNIQUE NOT NULL | Уникальный адрес электронной почты | |
password | VARCHAR(255) NOT NULL | Хэшированный пароль пользователя |
role | VARCHAR(50) DEFAULT 'user' | Роль пользователя (например, "user", "admin") |
created_at | TIMESTAMP DEFAULT CURRENT_TIMESTAMP | Дата и время регистрации пользователя |
categories
(Категории)Содержит основные категории и подкатегории товаров и услуг.
Поле | Тип | Описание |
---|---|---|
id | SERIAL PRIMARY KEY | Уникальный идентификатор категории |
name | VARCHAR(255) NOT NULL | Название категории |
parent_id | INTEGER REFERENCES categories(id) | Идентификатор родительской категории (если есть) |
type | VARCHAR(50) NOT NULL | Тип категории ("product" или "service") |
products
(Товары)Содержит информацию о товарах, предлагаемых на сайте.
Поле | Тип | Описание |
---|---|---|
id | SERIAL PRIMARY KEY | Уникальный идентификатор товара |
name | VARCHAR(255) NOT NULL | Название товара |
description | TEXT | Описание товара |
price | DECIMAL(10,2) NOT NULL | Цена товара |
category_id | INTEGER REFERENCES categories(id) | Идентификатор категории товара |
image_url | VARCHAR(255) | URL изображения товара |
created_at | TIMESTAMP DEFAULT CURRENT_TIMESTAMP | Дата добавления товара |
services
(Услуги)Содержит информацию об услугах, предлагаемых на сайте.
Поле | Тип | Описание |
---|---|---|
id | SERIAL PRIMARY KEY | Уникальный идентификатор услуги |
name | VARCHAR(255) NOT NULL | Название услуги |
description | TEXT | Описание услуги |
price | DECIMAL(10,2) | Цена услуги (если применимо) |
category_id | INTEGER REFERENCES categories(id) | Идентификатор категории услуги |
image_url | VARCHAR(255) | URL изображения услуги |
created_at | TIMESTAMP DEFAULT CURRENT_TIMESTAMP | Дата добавления услуги |
projects
(Примеры проектов)Содержит примеры реализованных проектов для каждой услуги, включая фотографии и комментарии.
Поле | Тип | Описание |
---|---|---|
id | SERIAL PRIMARY KEY | Уникальный идентификатор проекта |
service_id | INTEGER REFERENCES services(id) | Идентификатор услуги, к которой относится проект |
name | VARCHAR(255) NOT NULL | Название проекта |
description | TEXT | Описание проекта |
image_url | VARCHAR(255) | URL изображения проекта |
created_at | TIMESTAMP DEFAULT CURRENT_TIMESTAMP | Дата добавления проекта |
comments
(Комментарии)Позволяет пользователям оставлять комментарии к проектам.
Поле | Тип | Описание |
---|---|---|
id | SERIAL PRIMARY KEY | Уникальный идентификатор комментария |
project_id | INTEGER REFERENCES projects(id) | Идентификатор проекта, к которому относится комментарий |
user_id | INTEGER REFERENCES users(id) | Идентификатор пользователя, оставившего комментарий |
comment | TEXT NOT NULL | Текст комментария |
created_at | TIMESTAMP DEFAULT CURRENT_TIMESTAMP | Дата добавления комментария |
orders
(Заказы)Хранит информацию о заказах, оформленных пользователями на товары и услуги.
Поле | Тип | Описание |
---|---|---|
id | SERIAL PRIMARY KEY | Уникальный идентификатор заказа |
user_id | INTEGER REFERENCES users(id) | Идентификатор пользователя, оформившего заказ |
total_price | DECIMAL(10,2) NOT NULL | Общая стоимость заказа |
status | VARCHAR(50) DEFAULT 'pending' | Статус заказа (например, "pending", "completed") |
created_at | TIMESTAMP DEFAULT CURRENT_TIMESTAMP | Дата и время создания заказа |
order_items
(Элементы заказа)Содержит детали по каждому элементу заказа, будь то товар или услуга.
Поле | Тип | Описание |
---|---|---|
id | SERIAL PRIMARY KEY | Уникальный идентификатор элемента заказа |
order_id | INTEGER REFERENCES orders(id) | Идентификатор заказа |
product_id | INTEGER REFERENCES products(id) | Идентификатор товара (если применимо) |
service_id | INTEGER REFERENCES services(id) | Идентификатор услуги (если применимо) |
quantity | INTEGER NOT NULL | Количество товаров или услуг |
price | DECIMAL(10,2) NOT NULL | Цена за единицу |
Ниже приведены примеры SQL-запросов для создания вышеописанных таблиц в PostgreSQL:
-- Таблица пользователей
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role VARCHAR(50) DEFAULT 'user',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Таблица категорий
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
parent_id INTEGER REFERENCES categories(id),
type VARCHAR(50) NOT NULL
);
-- Таблица товаров
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL,
category_id INTEGER REFERENCES categories(id),
image_url VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Таблица услуг
CREATE TABLE services (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
price DECIMAL(10,2),
category_id INTEGER REFERENCES categories(id),
image_url VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Таблица проектов
CREATE TABLE projects (
id SERIAL PRIMARY KEY,
service_id INTEGER REFERENCES services(id),
name VARCHAR(255) NOT NULL,
description TEXT,
image_url VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Таблица комментариев
CREATE TABLE comments (
id SERIAL PRIMARY KEY,
project_id INTEGER REFERENCES projects(id),
user_id INTEGER REFERENCES users(id),
comment TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Таблица заказов
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
total_price DECIMAL(10,2) NOT NULL,
status VARCHAR(50) DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Таблица элементов заказа
CREATE TABLE order_items (
id SERIAL PRIMARY KEY,
order_id INTEGER REFERENCES orders(id),
product_id INTEGER REFERENCES products(id),
service_id INTEGER REFERENCES services(id),
quantity INTEGER NOT NULL,
price DECIMAL(10,2) NOT NULL
);
Для разработки backend необходимо установить Node.js и следующие пакеты с помощью npm
:
npm install express pg bcryptjs jsonwebtoken dotenv body-parser cors helmet
Рекомендуемая структура проекта:
/backend
/config
db.js
/controllers
authController.js
productController.js
serviceController.js
orderController.js
/models
User.js
Product.js
Service.js
Order.js
/routes
authRoutes.js
productRoutes.js
serviceRoutes.js
orderRoutes.js
app.js
.env
Создайте файл .env
в корне проекта и добавьте следующие переменные окружения:
DB_HOST=localhost
DB_PORT=5432
DB_USER=your_db_user
DB_PASSWORD=your_db_password
DB_NAME=your_db_name
SECRET_KEY=your_secret_key
Создайте файл db.js
в папке config
для подключения к PostgreSQL:
// config/db.js
const { Pool } = require('pg');
const dotenv = require('dotenv');
dotenv.config();
const pool = new Pool({
user: process.env.DB_USER,
host: process.env.DB_HOST,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT,
});
module.exports = pool;
Пример модели пользователя:
// models/User.js
const pool = require('../config/db');
const createUser = async (username, email, password, role) => {
const result = await pool.query(
'INSERT INTO users (username, email, password, role) VALUES ($1, $2, $3, $4) RETURNING *',
[username, email, password, role]
);
return result.rows[0];
};
const getUserByEmail = async (email) => {
const result = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
return result.rows[0];
};
module.exports = { createUser, getUserByEmail };
Пример контроллера аутентификации:
// controllers/authController.js
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const register = async (req, res) => {
const { username, email, password } = req.body;
try {
const existingUser = await User.getUserByEmail(email);
if (existingUser) {
return res.status(400).json({ message: 'Пользователь с таким email уже существует' });
}
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = await User.createUser(username, email, hashedPassword, 'user');
res.status(201).json(newUser);
} catch (error) {
res.status(500).json({ message: 'Ошибка сервера' });
}
};
const login = async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.getUserByEmail(email);
if (!user) {
return res.status(401).json({ message: 'Неверный email или пароль' });
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(401).json({ message: 'Неверный email или пароль' });
}
const token = jwt.sign({ id: user.id, role: user.role }, process.env.SECRET_KEY, { expiresIn: '1h' });
res.status(200).json({ token });
} catch (error) {
res.status(500).json({ message: 'Ошибка сервера' });
}
};
module.exports = { register, login };
Пример маршрутов аутентификации:
// routes/authRoutes.js
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
router.post('/register', authController.register);
router.post('/login', authController.login);
module.exports = router;
Конфигурация и запуск сервера:
// app.js
const express = require('express');
const bodyParser = require('body-parser');
const dotenv = require('dotenv');
const cors = require('cors');
const helmet = require('helmet');
const authRoutes = require('./routes/authRoutes');
const productRoutes = require('./routes/productRoutes');
const serviceRoutes = require('./routes/serviceRoutes');
const orderRoutes = require('./routes/orderRoutes');
dotenv.config();
const app = express();
app.use(helmet());
app.use(cors());
app.use(bodyParser.json());
app.use('/api/auth', authRoutes);
app.use('/api/products', productRoutes);
app.use('/api/services', serviceRoutes);
app.use('/api/orders', orderRoutes);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Сервер запущен на порту ${PORT}`));
Использование библиотеки bcryptjs
для надежного хэширования паролей пользователей:
const bcrypt = require('bcryptjs');
const hashPassword = async (password) => {
const salt = await bcrypt.genSalt(10);
return await bcrypt.hash(password, salt);
};
const comparePassword = async (password, hashedPassword) => {
return await bcrypt.compare(password, hashedPassword);
};
JSON Web Tokens (JWT) обеспечивают безопасную передачу информации между клиентом и сервером:
// Генерация токена
const token = jwt.sign({ id: user.id, role: user.role }, process.env.SECRET_KEY, { expiresIn: '1h' });
// Верификация токена
const authenticate = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ message: 'Нет токена, авторизуйтесь' });
try {
const decoded = jwt.verify(token, process.env.SECRET_KEY);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ message: 'Токен недействителен' });
}
};
Применение middleware для защиты маршрутов, доступных только авторизованным пользователям:
// Пример защищенного маршрута
const express = require('express');
const router = express.Router();
const { authenticate } = require('../middleware/authenticate');
const orderController = require('../controllers/orderController');
router.post('/', authenticate, orderController.createOrder);
router.get('/:id', authenticate, orderController.getOrderById);
module.exports = router;
Пример контроллера для создания заказа:
// controllers/orderController.js
const pool = require('../config/db');
const createOrder = async (req, res) => {
const { items } = req.body; // items: массив объектов с product_id/service_id и quantity
const userId = req.user.id;
let totalPrice = 0;
try {
// Начало транзакции
await pool.query('BEGIN');
// Расчет общей стоимости
for (const item of items) {
if (item.product_id) {
const product = await pool.query('SELECT price FROM products WHERE id = $1', [item.product_id]);
if (product.rows.length === 0) throw new Error('Товар не найден');
totalPrice += parseFloat(product.rows[0].price) * item.quantity;
}
if (item.service_id) {
const service = await pool.query('SELECT price FROM services WHERE id = $1', [item.service_id]);
if (service.rows.length === 0) throw new Error('Услуга не найдена');
totalPrice += parseFloat(service.rows[0].price) * item.quantity;
}
}
// Создание заказа
const order = await pool.query(
'INSERT INTO orders (user_id, total_price, status) VALUES ($1, $2, $3) RETURNING *',
[userId, totalPrice, 'pending']
);
const orderId = order.rows[0].id;
// Добавление элементов заказа
for (const item of items) {
await pool.query(
'INSERT INTO order_items (order_id, product_id, service_id, quantity, price) VALUES ($1, $2, $3, $4, $5)',
[
orderId,
item.product_id || null,
item.service_id || null,
item.quantity,
item.price
]
);
}
// Коммит транзакции
await pool.query('COMMIT');
res.status(201).json({ orderId, totalPrice });
} catch (error) {
// Откат транзакции в случае ошибки
await pool.query('ROLLBACK');
res.status(500).json({ message: error.message });
}
};
const getOrderById = async (req, res) => {
const orderId = req.params.id;
const userId = req.user.id;
try {
const order = await pool.query('SELECT * FROM orders WHERE id = $1 AND user_id = $2', [orderId, userId]);
if (order.rows.length === 0) return res.status(404).json({ message: 'Заказ не найден' });
res.status(200).json(order.rows[0]);
} catch (error) {
res.status(500).json({ message: 'Ошибка сервера' });
}
};
module.exports = { createOrder, getOrderById };
Пример маршрутов для управления заказами:
// routes/orderRoutes.js
const express = require('express');
const router = express.Router();
const orderController = require('../controllers/orderController');
const { authenticate } = require('../middleware/authenticate');
router.post('/', authenticate, orderController.createOrder);
router.get('/:id', authenticate, orderController.getOrderById);
module.exports = router;
Для повышения производительности базы данных рекомендуется использовать индексы:
-- Создание индекса для быстрого поиска по email
CREATE INDEX idx_users_email ON users(email);
-- Индекс для поиска товаров по категории
CREATE INDEX idx_products_category_id ON products(category_id);
Использование библиотек helmet
и cors
для обеспечения безопасности приложения:
// app.js
const helmet = require('helmet');
const cors = require('cors');
app.use(helmet());
app.use(cors());
Для упрощения поддержки и масштабирования проекта рекомендуется разделить код на отдельные модули: контроллеры, модели, маршруты и т.д.
Для повышения производительности и надежности приложения можно использовать кластеризацию Node.js с помощью модуля cluster
.
// server.js
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const numCPUs = os.cpus().length;
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Рабочий ${worker.process.pid} завершился`);
cluster.fork();
});
} else {
require('./app');
}
Данное руководство предоставляет подробную информацию о создании базы данных и настройке backend для веб-сайта, предлагающего товары и услуги. Использование PostgreSQL обеспечивает надежное хранение данных, а Node.js с использованием Express позволяет эффективно реализовать функционал аутентификации, управления товарами, услугами и заказами. Следуя предложенной структуре и рекомендациям по безопасности и масштабированию, вы сможете разработать стабильное и масштабируемое веб-приложение.