lazarev_andrey_lab_3 #87
1
lazarev_andrey_lab_3/.gitignore
vendored
Normal file
1
lazarev_andrey_lab_3/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
venv/
|
76
lazarev_andrey_lab_3/README.md
Normal file
76
lazarev_andrey_lab_3/README.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# Лабораторная работа №2
|
||||||
|
|
||||||
|
## Описание проекта
|
||||||
|
|
||||||
|
Проект разворачивает 3 программы в отдельных контейнерах с использованием Docker Compose:
|
||||||
|
1. **author_service** - сервис, с CRUD операциями для авторов;
|
||||||
|
2. **publication_service** - сервис, с CRUD операциями для публикаций;
|
||||||
|
3. **nginx** - веб-сервер и прокси-сервер, является маршрутизатором.
|
||||||
|
|
||||||
|
Между первыми двумя сервисами имеется связь один(`Автор`) ко многим(`Публикация`).
|
||||||
|
|
||||||
|
## Струкутура проекта
|
||||||
|
|
||||||
|
### Проект состоит из:
|
||||||
|
|
||||||
|
- 2 папки(author_service, publication_service)
|
||||||
|
- Каждая папка содержит в себе файл с расширением `.py` с кодом программы;
|
||||||
|
- Кадлая папка сожержит в себе файл `Dockerfile` с инструкцией по созданию Docker образа.
|
||||||
|
|
||||||
|
- Файл `.gitignore` для исключения временных файлов директории `venv/`;
|
||||||
|
|
||||||
|
- Файл `docker-compose.yml` с конфигурацией Docker Compose;
|
||||||
|
|
||||||
|
- Файл `nginx.conf` конфигурации для веб-сервера NGINX с параметрами работы сервера;
|
||||||
|
|
||||||
|
- Файл `requirements.txt` с перечислением всех необходимых библиотек для запуска.
|
||||||
|
|
||||||
|
Комментарии в файлах.
|
||||||
|
|
||||||
|
## Запуск
|
||||||
|
|
||||||
|
1. Скачать и установить Docker и Docker Compose;
|
||||||
|
2. Перейти в директорию с файлом docker-compose.yml;
|
||||||
|
3. Открыть консоль и запустить сервисы командой
|
||||||
|
```bash
|
||||||
|
docker-compose up --build -d
|
||||||
|
```
|
||||||
|
4. Дождаться запуска всех сервисов
|
||||||
|
```bash
|
||||||
|
[+] Running 3/3
|
||||||
|
✔ Container lazarev_andrey_lab_2-generate-files-1 Started 0.5s
|
||||||
|
✔ Container lazarev_andrey_lab_2-first-1 Started 1.3s
|
||||||
|
✔ Container lazarev_andrey_lab_2-second-1 Started 2.0s
|
||||||
|
```
|
||||||
|
5. Остановка всех сервисов
|
||||||
|
Для завершения работы с сервисами необходимо выполнить команду:
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
Дождаться завершения работы:
|
||||||
|
```bash
|
||||||
|
[+] Running 4/4
|
||||||
|
✔ Container lazarev_andrey_lab_2-second-1 Removed 0.0s
|
||||||
|
✔ Container lazarev_andrey_lab_2-first-1 Removed 0.0s
|
||||||
|
✔ Container lazarev_andrey_lab_2-generate-files-1 Removed 0.0s
|
||||||
|
✔ Network lazarev_andrey_lab_2_default Removed 0.4s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cписок команд
|
||||||
|
- Author_service
|
||||||
|
- `http://localhost:8000/author_service/author` - список авторов
|
||||||
|
- `http://localhost:8000/author_service/author/{id автора}` - конкретный автор
|
||||||
|
- `http://localhost:8000/author_service/author/full/{id автора}` - автор и полный список его публикаций
|
||||||
|
- `http://localhost:8000/author_service/author?name={имя}&second_name={фамилия}&age={возраст}` - добавление нового автора
|
||||||
|
- `http://localhost:8000/author_service/author/{id автора}?name={новое имя}` - изменение имени автора
|
||||||
|
|
||||||
|
- Publication_service
|
||||||
|
- `http://localhost:8000/publication_service/publication` - список публикаций
|
||||||
|
- `http://localhost:8000/publication_service/publication/{id публикации}` - конкретная публикация
|
||||||
|
- `http://localhost:8000/publication_service/publication/full/{id публикации}` - публикация и полная информация об авторе
|
||||||
|
- `http://localhost:8000/publication_service/publication?name={название}&public_year={год выпуска}&author_id={id автора}` - добавление новой публикации
|
||||||
|
- `http://localhost:8000/publication_service/publication/{id публикации}?name={новое название}` - изменение названия публикации
|
||||||
|
## Видеодемонстрация работоспособности
|
||||||
|
|
||||||
|
[Демонстрация работы сервисов](https://files.ulstu.ru/s/5D2i6gbLn6r2jsA)
|
||||||
|
|
17
lazarev_andrey_lab_3/author_service/Dockerfile
Normal file
17
lazarev_andrey_lab_3/author_service/Dockerfile
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Использует базовый образ Python 3.9 на основе slim-версии
|
||||||
|
FROM python:3.10-slim
|
||||||
|
|
||||||
|
# Устанавливаю рабочую директорию внутри контейнера
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Копирую файл requirements.txt в контейнер
|
||||||
|
COPY requirements.txt /app/
|
||||||
|
|
||||||
|
# Устанавливаю зависимости
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
#аналогично копирую
|
||||||
|
COPY author_service/main.py .
|
||||||
|
|
||||||
|
# Задает команду для запуска контейнера
|
||||||
|
CMD ["python", "main.py"]
|
110
lazarev_andrey_lab_3/author_service/main.py
Normal file
110
lazarev_andrey_lab_3/author_service/main.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from uuid import UUID, uuid4
|
||||||
|
import requests
|
||||||
|
|
||||||
|
#Инициализация веб-приложения
|
||||||
|
app = FastAPI(title="Author service")
|
||||||
|
|
||||||
|
#Строка подключения к публикациям
|
||||||
|
publication_url='http://publication_service:8009'
|
||||||
|
|
||||||
|
#Сущность автор с полями имя, фамилия, возраст.
|
||||||
|
class Author(BaseModel):
|
||||||
|
uuid_: UUID = Field(default_factory=uuid4)
|
||||||
|
name: str | None
|
||||||
|
second_name: str | None
|
||||||
|
age: int | None
|
||||||
|
|
||||||
|
#Заранее заполненный список авторов, в некоторых есть uuid они пригодятся при создании публикаций
|
||||||
|
data: list[Author] = [
|
||||||
|
Author(uuid_="92e78b56-0026-4561-b9f6-ba628110c900", name="Андрей", second_name="Лазарев", age=21),
|
||||||
|
Author(uuid_="3203b355-d844-4d5a-ad91-a9e0135cd9d9",name="Павел", second_name="Сорокин", age=20),
|
||||||
|
Author(name="Дарья", second_name="Балберова", age=21),
|
||||||
|
Author(name="Дмитрий", second_name="Курило", age=24),
|
||||||
|
Author(name="Александр", second_name="Дырночкин", age=24)
|
||||||
|
]
|
||||||
|
|
||||||
|
#Получить список всех авторов
|
||||||
|
@app.get("/author", tags=["Author"])
|
||||||
|
def all_authors():
|
||||||
|
return data
|
||||||
|
|
||||||
|
#Получить одного автора по uuid
|
||||||
|
@app.get("/author/{author_id}", tags=["Author"])
|
||||||
|
def get_author(author_id: UUID):
|
||||||
|
author = next((x for x in data if x.uuid_ == author_id), None)
|
||||||
|
|
||||||
|
if not author:
|
||||||
|
return HTTPException(status_code=404, detail="Автор не найден")
|
||||||
|
|
||||||
|
return author
|
||||||
|
|
||||||
|
#Получить одного автора по uuid с списком его публикаций
|
||||||
|
@app.get("/author/full/{author_id}")
|
||||||
|
def get_publications_by_author(author_id: UUID):
|
||||||
|
author = get_author(author_id)
|
||||||
|
|
||||||
|
if not author:
|
||||||
|
return HTTPException(status_code=404, detail="Автор не найден")
|
||||||
|
|
||||||
|
publications = requests.get(f"{publication_url}/publication/author/{author_id}")
|
||||||
|
|
||||||
|
if not publications:
|
||||||
|
return HTTPException(status_code=404, detail="Публикации не найдены")
|
||||||
|
|
||||||
|
result = author.model_dump()
|
||||||
|
result['publications'] = publications.json()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
#Добавление нового автора, все поля обязательные
|
||||||
|
@app.post("/author", tags=["Author"])
|
||||||
|
def add_author(name: str, second_name: str, age: int):
|
||||||
|
author = next((x for x in data if x.name == name and x.second_name == second_name and x.age == age), None)
|
||||||
|
|
||||||
|
if author:
|
||||||
|
return HTTPException(status_code=404, detail="Такой автор уже существует")
|
||||||
|
|
||||||
|
try:
|
||||||
|
data.append(Author(name=name, second_name=second_name, age=age))
|
||||||
|
return JSONResponse(content={"message": "Автор успешно добавлен"}, status_code=200)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return HTTPException(status_code=404, detail={"Автор не был добавлен с ошибкой": str(e)})
|
||||||
|
|
||||||
|
#Изменение автора по uuid
|
||||||
|
@app.put("/author/{author_id}", tags=["Author"])
|
||||||
|
def update_author(author_id: UUID, name: str = None , second_name: str = None, age: int = None):
|
||||||
|
author = get_author(author_id)
|
||||||
|
if author:
|
||||||
|
index = data.index(author)
|
||||||
|
if name:
|
||||||
|
data[index].name = name
|
||||||
|
if second_name:
|
||||||
|
data[index].second_name = second_name
|
||||||
|
if age:
|
||||||
|
data[index].age = age
|
||||||
|
return JSONResponse(content={"message": "Автор успешно изменен"}, status_code=200)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return HTTPException(status_code=404, detail={"Автор не найден": {author}})
|
||||||
|
|
||||||
|
#Удаление автора по uuid
|
||||||
|
@app.delete("/author/{author_id}", tags=["Author"])
|
||||||
|
def delete_author(author_id: UUID):
|
||||||
|
author = get_author(author_id)
|
||||||
|
|
||||||
|
if author:
|
||||||
|
index = data.index(author)
|
||||||
|
del data[index]
|
||||||
|
return JSONResponse(content={"message": "Автор успешно удален"}, status_code=200)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return HTTPException(status_code=404, detail={"Автор не найден": {author}})
|
||||||
|
|
||||||
|
#Запуск
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8008)
|
26
lazarev_andrey_lab_3/docker-compose.yml
Normal file
26
lazarev_andrey_lab_3/docker-compose.yml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
|
||||||
|
author_service:
|
||||||
|
build:
|
||||||
|
context: . #Контекст сборки — текущая директория (корневая папка проекта).
|
||||||
|
dockerfile: ./author_service/Dockerfile # Путь до Dockerfile для сборки контейнера.
|
||||||
|
expose: # Указывает, какой порт будет открыт внутри контейнера.
|
||||||
|
- 8008
|
||||||
|
|
||||||
|
publication_service:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./publication_service/Dockerfile
|
||||||
|
expose:
|
||||||
|
- 8009
|
||||||
|
|
||||||
|
nginx: # Третий сервис, называемый "nginx".
|
||||||
|
image: nginx # Используется готовый образ NGINX из Docker Hub.
|
||||||
|
ports: # Публикует порты для доступа к NGINX.
|
||||||
|
- 8000:8000 # Проброс порта: внешний порт 8000 связан с внутренним портом 8000.
|
||||||
|
volumes: # Монтирует локальные файлы/директории в контейнер.
|
||||||
|
- ./nginx.conf:/etc/nginx/nginx.conf # Локальный файл nginx.conf будет монтирован в контейнер по пути /etc/nginx/nginx.conf.
|
||||||
|
depends_on: # Зависимости. NGINX будет запускаться после запуска указанных сервисов.
|
||||||
|
- author_service # NGINX зависит от запуска author_service.
|
||||||
|
- publication_service # NGINX зависит от запуска publication_service.
|
27
lazarev_andrey_lab_3/nginx.conf
Normal file
27
lazarev_andrey_lab_3/nginx.conf
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
server {
|
||||||
|
listen 8000;
|
||||||
|
listen [::]:8000;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
location /author_service/ {
|
||||||
|
proxy_pass http://author_service:8008/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-Proto $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Prefix $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /publication_service/ {
|
||||||
|
proxy_pass http://publication_service:8009/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-Proto $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Prefix $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
lazarev_andrey_lab_3/publication_service/Dockerfile
Normal file
12
lazarev_andrey_lab_3/publication_service/Dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#описание в Dockerfile author_service
|
||||||
|
FROM python:3.10-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY publication_service/main.py .
|
||||||
|
|
||||||
|
CMD ["python", "main.py"]
|
113
lazarev_andrey_lab_3/publication_service/main.py
Normal file
113
lazarev_andrey_lab_3/publication_service/main.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from uuid import UUID, uuid4
|
||||||
|
import requests
|
||||||
|
|
||||||
|
#Инициализация веб-приложения
|
||||||
|
app = FastAPI(title="Publication service")
|
||||||
|
|
||||||
|
#Строка подключения к авторам
|
||||||
|
author_url='http://author_service:8008'
|
||||||
|
|
||||||
|
#Сущность публикации с полями название, год публикации, ид автора.
|
||||||
|
class Publication(BaseModel):
|
||||||
|
uuid_: UUID = Field(default_factory=uuid4)
|
||||||
|
name: str | None
|
||||||
|
public_year: int | None
|
||||||
|
author_id: UUID | None
|
||||||
|
|
||||||
|
#Заранее заполненный список публикаций, в некоторых есть uuid они пригодятся при создании публикаций
|
||||||
|
data: list[Publication] = [
|
||||||
|
Publication(uuid_="92e78b56-0026-4561-b9f6-ba628110c901", name="книга 1", public_year=2024, author_id="92e78b56-0026-4561-b9f6-ba628110c900"),
|
||||||
|
Publication(name="книга 2", public_year=2022, author_id="92e78b56-0026-4561-b9f6-ba628110c900"),
|
||||||
|
Publication(name="книга 3", public_year=2003, author_id="3203b355-d844-4d5a-ad91-a9e0135cd9d9"),
|
||||||
|
Publication(name="книга 4", public_year=2020, author_id="92e78b56-0026-4561-b9f6-ba628110c900"),
|
||||||
|
Publication(name="книга 5", public_year=2019, author_id="3203b355-d844-4d5a-ad91-a9e0135cd9d9")
|
||||||
|
]
|
||||||
|
|
||||||
|
#Получить список всех публикаций
|
||||||
|
@app.get("/publication", tags=["Publication"])
|
||||||
|
def all_publications():
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
#Получить одной публикации по uuid
|
||||||
|
@app.get("/publication/{publication_id}", tags=["Publication"])
|
||||||
|
def get_publication(publication_id: UUID):
|
||||||
|
publication = next((x for x in data if x.uuid_ == publication_id), None)
|
||||||
|
|
||||||
|
if not publication:
|
||||||
|
return HTTPException(status_code=404, detail="Публикация не найдена")
|
||||||
|
|
||||||
|
return publication
|
||||||
|
|
||||||
|
#Получить одной публикации по uuid с информацие об ее авторе
|
||||||
|
@app.get("/publication/full/{publication_id}")
|
||||||
|
def get_full_publication(publication_id: UUID):
|
||||||
|
publication = get_publication(publication_id)
|
||||||
|
|
||||||
|
if not publication:
|
||||||
|
return HTTPException(status_code=404, detail="Публикаций не найдена")
|
||||||
|
|
||||||
|
author = requests.get(f"{author_url}/author/{publication.author_id}")
|
||||||
|
|
||||||
|
if not author:
|
||||||
|
return HTTPException(status_code=404, detail="Автор не найден")
|
||||||
|
|
||||||
|
result = publication.model_dump()
|
||||||
|
result['author_info'] = author.json()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
#Добавление новой публикации, все поля обязательные
|
||||||
|
@app.post("/publication", tags=["Publication"])
|
||||||
|
def add_publication(name: str, public_year: int, author_id: UUID):
|
||||||
|
author = next((x for x in data if x.name == name and x.public_year == public_year and x.author_id == author_id), None)
|
||||||
|
|
||||||
|
if author:
|
||||||
|
return HTTPException(status_code=404, detail="Такая публикация уже существует")
|
||||||
|
|
||||||
|
try:
|
||||||
|
data.append(Publication(name=name, public_year=public_year, author_id=author_id))
|
||||||
|
return JSONResponse(content={"message": "Публикация успешно добавлена"}, status_code=200)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return HTTPException(status_code=404, detail={"Публикация не была добавлена с ошибкой": str(e)})
|
||||||
|
|
||||||
|
#Изменение публикации по uuid
|
||||||
|
@app.put("/publication/{publication_id}", tags=["Publication"])
|
||||||
|
def update_publication(publication_id: UUID, name: str = None, public_year: int = None, author_id: UUID = None):
|
||||||
|
publication = get_publication(publication_id)
|
||||||
|
|
||||||
|
if publication:
|
||||||
|
index = data.index(publication)
|
||||||
|
if name:
|
||||||
|
data[index].name = name
|
||||||
|
if public_year:
|
||||||
|
data[index].public_year = public_year
|
||||||
|
if author_id:
|
||||||
|
data[index].author_id = author_id
|
||||||
|
return JSONResponse(content={"message": "Публикация успешно изменена"}, status_code=200)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return HTTPException(status_code=404, detail={"Публикация не найдена": {publication}})
|
||||||
|
|
||||||
|
#Удаление публикации по uuid
|
||||||
|
@app.delete("/publication/{publication_id}", tags=["Publication"])
|
||||||
|
def delete_publication(publication_id: UUID):
|
||||||
|
publication = get_publication(publication_id)
|
||||||
|
|
||||||
|
if publication:
|
||||||
|
index = data.index(publication)
|
||||||
|
del data[index]
|
||||||
|
|
||||||
|
return JSONResponse(content={"message": "Публикация успешно удалена"}, status_code=200)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return HTTPException(status_code=404, detail={"Публикация не найдена": {publication}})
|
||||||
|
|
||||||
|
#Запуск
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8009)
|
5
lazarev_andrey_lab_3/requirements.txt
Normal file
5
lazarev_andrey_lab_3/requirements.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
fastapi==0.115.0
|
||||||
|
uvicorn==0.31.0
|
||||||
|
pydantic==2.9.2
|
||||||
|
pydantic_core==2.23.4
|
||||||
|
requests==2.32.3
|
Loading…
Reference in New Issue
Block a user