- Реализована JWT аутентификация с organization-scope - Добавлено хеширование паролей через bcrypt - Созданы репозитории для организаций и пользователей - Реализован AuthService с бизнес-логикой - Добавлен AuthMiddleware для проверки токенов - Созданы handlers для регистрации и входа - Обновлён API сервер для использования аутентификации Готово для этапа 3 - API структура
506 lines
18 KiB
Markdown
506 lines
18 KiB
Markdown
# Детальный план разработки Core Service
|
||
|
||
## 🎯 Цель
|
||
Реализация Core Service для ERP MVP с упрощённой архитектурой: Go + PostgreSQL + REST API без gRPC, Redis и Document Service.
|
||
|
||
## 📋 Общие принципы
|
||
- **REST API** вместо gRPC
|
||
- **PostgreSQL** как единственная БД (без Redis)
|
||
- **JWT аутентификация** с organization-scope
|
||
- **Структурированное логирование** (без Prometheus на MVP)
|
||
- **Валидация данных** на всех уровнях
|
||
|
||
---
|
||
|
||
## 🚀 Этап 1: Фундамент (Недели 1-2)
|
||
|
||
### Шаг 1.1: Очистка и настройка проекта
|
||
- [x] Удалить зависимости: `grpc`, `redis`, `prometheus`
|
||
- [x] Обновить `go.mod` - оставить только необходимые пакеты
|
||
- [x] Настроить структуру проекта согласно Go standards
|
||
- [x] Добавить `.env` для конфигурации
|
||
|
||
**Файлы для изменения:**
|
||
```
|
||
go.mod - удалить grpc, redis, prometheus ✅
|
||
cmd/main.go - убрать redis, grpc клиенты ✅
|
||
internal/config/config.go - упростить конфигурацию ✅
|
||
```
|
||
|
||
### Шаг 1.2: Базовая конфигурация
|
||
- [x] Создать `internal/config/config.go` с упрощённой структурой
|
||
- [x] Добавить поддержку `.env` файлов
|
||
- [x] Настроить логирование через logrus
|
||
- [x] Добавить health check endpoint
|
||
|
||
**Структура конфигурации:**
|
||
```go
|
||
type Config struct {
|
||
Server ServerConfig
|
||
Database DatabaseConfig
|
||
JWT JWTConfig
|
||
}
|
||
|
||
type ServerConfig struct {
|
||
Port string
|
||
Host string
|
||
}
|
||
|
||
type DatabaseConfig struct {
|
||
Host string
|
||
Port string
|
||
User string
|
||
Password string
|
||
DBName string
|
||
SSLMode string
|
||
}
|
||
|
||
type JWTConfig struct {
|
||
Secret string
|
||
TTL time.Duration
|
||
}
|
||
```
|
||
|
||
### Шаг 1.3: Подключение к базе данных
|
||
- [x] Создать `internal/database/connection.go`
|
||
- [x] Настроить подключение к PostgreSQL
|
||
- [x] Добавить миграции через `golang-migrate`
|
||
- [x] Создать базовые таблицы
|
||
|
||
**Структура БД (упрощённая):**
|
||
```sql
|
||
-- organizations
|
||
CREATE TABLE organizations (
|
||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||
name VARCHAR(255) NOT NULL,
|
||
type VARCHAR(100),
|
||
settings JSONB,
|
||
created_at TIMESTAMP DEFAULT NOW()
|
||
);
|
||
|
||
-- users
|
||
CREATE TABLE users (
|
||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
|
||
email VARCHAR(255) UNIQUE NOT NULL,
|
||
password_hash VARCHAR(255) NOT NULL,
|
||
role VARCHAR(50) DEFAULT 'user',
|
||
created_at TIMESTAMP DEFAULT NOW()
|
||
);
|
||
|
||
-- storage_locations
|
||
CREATE TABLE storage_locations (
|
||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
|
||
parent_id UUID REFERENCES storage_locations(id) ON DELETE CASCADE,
|
||
name VARCHAR(255) NOT NULL,
|
||
address VARCHAR(100) NOT NULL,
|
||
type VARCHAR(50) NOT NULL,
|
||
coordinates JSONB,
|
||
created_at TIMESTAMP DEFAULT NOW()
|
||
);
|
||
|
||
-- items
|
||
CREATE TABLE items (
|
||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
|
||
name VARCHAR(255) NOT NULL,
|
||
description TEXT,
|
||
category VARCHAR(100),
|
||
created_at TIMESTAMP DEFAULT NOW()
|
||
);
|
||
|
||
-- item_placements
|
||
CREATE TABLE item_placements (
|
||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
|
||
item_id UUID REFERENCES items(id) ON DELETE CASCADE,
|
||
location_id UUID REFERENCES storage_locations(id) ON DELETE CASCADE,
|
||
quantity INTEGER DEFAULT 1,
|
||
created_at TIMESTAMP DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
### Шаг 1.4: Базовые модели
|
||
- [x] Создать `internal/models/` с основными структурами
|
||
- [x] Добавить валидацию через `validator`
|
||
- [x] Реализовать JSON теги для API
|
||
|
||
**Основные модели:**
|
||
```go
|
||
// internal/models/organization.go
|
||
type Organization struct {
|
||
ID uuid.UUID `json:"id" db:"id"`
|
||
Name string `json:"name" validate:"required"`
|
||
Type string `json:"type"`
|
||
Settings JSON `json:"settings"`
|
||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||
}
|
||
|
||
// internal/models/user.go
|
||
type User struct {
|
||
ID uuid.UUID `json:"id" db:"id"`
|
||
OrganizationID uuid.UUID `json:"organization_id" db:"organization_id"`
|
||
Email string `json:"email" validate:"required,email"`
|
||
PasswordHash string `json:"-" db:"password_hash"`
|
||
Role string `json:"role"`
|
||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||
}
|
||
|
||
// internal/models/storage_location.go
|
||
type StorageLocation struct {
|
||
ID uuid.UUID `json:"id" db:"id"`
|
||
OrganizationID uuid.UUID `json:"organization_id" db:"organization_id"`
|
||
ParentID *uuid.UUID `json:"parent_id,omitempty" db:"parent_id"`
|
||
Name string `json:"name" validate:"required"`
|
||
Address string `json:"address" validate:"required"`
|
||
Type string `json:"type" validate:"required"`
|
||
Coordinates JSON `json:"coordinates"`
|
||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||
}
|
||
|
||
// internal/models/item.go
|
||
type Item struct {
|
||
ID uuid.UUID `json:"id" db:"id"`
|
||
OrganizationID uuid.UUID `json:"organization_id" db:"organization_id"`
|
||
Name string `json:"name" validate:"required"`
|
||
Description string `json:"description"`
|
||
Category string `json:"category"`
|
||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||
}
|
||
|
||
// internal/models/item_placement.go
|
||
type ItemPlacement struct {
|
||
ID uuid.UUID `json:"id" db:"id"`
|
||
OrganizationID uuid.UUID `json:"organization_id" db:"organization_id"`
|
||
ItemID uuid.UUID `json:"item_id" db:"item_id"`
|
||
LocationID uuid.UUID `json:"location_id" db:"location_id"`
|
||
Quantity int `json:"quantity" validate:"min=1"`
|
||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔐 Этап 2: Аутентификация (Неделя 2)
|
||
|
||
### Шаг 2.1: JWT аутентификация
|
||
- [x] Создать `internal/auth/jwt.go`
|
||
- [x] Реализовать генерацию и валидацию JWT токенов
|
||
- [x] Добавить organization-scope в токены
|
||
- [x] Создать middleware для проверки аутентификации
|
||
|
||
**JWT структура:**
|
||
```go
|
||
type Claims struct {
|
||
UserID uuid.UUID `json:"user_id"`
|
||
OrganizationID uuid.UUID `json:"organization_id"`
|
||
Email string `json:"email"`
|
||
Role string `json:"role"`
|
||
jwt.RegisteredClaims
|
||
}
|
||
```
|
||
|
||
### Шаг 2.2: Хеширование паролей
|
||
- [x] Создать `internal/auth/password.go`
|
||
- [x] Использовать bcrypt для хеширования
|
||
- [x] Добавить функции проверки паролей
|
||
|
||
### Шаг 2.3: API endpoints для аутентификации
|
||
- [x] `POST /api/auth/register` - регистрация организации и пользователя
|
||
- [x] `POST /api/auth/login` - вход в систему
|
||
- [x] `POST /api/auth/refresh` - обновление токена (опционально)
|
||
|
||
**Структура запросов:**
|
||
```go
|
||
type RegisterRequest struct {
|
||
OrganizationName string `json:"organization_name" validate:"required"`
|
||
UserEmail string `json:"user_email" validate:"required,email"`
|
||
UserPassword string `json:"user_password" validate:"required,min=8"`
|
||
OrganizationType string `json:"organization_type"`
|
||
}
|
||
|
||
type LoginRequest struct {
|
||
Email string `json:"email" validate:"required,email"`
|
||
Password string `json:"password" validate:"required"`
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🏗️ Этап 3: API структура (Неделя 3)
|
||
|
||
### Шаг 3.1: Базовые handlers
|
||
- [ ] Создать `internal/api/handlers/` с базовыми структурами
|
||
- [ ] Реализовать middleware для CORS, логирования, аутентификации
|
||
- [ ] Добавить обработку ошибок
|
||
|
||
**Структура handlers:**
|
||
```
|
||
internal/api/
|
||
├── handlers/
|
||
│ ├── auth.go
|
||
│ ├── organizations.go
|
||
│ ├── locations.go
|
||
│ ├── items.go
|
||
│ └── operations.go
|
||
├── middleware/
|
||
│ ├── auth.go
|
||
│ ├── cors.go
|
||
│ ├── logging.go
|
||
│ └── error_handler.go
|
||
└── server.go
|
||
```
|
||
|
||
### Шаг 3.2: Repository pattern
|
||
- [ ] Создать `internal/repository/` для работы с БД
|
||
- [ ] Реализовать CRUD операции для всех сущностей
|
||
- [ ] Добавить organization-scope фильтрацию
|
||
|
||
**Основные репозитории:**
|
||
```go
|
||
// internal/repository/organizations.go
|
||
type OrganizationRepository interface {
|
||
Create(ctx context.Context, org *models.Organization) error
|
||
GetByID(ctx context.Context, id uuid.UUID) (*models.Organization, error)
|
||
Update(ctx context.Context, org *models.Organization) error
|
||
}
|
||
|
||
// internal/repository/users.go
|
||
type UserRepository interface {
|
||
Create(ctx context.Context, user *models.User, password string) error
|
||
GetByEmail(ctx context.Context, email string) (*models.User, error)
|
||
GetByID(ctx context.Context, id uuid.UUID) (*models.User, error)
|
||
}
|
||
|
||
// internal/repository/locations.go
|
||
type LocationRepository interface {
|
||
Create(ctx context.Context, location *models.StorageLocation) error
|
||
GetByID(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.StorageLocation, error)
|
||
GetByOrganization(ctx context.Context, orgID uuid.UUID) ([]*models.StorageLocation, error)
|
||
Update(ctx context.Context, location *models.StorageLocation) error
|
||
Delete(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error
|
||
}
|
||
|
||
// internal/repository/items.go
|
||
type ItemRepository interface {
|
||
Create(ctx context.Context, item *models.Item) error
|
||
GetByID(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.Item, error)
|
||
GetByOrganization(ctx context.Context, orgID uuid.UUID) ([]*models.Item, error)
|
||
Search(ctx context.Context, orgID uuid.UUID, query string) ([]*models.Item, error)
|
||
Update(ctx context.Context, item *models.Item) error
|
||
Delete(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error
|
||
}
|
||
```
|
||
|
||
### Шаг 3.3: Service layer
|
||
- [ ] Создать `internal/service/` для бизнес-логики
|
||
- [ ] Реализовать валидацию и обработку данных
|
||
- [ ] Добавить транзакции для сложных операций
|
||
|
||
---
|
||
|
||
## 📍 Этап 4: Шаблоны помещений (Неделя 4)
|
||
|
||
### Шаг 4.1: Система шаблонов
|
||
- [ ] Создать `internal/templates/` для шаблонов помещений
|
||
- [ ] Реализовать 3 базовых шаблона: Гараж, Мастерская, Склад
|
||
- [ ] Добавить генерацию адресов мест
|
||
|
||
**Шаблоны:**
|
||
```go
|
||
type Template struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Description string `json:"description"`
|
||
Zones []Zone `json:"zones"`
|
||
AddressRules []AddressRule `json:"address_rules"`
|
||
}
|
||
|
||
type Zone struct {
|
||
Name string `json:"name"`
|
||
Type string `json:"type"` // "cabinet", "floor", "table"
|
||
Rows int `json:"rows"`
|
||
Columns int `json:"columns"`
|
||
Position Position `json:"position"`
|
||
}
|
||
|
||
type AddressRule struct {
|
||
ZoneType string `json:"zone_type"`
|
||
Prefix string `json:"prefix"`
|
||
Format string `json:"format"` // "Ш{zone}-П{row}-Я{col}"
|
||
}
|
||
```
|
||
|
||
### Шаг 4.2: API для шаблонов
|
||
- [ ] `GET /api/templates` - список доступных шаблонов
|
||
- [ ] `POST /api/templates/:id/apply` - применение шаблона к организации
|
||
- [ ] Генерация мест хранения из шаблона
|
||
|
||
### Шаг 4.3: Адресация мест
|
||
- [ ] Реализовать систему адресации (Ш1-П2-Я3, З1-У2)
|
||
- [ ] Автоматическая генерация адресов при создании мест
|
||
- [ ] Валидация уникальности адресов в рамках организации
|
||
|
||
---
|
||
|
||
## 🔍 Этап 5: Операции (Неделя 5)
|
||
|
||
### Шаг 5.1: Размещение товаров
|
||
- [ ] `POST /api/operations/place-item` - размещение товара
|
||
- [ ] Валидация доступности места
|
||
- [ ] Обновление статуса места
|
||
- [ ] Логирование операций
|
||
|
||
**Структура запроса:**
|
||
```go
|
||
type PlaceItemRequest struct {
|
||
ItemID uuid.UUID `json:"item_id" validate:"required"`
|
||
LocationID uuid.UUID `json:"location_id" validate:"required"`
|
||
Quantity int `json:"quantity" validate:"required,min=1"`
|
||
}
|
||
```
|
||
|
||
### Шаг 5.2: Поиск товаров
|
||
- [ ] `GET /api/operations/search` - поиск по названию, адресу, категории
|
||
- [ ] Оптимизация запросов через индексы
|
||
- [ ] Пагинация результатов
|
||
|
||
**Параметры поиска:**
|
||
```go
|
||
type SearchRequest struct {
|
||
Query string `form:"q"`
|
||
Category string `form:"category"`
|
||
Address string `form:"address"`
|
||
Page int `form:"page,default=1"`
|
||
PageSize int `form:"page_size,default=20"`
|
||
}
|
||
```
|
||
|
||
### Шаг 5.3: Перемещение товаров
|
||
- [ ] `POST /api/operations/move-item` - перемещение между местами
|
||
- [ ] Валидация наличия товара в исходном месте
|
||
- [ ] Атомарность операции через транзакции
|
||
|
||
---
|
||
|
||
## 🧪 Этап 6: Тестирование и полировка (Неделя 6)
|
||
|
||
### Шаг 6.1: Unit тесты
|
||
- [ ] Тесты для всех репозиториев
|
||
- [ ] Тесты для сервисов
|
||
- [ ] Тесты для handlers
|
||
- [ ] Покрытие кода > 80%
|
||
|
||
### Шаг 6.2: Интеграционные тесты
|
||
- [ ] Тесты API endpoints
|
||
- [ ] Тесты с реальной БД
|
||
- [ ] Тесты аутентификации
|
||
|
||
### Шаг 6.3: Производительность
|
||
- [ ] Оптимизация SQL запросов
|
||
- [ ] Добавление индексов
|
||
- [ ] Тестирование под нагрузкой
|
||
- [ ] Мониторинг медленных запросов
|
||
|
||
### Шаг 6.4: Документация API
|
||
- [ ] Swagger/OpenAPI документация
|
||
- [ ] Примеры запросов и ответов
|
||
- [ ] Описание ошибок
|
||
|
||
---
|
||
|
||
## 📊 Критерии готовности
|
||
|
||
### Функциональные критерии
|
||
- [ ] Все CRUD операции работают для items, locations
|
||
- [ ] Аутентификация с organization-scope
|
||
- [ ] Шаблоны помещений генерируют структуру
|
||
- [ ] Операции размещения/поиска/перемещения работают
|
||
- [ ] API отвечает на все запросы
|
||
|
||
### Технические критерии
|
||
- [ ] Время отклика API ≤ 200мс
|
||
- [ ] Покрытие тестами > 80%
|
||
- [ ] Все endpoints документированы
|
||
- [ ] Логирование работает корректно
|
||
- [ ] Graceful shutdown реализован
|
||
|
||
### Безопасность
|
||
- [ ] JWT токены валидируются
|
||
- [ ] Organization-scope на всех данных
|
||
- [ ] Валидация входных данных
|
||
- [ ] SQL injection protection
|
||
|
||
---
|
||
|
||
## 🚨 Риски и митигация
|
||
|
||
### Высокий риск
|
||
- **Производительность БД при большом количестве данных**
|
||
- Митигация: Индексы, пагинация, мониторинг запросов
|
||
|
||
### Средний риск
|
||
- **JWT токены без refresh механизма**
|
||
- Митигация: Короткий TTL, возможность перелогина
|
||
|
||
### Низкий риск
|
||
- **Отсутствие кэширования**
|
||
- Митигация: Оптимизация SQL, индексы
|
||
|
||
---
|
||
|
||
## 📁 Структура проекта (финальная)
|
||
|
||
```
|
||
app/core-service/
|
||
├── cmd/
|
||
│ └── main.go
|
||
├── internal/
|
||
│ ├── api/
|
||
│ │ ├── handlers/
|
||
│ │ ├── middleware/
|
||
│ │ └── server.go
|
||
│ ├── auth/
|
||
│ │ ├── jwt.go
|
||
│ │ └── password.go
|
||
│ ├── config/
|
||
│ │ └── config.go
|
||
│ ├── database/
|
||
│ │ ├── connection.go
|
||
│ │ └── migrations/
|
||
│ ├── models/
|
||
│ │ ├── organization.go
|
||
│ │ ├── user.go
|
||
│ │ ├── storage_location.go
|
||
│ │ ├── item.go
|
||
│ │ └── item_placement.go
|
||
│ ├── repository/
|
||
│ │ ├── organizations.go
|
||
│ │ ├── users.go
|
||
│ │ ├── locations.go
|
||
│ │ └── items.go
|
||
│ ├── service/
|
||
│ │ ├── auth_service.go
|
||
│ │ ├── location_service.go
|
||
│ │ └── item_service.go
|
||
│ └── templates/
|
||
│ ├── templates.go
|
||
│ └── address_rules.go
|
||
├── migrations/
|
||
├── tests/
|
||
├── go.mod
|
||
├── go.sum
|
||
├── Dockerfile
|
||
└── .env.example
|
||
```
|
||
|
||
---
|
||
|
||
## 🎯 Следующие шаги после Core Service
|
||
|
||
1. **Frontend разработка** - Angular PWA с QR сканером
|
||
2. **Интеграция** - тестирование API с фронтендом
|
||
3. **Пилот** - тестирование с реальными пользователями
|
||
4. **Мониторинг** - сбор метрик и обратной связи
|