Files

98 lines
2.9 KiB
Go

package common
import (
"bytes"
"strconv"
"strings"
)
// 内联媒体(data URL)处理工具。
//
// 多模态请求体里的图片/音频以 `data:<mime>;base64,<载荷>` 的形式内联在 JSON 中,
// 单个载荷动辄数 MB。这类载荷对三条链路都是纯负担:
// - 预扣估算按字节折算会把一张图估成几十万 token,直接冻穿租户余额
// - 审计落库会把整段 base64 灌进 aud_request_logs
// - 敏感词匹配白扫几 MB,还可能在 base64 串里误命中
//
// 这里提供统一的扫描与剥离能力,三处共用,避免各写一份。
// inlineMediaMarker data URL 中 base64 载荷的起始标记。
const inlineMediaMarker = ";base64,"
// inlineMediaMinPayload 认定为内联媒体的最小载荷长度。
// 真实的图片/音频 base64 远大于此,设下限是为了不误伤正文里恰好出现该标记的短文本。
const inlineMediaMinPayload = 64
// ScanInlineMediaBytes 扫描内容中的 data URL base64 载荷,返回载荷总字节数与媒体个数。
//
// 纯字节扫描不做 JSON 反序列化——调用点在请求热路径上,且请求体可能有数 MB。
// base64 字母表不含 `"` 与 `\`,故以二者作为 JSON 字符串内载荷的终止符。
func ScanInlineMediaBytes(b []byte) (payloadBytes, count int) {
marker := []byte(inlineMediaMarker)
rest := b
for {
idx := bytes.Index(rest, marker)
if idx < 0 {
return
}
rest = rest[idx+len(marker):]
end := bytes.IndexAny(rest, "\"\\")
if end < 0 {
end = len(rest)
}
if end >= inlineMediaMinPayload {
payloadBytes += end
count++
}
rest = rest[end:]
}
}
// StripInlineMediaBytes 是 StripInlineMedia 的字节切片版本。
// 未命中内联媒体时原样返回入参切片,不产生任何拷贝——请求热路径上的绝大多数
// 请求体是纯文本,这条快路径让它们零开销通过。
func StripInlineMediaBytes(b []byte) []byte {
if !bytes.Contains(b, []byte(inlineMediaMarker)) {
return b
}
return []byte(StripInlineMedia(string(b)))
}
// StripInlineMedia 把 base64 载荷替换成长度占位,保留 mime 前缀便于排查:
//
// data:image/png;base64,iVBORw0KGgo... → data:image/png;base64,<省略 2097152 字节>
//
// 未命中任何内联媒体时原样返回,不产生额外分配。
func StripInlineMedia(s string) string {
if !strings.Contains(s, inlineMediaMarker) {
return s
}
var sb strings.Builder
rest := s
for {
idx := strings.Index(rest, inlineMediaMarker)
if idx < 0 {
sb.WriteString(rest)
break
}
head := idx + len(inlineMediaMarker)
sb.WriteString(rest[:head])
rest = rest[head:]
end := strings.IndexAny(rest, "\"\\")
if end < 0 {
end = len(rest)
}
if end >= inlineMediaMinPayload {
sb.WriteString("<省略 ")
sb.WriteString(strconv.Itoa(end))
sb.WriteString(" 字节>")
} else {
sb.WriteString(rest[:end])
}
rest = rest[end:]
}
return sb.String()
}