- •Курсовая работа по дисциплине
- •Введение
- •Постановка задачи
- •Обоснование выбора технологий
- •Разработка структуры программы
- •4.1. Общая архитектура клиент-серверного взаимодействия
- •4.2. Структура серверного приложения (Flask)
- •4.3. Система хранения данных
- •4.4. Схема базы данных
- •Разработка ключевых модулей серверной части
- •5.1. Модуль инициализации и конфигурации
- •5.2. Модуль работы с данными клиник и записей (json-слой)
- •5.3. Модуль взаимодействия с базой данных (MySql-слой)
- •5.4. Модуль маршрутизации и обработки запросов (Flask-пути)
- •5.5. Модуль управления сессиями и аутентификацией
- •Сценарии пользователя
- •Заключение
- •Приложение
Приложение
Server.py
from flask import (
Flask,
render_template,
request,
jsonify,
send_from_directory,
session,
redirect,
url_for,
)
import os
import json
from datetime import datetime, timedelta
from flask_cors import CORS
import pymysql
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, static_folder='static', template_folder='templates')
CORS(app)
# Ключ для сессий
app.secret_key = 'change_this_secret_key'
# Папка и файлы с данными (клиники + слоты)
DATA_DIR = os.path.join(BASE_DIR, 'data')
CLINICS_FILE = os.path.join(DATA_DIR, 'clinics.json')
APPOINTMENTS_FILE = os.path.join(DATA_DIR, 'appointments.json')
os.makedirs(DATA_DIR, exist_ok=True)
clinics = {}
appointments = []
# ----------- Подключение к MySQL -----------
def get_db_connection():
"""
Подключение к MySQL в AMPPS.
База: webauth, пользователь: root/mysql.
"""
return pymysql.connect(
host='localhost',
user='root',
password='mysql',
database='webauth',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor,
)
# ----------- Работа с клиниками -----------
def save_clinics():
with open(CLINICS_FILE, 'w', encoding='utf-8') as f:
json.dump(clinics, f, ensure_ascii=False, indent=2)
def load_clinics():
"""
Загружает клиники из файла, если он есть.
Если файла нет — создаёт тестовые данные и сохраняет.
"""
global clinics
if os.path.exists(CLINICS_FILE):
with open(CLINICS_FILE, 'r', encoding='utf-8') as f:
clinics = json.load(f)
else:
clinics = {
"1": {
"id": "1",
"name": "Городская поликлиника №1",
"address": "ул. Пушкина, д. 10",
"phone": "+7 (812) 111-11-11",
"description": "Многопрофильная городская поликлиника.",
"services": [
{"name": "Терапевт", "price": 1500},
{"name": "Кардиолог", "price": 2000},
{"name": "Невролог", "price": 2100},
{"name": "Анализы крови", "price": 1200},
],
},
"2": {
"id": "2",
"name": "Клиника «Семейный доктор»",
"address": "пр. Мира, д. 25",
"phone": "+7 (812) 222-22-22",
"description": "Частная семейная клиника.",
"services": [
{"name": "Педиатр", "price": 1700},
{"name": "Прививки", "price": 600},
{"name": "Медосмотр", "price": 2200},
],
},
"3": {
"id": "3",
"name": "Медицинский центр «Здоровье»",
"address": "Невский пр., д. 50",
"phone": "+7 (812) 333-33-33",
"description": "Диагностика и лечение взрослых.",
"services": [
{"name": "Терапевт", "price": 1600},
{"name": "УЗИ", "price": 2500},
{"name": "ЭКГ", "price": 900},
],
},
}
save_clinics()
# ----------- Работа со слотами записей -----------
def save_appointments():
with open(APPOINTMENTS_FILE, 'w', encoding='utf-8') as f:
json.dump(appointments, f, ensure_ascii=False, indent=2)
def generate_sample_appointments():
"""
Генерирует тестовые слоты на 3 дня вперёд для каждой клиники и врача.
"""
result = []
today = datetime.today().date()
doctors_map = {
"1": ["Иванов И.И. (терапевт)", "Петров П.П. (кардиолог)"],
"2": ["Сидорова С.С. (педиатр)", "Алексеев А.А. (терапевт)"],
"3": ["Кузнецов К.К. (терапевт)", "Новикова Н.Н. (невролог)"],
}
for clinic_id, clinic in clinics.items():
doctors = doctors_map.get(clinic_id, ["Дежурный врач"])
base_price = clinic["services"][0]["price"] if clinic.get("services") else 1500
for doctor in doctors:
for day_offset in range(3):
date = today + timedelta(days=day_offset)
for hour in (9, 11, 13, 15):
dt = datetime.combine(date, datetime.min.time()).replace(hour=hour)
slot_id = f"{clinic_id}-{doctor}-{dt.strftime('%Y%m%d%H%M')}"
result.append(
{
"id": slot_id,
"clinicId": clinic_id,
"clinicName": clinic["name"],
"doctor": doctor,
"time": dt.strftime("%Y-%m-%d %H:%M"),
"isTaken": False,
"patientName": "",
"cost": base_price,
}
)
return result
def load_appointments():
"""
Загружает слоты из файла или создаёт тестовые.
"""
global appointments
if os.path.exists(APPOINTMENTS_FILE):
with open(APPOINTMENTS_FILE, 'r', encoding='utf-8') as f:
appointments = json.load(f)
else:
appointments = generate_sample_appointments()
save_appointments()
# ----------- Вспомогательные функции для MySQL -----------
def log_appointment_to_db(appt: dict):
"""
Записывает информацию о записи на приём в таблицу appointments_log (MySQL).
"""
conn = None
try:
conn = get_db_connection()
with conn.cursor() as cursor:
cursor.execute(
"""
INSERT INTO appointments_log
(user_id, username, clinic_name, doctor, appointment_time, cost)
VALUES (%s, %s, %s, %s, %s, %s)
""",
(
session.get('user_id'),
session.get('username'),
appt.get('clinicName'),
appt.get('doctor'),
appt.get('time'),
appt.get('cost'),
),
)
conn.commit()
except Exception as e:
print(f"Ошибка записи appointment в MySQL: {e}")
finally:
if conn is not None:
conn.close()
# ----------- Страницы -----------
@app.route('/')
def index():
"""
Главная страница.
Показывает имя пользователя, если он зарегистрирован.
"""
username = session.get('username')
return render_template('index.html', username=username)
@app.route('/register', methods=['GET', 'POST'])
def register():
"""
Регистрация пользователя в MySQL (таблица users) + авторизация через сессию.
"""
error = None
if request.method == 'POST':
username = (request.form.get('username') or '').strip()
email = (request.form.get('email') or '').strip()
password = request.form.get('password') or ''
if not username or not email or not password:
error = 'Заполните все поля.'
else:
conn = None
try:
conn = get_db_connection()
with conn.cursor() as cursor:
cursor.execute(
"SELECT id FROM users WHERE username=%s OR email=%s",
(username, email),
)
exists = cursor.fetchone()
if exists:
error = 'Пользователь с таким именем или email уже существует.'
else:
cursor.execute(
"INSERT INTO users (username, email, password) VALUES (%s, %s, %s)",
(username, email, password),
)
conn.commit()
user_id = cursor.lastrowid
session['user_id'] = user_id
session['username'] = username
return redirect(url_for('index'))
except Exception as e:
print(f"Ошибка при регистрации: {e}")
error = 'Ошибка при сохранении в базу данных.'
finally:
if conn is not None:
conn.close()
return render_template('register.html', error=error)
@app.route('/logout')
def logout():
"""Выход — очистка сессии и возврат на главную."""
session.clear()
return redirect(url_for('index'))
# ----------- API: клиники и слоты -----------
@app.route('/api/clinics')
def api_clinics():
return jsonify({'success': True, 'clinics': clinics})
@app.route('/api/appointments')
def api_appointments():
return jsonify({'success': True, 'appointments': appointments})
@app.route('/api/book', methods=['POST'])
def api_book():
"""
Запись на приём
"""
if 'username' not in session:
return jsonify({'success': False, 'message': 'Сначала зарегистрируйтесь на сайте.'}), 403
try:
data = request.get_json(force=True, silent=False)
except Exception:
data = None
if not data:
return jsonify({'success': False, 'message': 'Нет данных.'}), 400
appointment_id = data.get('id')
if not appointment_id:
return jsonify({'success': False, 'message': 'Не выбрано время записи.'}), 400
global appointments
now = datetime.now()
for appt in appointments:
if appt['id'] == appointment_id:
# уже занято?
if appt.get('isTaken'):
return jsonify({'success': False, 'message': 'Это время уже занято.'}), 400
# проверка на прошедшее время
try:
slot_dt = datetime.strptime(appt['time'], '%Y-%m-%d %H:%M')
if slot_dt < now:
return jsonify({'success': False, 'message': 'Нельзя записаться на прошедшее время.'}), 400
except Exception as e:
print(f"Ошибка парсинга времени слота: {e}")
appt['isTaken'] = True
appt['patientName'] = session['username']
save_appointments()
# логируем в MySQL
log_appointment_to_db(appt)
return jsonify({'success': True, 'message': 'Вы успешно записаны!'})
return jsonify({'success': False, 'message': 'Слот записи не найден.'}), 404
@app.route('/api/my_appointments')
def api_my_appointments():
"""
Возвращает список записей текущего пользователя из MySQL (appointments_log).
Используется вкладкой "Мои записи".
"""
if 'username' not in session:
return jsonify({'success': False, 'message': 'Вы не авторизованы.'}), 403
conn = None
try:
conn = get_db_connection()
with conn.cursor() as cursor:
cursor.execute(
"""
SELECT id,
clinic_name, doctor, appointment_time, cost, created_at
FROM appointments_log
WHERE username = %s
ORDER BY appointment_time DESC
""",
(session['username'],),
)
rows = cursor.fetchall()
return jsonify({'success': True, 'appointments': rows})
except Exception as e:
print(f"Ошибка чтения appointments_log: {e}")
return jsonify({'success': False, 'message': 'Ошибка чтения записей.'}), 500
finally:
if conn is not None:
conn.close()
# ----------- API: заказ из корзины -----------
@app.route('/api/cart/order', methods=['POST'])
def api_cart_order():
"""
Оформление заказа из корзины услуг:
- проверка авторизации
- получение списка позиций и итоговой суммы
- сохранение в MySQL (таблица orders)
"""
if 'username' not in session:
return jsonify({'success': False, 'message': 'Сначала зарегистрируйтесь на сайте.'}), 403
try:
data = request.get_json(force=True, silent=False)
except Exception:
data = None
if not data:
return jsonify({'success': False, 'message': 'Нет данных.'}), 400
items = data.get('items') or []
total_price = data.get('total_price') or 0
if not items:
return jsonify({'success': False, 'message': 'Корзина пуста.'}), 400
conn = None
try:
conn = get_db_connection()
with conn.cursor() as cursor:
cursor.execute(
"""
INSERT INTO orders (user_id, username, items_json, total_price)
VALUES (%s, %s, %s, %s)
""",
(
session.get('user_id'),
session.get('username'),
json.dumps(items, ensure_ascii=False),
int(total_price),
),
)
conn.commit()
return jsonify({'success': True, 'message': 'Заказ успешно оформлен и сохранён в базе данных.'})
except Exception as e:
print(f"Ошибка сохранения заказа: {e}")
return jsonify({'success': False, 'message': 'Ошибка при сохранении заказа.'}), 500
finally:
if conn is not None:
conn.close()
# ----------- Статика и запуск -----------
@app.route('/static/<path:filename>')
def static_files(filename: str):
return send_from_directory(app.static_folder, filename)
@app.route('/favicon.ico')
def favicon():
return send_from_directory(app.static_folder, 'favicon.ico')
if __name__ == '__main__':
print('Загружаем данные...')
load_clinics()
load_appointments()
print('Сервер запускается на http://localhost:5000')
app.run(debug=True, port=5000)
