Compare commits

..

8 Commits

Author SHA1 Message Date
60ef5724cd Merge pull request 'sergeev_evgenii_lab_2 is done!' (#128) from sergeev_evgenii_lab_2 into main
Reviewed-on: http://student.git.athene.tech/Alexey/DAS_2023_1/pulls/128
2024-01-18 16:04:59 +04:00
6827a64c4d Merge pull request 'kutygin_andrey_lab_3_ready' (#120) from kutygin_andrey_lab_3 into main
Reviewed-on: http://student.git.athene.tech/Alexey/DAS_2023_1/pulls/120
2024-01-18 16:03:23 +04:00
sergeevevgen
46f2a8da94 + 2024-01-16 17:12:52 +04:00
Евгений Сергеев
84bd5277a9 done! 2024-01-16 16:57:20 +04:00
Евгений Сергеев
bb78e6823b done! 2024-01-16 16:05:42 +04:00
sergeevevgen
ea990fd848 + 2024-01-16 14:52:17 +04:00
a284e473a9 kutygin_andrey_lab_3_ready 2024-01-16 12:10:49 +04:00
Евгений Сергеев
36c429cb4f init 2024-01-13 19:17:58 +04:00
64 changed files with 1930 additions and 182 deletions

View File

@ -1,106 +0,0 @@
## Лабораторная работа 6. Вариант 4.
### Задание
Реализовать нахождение детерминанта квадратной матрицы.
- Создать алгоритм параллельного нахождения детерминанта квадратной матрицы,
- Предусмотреть задание потоков вручную, для работы параллельного алгоритма нахождения определителя как обычного.
### Как запустить
Для запуска программы необходимо с помощью командной строки в корневой директории файлов прокета прописать:
```
python main.py
```
Результат работы программы будет выведен в консоль.
### Используемые технологии
- Библиотека `numpy`, используемая для обработки массивов данных и вычислений.
- Библиотека `concurrent.futures`- высокоуровневый интерфейс для выполнения параллельных и асинхронных задач.
- `ThreadPoolExecutor` - класс пула потоков для выполнения задач в нескольких потоках. Он использует пул потоков, чтобы автоматически управлять созданием и выполнением потоков, что обеспечивает простой способ распараллеливания задач. Метод `submit()` позволяет отправлять задачи в пул потоков, и возвращает объект `Future`, который представляет результат выполнения задачи.
- Библиотека `time`, используемая для измерения времени работы программы.
- Библиотека `psutil`, используемая для отслеживания нагрузки на процессор и количества загруженной оперативной памяти.
### Описание работы
#### Распараллеливание задачи нахождения определителя
Воспользуемся правилом нахождения определителя с помощью миноров матрицы:
- Определитель матрицы равен сумме произведений элементов строки (столбца) на соответствующие алгебраические дополнения.
```
|a1, a2, a3| |b2, b3| |b1, b3| |b1, b2|
|b1, b2, b3| = (+)a1 x |c2, c3| + (-)a2 x |c1, c3| + (+)a3 x |c1, c2|
|c1, c2, c3|
```
Таким образом, мы можем выбрать 1ю строку матрицы и параллельно вычислить определитель каждого минора матрицы, умножив его на соответсвующий элемент.
#### Разработка программы
Для начала создадим функцию вычисления детерминанта минора матрицы с его умножением на соответсвующий элемент строки:
```python
def calculate_determinant(args):
matrix, i = args
multiplier = matrix[0][i]
if i % 2 != 0:
multiplier *= -1
matrix = np.delete(matrix, 0, axis=0)
submatrix = np.delete(matrix, i, axis=1)
return np.linalg.det(submatrix) * multiplier
```
Если элемент находится на нечётной позиции в строке, то, по правилу, знак множителя меняется на противоположный.
Теперь перейдём к алгоритму параллельного вычисления детерминанта матрицы. Создадим пул потоков с помощью класса `concurrent.futures.ThreadPoolExecutor`, в который будем передавать желаемое количество потоков:
```python
with concurrent.futures.ThreadPoolExecutor(max_workers=n) as executor:
results = []
start_time = time.time()
for i in range(n):
results.append(executor.submit(calculate_determinant, args=(matrix, i)))
result = np.sum([res.result() for res in results])
end_time = time.time()
```
В пул потоков в качестве аргументов мы передаём последовательно матрицу и номер столбца элемента-множителя, а в качестве их обработчика указываем ранее созданный метод `calculate_determinant`. Массив определителей миноров, умноженных на соответсвующие множители суммируется методом `sum` и записывается в результат.
> **Note**
>
> Поскольку определение детерминанта матрицы распараллеливается по 1му порядку миноров, задавать значение кол-ва потоков больше числа элементов строки-множителя (1й строки матрицы) не имеет значения. По сути, самым оптимальным решением будет являться состояние, когда каждый поток ищет определитель определённого минора, составленного по определённому элементу строки матрицы.
Создадим тестовый метод и проверим работу калькулятора вычисления определителя на грамотность:
```python
mx = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
return parallel_determinant(mx, parallel)
```
Результат вычислений: `0`
Алгоритм работает верно.
#### Замеры параметров
Бенчмарки в данной лабораторной работе задаются аналогично предыдущей. Прогоним все бенчмарки и сравним измеряемые показатели при максимальной многопоточности (кол-во потоков = кол-ву эл-тов 1й строки матрицы) и монопоточности.
Результаты (параллельный - слева, обычный - справа):
```
50 * 50
_________________________________________________________________
Время выполнения: 0.0129.. сек. | Время выполнения: 0.0010.. сек.
Загрузка ЦП: 5.4% | Загрузка ЦП: 4.8%
Использование ОЗУ: 71.6% | Использование ОЗУ: 71.7%
75 * 75
_________________________________________________________________
Время выполнения: 0.0220.. сек. | Время выполнения: 0.0.. сек.
Загрузка ЦП: 5.7% | Загрузка ЦП: 1.2%
Использование ОЗУ: 71.1% | Использование ОЗУ: 71.0%
125 * 125
_________________________________________________________________
Время выполнения: 0.5048.. сек. | Время выполнения: 0.0070.. сек.
Загрузка ЦП: 5.6% | Загрузка ЦП: 2.5%
Использование ОЗУ: 71.2% | Использование ОЗУ: 71.7%
```
### Вывод
По результатам замеров видно, что при любых размерностях матрицы, монопоточное вычисление определителя происходит эффективнее как по времени, так и по ресурс-затратности. По загруженности ЦП можно убедиться, что многопоточное определение детерминанта матрицы работает корректно, тк загрузка процессора при использовании нескольких потоков всегда выше, чем при использовании монопоточности.
Таким образом, применение парралельных вычислений при решении данной задачи показали себя не учшим образом. Связанно это может быть с оптимальностью алгоритма вычисления определителя функции `det` библиотеки `numpy`, с недостаточной оптимальностью алгоритма распараллеливания вычисления (находится минор только последующего порядка, а не рассчитывается до 1го в связи с рекурсивностью выбранного метода определения детерминанта) или с недостаточно большой размерностью матрицы (оперативной памяти данной машины хватает на вычисления определителя матрицы, максимальной размерностью 125х125)
### Видео
https://youtu.be/ayDhflcf7PM

View File

@ -1,76 +0,0 @@
import time
import numpy as np
import concurrent.futures
import psutil
def calculate_determinant(args):
matrix, i = args
multiplier = matrix[0][i]
if i % 2 != 0:
multiplier *= -1
matrix = np.delete(matrix, 0, axis=0)
submatrix = np.delete(matrix, i, axis=1)
return np.linalg.det(submatrix) * multiplier
def parallel_determinant(matrix, parallel):
memory = psutil.virtual_memory()
n = matrix.shape[0]
if parallel:
with concurrent.futures.ThreadPoolExecutor(max_workers=n) as executor:
results = []
start_time = time.time()
for i in range(n):
results.append(executor.submit(calculate_determinant, args=(matrix, i)))
result = np.sum([res.result() for res in results])
end_time = time.time()
else:
start_time = time.time()
result = np.linalg.det(matrix)
end_time = time.time()
execution_time = end_time - start_time
return result, execution_time, psutil.cpu_percent(interval=1), memory.percent
def test(parallel):
mx = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
result = parallel_determinant(mx, parallel)
print(f"Определитель матрицы: {result[0]}")
print(f"Время выполнения: {result[1]} сек.")
print(f"Загрузка ЦП: {result[2]} %")
print(f"Использование ОЗУ: {result[3]} %")
def bench50x50(parallel):
mx = np.random.randint(0, 100, size=(50, 50))
result = parallel_determinant(mx, parallel)
print(f"Определитель матрицы: {result[0]}")
print(f"Время выполнения: {result[1]} сек.")
print(f"Загрузка ЦП: {result[2]} %")
print(f"Использование ОЗУ: {result[3]} %")
def bench75x75(parallel):
mx = np.random.randint(0, 100, size=(75, 75))
result = parallel_determinant(mx, parallel)
print(f"Определитель матрицы: {result[0]}")
print(f"Время выполнения: {result[1]} сек.")
print(f"Загрузка ЦП: {result[2]} %")
print(f"Использование ОЗУ: {result[3]} %")
def bench125x125(parallel):
mx = np.random.randint(0, 100, size=(125, 125))
result = parallel_determinant(mx, parallel)
print(f"Определитель матрицы: {result[0]}")
print(f"Время выполнения: {result[1]} сек.")
print(f"Загрузка ЦП: {result[2]} %")
print(f"Использование ОЗУ: {result[3]} %")
if __name__ == '__main__':
# test(parallel=False)
# bench50x50(parallel=False)
# bench75x75(parallel=False)
bench125x125(parallel=False)

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="fb3a6329-81a8-41fd-8008-b8bea3f6c964" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectId" id="2ad3GS5xSIilTTmSvnMeDhv4jNu" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"last_opened_file_path": "C:/Users/kutyg/Downloads/pibd-22-internet-programming-Lab5 (1)/kutygin_andrey_lab_2/worker_2"
}
}]]></component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="C:\Users\kutyg\Downloads\pibd-22-internet-programming-Lab5 (1)\kutygin_andrey_lab_2\worker_2" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="C:\Users\kutyg\Downloads\pibd-22-internet-programming-Lab5 (1)\kutygin_andrey_lab_2\worker_2\src" />
<recent name="C:\Users\kutyg\Downloads\pibd-22-internet-programming-Lab5 (1)\kutygin_andrey_lab_2\worker_1\src" />
</key>
</component>
<component name="RunManager" selected="Docker.worker_2/Dockerfile">
<configuration name="docker-compose.yml: Compose Deployment" type="docker-deploy" factoryName="docker-compose.yml" temporary="true" server-name="Docker">
<deployment type="docker-compose.yml">
<settings>
<option name="sourceFilePath" value="docker-compose.yml" />
</settings>
</deployment>
<method v="2" />
</configuration>
<configuration name="worker_1/Dockerfile" type="docker-deploy" factoryName="dockerfile" temporary="true" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="sourceFilePath" value="worker_1/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
<configuration name="worker_2/Dockerfile" type="docker-deploy" factoryName="dockerfile" temporary="true" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="sourceFilePath" value="worker_2/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
<recent_temporary>
<list>
<item itemvalue="Docker.worker_2/Dockerfile" />
<item itemvalue="Docker.worker_1/Dockerfile" />
<item itemvalue="Docker.docker-compose.yml: Compose Deployment" />
</list>
</recent_temporary>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="fb3a6329-81a8-41fd-8008-b8bea3f6c964" name="Changes" comment="" />
<created>1704634533824</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1704634533824</updated>
</task>
<servers />
</component>
</project>

