135 lines
4.8 KiB
Go
135 lines
4.8 KiB
Go
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)
|
||
}
|
||
} |