You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

99 lines
3.2 KiB

package controlplane
import (
"database/sql"
"encoding/base64"
"errors"
"strings"
)
const (
maxTaskImages = 10
maxTaskImageBytes = 12 * 1024 * 1024
)
// TaskImageInput is a browser data URL supplied only when creating a task.
// It is deliberately never copied to task output or audit fields.
type TaskImageInput struct {
Name string `json:"name"`
DataURL string `json:"dataURL"`
}
type taskImage struct {
Name string `json:"name"`
MediaType string `json:"media_type"`
DataURL string `json:"data_url"`
}
// TaskAttachment is the deliberately small preview returned only by the local
// task-detail view. It is never used for audit records, task output, or logs.
// The data URL is needed by the local WebView to render a safe thumbnail.
type TaskAttachment struct {
Name string `json:"name"`
MediaType string `json:"mediaType"`
DataURL string `json:"dataURL"`
}
func normalizeTaskImages(inputs []TaskImageInput) ([]taskImage, error) {
if len(inputs) > maxTaskImages {
return nil, errors.New("每项任务最多附加 10 张图片")
}
images := make([]taskImage, 0, len(inputs))
total := 0
for index, input := range inputs {
parts := strings.SplitN(input.DataURL, ",", 2)
if len(parts) != 2 || !strings.HasPrefix(parts[0], "data:") || !strings.HasSuffix(parts[0], ";base64") {
return nil, errors.New("图片格式无效")
}
mediaType := strings.TrimSuffix(strings.TrimPrefix(parts[0], "data:"), ";base64")
if mediaType != "image/png" && mediaType != "image/jpeg" && mediaType != "image/gif" && mediaType != "image/webp" {
return nil, errors.New("只支持 PNG、JPEG、GIF 或 WebP 图片")
}
decoded, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil || len(decoded) == 0 {
return nil, errors.New("图片内容无效")
}
total += len(decoded)
if total > maxTaskImageBytes {
return nil, errors.New("一项任务的图片总大小不能超过 12 MB")
}
name := strings.TrimSpace(input.Name)
if name == "" {
name = "图片" + stringLine(index+1)
}
images = append(images, taskImage{Name: name[:min(len(name), 120)], MediaType: mediaType, DataURL: input.DataURL})
}
return images, nil
}
func (c *ControlPlane) taskImages(taskID string) ([]taskImage, error) {
rows, err := c.db.Query("SELECT name, media_type, data_url FROM task_images WHERE task_id = ? ORDER BY position", taskID)
if err != nil {
return nil, errors.New("无法读取任务图片")
}
defer rows.Close()
images := make([]taskImage, 0)
for rows.Next() {
var image taskImage
if err := rows.Scan(&image.Name, &image.MediaType, &image.DataURL); err != nil {
return nil, errors.New("无法读取任务图片")
}
images = append(images, image)
}
if err := rows.Err(); err != nil && err != sql.ErrNoRows {
return nil, errors.New("无法读取任务图片")
}
return images, nil
}
func (c *ControlPlane) taskAttachmentPreviews(taskID string) ([]TaskAttachment, error) {
images, err := c.taskImages(taskID)
if err != nil {
return nil, err
}
previews := make([]TaskAttachment, 0, len(images))
for _, image := range images {
previews = append(previews, TaskAttachment{Name: image.Name, MediaType: image.MediaType, DataURL: image.DataURL})
}
return previews, nil
}