3
kutygin_andrey_lab_3/.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JpaBuddyIdeaProjectConfig">
<option name="renamerInitialized" value="true" />
</component>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="ProjectType">
<option name="id" value="jpab" />
</component>
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/lab3.iml" filepath="$PROJECT_DIR$/.idea/lab3.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,110 @@
## Задание
Цель: изучение шаблона проектирования gateway, построения синхронного обмена между микросервисами и архитектурного стиля RESTful API.
Задачи:
- Создать 2 микросервиса, реализующих CRUD на связанных сущностях.
- Реализовать механизм синхронного обмена сообщениями между микросервисами.
- Реализовать шлюз на основе прозрачного прокси-сервера nginx.
## Ход работы
### Разворачивание сервисов:
Были разработаны два приложения на Java с использованием средства автоматизации сборки проектов Gradle и с использованием библиотеки spring-boot:
- categoryService - работа с дисциплинами (crud)
- productService - работа с продуктами (crud). При создании продукта выбирается id категории, и через nginx и rest template происходит получение данных о категории с этим id
### Обмен сообщениями
При создании плана обучения выбирается id категории, и через nginx и rest template происходит получение данных о категории с этим id
### Dockerfile
Идентичные докерфайлы для приложений:
```
FROM openjdk:17
RUN mkdir -p /usr/src/app/
WORKDIR /usr/src/app/
COPY . /usr/src/app/
RUN ./gradlew clean build
EXPOSE 8089
ENTRYPOINT ["java","-jar","build/libs/lab3-0.0.1-SNAPSHOT.jar"]
```
### docker-compose.yml
Файл, соединяющий сервисы (содержащий настройку Docker):
```
version: "3" #формат конфигурации Docker Compose версии 3
services: #определение сервисов
category:
build:
context: /categoryService #путь к контексту сборки
dockerfile: Dockerfile #имя докерфайла
ports:
- "8089:8089" #проброс портов
networks:
- netwrk #сеть
product:
build:
context: /productService #путь к контексту сборки
dockerfile: Dockerfile #имя докерфайла
ports:
- "8090:8090" #проброс портов
networks:
- netwrk #сеть
nginx:
image: nginx:latest #образ для контейнера
ports:
- "8091:80" #проброс портов
networks:
- netwrk #сеть
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf #монтирует локальный файл конфигурации
depends_on: #зависимость от сервисов
- category
- product
networks:
netwrk:
driver: bridge #изолированная сеть
```
### nginx.conf
Настройка nginx:
```
http {
server {
listen 80;
listen [::]:80;
server_name localhost;
location /categoryService/ {
proxy_pass http://category:8089/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Prefix /categoryService;
}
location /productService/ {
proxy_pass http://product:8090/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Prefix /productService;
}
}
}
events {
worker_connections 1024;
}
```
## Результат
Видео: https://disk.yandex.ru/d/8Lcvb0H9LPNSKw

