fix: 修复前端无法加载七牛云图片的问题

1. 修复 buildURL 中 url.PathEscape 对路径分隔符 / 的错误编码(/→%2F),
   改为逐段编码后拼接
2. AssetResponse 新增 key 字段,返回七牛云对象存储 Key
3. 前端改用 /api/v1/assets/download 接口加载图片,通过后端 302 重定向
   访问 CDN,避免浏览器直接访问 CDN 可能出现的跨域或编码问题
4. 更新 api.md 文档,补充 key 字段说明和 download 接口公开属性
This commit is contained in:
2026-05-25 15:25:40 +08:00
parent 50caad8e6c
commit 2f8e7ead17
5 changed files with 20 additions and 5 deletions
+2
View File
@@ -36,6 +36,7 @@ type GenerateResponse struct {
// AssetResponse 单个素材响应。 // AssetResponse 单个素材响应。
type AssetResponse struct { type AssetResponse struct {
Key string `json:"key"`
URL string `json:"url"` URL string `json:"url"`
Format string `json:"format"` Format string `json:"format"`
} }
@@ -156,6 +157,7 @@ func runPipelineBg(projectID, taskID string, req GenerateRequest) {
return return
} }
assets[i] = AssetResponse{ assets[i] = AssetResponse{
Key: key,
URL: cdnURL, URL: cdnURL,
Format: a.Format, Format: a.Format,
} }
+5 -1
View File
@@ -94,5 +94,9 @@ func (s *StorageService) Delete(ctx context.Context, key string) error {
// buildURL 根据配置构建完整的 CDN URL。 // buildURL 根据配置构建完整的 CDN URL。
func (s *StorageService) buildURL(key string) string { func (s *StorageService) buildURL(key string) string {
host := strings.TrimRight(s.cfg.CDNHost, "/") host := strings.TrimRight(s.cfg.CDNHost, "/")
return fmt.Sprintf("%s/%s", host, url.PathEscape(key)) segments := strings.Split(key, "/")
for i, seg := range segments {
segments[i] = url.PathEscape(seg)
}
return fmt.Sprintf("%s/%s", host, strings.Join(segments, "/"))
} }
+9 -3
View File
@@ -483,8 +483,8 @@ POST /api/v1/prompt/optimize
"data": { "data": {
"assets": [ "assets": [
{ {
"id": "asset_001", "key": "generation/proj_abc123/task_xyz789/0.png",
"url": "https://cdn.example.com/users/user_a1B2c3D4/projects/proj_abc123/tasks/task_xyz789/output/spritesheet.png", "url": "https://cdn.example.com/generation/proj_abc123/task_xyz789/0.png",
"format": "png", "format": "png",
"width": 256, "width": 256,
"height": 64, "height": 64,
@@ -500,9 +500,15 @@ POST /api/v1/prompt/optimize
} }
``` ```
| 字段 | 类型 | 说明 |
|------|------|------|
| `key` | string | 七牛云对象存储 Key |
| `url` | string | CDN 直接访问 URL |
| `format` | string | 图片格式(png 等) |
### GET /api/v1/assets/download ### GET /api/v1/assets/download
需认证。返回 302 重定向到七牛云 CDN URL。 公开接口(无需认证)。返回 302 重定向到七牛云 CDN URL。前端可通过此接口加载素材图片,作为 CDN 直接访问的替代方案。
| 参数 | 类型 | 说明 | | 参数 | 类型 | 说明 |
|------|------|------| |------|------|------|
+2 -1
View File
@@ -21,7 +21,8 @@ export async function getAssets(taskId: string): Promise<Asset[]> {
const resp = await get<AssetsResponse>(`/api/v1/tasks/${taskId}/assets`) const resp = await get<AssetsResponse>(`/api/v1/tasks/${taskId}/assets`)
return resp.assets.map((a, i) => ({ return resp.assets.map((a, i) => ({
id: `asset-${i}`, id: `asset-${i}`,
url: a.url, key: a.key,
url: `/api/v1/assets/download?key=${encodeURIComponent(a.key)}`,
format: a.format, format: a.format,
width: resp.metadata.frameWidth, width: resp.metadata.frameWidth,
height: resp.metadata.frameHeight, height: resp.metadata.frameHeight,
+2
View File
@@ -79,6 +79,7 @@ export interface Task {
// 素材 // 素材
export interface Asset { export interface Asset {
id: string id: string
key: string
url: string url: string
format: string format: string
width: number width: number
@@ -114,6 +115,7 @@ export interface GenerateResponse {
// 素材列表响应 — 对应 GET /api/v1/tasks/:taskId/assets // 素材列表响应 — 对应 GET /api/v1/tasks/:taskId/assets
export interface AssetsResponse { export interface AssetsResponse {
assets: { assets: {
key: string
url: string url: string
format: string format: string
}[] }[]