Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Программная инженерия. Курсовые / Вебтехнологии_Курсовая_Яковлев.docx
Скачиваний:
0
Добавлен:
04.01.2026
Размер:
800.32 Кб
Скачать

Приложение

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)