This commit is contained in:
parent
53e270015c
commit
c58b578023
4 changed files with 47 additions and 94 deletions
|
|
@ -18,16 +18,16 @@ import (
|
||||||
// }
|
// }
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
db *sqlc.Queries
|
db *sqlc.Queries
|
||||||
ImageServerSocket string
|
ImageServerURL string
|
||||||
RPCclient *rmq.RPCClient
|
RPCclient *rmq.RPCClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(db *sqlc.Queries, ImageServerSocket string, rpcclient *rmq.RPCClient) *Server {
|
func NewServer(db *sqlc.Queries, ImageServerURL string, rpcclient *rmq.RPCClient) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
db: db,
|
db: db,
|
||||||
ImageServerSocket: ImageServerSocket,
|
ImageServerURL: ImageServerURL,
|
||||||
RPCclient: rpcclient,
|
RPCclient: rpcclient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
oapi "nyanimedb/api"
|
oapi "nyanimedb/api"
|
||||||
sqlc "nyanimedb/sql"
|
sqlc "nyanimedb/sql"
|
||||||
|
|
@ -24,14 +23,13 @@ import (
|
||||||
|
|
||||||
// PostMediaUpload implements oapi.StrictServerInterface.
|
// PostMediaUpload implements oapi.StrictServerInterface.
|
||||||
func (s *Server) PostMediaUpload(ctx context.Context, request oapi.PostMediaUploadRequestObject) (oapi.PostMediaUploadResponseObject, error) {
|
func (s *Server) PostMediaUpload(ctx context.Context, request oapi.PostMediaUploadRequestObject) (oapi.PostMediaUploadResponseObject, error) {
|
||||||
// Получаем multipart body
|
// 1. Получаем multipart body
|
||||||
mp := request.MultipartBody
|
mp := request.MultipartBody
|
||||||
if mp == nil {
|
if mp == nil {
|
||||||
log.Errorf("PostMedia without body")
|
log.Errorf("PostMedia without body")
|
||||||
return oapi.PostMediaUpload400JSONResponse("Multipart body is required"), nil
|
return oapi.PostMediaUpload400JSONResponse("Multipart body is required"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Парсим первую часть (предполагаем, что файл в поле "file")
|
|
||||||
part, err := mp.NextPart()
|
part, err := mp.NextPart()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("PostMedia without file")
|
log.Errorf("PostMedia without file")
|
||||||
|
|
@ -39,7 +37,6 @@ func (s *Server) PostMediaUpload(ctx context.Context, request oapi.PostMediaUplo
|
||||||
}
|
}
|
||||||
defer part.Close()
|
defer part.Close()
|
||||||
|
|
||||||
// Читаем ВЕСЬ файл в память
|
|
||||||
data, err := io.ReadAll(part)
|
data, err := io.ReadAll(part)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("PostMedia cannot read file")
|
log.Errorf("PostMedia cannot read file")
|
||||||
|
|
@ -47,108 +44,65 @@ func (s *Server) PostMediaUpload(ctx context.Context, request oapi.PostMediaUplo
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
log.Errorf("PostMedia empty file")
|
|
||||||
return oapi.PostMediaUpload400JSONResponse("Empty file"), nil
|
return oapi.PostMediaUpload400JSONResponse("Empty file"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка MIME по первым 512 байтам
|
// 2. Проверка и декодирование (оставляем как было)
|
||||||
mimeType := http.DetectContentType(data)
|
mimeType := http.DetectContentType(data)
|
||||||
if mimeType != "image/jpeg" && mimeType != "image/png" && mimeType != "image/webp" {
|
|
||||||
log.Errorf("PostMedia bad type")
|
|
||||||
return oapi.PostMediaUpload400JSONResponse("Bad data type"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Декодируем изображение из буфера
|
|
||||||
var img image.Image
|
var img image.Image
|
||||||
switch mimeType {
|
switch mimeType {
|
||||||
case "image/jpeg":
|
case "image/jpeg":
|
||||||
{
|
img, err = jpeg.Decode(bytes.NewReader(data))
|
||||||
img, err = jpeg.Decode(bytes.NewReader(data))
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("PostMedia cannot decode file: %v", err)
|
|
||||||
return oapi.PostMediaUpload500Response{}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "image/png":
|
case "image/png":
|
||||||
{
|
img, err = png.Decode(bytes.NewReader(data))
|
||||||
img, err = png.Decode(bytes.NewReader(data))
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("PostMedia cannot decode file: %v", err)
|
|
||||||
return oapi.PostMediaUpload500Response{}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "image/webp":
|
case "image/webp":
|
||||||
{
|
img, err = webp.Decode(bytes.NewReader(data))
|
||||||
img, err = webp.Decode(bytes.NewReader(data))
|
default:
|
||||||
if err != nil {
|
log.Errorf("PostMedia unsupported type: %s", mimeType)
|
||||||
log.Errorf("PostMedia cannot decode file: %v", err)
|
return oapi.PostMediaUpload400JSONResponse("Unsupported image type"), nil
|
||||||
return oapi.PostMediaUpload500Response{}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Перекодируем в PNG (как было в оригинале)
|
|
||||||
var buf bytes.Buffer
|
|
||||||
err = imaging.Encode(&buf, img, imaging.PNG)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("PostMedia failed to re-encode JPEG: %v", err)
|
log.Errorf("PostMedia decode error: %v", err)
|
||||||
return oapi.PostMediaUpload500Response{}, nil
|
return oapi.PostMediaUpload500Response{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// 3. Перекодируем в PNG перед отправкой
|
||||||
// Взаимодействие с Image Storage Service через Unix Socket
|
var buf bytes.Buffer
|
||||||
// ---------------------------------------------------------
|
if err := imaging.Encode(&buf, img, imaging.PNG); err != nil {
|
||||||
|
log.Errorf("PostMedia encode error: %v", err)
|
||||||
|
return oapi.PostMediaUpload500Response{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Формируем Multipart Body для запроса к хранилищу
|
// 4. Подготавливаем запрос к внешнему хранилищу (Image Server)
|
||||||
body := &bytes.Buffer{}
|
body := &bytes.Buffer{}
|
||||||
writer := multipart.NewWriter(body)
|
writer := multipart.NewWriter(body)
|
||||||
|
|
||||||
// Поле "file" (обязательное по спецификации хранилища)
|
// Создаем часть с файлом
|
||||||
// Имя файла ставим фиксированное, так как хранилище генерирует sha1
|
partWriter, err := writer.CreateFormFile("file", "image.png")
|
||||||
partWriter, err := writer.CreateFormFile("file", "upload.png")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("PostMedia failed to create form file: %v", err)
|
|
||||||
return oapi.PostMediaUpload500Response{}, nil
|
return oapi.PostMediaUpload500Response{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Копируем перекодированное PNG изображение в форму
|
|
||||||
if _, err := io.Copy(partWriter, &buf); err != nil {
|
if _, err := io.Copy(partWriter, &buf); err != nil {
|
||||||
log.Errorf("PostMedia failed to write body: %v", err)
|
|
||||||
return oapi.PostMediaUpload500Response{}, nil
|
return oapi.PostMediaUpload500Response{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Поле "subdir" (опционально, ставим "posters" или можно вынести в конфиг)
|
// Добавляем subdir
|
||||||
if err := writer.WriteField("subdir", "posters"); err != nil {
|
_ = writer.WriteField("subdir", "posters")
|
||||||
log.Errorf("PostMedia failed to write field subdir: %v", err)
|
writer.Close()
|
||||||
return oapi.PostMediaUpload500Response{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Закрываем writer, чтобы записать boundary
|
// Формируем полный URL (s.AppConfig.ImageSocket теперь base_url, например "http://storage.local")
|
||||||
if err := writer.Close(); err != nil {
|
// Убедимся, что путь не дублирует слэши
|
||||||
log.Errorf("PostMedia failed to close multipart writer: %v", err)
|
uploadURL := strings.TrimSuffix(s.ImageServerURL, "/") + "/upload"
|
||||||
return oapi.PostMediaUpload500Response{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Настраиваем клиент для Unix сокета
|
// 5. Отправляем обычный HTTP POST
|
||||||
socketPath := s.ImageServerSocket
|
reqUpstream, err := http.NewRequestWithContext(ctx, "POST", uploadURL, body)
|
||||||
transport := &http.Transport{
|
|
||||||
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
|
|
||||||
return net.Dial("unix", socketPath)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
client := &http.Client{Transport: transport}
|
|
||||||
|
|
||||||
// 3. Создаем запрос
|
|
||||||
// Хост в URL ("http://unix") игнорируется транспортом, важен путь "/upload"
|
|
||||||
reqUpstream, err := http.NewRequest("POST", "http://unix/upload", body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("PostMedia failed to create upstream request: %v", err)
|
|
||||||
return oapi.PostMediaUpload500Response{}, nil
|
return oapi.PostMediaUpload500Response{}, nil
|
||||||
}
|
}
|
||||||
reqUpstream.Header.Set("Content-Type", writer.FormDataContentType())
|
reqUpstream.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
// 4. Отправляем запрос
|
resp, err := http.DefaultClient.Do(reqUpstream)
|
||||||
resp, err := client.Do(reqUpstream)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("PostMedia upstream request failed: %v", err)
|
log.Errorf("PostMedia upstream request failed: %v", err)
|
||||||
return oapi.PostMediaUpload500Response{}, nil
|
return oapi.PostMediaUpload500Response{}, nil
|
||||||
|
|
@ -156,21 +110,20 @@ func (s *Server) PostMediaUpload(ctx context.Context, request oapi.PostMediaUplo
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
bodyErr, _ := io.ReadAll(resp.Body)
|
log.Errorf("PostMedia storage returned status: %d", resp.StatusCode)
|
||||||
log.Errorf("PostMedia upstream error %d: %s", resp.StatusCode, string(bodyErr))
|
|
||||||
return oapi.PostMediaUpload500Response{}, nil
|
return oapi.PostMediaUpload500Response{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Разбираем ответ {"path": "..."}
|
// 6. Получаем путь из ответа хранилища
|
||||||
var storageResp struct {
|
var storageResp struct {
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&storageResp); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&storageResp); err != nil {
|
||||||
log.Errorf("PostMedia failed to decode upstream response: %v", err)
|
|
||||||
return oapi.PostMediaUpload500Response{}, nil
|
return oapi.PostMediaUpload500Response{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("File uploaded to image storage: %s", storageResp.Path)
|
// В storageResp.Path теперь лежит что-то вроде "posters/a1/b2/hash.png"
|
||||||
|
log.Infof("Successfully uploaded: %s", storageResp.Path)
|
||||||
|
|
||||||
params := sqlc.CreateImageParams{
|
params := sqlc.CreateImageParams{
|
||||||
StorageType: sqlc.StorageTypeTLocal,
|
StorageType: sqlc.StorageTypeTLocal,
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ func main() {
|
||||||
|
|
||||||
rpcClient := rmq.NewRPCClient(rmqConn, 30*time.Second)
|
rpcClient := rmq.NewRPCClient(rmqConn, 30*time.Second)
|
||||||
|
|
||||||
server := handlers.NewServer(queries, AppConfig.ImageServerSocket, rpcClient)
|
server := handlers.NewServer(queries, AppConfig.ImageServerURL, rpcClient)
|
||||||
|
|
||||||
r.Use(cors.New(cors.Config{
|
r.Use(cors.New(cors.Config{
|
||||||
AllowOrigins: []string{AppConfig.ServiceAddress},
|
AllowOrigins: []string{AppConfig.ServiceAddress},
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Mode string
|
Mode string
|
||||||
ServiceAddress string `toml:"ServiceAddress" env:"SERVICE_ADDRESS"`
|
ServiceAddress string `toml:"ServiceAddress" env:"SERVICE_ADDRESS"`
|
||||||
DdUrl string `toml:"DbUrl" env:"DATABASE_URL"`
|
DdUrl string `toml:"DbUrl" env:"DATABASE_URL"`
|
||||||
JwtPrivateKey string `toml:"JwtPrivateKey" env:"JWT_PRIVATE_KEY"`
|
JwtPrivateKey string `toml:"JwtPrivateKey" env:"JWT_PRIVATE_KEY"`
|
||||||
LogLevel string `toml:"LogLevel" env:"LOG_LEVEL"`
|
LogLevel string `toml:"LogLevel" env:"LOG_LEVEL"`
|
||||||
RmqURL string `toml:"RabbitMQUrl" env:"RABBITMQ_URL"`
|
RmqURL string `toml:"RabbitMQUrl" env:"RABBITMQ_URL"`
|
||||||
AuthEnabled string `toml:"AuthEnabled" env:"AUTH_ENABLED"`
|
AuthEnabled string `toml:"AuthEnabled" env:"AUTH_ENABLED"`
|
||||||
ImageServerSocket string `toml:"ImageServerSocket" env:"IMAGES_BASE_URL"`
|
ImageServerURL string `toml:"ImageServerURL" env:"IMAGES_BASE_URL"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue