update
This commit is contained in:
@ -1,109 +1,71 @@
|
||||
// Package imgurl 把数据库里存储的图片存储值拼装为对外可访问的完整 URL。
|
||||
//
|
||||
// 存储约定(见迁移 007):库里只存「七牛 key」(如 runway/abc.jpg)或「本地相对路径」
|
||||
// 存储约定(见迁移 007):库里只存「对象 key」(如 runway/abc.jpg)或「本地相对路径」
|
||||
// (如 /uploads/runway/x.jpg),绝不存完整 URL。这样做两个好处:
|
||||
// 1. 换 CDN 域名只改配置,无需迁移全表;
|
||||
// 2. 做「普通 / 高清」分级时无需从 URL 反向解析 key。
|
||||
//
|
||||
// 分级与安全(付费墙):
|
||||
// - 高清(hd=true,仅 VIP):私有空间下载签名 URL(base_url/key-StyleVip,如 key-vip),限时有效;
|
||||
// 免费/未登录用户拿不到 VIP 样式 key,即使从别处得到 key,因无签名也会被七牛拒绝(403)。
|
||||
// - 普通(hd=false,所有人):base_url/key-StyleNormal(如 key-free)的私有空间签名 URL,长过期(供 SSG 静态页与 CDN 缓存)。
|
||||
// 质量模型(2026-09-11 调整):VIP 与免费用户现在看到同一质量,高清样式名为 `high`,
|
||||
// 由缤纷云 CoreIX 公开样式提供(base_url/key!style:high),匿名可访问、免 token。
|
||||
// 旧「免费低清 / VIP 原图预签名」付费墙已取消,故 Composer 不再区分 tier。
|
||||
// - 列表缩略图:另走 `thumb` 公开样式(ComposeThumb),仅用于列表/卡片等小尺寸场景,省流量。
|
||||
// - 本地兜底 /uploads/*:原样返回(前端 toAbs 拼 host)。
|
||||
// - 外链 http(s):原样返回(历史遗留 / vogue 等,无法套七牛样式,签名无意义)。
|
||||
//
|
||||
// 七牛私有空间下载签名:对 "base_url/key?e=<deadline>" 做 HMAC-SHA1(SK),urlsafe base64 后拼
|
||||
// "AK:sign" 作为 token。签名覆盖完整 path(含 -StyleNormal / -StyleVip 后缀),七牛校验通过才返回资源。
|
||||
// 前置:bucket 须在七牛控制台设为「私有」,并把 base_url 改为自定义 HTTPS 域名。
|
||||
// - 外链 http(s):原样返回(历史遗留 / vogue 等,无法套样式)。
|
||||
package imgurl
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Composer 图片 URL 拼装器。
|
||||
type Composer struct {
|
||||
// BaseURL 七牛 bucket 绑定域名,如 https://cdn.toom-studio.com(末尾无斜杠)。
|
||||
// BaseURL 对外访问域名,如 https://toomstudio.s3.bitiful.net(末尾无斜杠)。
|
||||
BaseURL string
|
||||
// StyleNormal 普通图七牛命名样式名(对应七牛控制台创建的样式),如 "free"。
|
||||
// 访问即 base_url/key-StyleNormal。务必与控制台样式名一致(如 free 或 free.webp)。
|
||||
StyleNormal string
|
||||
// StyleVip VIP 高清七牛命名样式名(对应七牛控制台创建的样式),如 "vip"。
|
||||
// 访问即 base_url/key-StyleVip。VIP 用户走此样式;务必与控制台样式名一致。
|
||||
StyleVip string
|
||||
// LocalBase 本地兜底图片前缀;通常空——本地 /uploads 路径由前端 toAbs 拼 host。
|
||||
LocalBase string
|
||||
// AK/SK 七牛 AccessKey/SecretKey,用于私有空间下载签名。未配置时回落为公开 URL。
|
||||
AK string
|
||||
SK string
|
||||
// SignTTLNormal 普通/压缩图(含命名样式 -free)签名过期秒;长过期供 SSG 静态页与 CDN 缓存。
|
||||
SignTTLNormal int64
|
||||
// SignTTLHD VIP 高清原图签名过期秒;短过期使泄漏窗口小。
|
||||
SignTTLHD int64
|
||||
// StyleDisplay 全量展示图 CoreIX 样式名(如 "high",对应控制台建的公开样式);
|
||||
// 若值含 "=" 则视为查询串(如 "w=1080&q=80&fmt=webp"),拼成 base_url/key?查询串。
|
||||
StyleDisplay string
|
||||
// StyleThumb 列表缩略图 CoreIX 样式名(如 "thumb");仅用于列表/卡片等小尺寸场景。
|
||||
// 留空时回落 StyleDisplay(无独立缩略图样式时行为与旧版一致)。
|
||||
StyleThumb string
|
||||
}
|
||||
|
||||
// New 构造 Composer。styleNormal 缺省回落 "free";styleVip 缺省回落 "vip";两个 TTL 缺省回落 31536000 / 3600。
|
||||
func New(baseURL, styleNormal, styleVip, localBase, ak, sk string, ttlNormal, ttlHD int64) *Composer {
|
||||
if styleNormal == "" {
|
||||
styleNormal = "free"
|
||||
// New 构造 Composer。
|
||||
// - styleDisplay 缺省回落 "high"(高清公开样式,VIP 与免费用户同质量);
|
||||
// - styleThumb 缺省回落 styleDisplay(未单独配置缩略图样式时,列表与详情同图)。
|
||||
func New(baseURL, styleDisplay, styleThumb string) *Composer {
|
||||
if styleDisplay == "" {
|
||||
styleDisplay = "high"
|
||||
}
|
||||
if styleVip == "" {
|
||||
styleVip = "vip"
|
||||
}
|
||||
if ttlNormal <= 0 {
|
||||
ttlNormal = 31536000
|
||||
}
|
||||
if ttlHD <= 0 {
|
||||
ttlHD = 3600
|
||||
if styleThumb == "" {
|
||||
styleThumb = styleDisplay
|
||||
}
|
||||
return &Composer{
|
||||
BaseURL: strings.TrimRight(baseURL, "/"),
|
||||
StyleNormal: styleNormal,
|
||||
StyleVip: styleVip,
|
||||
LocalBase: strings.TrimRight(localBase, "/"),
|
||||
AK: ak,
|
||||
SK: sk,
|
||||
SignTTLNormal: ttlNormal,
|
||||
SignTTLHD: ttlHD,
|
||||
BaseURL: strings.TrimRight(baseURL, "/"),
|
||||
StyleDisplay: styleDisplay,
|
||||
StyleThumb: styleThumb,
|
||||
}
|
||||
}
|
||||
|
||||
// sign 生成七牛私有空间下载签名 URL。
|
||||
//
|
||||
// rawKey 是待访问的对象路径(可能含 -StyleNormal 后缀)。签名字符串为
|
||||
// "base_url/rawKey?e=<deadline>",对其做 HMAC-SHA1(SK) 后 urlsafe base64 得到 token。
|
||||
//
|
||||
// 无 AK/SK 或 BaseURL 时回落为公开 URL(兼容未配置七牛的开发态:公开空间会忽略 token 直接返回)。
|
||||
func (c *Composer) sign(rawKey string, ttl int64) string {
|
||||
if c.BaseURL == "" {
|
||||
return rawKey
|
||||
}
|
||||
if c.AK == "" || c.SK == "" {
|
||||
return c.BaseURL + "/" + rawKey
|
||||
}
|
||||
deadline := time.Now().Unix() + ttl
|
||||
toSign := fmt.Sprintf("%s/%s?e=%d", c.BaseURL, rawKey, deadline)
|
||||
mac := hmac.New(sha1.New, []byte(c.SK))
|
||||
mac.Write([]byte(toSign))
|
||||
enc := base64.URLEncoding.EncodeToString(mac.Sum(nil))
|
||||
return fmt.Sprintf("%s&token=%s:%s", toSign, c.AK, enc)
|
||||
}
|
||||
|
||||
// Compose 把存储值拼成可访问 URL。
|
||||
// Compose 把存储值拼成可访问 URL(详情/大图场景,统一走 StyleDisplay)。
|
||||
//
|
||||
// - stored 为空:返回 ""
|
||||
// - 本地兜底 /uploads/*:原样返回(前端 toAbs 拼 host)
|
||||
// - 已是完整 http(s) URL(历史遗留 / 外链图,如 vogue):hd 原样返回;
|
||||
// normal 尝试从 base_url 还原 key 再套样式签名,还原失败原样返回(外链无法套样式)
|
||||
// - 七牛 key:hd → 原图签名 URL(短过期);normal → 普通图(命名样式)签名 URL(长过期)
|
||||
//
|
||||
// 这样免费/未登录用户永远拿不到「不带签名的原图 URL」;VIP 拿到的是限时签名原图 URL。
|
||||
func (c *Composer) Compose(stored string, hd bool) string {
|
||||
// - 已是完整 http(s) URL(历史遗留 / 外链图):原样返回(无法套样式)
|
||||
// - key:base_url/key!style:high(或查询串模式)
|
||||
func (c *Composer) Compose(stored string) string {
|
||||
return c.compose(stored, c.StyleDisplay)
|
||||
}
|
||||
|
||||
// ComposeThumb 把存储值拼成「列表缩略图」URL(卡片 / 列表封面 / 图条等小尺寸场景)。
|
||||
// 与 Compose 共用同一套兜底逻辑,仅样式名换成 StyleThumb(未配置时回落 StyleDisplay)。
|
||||
// 注意:仅适用于同一 base_url 下的对象 key;外链与 /uploads 路径不做处理(无法套样式)。
|
||||
func (c *Composer) ComposeThumb(stored string) string {
|
||||
return c.compose(stored, c.StyleThumb)
|
||||
}
|
||||
|
||||
// compose 应用指定样式拼装 URL。
|
||||
func (c *Composer) compose(stored, style string) string {
|
||||
stored = strings.TrimSpace(stored)
|
||||
if stored == "" {
|
||||
return ""
|
||||
@ -112,22 +74,22 @@ func (c *Composer) Compose(stored string, hd bool) string {
|
||||
if strings.HasPrefix(stored, "/uploads/") {
|
||||
return stored
|
||||
}
|
||||
// 已是完整 URL(历史遗留 / 外链图)。
|
||||
// 已是完整 URL(历史遗留 / 外链图):原样返回(无法套样式)。
|
||||
if strings.HasPrefix(stored, "http://") || strings.HasPrefix(stored, "https://") {
|
||||
if hd {
|
||||
return stored
|
||||
}
|
||||
// 同域七牛 URL:剥掉 base_url 还原 key,再套普通样式签名。
|
||||
if c.BaseURL != "" {
|
||||
if key, ok := strings.CutPrefix(stored, c.BaseURL+"/"); ok {
|
||||
return c.sign(key+"-"+c.StyleNormal, c.SignTTLNormal)
|
||||
}
|
||||
}
|
||||
return stored // 外链图无法套样式,原样返回
|
||||
return stored
|
||||
}
|
||||
// 七牛 key:VIP 高清(-StyleVip 命名样式)签名(短过期);免费图(-StyleNormal 命名样式)签名(长过期)。
|
||||
if hd {
|
||||
return c.sign(stored+"-"+c.StyleVip, c.SignTTLHD)
|
||||
}
|
||||
return c.sign(stored+"-"+c.StyleNormal, c.SignTTLNormal)
|
||||
// 对象 key:按指定样式拼装(命名样式 key!style:xxx 或查询串 key?w=..&q=..)。
|
||||
return c.styledURL(stored, style)
|
||||
}
|
||||
|
||||
// styledURL 按样式名生成地址:命名样式(key!style:high)或查询串(key?w=..&q=..)两种模式。
|
||||
func (c *Composer) styledURL(key, style string) string {
|
||||
if strings.Contains(style, "=") {
|
||||
sep := "?"
|
||||
if strings.Contains(style, "?") {
|
||||
sep = "&"
|
||||
}
|
||||
return fmt.Sprintf("%s/%s%s%s", c.BaseURL, key, sep, style)
|
||||
}
|
||||
return fmt.Sprintf("%s/%s!style:%s", c.BaseURL, key, style)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user