feat(media-supply): integrate object storage for media supply service and enhance image upload handling
Backend CI / Backend (push) Successful in 16m49s

This commit is contained in:
2026-06-03 22:11:22 +08:00
parent d098633a67
commit ae455ea21c
10 changed files with 765 additions and 52 deletions
@@ -8,8 +8,10 @@ import (
"fmt"
"io"
"mime"
"mime/multipart"
"net/http"
"net/http/cookiejar"
"net/textproto"
"net/url"
"regexp"
"strconv"
@@ -215,6 +217,22 @@ type meijiequanLoginResponse struct {
Raw json.RawMessage `json:"-"`
}
type MeijiequanUploadImageRequest struct {
Filename string
ContentType string
Content []byte
}
type MeijiequanUploadImageResponse struct {
State string `json:"state"`
URL string `json:"url"`
Title string `json:"title"`
Original string `json:"original"`
Type string `json:"type"`
Size int64 `json:"size"`
Raw json.RawMessage `json:"-"`
}
func NewMeijiequanClient(redis *goredis.Client, cfg config.MeijiequanConfig) *MeijiequanClient {
client := &MeijiequanClient{
redis: redis,
@@ -795,6 +813,87 @@ func (c *MeijiequanClient) CreateOrder(ctx context.Context, req MeijiequanCreate
return result, nil
}
func (c *MeijiequanClient) UploadEditorImage(ctx context.Context, req MeijiequanUploadImageRequest) (*MeijiequanUploadImageResponse, error) {
content := req.Content
if len(content) == 0 {
return nil, fmt.Errorf("meijiequan image content is empty")
}
var result *MeijiequanUploadImageResponse
if err := c.withUpstreamLock(ctx, func(lockCtx context.Context) error {
cfg := c.config()
client, csrf, err := c.httpClient(lockCtx)
if err != nil {
return err
}
var body bytes.Buffer
writer := multipart.NewWriter(&body)
filename := strings.TrimSpace(req.Filename)
if filename == "" {
filename = "image.png"
}
partHeaders := make(textproto.MIMEHeader)
partHeaders.Set("Content-Disposition", mime.FormatMediaType("form-data", map[string]string{
"name": "upfile",
"filename": filename,
}))
if contentType := strings.TrimSpace(req.ContentType); contentType != "" {
partHeaders.Set("Content-Type", contentType)
}
part, err := writer.CreatePart(partHeaders)
if err != nil {
return err
}
if _, err := part.Write(content); err != nil {
return err
}
if err := writer.Close(); err != nil {
return err
}
httpReq, err := http.NewRequestWithContext(lockCtx, http.MethodPost, cfg.BaseURL+"/vendor/ueditor/php/controller.php?action=uploadimage&category=yhdoc", &body)
if err != nil {
return err
}
c.applyHeaders(httpReq, csrf)
httpReq.Header.Set("Content-Type", writer.FormDataContentType())
httpReq.Header.Set("Referer", cfg.BaseURL+"/advSupply/article/fillContent?is_clear=1")
resp, err := client.Do(httpReq)
if err != nil {
return err
}
defer resp.Body.Close()
if isMeijiequanAuthResponse(resp) {
return errMeijiequanAuthRequired
}
responseBody, err := io.ReadAll(io.LimitReader(resp.Body, 5<<20))
if err != nil {
return err
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("meijiequan upload image http %d: %s", resp.StatusCode, string(responseBody))
}
var parsed MeijiequanUploadImageResponse
if err := json.Unmarshal(responseBody, &parsed); err != nil {
return fmt.Errorf("meijiequan upload image decode: %w", err)
}
parsed.Raw = append(parsed.Raw[:0], responseBody...)
if !strings.EqualFold(strings.TrimSpace(parsed.State), "SUCCESS") || strings.TrimSpace(parsed.URL) == "" {
message := strings.TrimSpace(parsed.State)
if message == "" {
message = "empty image url"
}
return fmt.Errorf("meijiequan upload image failed: %s", message)
}
parsed.URL = strings.TrimSpace(parsed.URL)
result = &parsed
return nil
}); err != nil {
return nil, err
}
return result, nil
}
func (c *MeijiequanClient) ListPublishedArticles(ctx context.Context, userID string, page, pageSize int) (MeijiequanPublishedArticlePage, error) {
return c.listArticles(ctx, userID, "2", page, pageSize)
}