View File

@ -0,0 +1,42 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

View File

@ -0,0 +1,7 @@
FROM openjdk:17
RUN mkdir -p /usr/src/app/
WORKDIR /usr/src/app/
COPY . /usr/src/app/
RUN ./gradlew clean build
EXPOSE 8089
ENTRYPOINT ["java","-jar","build/libs/lab3-0.0.1-SNAPSHOT.jar"]

View File

@ -0,0 +1,34 @@
plugins {
id 'org.springframework.boot' version '2.6.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'categoryApp'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2:2.1.210'
implementation 'org.hibernate.validator:hibernate-validator'
implementation 'org.springdoc:springdoc-openapi-ui:1.6.5'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.6.5'
}
tasks.named('test') {
useJUnitPlatform()
}

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Wed Jan 10 18:16:23 GMT+04:00 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,2 @@
rootProject.name = "lab3"

View File

@ -0,0 +1,12 @@
package categoryApp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@ -0,0 +1,48 @@
package categoryApp.controller;
import categoryApp.model.CategoryDto;
import org.springframework.web.bind.annotation.*;
import categoryApp.service.*;
import javax.validation.Valid;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/")
public class CategoryController {
private final CategoryService categoryService;
public CategoryController(CategoryService categoryService) {
this.categoryService = categoryService;
}
@GetMapping("/{id}")
public CategoryDto getCategory(@PathVariable Long id) {
return new CategoryDto(categoryService.findCategory(id));
}
@GetMapping("/")
public List<CategoryDto> getCategorys() {
return categoryService.findAllCategorys().stream()
.map(CategoryDto::new)
.collect(Collectors.toList());
}
@PostMapping("/")
public CategoryDto createCategory(@RequestBody @Valid CategoryDto categoryDto) {
return new CategoryDto(categoryService.addCategory(categoryDto));
}
@PutMapping("/{id}")
public CategoryDto updateCategory(@RequestBody @Valid CategoryDto categoryDto) {
return new CategoryDto(categoryService.updateCategory(categoryDto));
}
@DeleteMapping("/{id}")
public CategoryDto deleteCategory(@PathVariable Long id) {
return new CategoryDto(categoryService.deleteCategory(id));
}
}

