- 后端新增 POST /admin/api/my-images 上传接口:图片存入用户 owner 目录并生成缩略图,复用 /images 服务与 my-images picker - 本地图片三种入画布路径:上传按钮 / 拖入舞台(落点即拖放 位置) / Ctrl+V 粘贴 - 节点选中后显示悬浮工具条(预览/复制/置顶/删除)与右下角 缩放手柄;图片等比缩放,便签自由缩放;Ctrl+D 复制选中(含 相互连线);双击空白建笔记;按 1 / ⤢ 按钮适应全部内容 - 整套画布 UI 换 Memphis:纸张底+墨点网格、黑边硬影节点、 黄色贴纸便签、橙色连线/选中态、虚线框选、顶栏/生成面板/ 预览灯箱/图片选择器全部换墨边 chunky 样式,随站点明暗主题 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
51 lines
1.5 KiB
Go
51 lines
1.5 KiB
Go
package service
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
_ "image/gif"
|
|
_ "image/jpeg"
|
|
_ "image/png"
|
|
"strings"
|
|
"time"
|
|
|
|
_ "golang.org/x/image/webp"
|
|
)
|
|
|
|
// SaveOwnedImage stores a user-uploaded image under their owner directory so it
|
|
// behaves like a generated output: it shows up in the 我的图片 picker, can be
|
|
// dropped onto the infinite canvas, and is served by /images/<owner>/<name>.
|
|
// format must be a decoder-registered image format ("png", "jpeg", ...).
|
|
func (s *AdminReadService) SaveOwnedImage(ctx context.Context, owner, format string, data []byte) (string, error) {
|
|
owner = strings.TrimSpace(owner)
|
|
if owner == "" || s.store == nil || !s.store.Configured() {
|
|
return "", errors.New("storage not configured")
|
|
}
|
|
ext := strings.ToLower(strings.TrimSpace(format))
|
|
contentType := ""
|
|
switch ext {
|
|
case "jpeg":
|
|
ext = "jpg"
|
|
contentType = "image/jpeg"
|
|
case "png", "gif", "webp":
|
|
contentType = "image/" + ext
|
|
default:
|
|
return "", errors.New("unsupported image format")
|
|
}
|
|
if _, _, err := image.DecodeConfig(bytes.NewReader(data)); err != nil {
|
|
return "", errors.New("invalid image data")
|
|
}
|
|
key := fmt.Sprintf("%s/up_%d%s.%s", owner, time.Now().Unix(), randomUpper(6), ext)
|
|
if err := s.store.Put(ctx, key, data, contentType); err != nil {
|
|
return "", err
|
|
}
|
|
// Best-effort thumbnail, same convention as generated outputs.
|
|
if thumb, terr := makeThumbnail(data); terr == nil {
|
|
_ = s.store.Put(ctx, ThumbKey(key), thumb, "image/jpeg")
|
|
}
|
|
return key, nil
|
|
}
|