From 0d090476dc92a283ed0eac80a715157323c59948 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Mon, 12 Jan 2026 19:29:01 +0800 Subject: [PATCH] :art: https://github.com/siyuan-note/siyuan/pull/16714#issuecomment-3738100178 Signed-off-by: Daniel <845765@qq.com> --- kernel/util/file.go | 105 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 92 insertions(+), 13 deletions(-) diff --git a/kernel/util/file.go b/kernel/util/file.go index aebeab4f9..4188ac39f 100644 --- a/kernel/util/file.go +++ b/kernel/util/file.go @@ -17,7 +17,6 @@ package util import ( - "bytes" "io" "io/fs" "mime" @@ -247,23 +246,103 @@ func FilterUploadFileName(name string) string { } func TruncateLenFileName(name string) (ret string) { - // 插入资源文件时文件名长度最大限制 189 字节 https://github.com/siyuan-note/siyuan/issues/7099 + // 插入资源文件时文件名长度最大限制 189 字节 + const maxTotal = 189 + ext := filepath.Ext(name) - var byteCount int - truncated := false - buf := bytes.Buffer{} - for _, r := range name { - byteCount += utf8.RuneLen(r) - if 189-len(ext) < byteCount { - truncated = true + base := name[:len(name)-len(ext)] + + id := ast.NewNodeID() + // 预留 "-" + id + "-" 的字节数 + reservedForID := len(id) + 2 + + available := maxTotal - len(ext) - reservedForID + if available <= 0 { + // 空间不足以保留 base,退回到 "-id-" + ext(若仍超限则只返回扩展名) + candidate := "-" + id + "-" + ext + if len(candidate) <= maxTotal { + return candidate + } + if len(ext) <= maxTotal { + return ext + } + return ext + } + + // 若 base 整体在可用字节内,直接返回原名 + if len(base) <= available { + return name + } + + runes := []rune(base) + n := len(runes) + if n == 0 { + candidate := "-" + id + "-" + ext + if len(candidate) <= maxTotal { + return candidate + } + if len(ext) <= maxTotal { + return ext + } + return ext + } + + // 计算每个 rune 的字节长度 + runeBytes := make([]int, n) + for i, r := range runes { + runeBytes[i] = utf8.RuneLen(r) + } + + // 从后往前尝试最多保留 3 个 rune,找到最大的 tailSize 使得 tailBytes <= available + tailSize := 0 + tailBytes := 0 + for t := 1; t <= 3 && t <= n; t++ { + b := 0 + for i := n - t; i < n; i++ { + b += runeBytes[i] + } + if b <= available { + tailSize = t + tailBytes = b + } else { break } - buf.WriteRune(r) } - if truncated { - buf.WriteString(ext) + + if tailSize == 0 { + // 连一个尾部 rune 都放不下,回退到 "-id-" 或扩展名 + candidate := "-" + id + "-" + ext + if len(candidate) <= maxTotal { + return candidate + } + if len(ext) <= maxTotal { + return ext + } + return ext } - ret = buf.String() + + // 可用于前缀的字节 + availFront := available - tailBytes + // 前缀最多不能覆盖到尾部 + maxFrontCount := n - tailSize + + var frontRunes []rune + frontBytes := 0 + for i := 0; i < maxFrontCount; i++ { + if frontBytes+runeBytes[i] > availFront { + break + } + frontBytes += runeBytes[i] + frontRunes = append(frontRunes, runes[i]) + } + + // 若前缀与尾部合并能覆盖全部 runes,则说明无需截断(理论上前面已判断过,但再验证一次) + if len(frontRunes) == maxFrontCount { + return name + } + + tailRunes := runes[n-tailSize:] + ret = string(frontRunes) + string(tailRunes) + "-" + id + "-" + ext return }