feat: add chunked desktop client release uploads
This commit is contained in:
@@ -72,12 +72,25 @@ func (c *aliyunClient) Validate() error {
|
||||
}
|
||||
|
||||
func (c *aliyunClient) PutBytes(ctx context.Context, objectKey string, content []byte, contentType string) error {
|
||||
return c.PutReader(ctx, objectKey, bytes.NewReader(content), int64(len(content)), contentType)
|
||||
}
|
||||
|
||||
func (c *aliyunClient) PutReader(
|
||||
ctx context.Context,
|
||||
objectKey string,
|
||||
reader io.Reader,
|
||||
size int64,
|
||||
contentType string,
|
||||
) error {
|
||||
if err := c.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.TrimSpace(objectKey) == "" {
|
||||
return fmt.Errorf("object key is required")
|
||||
}
|
||||
if reader == nil {
|
||||
return fmt.Errorf("object reader is required")
|
||||
}
|
||||
|
||||
exists, err := c.client.IsBucketExist(c.bucket)
|
||||
if err != nil {
|
||||
@@ -113,11 +126,11 @@ func (c *aliyunClient) PutBytes(ctx context.Context, objectKey string, content [
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bucket.PutObject(objectKey, bytes.NewReader(content), options...); err != nil {
|
||||
if err := bucket.PutObject(objectKey, reader, options...); err != nil {
|
||||
c.logError(ctx, "aliyun put object failed",
|
||||
zap.String("bucket", c.bucket),
|
||||
zap.String("object_key", objectKey),
|
||||
zap.Int("content_size", len(content)),
|
||||
zap.Int64("content_size", size),
|
||||
zap.String("object_acl", strings.TrimSpace(c.cfg.ObjectACL)),
|
||||
zap.String("content_type", contentType),
|
||||
zap.String("aliyun_error_code", aliyunErrorCode(err)),
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -27,6 +28,10 @@ type Client interface {
|
||||
DownloadURL(objectKey string) (string, error)
|
||||
}
|
||||
|
||||
type ReaderClient interface {
|
||||
PutReader(ctx context.Context, objectKey string, reader io.Reader, size int64, contentType string) error
|
||||
}
|
||||
|
||||
type ManagementClient interface {
|
||||
Client
|
||||
List(ctx context.Context, in ListInput) (*ListResult, error)
|
||||
@@ -106,6 +111,10 @@ func (c disabledClient) PutBytes(context.Context, string, []byte, string) error
|
||||
return c.Validate()
|
||||
}
|
||||
|
||||
func (c disabledClient) PutReader(context.Context, string, io.Reader, int64, string) error {
|
||||
return c.Validate()
|
||||
}
|
||||
|
||||
func (c disabledClient) GetBytes(context.Context, string) ([]byte, error) {
|
||||
return nil, c.Validate()
|
||||
}
|
||||
|
||||
@@ -103,6 +103,16 @@ func (c *minioClient) PutBytes(
|
||||
objectKey string,
|
||||
content []byte,
|
||||
contentType string,
|
||||
) error {
|
||||
return c.PutReader(ctx, objectKey, bytes.NewReader(content), int64(len(content)), contentType)
|
||||
}
|
||||
|
||||
func (c *minioClient) PutReader(
|
||||
ctx context.Context,
|
||||
objectKey string,
|
||||
reader io.Reader,
|
||||
size int64,
|
||||
contentType string,
|
||||
) error {
|
||||
if err := c.Validate(); err != nil {
|
||||
return err
|
||||
@@ -110,6 +120,9 @@ func (c *minioClient) PutBytes(
|
||||
if strings.TrimSpace(objectKey) == "" {
|
||||
return fmt.Errorf("object key is required")
|
||||
}
|
||||
if reader == nil {
|
||||
return fmt.Errorf("object reader is required")
|
||||
}
|
||||
|
||||
exists, err := c.client.BucketExists(ctx, c.bucket)
|
||||
if err != nil {
|
||||
@@ -131,14 +144,14 @@ func (c *minioClient) PutBytes(
|
||||
}
|
||||
}
|
||||
|
||||
_, err = c.client.PutObject(ctx, c.bucket, objectKey, bytes.NewReader(content), int64(len(content)), minio.PutObjectOptions{
|
||||
_, err = c.client.PutObject(ctx, c.bucket, objectKey, reader, size, minio.PutObjectOptions{
|
||||
ContentType: contentType,
|
||||
})
|
||||
if err != nil {
|
||||
c.logError(ctx, "minio put object failed",
|
||||
zap.String("bucket", c.bucket),
|
||||
zap.String("object_key", objectKey),
|
||||
zap.Int("content_size", len(content)),
|
||||
zap.Int64("content_size", size),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("put minio object: %w", err)
|
||||
|
||||
@@ -2,6 +2,7 @@ package objectstorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -31,6 +32,19 @@ func (c *ReloadableClient) PutBytes(ctx context.Context, objectKey string, conte
|
||||
return c.snapshot().PutBytes(ctx, objectKey, content, contentType)
|
||||
}
|
||||
|
||||
func (c *ReloadableClient) PutReader(ctx context.Context, objectKey string, reader io.Reader, size int64, contentType string) error {
|
||||
client := c.snapshot()
|
||||
readerClient, ok := client.(ReaderClient)
|
||||
if !ok {
|
||||
content, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.PutBytes(ctx, objectKey, content, contentType)
|
||||
}
|
||||
return readerClient.PutReader(ctx, objectKey, reader, size, contentType)
|
||||
}
|
||||
|
||||
func (c *ReloadableClient) GetBytes(ctx context.Context, objectKey string) ([]byte, error) {
|
||||
return c.snapshot().GetBytes(ctx, objectKey)
|
||||
}
|
||||
|
||||
@@ -99,17 +99,21 @@ var routeDocs = map[string]routeDoc{
|
||||
"DELETE /api/ops/object-storage/folders": {"删除对象存储目录", "按前缀递归删除目录下对象。"},
|
||||
|
||||
// --- Ops:桌面客户端版本 ---
|
||||
"GET /api/ops/desktop-client/releases": {"桌面客户端版本列表", "分页查询桌面客户端版本配置。"},
|
||||
"POST /api/ops/desktop-client/releases/upload": {"上传桌面客户端包", "Multipart 上传桌面客户端安装包或更新包。"},
|
||||
"POST /api/ops/desktop-client/releases": {"新建桌面客户端版本", "创建桌面客户端版本配置。"},
|
||||
"PATCH /api/ops/desktop-client/releases/:id": {"更新桌面客户端版本", "更新桌面客户端版本配置。"},
|
||||
"GET /api/ops/desktop-client/releases/:id/download-url": {"解析桌面客户端下载地址", "解析指定桌面客户端版本的下载地址。"},
|
||||
"POST /api/ops/desktop-client/releases/:id/enabled": {"切换桌面客户端版本状态", "启用或禁用指定桌面客户端版本。"},
|
||||
"DELETE /api/ops/desktop-client/releases/:id": {"删除桌面客户端版本", "删除指定桌面客户端版本配置。"},
|
||||
"GET /api/ops/desktop-client/bug-reports": {"客户端 Bug 列表", "分页查询桌面客户端崩溃和用户反馈记录。"},
|
||||
"GET /api/ops/desktop-client/bug-reports/:id": {"客户端 Bug 详情", "查看桌面客户端崩溃元数据、运行快照和 dump 对象。"},
|
||||
"PATCH /api/ops/desktop-client/bug-reports/:id": {"更新客户端 Bug 状态", "调整客户端 Bug 状态、级别和处理备注。"},
|
||||
"GET /api/ops/desktop-client/bug-reports/:id/dump-url": {"解析客户端 dump 下载地址", "生成指定客户端 Bug dump 文件的短期下载地址。"},
|
||||
"GET /api/ops/desktop-client/releases": {"桌面客户端版本列表", "分页查询桌面客户端版本配置。"},
|
||||
"POST /api/ops/desktop-client/releases/upload": {"上传桌面客户端包(兼容)", "兼容旧版单请求 Multipart 上传;新版前端使用分片上传。"},
|
||||
"POST /api/ops/desktop-client/release-uploads": {"初始化客户端包分片上传", "创建分片上传会话,返回 upload_id、分片大小和分片数量。"},
|
||||
"POST /api/ops/desktop-client/release-uploads/:upload_id/chunks/:chunk_index": {"上传客户端包分片", "Multipart 上传单个客户端安装包或更新包分片。"},
|
||||
"POST /api/ops/desktop-client/release-uploads/:upload_id/complete": {"完成客户端包分片上传", "校验全部分片,流式写入 OSS,并返回 Object Key、文件大小和 SHA256。"},
|
||||
"DELETE /api/ops/desktop-client/release-uploads/:upload_id": {"取消客户端包分片上传", "取消分片上传会话并清理临时分片。"},
|
||||
"POST /api/ops/desktop-client/releases": {"新建客户端版本", "创建桌面客户端版本配置。"},
|
||||
"PATCH /api/ops/desktop-client/releases/:id": {"更新客户端版本", "更新桌面客户端版本配置。"},
|
||||
"GET /api/ops/desktop-client/releases/:id/download-url": {"解析桌面客户端下载地址", "解析指定桌面客户端版本的下载地址。"},
|
||||
"POST /api/ops/desktop-client/releases/:id/enabled": {"切换客户端版本状态", "启用或禁用指定桌面客户端版本。"},
|
||||
"DELETE /api/ops/desktop-client/releases/:id": {"删除客户端版本", "删除指定桌面客户端版本配置。"},
|
||||
"GET /api/ops/desktop-client/bug-reports": {"客户端 Bug 列表", "分页查询桌面客户端崩溃和用户反馈记录。"},
|
||||
"GET /api/ops/desktop-client/bug-reports/:id": {"客户端 Bug 详情", "查看桌面客户端崩溃元数据、运行快照和 dump 对象。"},
|
||||
"PATCH /api/ops/desktop-client/bug-reports/:id": {"更新客户端 Bug 状态", "调整客户端 Bug 状态、级别和处理备注。"},
|
||||
"GET /api/ops/desktop-client/bug-reports/:id/dump-url": {"解析客户端 dump 下载地址", "生成指定客户端 Bug dump 文件的短期下载地址。"},
|
||||
|
||||
// --- Ops:合规 ---
|
||||
"GET /api/ops/compliance/dictionaries": {"合规词库列表", "分页查询内容合规词库。"},
|
||||
|
||||
@@ -419,6 +419,7 @@ func isMultipartRoute(route gin.RouteInfo) bool {
|
||||
path == "/api/ops/site-domain-mappings/import" ||
|
||||
path == "/api/ops/object-storage/objects/upload" ||
|
||||
path == "/api/ops/desktop-client/releases/upload" ||
|
||||
path == "/api/ops/desktop-client/release-uploads/:upload_id/chunks/:chunk_index" ||
|
||||
path == "/api/tenant/knowledge/items/file" ||
|
||||
path == "/api/tenant/kol/manage/profile/avatar" ||
|
||||
path == "/api/tenant/articles/:id/images"
|
||||
|
||||
Reference in New Issue
Block a user