diff --git a/rogashova_ekaterina_lab_3/.gitignore b/rogashova_ekaterina_lab_3/.gitignore new file mode 100644 index 0000000..eb53ea0 --- /dev/null +++ b/rogashova_ekaterina_lab_3/.gitignore @@ -0,0 +1,2 @@ +/.venv +/.idea \ No newline at end of file diff --git a/rogashova_ekaterina_lab_3/docker-compose.yml b/rogashova_ekaterina_lab_3/docker-compose.yml new file mode 100644 index 0000000..581d984 --- /dev/null +++ b/rogashova_ekaterina_lab_3/docker-compose.yml @@ -0,0 +1,27 @@ +services: + + task_service: + container_name: task_service + build: + context: . + dockerfile: ./task_service/Dockerfile + expose: + - 8081 + + teacher_service: + container_name: teacher_service + build: + context: . + dockerfile: ./teacher_service/Dockerfile + expose: + - 8082 + + nginx: + image: nginx:latest + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + depends_on: + - task_service + - teacher_service \ No newline at end of file diff --git a/rogashova_ekaterina_lab_3/nginx.conf b/rogashova_ekaterina_lab_3/nginx.conf new file mode 100644 index 0000000..a5f11e7 --- /dev/null +++ b/rogashova_ekaterina_lab_3/nginx.conf @@ -0,0 +1,25 @@ +events { worker_connections 1024; } + +http { + server { + listen 80; + listen [::]:80; + server_name localhost; + + location /task_service/ { + proxy_pass http://task_service:8081/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /teacher_service/ { + proxy_pass http://teacher_service:8082/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } +} \ No newline at end of file diff --git a/rogashova_ekaterina_lab_3/readme.md b/rogashova_ekaterina_lab_3/readme.md new file mode 100644 index 0000000..5e3689e --- /dev/null +++ b/rogashova_ekaterina_lab_3/readme.md @@ -0,0 +1,37 @@ +# Лабораторная работа №3 + +## Описание +Были использованы следующие сущности: +1. Учитель. Поля: email и ФИО +2. Задание. Поля: название, предмет и id учителя + +Сущности связаны связью один ко многим. У одного учителя может быть несколько заданий, у задания только один учитель. + +Для каждой сущности был написан отдельный сервис. + +Код каждого сервиса представляет собой полноценное API для работы с задачами и учителями. +Они включают основные операции CRUD (создание, чтение, обновление, удаление). Дополнительно можно вывести задачу с полной информацией об учителе. + +В качестве хранилища данных использовалась оперативная память приложения. + +Запросы к сервисам проксирует шлюз на основе Nginx. Для этого перед запуском nginx был описан конфигурационный файл nginx.conf, +в котором описан прослушиваемый порт и название сервера (в блоке server), маршруты до микросервисов и параметры проксирования (в блоке location). + +Конфигурация Nginx направлена на организацию и упрощение работы с микросервисами. + +Тестирование производится через расширение RestMan. + +## Как запустить это? + +Для запуска данной конфигурации Docker Compose выполните следующие шаги: + +1. Открыть терминал и перейти в директорию, где находится docker-compose.yml. +2. Выполнить команду +``` +docker compose up --build +``` +3. Дождаться, пока Docker Compose запустит все контейнеры. + + +## Видео +Работоспособность представлена на [видео](https://vk.com/video204968285_456240927). \ No newline at end of file diff --git a/rogashova_ekaterina_lab_3/requirements.txt b/rogashova_ekaterina_lab_3/requirements.txt new file mode 100644 index 0000000..494909e --- /dev/null +++ b/rogashova_ekaterina_lab_3/requirements.txt @@ -0,0 +1,2 @@ +Flask==3.0.3 +requests==2.32.3 \ No newline at end of file diff --git a/rogashova_ekaterina_lab_3/task_service/Dockerfile b/rogashova_ekaterina_lab_3/task_service/Dockerfile new file mode 100644 index 0000000..40f31ce --- /dev/null +++ b/rogashova_ekaterina_lab_3/task_service/Dockerfile @@ -0,0 +1,17 @@ +# Используем базовый образ Python +FROM python:3.12-slim + +# Устанавливаем рабочую директорию +WORKDIR /app + +# Копируем файл requirements.txt в контейнер +COPY requirements.txt . + +# Устанавливаем зависимости +RUN pip install --no-cache-dir -r requirements.txt + +# Копируем все файлы в контейнер +COPY task_service/task_service.py . + +# Команда для запуска Python-скрипта +CMD ["python", "task_service.py"] \ No newline at end of file diff --git a/rogashova_ekaterina_lab_3/task_service/task_service.py b/rogashova_ekaterina_lab_3/task_service/task_service.py new file mode 100644 index 0000000..d2a2f79 --- /dev/null +++ b/rogashova_ekaterina_lab_3/task_service/task_service.py @@ -0,0 +1,119 @@ +from flask import Flask, jsonify, request, abort +import uuid +import requests + +app = Flask(__name__) + +tasks = {} +teachers = {} + +@app.route('/tasks', methods=['GET']) +def get_tasks(): + # Получаем все задачи, но только с teacher_id. + return jsonify(list(tasks.values())) + +@app.route('/tasks_with_teachers', methods=['GET']) +def get_tasks_with_teachers(): + tasks_with_teachers = [] + + for task in tasks.values(): + teacher_id = task.get('teacher_id') + teacher_info = None + + if teacher_id: + try: + # Запрос информации об учителе + teacher_response = requests.get(f'http://teacher_service:8082/teachers/{teacher_id}') + if teacher_response.status_code == 200: + teacher_details = teacher_response.json() + # Формируем объект с нужной информацией об учителе + teacher_info = { + "fio": teacher_details.get("fio"), + "email": teacher_details.get("email") + } + except requests.exceptions.RequestException as e: + print(f"Error fetching teacher: {e}") + + # Формируем ответ с нужной структурой + tasks_with_teachers.append({ + 'id': task.task_id, + 'title': task.get('title'), + 'subject': task.get('subject'), + 'teacher_id': task.get('teacher_id'), + 'teacherInfo': teacher_info # Упрощенная информация об учителе + }) + + return jsonify(tasks_with_teachers) + +@app.route('/tasks/', methods=['GET']) +def get_task(task_id): + task = tasks.get(task_id) + if task is None: + abort(404) + + return jsonify(task) + +@app.route('/task_with_teacher/', methods=['GET']) +def get_task_with_teacher(task_id): + task = tasks.get(task_id) + if task is None: + abort(404) + + # Получаем информацию о назначенном учителе из teacher_service + teacher_info = None + teacher_id = task.get('teacher_id') + if teacher_id: + try: + teacher_response = requests.get(f'http://teacher_service:8082/teachers/{teacher_id}') + if teacher_response.status_code == 200: + teacher_details = teacher_response.json() + # Формируем объект с нужной информацией об учителе + teacher_info = { + "fio": teacher_details.get("fio"), + "email": teacher_details.get("email") + } + except requests.exceptions.RequestException as e: + print(f"Error fetching teacher: {e}") + + # Формируем ответ с нужной структурой + response = { + "id": task_id, # id задания + "title": task.get('title'), + "subject": task.get('subject'), + "teacher_id": teacher_id, + "teacherInfo": teacher_info # Упрощенная информация об учителе + } + + return jsonify(response) + +@app.route('/tasks', methods=['POST']) +def create_task(): + data = request.get_json() + task_id = str(uuid.uuid4()) + tasks[task_id] = { + 'id': task_id, + 'title': data['title'], + 'subject': data['subject'], + 'teacher_id': data['teacher_id'] # Принимаем teacher_id из запроса + } + return jsonify(tasks[task_id]), 201 + +@app.route('/tasks/', methods=['PUT']) +def update_task(task_id): + data = request.get_json() + task = tasks.get(task_id) + if task is None: + abort(404) + task['title'] = data.get('title', task['title']) + task['subject'] = data.get('subject', task['subject']) + return jsonify(task) + +@app.route('/tasks/', methods=['DELETE']) +def delete_task(task_id): + task = tasks.pop(task_id, None) + if task is None: + abort(404) + return '', 200 + +if __name__ == '__main__': + app.run(host='0.0.0.0', use_reloader=False, port=8081) diff --git a/rogashova_ekaterina_lab_3/teacher_service/Dockerfile b/rogashova_ekaterina_lab_3/teacher_service/Dockerfile new file mode 100644 index 0000000..a3bfc57 --- /dev/null +++ b/rogashova_ekaterina_lab_3/teacher_service/Dockerfile @@ -0,0 +1,17 @@ +# Используем базовый образ Python +FROM python:3.12-slim + +# Устанавливаем рабочую директорию +WORKDIR /app + +# Копируем файл requirements.txt в контейнер +COPY requirements.txt . + +# Устанавливаем зависимости +RUN pip install --no-cache-dir -r requirements.txt + +# Копируем все файлы в контейнер +COPY teacher_service/teacher_service.py . + +# Команда для запуска Python-скрипта +CMD ["python", "teacher_service.py"] \ No newline at end of file diff --git a/rogashova_ekaterina_lab_3/teacher_service/teacher_service.py b/rogashova_ekaterina_lab_3/teacher_service/teacher_service.py new file mode 100644 index 0000000..2c39cd0 --- /dev/null +++ b/rogashova_ekaterina_lab_3/teacher_service/teacher_service.py @@ -0,0 +1,48 @@ +from flask import Flask, jsonify, request, abort +import uuid + +app = Flask(__name__) + +teachers = {} + +@app.route('/teachers', methods=['GET']) +def get_teachers(): + return jsonify(list(teachers.values())) + +@app.route('/teachers/', methods=['GET']) +def get_teacher(teacher_id): + teacher = teachers.get(teacher_id) + if teacher is None: + abort(404) + return jsonify(teacher) + +@app.route('/teachers', methods=['POST']) +def create_teacher(): + data = request.get_json() + teacher_id = str(uuid.uuid4()) + teachers[teacher_id] = { + 'id': teacher_id, + 'fio': data['fio'], + 'email': data['email'] + } + return jsonify(teachers[teacher_id]), 201 + +@app.route('/teachers/', methods=['PUT']) +def update_teacher(teacher_id): + data = request.get_json() + teacher = teachers.get(teacher_id) + if teacher is None: + abort(404) + teacher['fio'] = data.get('fio', teacher['fio']) + teacher['email'] = data.get('email', teacher['email']) + return jsonify(teacher) + +@app.route('/teachers/', methods=['DELETE']) +def delete_teacher(teacher_id): + teacher = teachers.pop(teacher_id, None) + if teacher is None: + abort(404) + return '', 200 + +if __name__ == '__main__': + app.run(host='0.0.0.0', use_reloader=False, port=8082)