276 lines
8.5 KiB
Markdown
276 lines
8.5 KiB
Markdown
# 🛠 Руководство для разработчиков
|
||
|
||
## Локальный запуск
|
||
|
||
### Предварительные требования
|
||
|
||
- Docker и Docker Compose
|
||
- Git
|
||
- (Опционально) Java 17 + Maven 3.9+ для запуска backend вне Docker
|
||
|
||
### Первый запуск
|
||
|
||
```bash
|
||
# Создать Docker-сеть
|
||
docker network create proxy
|
||
|
||
# Собрать и запустить
|
||
docker compose up -d --build
|
||
|
||
# Убедиться, что всё работает
|
||
docker compose logs -f
|
||
```
|
||
|
||
Приложение доступно: **http://localhost:80**
|
||
|
||
### Пересборка после изменений
|
||
|
||
```bash
|
||
# Пересобрать только backend
|
||
docker compose up -d --build backend
|
||
|
||
# Пересобрать только frontend
|
||
docker compose up -d --build frontend
|
||
```
|
||
|
||
### Полный сброс данных
|
||
|
||
```bash
|
||
docker compose down -v # Удаляет БД
|
||
docker compose up -d # Пересоздаёт с нуля
|
||
```
|
||
|
||
---
|
||
|
||
## Соглашения о коде
|
||
|
||
### Java (Backend)
|
||
|
||
#### Именование
|
||
|
||
| Категория | Стиль | Пример |
|
||
|-----------|-------|--------|
|
||
| Классы | PascalCase | `LessonsController`, `LessonResponse` |
|
||
| Методы и переменные | camelCase | `getAllLessons()`, `teacherId` |
|
||
| Константы | UPPER_SNAKE_CASE | `ROLE_REDIRECTS` |
|
||
| Пакеты | lowercase | `com.magistr.app.controller` |
|
||
|
||
#### Архитектурные правила
|
||
|
||
- **Constructor Injection** — все зависимости через конструктор (не `@Autowired` на поля)
|
||
- **Controller → Repository** — контроллеры работают напрямую с репозиториями (без слоя service)
|
||
- **Префикс `/api/`** — все REST-эндпоинты
|
||
- **`ResponseEntity<?>`** — все мутирующие методы возвращают `ResponseEntity` с HTTP-статусом
|
||
- **Сообщения на русском** — все ошибки и уведомления на русском языке
|
||
|
||
#### Логирование
|
||
|
||
Используйте SLF4J:
|
||
|
||
```java
|
||
private static final Logger logger = LoggerFactory.getLogger(MyController.class);
|
||
|
||
// Информационные сообщения
|
||
logger.info("Запрос на получение всех занятий");
|
||
|
||
// Ошибки с полным стектрейсом
|
||
logger.error("Ошибка при сохранении: {}", e.getMessage(), e);
|
||
```
|
||
|
||
#### Валидация
|
||
|
||
- Для сложных правил — отдельные классы-валидаторы (`DayAndWeekValidator`, `TypeAndFormatLessonValidator`)
|
||
- Для простых — inline-проверки в контроллере с `ResponseEntity.badRequest()`
|
||
|
||
#### Импорты
|
||
|
||
```java
|
||
// 1. Static imports
|
||
import static org.junit.Assert.*;
|
||
|
||
// 2. Java/Jakarta
|
||
import java.util.*;
|
||
import jakarta.persistence.*;
|
||
|
||
// 3. External libraries
|
||
import org.springframework.web.bind.annotation.*;
|
||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||
|
||
// 4. Internal packages (wildcard для того же модуля)
|
||
import com.magistr.app.model.*;
|
||
import com.magistr.app.repository.*;
|
||
```
|
||
|
||
#### Форматирование
|
||
|
||
- **Отступы:** 4 пробела
|
||
- **Скобки:** K&R style (открывающая на той же строке)
|
||
- **Длина строки:** до 120 символов
|
||
- **Фигурные скобки** обязательны для `if`/`for`/`while`
|
||
|
||
### JavaScript (Frontend)
|
||
|
||
#### Именование
|
||
|
||
| Категория | Стиль | Пример |
|
||
|-----------|-------|--------|
|
||
| Файлы | kebab-case | `main.js`, `schedule-view.js` |
|
||
| Функции и переменные | camelCase | `loadUsers()`, `pageTitle` |
|
||
| Константы | UPPER_SNAKE_CASE | `API_BASE_URL` |
|
||
|
||
#### Модули
|
||
|
||
- ES6 Modules с `import`/`export`
|
||
- **Всегда указывать расширение:** `import { api } from './api.js';`
|
||
|
||
#### Лучшие практики
|
||
|
||
```javascript
|
||
// ✅ Предпочитайте const
|
||
const token = localStorage.getItem('token');
|
||
|
||
// ✅ Async/await вместо .then()
|
||
async function loadData() {
|
||
try {
|
||
const data = await api.get('/api/users');
|
||
} catch (e) {
|
||
console.error('Ошибка:', e.message);
|
||
}
|
||
}
|
||
|
||
// ✅ Template literals
|
||
const msg = `Найдено ${items.length} записей`;
|
||
|
||
// ✅ Деструктуризация
|
||
const { id, name, role } = user;
|
||
```
|
||
|
||
#### Форматирование
|
||
|
||
- **Отступы:** 4 пробела
|
||
- **Кавычки:** одинарные `'`
|
||
- **Точки с запятой:** обязательны
|
||
|
||
---
|
||
|
||
## Создание нового эндпоинта (пошагово)
|
||
|
||
### 1. Модель (если нужна новая таблица)
|
||
|
||
Создайте Flyway миграцию `V{N}__{description}.sql`:
|
||
|
||
```sql
|
||
-- backend/src/main/resources/db/migration/V3__add_absences.sql
|
||
CREATE TABLE IF NOT EXISTS absences (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
teacher_id BIGINT NOT NULL REFERENCES users(id),
|
||
reason VARCHAR(255) NOT NULL,
|
||
start_date DATE NOT NULL,
|
||
end_date DATE NOT NULL
|
||
);
|
||
```
|
||
|
||
Создайте JPA-сущность:
|
||
|
||
```java
|
||
@Entity
|
||
@Table(name = "absences")
|
||
public class Absence {
|
||
@Id
|
||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||
private Long id;
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### 2. Репозиторий
|
||
|
||
```java
|
||
public interface AbsenceRepository extends JpaRepository<Absence, Long> {
|
||
List<Absence> findByTeacherId(Long teacherId);
|
||
}
|
||
```
|
||
|
||
### 3. DTO (опционально)
|
||
|
||
```java
|
||
public record AbsenceResponse(Long id, String teacherName, String reason) {}
|
||
```
|
||
|
||
### 4. Контроллер
|
||
|
||
```java
|
||
@RestController
|
||
@RequestMapping("/api/absences")
|
||
public class AbsenceController {
|
||
private final AbsenceRepository absenceRepository;
|
||
|
||
public AbsenceController(AbsenceRepository absenceRepository) {
|
||
this.absenceRepository = absenceRepository;
|
||
}
|
||
|
||
@GetMapping
|
||
public List<Absence> getAll() {
|
||
return absenceRepository.findAll();
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Работа с миграциями Flyway
|
||
|
||
### Правила
|
||
|
||
1. **Никогда** не изменяйте уже закоммиченные файлы миграций
|
||
2. Имя файла: `V{номер}__{описание}.sql` (два подчёркивания!)
|
||
3. Нумерация строго инкрементальная: `V1`, `V2`, `V3`, ...
|
||
4. После добавления — перезапустите backend для применения
|
||
|
||
### Применение
|
||
|
||
```bash
|
||
# Локально — сброс и повтор всех миграций
|
||
docker compose down -v && docker compose up -d
|
||
|
||
# Продакшн — применить к существующим тенантам
|
||
kubectl rollout restart deployment backend -n magistr
|
||
```
|
||
|
||
---
|
||
|
||
## Структура пакетов (Backend)
|
||
|
||
```
|
||
com.magistr.app/
|
||
├── Application.java # Точка входа
|
||
├── config/
|
||
│ ├── AppConfig.java # Бины (BCryptPasswordEncoder)
|
||
│ ├── DataInitializer.java # Инициализация данных
|
||
│ └── tenant/ # Мультитенантность
|
||
│ ├── TenantConfig.java # POJO конфигурации тенанта
|
||
│ ├── TenantContext.java # ThreadLocal текущего тенанта
|
||
│ ├── TenantInterceptor.java # Определение тенанта из Host
|
||
│ ├── TenantRoutingDataSource.java # Маршрутизация к БД
|
||
│ ├── TenantDataSourceConfig.java # Spring-конфигурация
|
||
│ ├── TenantConfigWatcher.java # Периодическая синхронизация
|
||
│ └── ConfigMapUpdater.java # Обновление K8s ConfigMap
|
||
├── controller/ # REST-контроллеры
|
||
│ ├── AuthController.java
|
||
│ ├── LessonsController.java
|
||
│ ├── ClassroomController.java
|
||
│ ├── DatabaseController.java
|
||
│ ├── UserController.java
|
||
│ ├── GroupController.java
|
||
│ ├── SubjectController.java
|
||
│ ├── EquipmentController.java
|
||
│ ├── EducationFormController.java
|
||
│ └── TeacherSubjectController.java
|
||
├── dto/ # Data Transfer Objects
|
||
├── model/ # JPA-сущности
|
||
├── repository/ # Spring Data JPA
|
||
└── utils/ # Валидаторы
|
||
├── DayAndWeekValidator.java
|
||
└── TypeAndFormatLessonValidator.java
|
||
```
|