Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
package officialwebsiteser
import (
"regexp"
"strings"
"91porn-server/common/stderr"
"github.com/mozillazg/go-pinyin"
)
var (
seoSlugValidRe = regexp.MustCompile(`^[a-z]+$`) // 纯小写英文字母
seoSlugStripRe = regexp.MustCompile(`[^a-z]`) // 非 a-z 一律剔除
)
// seoSlugMaxLen 自动生成 slug 的最大长度(按拼音音节边界截断,避免长标题生成超长 slug)
const seoSlugMaxLen = 20
// GenerateSeoSlug 从标题生成纯小写字母 slug:中文转拼音、英文保留、其余字符剔除,
// 并按音节边界累加到 seoSlugMaxLen 为止(不从音节中间截断)。
// 例: "优雅Summer 2023" -> "youyasummer"
func GenerateSeoSlug(title string) string {
if title == "" {
return ""
}
args := pinyin.NewArgs()
// 非汉字(英文/数字/符号)原样返回,后续再统一剔除非 a-z
args.Fallback = func(r rune, a pinyin.Args) []string {
return []string{string(r)}
}
rows := pinyin.Pinyin(title, args)
var b strings.Builder
for _, row := range rows {
if len(row) == 0 {
continue
}
// 单个音节先剔除非 a-z、转小写
syl := seoSlugStripRe.ReplaceAllString(strings.ToLower(row[0]), "")
if syl == "" {
continue
}
// 到长度上限则在音节边界停止,避免 slug 过长
if b.Len()+len(syl) > seoSlugMaxLen {
break
}
b.WriteString(syl)
}
return b.String()
}
// ValidateSeoSlug 校验 slug 格式:非空且纯小写英文字母。
func ValidateSeoSlug(s string) bool {
return seoSlugValidRe.MatchString(s)
}
// ResolveSeoSlug 统一处理 seoSlug
// - 手填(provided 非空):校验纯小写字母格式,并查重(冲突报错)
// - 留空:由 title 生成拼音雏形,冲突时追加后缀去重
//
// exists 由各表提供(查各自表是否已占用该 slug)。
func ResolveSeoSlug(provided, title string, exists func(slug string) (bool, error)) (string, error) {
if provided != "" {
if !ValidateSeoSlug(provided) {
return "", stderr.ErrParamError
}
used, err := exists(provided)
if err != nil {
return "", err
}
if used {
return "", stderr.ErrParamError
}
return provided, nil
}
return UniqueSeoSlug(GenerateSeoSlug(title), exists)
}
// UniqueSeoSlug 基于 base 生成同表唯一的 slugexists 判断某 slug 是否已被占用。
// 冲突时依次追加纯小写字母后缀 a、b…z、aa、ab…zz、aaa… 直到命中未占用值;
// base 为空则返回空(走稀疏索引,不参与唯一约束)。
func UniqueSeoSlug(base string, exists func(slug string) (bool, error)) (string, error) {
if base == "" {
return "", nil
}
// n=0 先试 base 原值,之后依次追加 a、b…z、aa… 后缀(双射 26 进制递增)。
// 表内记录有限且各候选互不相同,必能在有限次内命中未占用值。
for n := 0; ; n++ {
candidate := base
if n > 0 {
candidate += seoSlugSuffix(n)
}
used, err := exists(candidate)
if err != nil {
return "", err
}
if !used {
return candidate, nil
}
}
}
// seoSlugSuffix 返回第 n 个纯小写字母后缀(n>=1),按双射 26 进制递增:
// 1->a 2->b … 26->z 27->aa 28->ab … 52->az 53->ba … 702->zz 703->aaa
func seoSlugSuffix(n int) string {
var buf []byte
for n > 0 {
n--
buf = append([]byte{byte('a' + n%26)}, buf...)
n /= 26
}
return string(buf)
}