docs: add UML and architecture documentation (Mermaid)
- docs/README.md — index and short system overview - 01-architecture.md — context, per-service components, Gradle modules, authentication/authorization, web interceptors (flowcharts) - 02-deployment.md — deployment diagram, containers/env/volumes, build stages, nginx, update sequence - 03-domain-model.md — class diagrams for identity/academic/progress and web, ER model of all schemas (cross-schema references noted) - 04-sequences.md — 10 sequence diagrams (login, JWT check, forced password change, lab import, distribution, submission, defense, grades+publication, content publishing, group page aggregation) - 05-states.md — state diagrams for defense session, grade publication, lab status, content visibility, submission lifecycle, variant assignment, password MS git log --oneline -3
This commit is contained in:
160
docs/01-architecture.md
Normal file
160
docs/01-architecture.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Архитектура системы
|
||||
|
||||
## 1. Контекст (какие внешние участники и системы)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
user["Преподаватель / студент<br/>(браузер)"]
|
||||
nginx["nginx<br/>lms.our-place.ru<br/>TLS, reverse proxy"]
|
||||
web["web BFF<br/>Thymeleaf + htmx<br/>:8090"]
|
||||
gw["gateway<br/>Spring Cloud Gateway<br/>:8080"]
|
||||
id["identity-service<br/>:8081"]
|
||||
ac["academic-service<br/>:8082"]
|
||||
pr["progress-service<br/>:8083"]
|
||||
db[("PostgreSQL 18<br/>схемы identity / academic / progress")]
|
||||
fs[("Файловое хранилище<br/>content-assets, submissions")]
|
||||
|
||||
user -->|HTTPS| nginx
|
||||
nginx -->|HTTP 127.0.0.1:8090| web
|
||||
web -->|Bearer JWT| gw
|
||||
gw --> id
|
||||
gw --> ac
|
||||
gw --> pr
|
||||
id --> db
|
||||
ac --> db
|
||||
pr --> db
|
||||
ac --> fs
|
||||
pr --> fs
|
||||
pr -->|REST, внутренняя сеть| ac
|
||||
pr -->|REST, внутренняя сеть| id
|
||||
id -.->|JWKS: /api/v1/auth/jwks.json| gw
|
||||
id -.->|JWKS| ac
|
||||
id -.->|JWKS| pr
|
||||
```
|
||||
|
||||
- Браузер никогда не получает JWT: токен живёт в HTTP-сессии web-сервиса
|
||||
(`lms.token`), а в браузер уходит только cookie сессии (`HttpOnly`, 30 дней).
|
||||
- Все REST-запросы web проксирует на gateway, добавляя `Authorization: Bearer`.
|
||||
- Gateway проверяет подпись токена по JWKS identity и передаёт вниз заголовки
|
||||
`X-Account-Id`, `X-Account-Login`, `X-Account-Role`.
|
||||
- Каждый сервис дополнительно валидирует JWT самостоятельно (resource server).
|
||||
|
||||
## 2. Компоненты по сервисам
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph web["web (BFF, :8090)"]
|
||||
wc["@Controller<br/>AuthPage, Learn, Student, Admin, ContentAdmin"]
|
||||
wi["Interceptor'ы:<br/>Csrf, Auth, Admin, MustChangePassword"]
|
||||
wapi["ApiClient (WebClient)<br/>+ DTO"]
|
||||
wmd["MarkdownRenderer<br/>(libs:markdown)"]
|
||||
wc --> wapi
|
||||
wc --> wmd
|
||||
wi --> wc
|
||||
end
|
||||
|
||||
subgraph gw["gateway (:8080)"]
|
||||
routes["Маршруты<br/>identity / academic / progress"]
|
||||
filter["JwtAuthenticationFilter<br/>order -100"]
|
||||
filter --> routes
|
||||
end
|
||||
|
||||
subgraph id["identity-service (:8081)"]
|
||||
idc["AuthController, GroupController, StudentController"]
|
||||
ids["AuthService, GroupService, Passwords,<br/>StudentImportParser, CredentialsExporter"]
|
||||
idr["AccountRepository, GroupRepository,<br/>EnrollmentRepository, StudentRepository, SettingRepository"]
|
||||
idc --> ids --> idr
|
||||
end
|
||||
|
||||
subgraph ac["academic-service (:8082)"]
|
||||
acc["LabController, ContentPublicController,<br/>ContentAdminController"]
|
||||
acs["LabService, ContentService,<br/>LabImportParser, AssetStorage"]
|
||||
acr["LabRepository, LabLevelRepository, VariantRepository,<br/>SemesterRepository, Content*Repository"]
|
||||
acc --> acs --> acr
|
||||
end
|
||||
|
||||
subgraph pr["progress-service (:8083)"]
|
||||
prc["ProgressController, DefenseAdminController,<br/>DefenseMyController"]
|
||||
prs["ProgressService, SubmissionService,<br/>DefenseService, GradePublicationService"]
|
||||
prr["VariantAssignmentRepository, StudentLabLevelRepository,<br/>LabGradeRepository, GradeCommentRepository,<br/>CommentTemplateRepository, SubmissionRepository,<br/>DefenseSessionRepository, GradePublicationRepository"]
|
||||
prcl["AcademicClient, IdentityClient"]
|
||||
prc --> prs
|
||||
prs --> prr
|
||||
prs --> prcl
|
||||
end
|
||||
|
||||
wapi -->|HTTP| routes
|
||||
routes --> idc
|
||||
routes --> acc
|
||||
routes --> prc
|
||||
prcl -->|GET /api/v1/labs/{id}| acc
|
||||
prcl -->|GET /api/v1/groups и /{id}| idc
|
||||
```
|
||||
|
||||
## 3. Модули Gradle
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
web[":web"] --> common[":libs:common-http"]
|
||||
web --> md[":libs:markdown"]
|
||||
gw[":services:gateway"] --> sec[":libs:security-jwt"]
|
||||
gw --> common
|
||||
id[":services:identity-service"] --> common
|
||||
id --> sec
|
||||
ac[":services:academic-service"] --> common
|
||||
ac --> sec
|
||||
ac --> md
|
||||
pr[":services:progress-service"] --> common
|
||||
pr --> sec
|
||||
tests[":libs:test-support"] -.-> id
|
||||
tests -.-> ac
|
||||
tests -.-> pr
|
||||
```
|
||||
|
||||
| Модуль | Назначение |
|
||||
|---|---|
|
||||
| `:libs:common-http` | единый формат ошибок (`ApiError`), иерархия `LmsException`, `LmsExceptionHandler`, генератор UUIDv7 (`Ids`) |
|
||||
| `:libs:security-jwt` | свойства JWT, парсинг RSA-ключей, автоконфигурация `JwtEncoder`/`JwtDecoder`/`PasswordEncoder`, enum `LmsRole` |
|
||||
| `:libs:markdown` | markdown → безопасный HTML (OWASP-санитайзер), таблицы, спойлеры `:::spoiler` |
|
||||
| `:libs:test-support` | константы для интеграционных тестов с PostgreSQL |
|
||||
|
||||
## 4. Аутентификация и авторизация
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
a["Клиент: POST /api/v1/auth/login"] --> b["gateway: путь публичный<br/>(login/setup/health/jwks)"]
|
||||
b --> c["identity: AuthService.login"]
|
||||
c --> d{"BCrypt совпал?"}
|
||||
d -->|нет| e["401 + ApiError"]
|
||||
d -->|да| f["JwtEncoder (RSA-2048, kid=lms)<br/>claims: sub, login, role, exp"]
|
||||
f --> g["AuthResponse{token,...}"]
|
||||
|
||||
h["Клиент: запрос к защищённому API<br/>Authorization: Bearer ..."] --> i["gateway JwtAuthenticationFilter"]
|
||||
i --> j{"Токен валиден по JWKS?"}
|
||||
j -->|нет| k["401 Unauthorized"]
|
||||
j -->|да| l["Добавляет X-Account-Id/Login/Role"]
|
||||
l --> m["Сервис: SecurityConfig<br/>oauth2ResourceServer(jwt)"]
|
||||
m --> n{"@PreAuthorize<br/>ROLE_ADMIN / isAuthenticated"}
|
||||
n -->|нет| o["403 Forbidden"]
|
||||
n -->|да| p["Контроллер → сервис → БД"]
|
||||
```
|
||||
|
||||
- Роли: `ADMIN`, `STUDENT`; конвертер ролей читает claim `role` как `ROLE_<role>`.
|
||||
- Публичные пути: `/actuator/**`, `/api/v1/auth/{health,setup,login,jwks.json}`,
|
||||
`/api/v1/content/tree`, `/api/v1/content/assets/**`,
|
||||
`/api/v1/content/{chapter}/{section}/{article}`.
|
||||
- RSA-ключи identity хранятся в таблице `settings` и публикуются в JWKS.
|
||||
|
||||
## 5. Рендеринг страниц и защита форм (web)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
req["HTTP-запрос"] --> csrf["CsrfInterceptor /**<br/>GET: выдаёт token в сессию<br/>POST: сверяет _csrf"]
|
||||
csrf --> must["MustChangePasswordInterceptor /**<br/>(кроме /change-password, /logout, /login, статики)"]
|
||||
must --> auth["AuthInterceptor<br/>/admin/**, /student/**, /change-password"]
|
||||
auth --> adm["AdminInterceptor /admin/**"]
|
||||
adm --> ctrl["Контроллер → ApiClient → gateway"]
|
||||
```
|
||||
|
||||
- `GlobalModelAdvice` кладёт `currentUser` (SessionUser) во все шаблоны.
|
||||
- Скрытые материалы и полное дерево материалов доступны только администратору.
|
||||
Reference in New Issue
Block a user