View File

@ -0,0 +1,53 @@
package categoryApp.model;
import javax.persistence.*;
@Entity
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String description;
public Category() {
}
public Category(String name, String description) {
this.name = name;
this.description = description;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public String toString() {
return "Category {" +
"id =" + id +
", name ='" + name + '\'' +
", desc ='" + description + '\'' +
'}';
}
}

View File

@ -0,0 +1,40 @@
package categoryApp.model;
public class CategoryDto {
private Long id;
private String name;
private String description;
public CategoryDto() {
}
public CategoryDto(Category category) {
this.id = category.getId();
this.name = String.format("%s", category.getName());
this.description = category.getDescription();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

View File

@ -0,0 +1,7 @@
package categoryApp.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import categoryApp.model.Category;
public interface CategoryRepository extends JpaRepository<Category, Long> {
}

View File

@ -0,0 +1,7 @@
package categoryApp.service;
public class CategoryNotFoundException extends RuntimeException{
public CategoryNotFoundException(Long id) {
super(String.format("Category with id [%s] is not found", id));
}
}

View File

@ -0,0 +1,49 @@
package categoryApp.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import categoryApp.model.*;
import categoryApp.repository.*;
import java.util.*;
@Service
public class CategoryService {
private final CategoryRepository categoryRepository;
public CategoryService(CategoryRepository categoryRepository) {
this.categoryRepository = categoryRepository;
}
@Transactional
public Category addCategory(CategoryDto categoryDto) {
final Category category = new Category(categoryDto.getName(), categoryDto.getDescription());
return categoryRepository.save(category);
}
@Transactional(readOnly = true)
public Category findCategory(Long id) {
final Optional<Category> category = categoryRepository.findById(id);
return category.orElseThrow(() -> new CategoryNotFoundException(id));
}
@Transactional(readOnly = true)
public List<Category> findAllCategorys() {
return categoryRepository.findAll();
}
@Transactional
public Category updateCategory(CategoryDto categoryDto) {
final Category currentCategory = findCategory(categoryDto.getId());
currentCategory.setName(categoryDto.getName());
currentCategory.setDescription(categoryDto.getDescription());
return categoryRepository.save(currentCategory);
}
@Transactional
public Category deleteCategory(Long id) {
final Category currentCategory = findCategory(id);
categoryRepository.delete(currentCategory);
return currentCategory;
}
}

View File

@ -0,0 +1,11 @@
spring.main.banner-mode=off
spring.datasource.url=jdbc:h2:file:./data
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true
spring.h2.console.settings.trace=false
spring.h2.console.settings.web-allow-others=false
server.port=8089

View File

@ -0,0 +1,35 @@
version: "3" #формат конфигурации Docker Compose версии 3
services: #определение сервисов
category:
build:
context: /categoryService #путь к контексту сборки
dockerfile: Dockerfile #имя докерфайла
ports:
- "8089:8089" #проброс портов
networks:
- netwrk #сеть
product:
build:
context: /productService #путь к контексту сборки
dockerfile: Dockerfile #имя докерфайла
ports:
- "8090:8090" #проброс портов
networks:
- netwrk #сеть
nginx:
image: nginx:latest #образ для контейнера
ports:
- "8091:80" #проброс портов
networks:
- netwrk #сеть
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf #монтирует локальный файл конфигурации
depends_on: #зависимость от сервисов
- category
- product
networks:
netwrk:
driver: bridge #изолированная сеть

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

View File

@ -0,0 +1,27 @@
http {
server {
listen 80;
listen [::]:80;
server_name localhost;
location /categoryService/ {
proxy_pass http://category:8089/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Prefix /categoryService;
}
location /productService/ {
proxy_pass http://product:8090/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Prefix /productService;
}
}
}
events {
worker_connections 1024;
}

View File

@ -0,0 +1,42 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

View File

@ -0,0 +1,7 @@
FROM openjdk:17
RUN mkdir -p /usr/src/app/
WORKDIR /usr/src/app/
COPY . /usr/src/app/
RUN ./gradlew clean build
EXPOSE 8089
ENTRYPOINT ["java","-jar","build/libs/lab3-0.0.1-SNAPSHOT.jar"]

View File

@ -0,0 +1,34 @@
plugins {
id 'org.springframework.boot' version '2.6.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'categoryApp'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2:2.1.210'
implementation 'org.hibernate.validator:hibernate-validator'
implementation 'org.springdoc:springdoc-openapi-ui:1.6.5'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.6.5'
}
tasks.named('test') {
useJUnitPlatform()
}

View File

@ -0,0 +1,6 @@
#Wed Jan 10 18:23:35 GMT+04:00 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,2 @@
rootProject.name = "lab3"

View File

@ -0,0 +1,19 @@
package productApp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

View File

@ -0,0 +1,48 @@
package productApp.controller;
import productApp.model.*;
import org.springframework.web.bind.annotation.*;
import productApp.service.*;
import javax.validation.Valid;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping("/{id}")
public ProductFullInfoDto getProduct(@PathVariable Long id) {
return productService.findProduct(id);
}
@GetMapping("/")
public List<ProductDto> getProducts() {
return productService.findAllProducts().stream()
.map(ProductDto::new)
.collect(Collectors.toList());
}
@PostMapping("/")
public ProductFullInfoDto createProduct(@RequestBody @Valid ProductDto productDto) {
return productService.addProduct(productDto);
}
@PutMapping("/{id}")
public ProductFullInfoDto updateProduct(@RequestBody @Valid ProductDto productDto) {
return productService.updateProduct(productDto);
}
@DeleteMapping("/{id}")
public ProductDto deleteClient(@PathVariable Long id) {
return new ProductDto(productService.deleteProduct(id));
}
}

View File

@ -0,0 +1,34 @@
package productApp.model;
import lombok.Data;
@Data
public class CategoryInfoDto {
private Long id;
private String name;
private String description;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

View File

@ -0,0 +1,71 @@
package productApp.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.persistence.*;
import lombok.Data;
@Data
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String description;
private Long categoryId;
public Product() {
}
public Product(String name, String description, Long categoryId) {
this.name = name;
this.description = description;
this.categoryId = categoryId;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
@Override
public String toString() {
return "Category {" +
"id =" + id +
", time =" + name +
", description='" + description + '\'' +
", categoryId=" + categoryId +
'}';
}
}

View File

@ -0,0 +1,52 @@
package productApp.model;
import lombok.Data;
@Data
public class ProductDto {
private Long id;
private String name;
private String description;
private Long categoryId;
public ProductDto() {
}
public ProductDto(Product product) {
this.id = product.getId();
this.name = product.getName();
this.description = String.format("%s", product.getDescription());
this.categoryId = product.getCategoryId();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
}

View File

@ -0,0 +1,62 @@
package productApp.model;
import lombok.Data;
@Data
public class ProductFullInfoDto {
private Long id;
private String name;
private String description;
private Long categoryId;
private CategoryInfoDto categoryInfo;
public ProductFullInfoDto() {
}
public ProductFullInfoDto(Product product, CategoryInfoDto categoryInfo) {
this.id = product.getId();
this.name = product.getName();
this.description = product.getDescription();
this.categoryId = product.getCategoryId();
this.categoryInfo = categoryInfo;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
/*public CategoryInfoDto getCategoryInfo() {
return categoryInfo;
}
public void setCategoryInfo(CategoryInfoDto categoryInfo) {
this.categoryInfo = categoryInfo;
}*/
}

View File

@ -0,0 +1,7 @@
package productApp.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import productApp.model.Product;
public interface ProductRepository extends JpaRepository<Product, Long> {
}

View File

@ -0,0 +1,7 @@
package productApp.service;
public class ProductNotFoundException extends RuntimeException{
public ProductNotFoundException(Long id) {
super(String.format("Learning Plan with id [%s] is not found", id));
}
}

View File

@ -0,0 +1,73 @@
package productApp.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import productApp.model.*;
import productApp.repository.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.*;
@Service
public class ProductService {
private final ProductRepository productRepository;
private final RestTemplate restTemplate;
private final String NginxUrl = "http://nginx/categoryService/";
@Autowired
public ProductService(ProductRepository productRepository, RestTemplate restTemplate) {
this.productRepository = productRepository;
this.restTemplate = restTemplate;
}
@Transactional
public ProductFullInfoDto addProduct(ProductDto productDto) {
final Product product = new Product(productDto.getName(), productDto.getDescription(), productDto.getCategoryId());
productRepository.save(product);
CategoryInfoDto categoryInfo = restTemplate.getForObject(NginxUrl + productDto.getCategoryId(), CategoryInfoDto.class);
return new ProductFullInfoDto(product, categoryInfo);
}
@Transactional(readOnly = true)
public ProductFullInfoDto findProduct(Long id) {
final Product product = productRepository.findById(id).orElse(null);
if (product == null) {
throw new ProductNotFoundException(id);
}
CategoryInfoDto categoryInfo = restTemplate.getForObject(NginxUrl + product.getCategoryId(), CategoryInfoDto.class);
return new ProductFullInfoDto(product, categoryInfo);
}
@Transactional(readOnly = true)
public Product findProductClear(Long id) {
final Optional<Product> product = productRepository.findById(id);
return product.orElseThrow(() -> new ProductNotFoundException(id));
}
@Transactional(readOnly = true)
public List<Product> findAllProducts() {
return productRepository.findAll();
}
@Transactional
public ProductFullInfoDto updateProduct(ProductDto productDto) {
final Product currentProduct = findProductClear(productDto.getId());
currentProduct.setName(productDto.getName());
currentProduct.setDescription(productDto.getDescription());
currentProduct.setCategoryId(productDto.getCategoryId());
productRepository.save(currentProduct);
CategoryInfoDto categoryInfo = restTemplate.getForObject(NginxUrl + productDto.getCategoryId(), CategoryInfoDto.class);
return new ProductFullInfoDto(currentProduct, categoryInfo);
}
@Transactional
public Product deleteProduct(Long id) {
final Product currentProduct = findProductClear(id);
productRepository.delete(currentProduct);
return currentProduct;
}
}

View File

@ -0,0 +1,11 @@
spring.main.banner-mode=off
spring.datasource.url=jdbc:h2:file:./data
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true
spring.h2.console.settings.trace=false
spring.h2.console.settings.web-allow-others=false
server.port=8090

View File

@ -0,0 +1,42 @@
## Задание
Развернуть два взаимосвязанных сервиса по варианту:
Вариант № 4 для первой программы:
* Сервис формирует файл /var/result/data.txt так, что каждая строка файла - количество символов в именах файлов из каталога /var/data.
Вариант № 2 для второй программы:
* Сервис ищет наименьшее число из файла /var/data/data.txt и сохраняет его третью степень в /var/result/result.txt.
## Выполнение
Были написаны два сервиса на языке python с использованием технологии flask.
Они выводят на страницу кнопки, при нажатии на которые происходит соответствующие действия по заданию
Для сервисов прописаны файлы Dockerfile, описывающие создание контейнеров:
* Для обоих контейнеров выбирается Python 11
* На оба контейнера пробрасываются порты, на которых работает приложение: 8081 для первого и 8082 для второго
* Внутри контейнеров создаются папки /work для файлов скриптов, папки /var/result, /var/data для обоих сервисов
* В оба контейнера устанавливается фреймворк Flask
* Выбирается рабочая директория /work и туда копируются файлы скриптов
* Командой запускаются сами скрипты
Общий yml-файл настроен следующим образом:
* блок services, где перечислены разворачиваемые сервисы.
* для каждого сервиса прописан build, где обозначается его папка
* для каждого сервиса прописано пробрасывание портов на хостовую машину
* для каждого сервиса прописано отображение внутриконтейнерных папок на хостовые
## Результат
Пример выполнения:
Исходные данные: четыре файла в папке /var/data с разным по длине названием
Ход работы: нажатие кнопок на странице первого сервиса, потом - второго
Созданные контейнеры:
![Контейнеры](images/containers.png)
Страница первого задания:
![Страница первого задания](images/exercise1.png)
Выходные данные при выполнении второго задания:
![Второе задание. Результат](images/exercise2.png)
## Ссылка на видео
https://youtu.be/CEAAr0xolxM

View File

@ -0,0 +1,19 @@
version: '3'
services:
service1:
build:
context: ./service1
ports:
- "8081:8081"
volumes:
- .\var\data:/var/data
- .\var\result:/var/result
service2:
build:
context: ./service2
ports:
- "8082:8082"
volumes:
- ./var/result:/var/result

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,15 @@
FROM python:3.11
ENV LISTEN_PORT=8081
EXPOSE 8081
RUN ["mkdir", "/work"]
RUN ["mkdir", "/var/data"]
RUN ["mkdir", "/var/result"]
RUN pip install Flask
WORKDIR /work
COPY index.html service1.py ./
CMD ["python", "service1.py"]

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Сервис № 1</title>
</head>
<body>
<form action="http://localhost:8081/ex">
<input type="submit" value="Выполнить 1ое задание"/>
</form>
</body>
</html>

View File

@ -0,0 +1,39 @@
from flask import Flask, redirect, render_template
import os
app = Flask(__name__, template_folder='')
@app.route('/')
def home():
return render_template("index.html")
@app.route('/ex')
def do():
current_directory = os.getcwd()
directory_elements = current_directory.split(os.path.sep) # Разделяем по разделителю каталогов
cur_d = os.path.sep.join(directory_elements[:-1])
data_dir = cur_d + '/var/data'
path_result_file = cur_d + '/var/result/data.txt'
try:
# Получаем список файлов в указанном каталоге
files = os.listdir(data_dir)
# Формируем путь к каждому файлу и считаем количество символов в именах
characters_count_list = [len(file) for file in files]
# Пишем результат в файл data.txt
with open(path_result_file, 'w') as result_file:
for count in characters_count_list:
result_file.write(f'{count}\n')
print(f'Файл успешно создан.')
except Exception as e:
print(f'Произошла ошибка: {e}')
return redirect("/")
app.run(host='0.0.0.0', port=8081)

View File

@ -0,0 +1,14 @@
FROM python:3.11
ENV LISTEN_PORT=8082
EXPOSE 8082
RUN ["mkdir", "/work"]
RUN ["mkdir", "/var/result"]
RUN pip install Flask
WORKDIR /work
COPY index.html result.html service2.py ./
CMD ["python", "service2.py"]

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Сервис № 2</title>
</head>
<body>
<form action="http://localhost:8082/ex">
<input type="submit" value="Выполнить 2ое задание"/>
</form>
</body>
</html>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Результат работы</title>
</head>
<body>
<h2>Результат: {{num}}</h2>
</body>
</html>

View File

@ -0,0 +1,43 @@
import os
from flask import Flask, render_template
app = Flask(__name__, template_folder='')
@app.route('/')
def home():
return render_template("index.html")
@app.route('/ex')
def do():
current_directory = os.getcwd()
directory_elements = current_directory.split(os.path.sep) # Разделяем по разделителю каталогов
cur_d = os.path.sep.join(directory_elements[:-1])
path_data_file = cur_d + '/var/result/data.txt'
path_result_file = cur_d + '/var/result/result.txt'
min_number = 0
try:
# Чтение чисел из файла
with open(path_data_file, 'r') as file:
numbers = [float(line.strip()) for line in file]
# Поиск минимального числа
min_number = min(numbers)
# Возведение минимального числа в третью степень
result = min_number ** 3
# Запись результата в файл
with open(path_result_file, 'w') as result_file:
result_file.write(str(result))
print(f'Наименьшее число из файла {path_data_file} в третьей степени сохранено в {path_result_file}.')
except Exception as e:
print(f'Произошла ошибка: {e}')
return render_template("result.html", num=min_number)
app.run(host='0.0.0.0', port=8082)

View File

View File