Files
tamakyi 08bbd17751 storage: 对齐图片对象 key 到生产口径
生产真实 key 为 `YYYY/MM/<UnixNano>.<ext>`(无前缀、无 day 层),
与 route/question/page.go:uploadImage 一致;internal/storage 里三个
上传函数仍用旧的 `picture/ 前缀 + YYYY/MM/DD + 15hex + 无扩展名`,从未上线。

- PictureKeyPrefix 置空,统一由 uploadKey 生成 key
- 扩展名跟随最终 ContentType(webp 转成功用 .webp,回退原图用原格式)
- OSS 原图直传通道取原文件名扩展名
- 新增 picture_test.go 锁定 key 格式,防回归
2026-10-29 20:04:31 +08:00

53 lines
1.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2022 E99p1ant. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package storage
import (
"fmt"
"path/filepath"
"strings"
"time"
)
// PictureKeyPrefix 是图片对象 key 的前缀。生产环境图片以无前缀方式存于
// bucket 根下(R2 PathStyle 下 bucket 名自动作为 URL 首段),故这里置空。
const PictureKeyPrefix = ""
// uploadKey 按生产口径生成对象 key:`YYYY/MM/<UnixNano><ext>`。
// 无前缀、无 day 层,文件名用纳秒时间戳保证唯一,扩展名与最终落盘内容类型一致。
func uploadKey(ext string) string {
now := time.Now()
return fmt.Sprintf("%s%d/%02d/%d%s", PictureKeyPrefix, now.Year(), int(now.Month()), now.UnixNano(), ext)
}
// extFromContentType 由 MIME 类型推导扩展名,用于保证对象 key 的扩展名
// 与 ContentType(以及实际落盘字节)严格一致,避免出现「.jpg 实为 webp」的错配。
func extFromContentType(contentType string) string {
switch contentType {
case "image/jpeg":
return ".jpg"
case "image/png":
return ".png"
case "image/gif":
return ".gif"
case "image/webp":
return ".webp"
default:
return ""
}
}
// originalExt 取原始文件名的扩展名,统一转小写。用于转换失败回退原图时
// 保留原始扩展名(与生产口径一致)。
func originalExt(filename string) string {
return strings.ToLower(filepath.Ext(filename))
}
// MaxAvatarSize is the max avatar size which is 2MB.
const MaxAvatarSize = 2 * 1024 * 1024
// MaxBackgroundSize is the max background size which is 2MB.
const MaxBackgroundSize = 2 * 1024 * 1024