add media service

This commit is contained in:
Дмитрий
2026-05-08 15:59:03 +03:00
commit 1a7251976d
5 changed files with 322 additions and 0 deletions

135
cmd/main.go Normal file
View File

@@ -0,0 +1,135 @@
package main
import (
"bytes"
"fmt"
"io"
"log/slog"
"net/http"
"path/filepath"
"time"
"lendry-erp/media/internal/processor"
"lendry-erp/media/internal/storage"
)
// InternalAuthMiddleware защищает Go-сервис.
// Он пропускает только те запросы, в которых API Gateway заботливо положил X-User-Id
func InternalAuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
userId := r.Header.Get("X-User-Id")
isModerator := r.Header.Get("X-Is-Admin") == "true"
if userId == "" {
slog.Warn("Блокировка несанкционированного доступа (отсутствует X-User-Id)", "ip", r.RemoteAddr)
http.Error(w, `{"error": "Unauthorized: API Gateway only"}`, http.StatusUnauthorized)
return
}
slog.Debug("Запрос авторизован Gateway", "user_id", userId, "moderator", isModerator)
next.ServeHTTP(w, r)
}
}
func main() {
// Подключение к MinIO
minioStore, err := storage.NewMinioStorage("minio:9000", "admin", "admin12345", "erp-media")
if err != nil {
panic(err)
}
// === Эндпоинт 1: ЗАГРУЗКА ПРИВАТНЫХ ФАЙЛОВ ===
http.HandleFunc("/upload", InternalAuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(50 << 20) // Лимит 50 МБ
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, "Файл не найден", http.StatusBadRequest)
return
}
defer file.Close()
mode := r.FormValue("mode")
if mode == "" {
mode = "chat"
}
fileBytes, err := io.ReadAll(file)
if err != nil {
http.Error(w, "Ошибка чтения файла", http.StatusInternalServerError)
return
}
finalBytes, contentType, err := processor.ProcessImage(fileBytes, mode)
if err != nil {
http.Error(w, "Ошибка обработки изображения", http.StatusInternalServerError)
return
}
ext := filepath.Ext(header.Filename)
if ext == "" {
ext = ".jpg"
}
objectName := fmt.Sprintf("%d%s", time.Now().UnixNano(), ext)
fileName, err := minioStore.Upload(r.Context(), objectName, bytes.NewReader(finalBytes), int64(len(finalBytes)), contentType)
if err != nil {
http.Error(w, "Ошибка загрузки в MinIO", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"success": true, "fileName": "%s", "size": %d}`, fileName, len(finalBytes))
}))
// === Эндпоинт 2: ПОЛУЧЕНИЕ ПРИВАТНОЙ ССЫЛКИ ===
http.HandleFunc("/media/url", InternalAuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
fileName := r.URL.Query().Get("file")
if fileName == "" {
http.Error(w, "Укажите имя файла", http.StatusBadRequest)
return
}
// ВАЖНО: Раз запрос дошел сюда, значит API Gateway УЖЕ проверил
// JWT токен и принадлежность пользователя к чату (через gRPC).
// Никакие проверки БД в Go больше не нужны. Мы доверяем Gateway!
expiry := time.Minute * 2 // Приватные ссылки живут 2 минуты
presignedURL, err := minioStore.GeneratePresignedURL(r.Context(), fileName, expiry)
if err != nil {
http.Error(w, "Ошибка генерации ссылки", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"url": "%s"}`, presignedURL)
}))
// === Эндпоинт 3: ПОЛУЧЕНИЕ ПУБЛИЧНОЙ ССЫЛКИ (Аватарки, Баннеры) ===
// Обрати внимание: этот эндпоинт НЕ обернут в InternalAuthMiddleware
http.HandleFunc("/media/public/url", func(w http.ResponseWriter, r *http.Request) {
fileName := r.URL.Query().Get("file")
if fileName == "" {
http.Error(w, "Укажите имя файла", http.StatusBadRequest)
return
}
// Публичные ссылки можно делать более долгоживущими (например, на 24 часа),
// чтобы браузер мог их эффективно кэшировать
expiry := time.Hour * 24
presignedURL, err := minioStore.GeneratePresignedURL(r.Context(), fileName, expiry)
if err != nil {
http.Error(w, "Ошибка генерации ссылки", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"url": "%s"}`, presignedURL)
})
slog.Info("Media Service запущен на внутреннем порту :8081")
if err := http.ListenAndServe(":8081", nil); err != nil {
slog.Error("Ошибка работы HTTP сервера", "err", err)
}
}