Files
media-service/cmd/main.go
Дмитрий 1a7251976d add media service
2026-05-08 15:59:03 +03:00

135 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}