支持消息 Markdown 与欢迎语富文本
聊天/离线提示使用 Markdown 安全渲染;欢迎语改为 TipTap 富文本(颜色字号),DOMPurify 白名单清洗后展示。
This commit is contained in:
@@ -24,10 +24,34 @@ const (
|
||||
defaultAgentNickname = "在线客服"
|
||||
defaultTimezone = "Asia/Shanghai"
|
||||
|
||||
maxWelcomeSegments = 10
|
||||
maxWelcomeTextLen = 500
|
||||
maxWelcomeSegments = 10
|
||||
maxWelcomeTextLen = 1000 // 纯文字上限(HTML 标签不计)
|
||||
maxWelcomeHTMLLen = 8000 // 富文本原始 HTML 上限
|
||||
)
|
||||
|
||||
// stripHTMLTags 粗暴去标签,用于字数与摘要(欢迎语 HTML)。
|
||||
func stripHTMLTags(s string) string {
|
||||
var b strings.Builder
|
||||
inTag := false
|
||||
for _, r := range s {
|
||||
switch {
|
||||
case r == '<':
|
||||
inTag = true
|
||||
case r == '>':
|
||||
inTag = false
|
||||
case !inTag:
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
// 简单实体
|
||||
out := b.String()
|
||||
out = strings.ReplaceAll(out, " ", " ")
|
||||
out = strings.ReplaceAll(out, "&", "&")
|
||||
out = strings.ReplaceAll(out, "<", "<")
|
||||
out = strings.ReplaceAll(out, ">", ">")
|
||||
return strings.TrimSpace(out)
|
||||
}
|
||||
|
||||
// WelcomeSegment 单段欢迎语:文本或图片(content 为正文或对象存储 URL)。
|
||||
type WelcomeSegment struct {
|
||||
Type string `json:"type"` // text | image
|
||||
@@ -47,9 +71,21 @@ func defaultWelcomeMessagesJSON() string {
|
||||
|
||||
func firstWelcomeText(segs []WelcomeSegment) string {
|
||||
for _, s := range segs {
|
||||
if s.Type == "text" && strings.TrimSpace(s.Content) != "" {
|
||||
return s.Content
|
||||
if s.Type != "text" {
|
||||
continue
|
||||
}
|
||||
plain := stripHTMLTags(s.Content)
|
||||
if plain == "" {
|
||||
plain = strings.TrimSpace(s.Content)
|
||||
}
|
||||
if plain == "" {
|
||||
continue
|
||||
}
|
||||
// welcome_message 列 size:500
|
||||
if utf8.RuneCountInString(plain) > 500 {
|
||||
return string([]rune(plain)[:500])
|
||||
}
|
||||
return plain
|
||||
}
|
||||
return defaultWelcomeMessage
|
||||
}
|
||||
@@ -96,11 +132,20 @@ func normalizeWelcomeSegments(input []WelcomeSegment) ([]WelcomeSegment, string,
|
||||
c := strings.TrimSpace(seg.Content)
|
||||
switch t {
|
||||
case "text":
|
||||
if c == "" {
|
||||
// 允许 HTML 富文本;禁止明显脚本
|
||||
lower := strings.ToLower(c)
|
||||
if strings.Contains(lower, "<script") || strings.Contains(lower, "javascript:") {
|
||||
return nil, "", fmt.Errorf("第 %d 段欢迎语包含不安全内容", i+1)
|
||||
}
|
||||
plain := stripHTMLTags(c)
|
||||
if plain == "" {
|
||||
return nil, "", fmt.Errorf("第 %d 段文本不能为空", i+1)
|
||||
}
|
||||
if utf8.RuneCountInString(c) > maxWelcomeTextLen {
|
||||
return nil, "", fmt.Errorf("第 %d 段文本不能超过 %d 字", i+1, maxWelcomeTextLen)
|
||||
if utf8.RuneCountInString(plain) > maxWelcomeTextLen {
|
||||
return nil, "", fmt.Errorf("第 %d 段文字不能超过 %d 字", i+1, maxWelcomeTextLen)
|
||||
}
|
||||
if utf8.RuneCountInString(c) > maxWelcomeHTMLLen {
|
||||
return nil, "", fmt.Errorf("第 %d 段内容过长", i+1)
|
||||
}
|
||||
out = append(out, WelcomeSegment{Type: "text", Content: c})
|
||||
case "image":
|
||||
|
||||
Reference in New Issue
Block a user