同步包路径与路由,去掉 SPA 构建步骤,对齐 Gitea 式 LICENSE,并更新规格/规则与占位 SSR。 Co-authored-by: Cursor <cursoragent@cursor.com>
66 lines
1.7 KiB
Go
66 lines
1.7 KiB
Go
package services
|
||
|
||
import (
|
||
"regexp"
|
||
"strings"
|
||
|
||
"git.iioio.com/freefire/jiang13-forum/models"
|
||
)
|
||
|
||
const maxMentionsPerContent = 10
|
||
|
||
// 与前端 highlightMentions 字符集对齐(字母数字下划线中文,兼容历史 -)
|
||
// Go RE2 不支持 JS 的 \uXXXX,需用 \x{HHHH}
|
||
var mentionPattern = regexp.MustCompile(`@([0-9A-Za-z_\x{4e00}-\x{9fa5}-]+)`)
|
||
|
||
// ExtractMentionNames 从纯文本提取 @提及名(去重、保序)
|
||
func ExtractMentionNames(text string) []string {
|
||
matches := mentionPattern.FindAllStringSubmatch(text, -1)
|
||
if len(matches) == 0 {
|
||
return nil
|
||
}
|
||
seen := make(map[string]struct{}, len(matches))
|
||
out := make([]string, 0, len(matches))
|
||
for _, m := range matches {
|
||
name := strings.TrimSpace(m[1])
|
||
if name == "" {
|
||
continue
|
||
}
|
||
key := strings.ToLower(name)
|
||
if _, ok := seen[key]; ok {
|
||
continue
|
||
}
|
||
seen[key] = struct{}{}
|
||
out = append(out, name)
|
||
if len(out) >= maxMentionsPerContent {
|
||
break
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// ResolveMentionUserIDs 将提及名解析为用户 ID(优先 username,其次 nickname;排除 excludeUserID)
|
||
func ResolveMentionUserIDs(names []string, excludeUserID uint) []uint {
|
||
if len(names) == 0 {
|
||
return nil
|
||
}
|
||
ids := make([]uint, 0, len(names))
|
||
seen := make(map[uint]struct{}, len(names))
|
||
for _, name := range names {
|
||
var u models.User
|
||
err := models.DB.Select("id").Where("username = ?", name).First(&u).Error
|
||
if err != nil {
|
||
err = models.DB.Select("id").Where("nickname = ?", name).First(&u).Error
|
||
}
|
||
if err != nil || u.ID == 0 || u.ID == excludeUserID {
|
||
continue
|
||
}
|
||
if _, ok := seen[u.ID]; ok {
|
||
continue
|
||
}
|
||
seen[u.ID] = struct{}{}
|
||
ids = append(ids, u.ID)
|
||
}
|
||
return ids
|
||
}
|