- handler 只保留请求绑定、权限检查与响应映射;业务校验顺序、状态码与文案逐字保留 - 新增 service.Error 语义错误,由 handler 统一映射 HTTP 响应 - session 875→361 行、widget 745→321 行、settings 540→73 行 - 自动分配/租户设置(工作时间、欢迎语)/消息校验/黑名单检查/联系方式提取随迁 service 包 - 相关单元测试随迁 service 包,go test ./... 全部通过
59 lines
1.5 KiB
Go
59 lines
1.5 KiB
Go
package service
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"kefu-cloud/server/internal/model"
|
|
)
|
|
|
|
// NormalizeDeviceKey 规范化设备指纹:优先客户端 device_id;否则用 UA 摘要哈希兜底。
|
|
func NormalizeDeviceKey(deviceID, userAgent string) string {
|
|
id := strings.TrimSpace(deviceID)
|
|
if id != "" {
|
|
if utf8.RuneCountInString(id) > 64 {
|
|
id = string([]rune(id)[:64])
|
|
}
|
|
return id
|
|
}
|
|
ua := strings.TrimSpace(userAgent)
|
|
if ua == "" {
|
|
return ""
|
|
}
|
|
sum := sha256.Sum256([]byte(strings.ToLower(ua)))
|
|
return hex.EncodeToString(sum[:])[:32]
|
|
}
|
|
|
|
// IsBlacklisted 检查租户下 IP/设备是否在有效黑名单中。
|
|
func IsBlacklisted(tenantID uint, kind, value string) (bool, *model.BlacklistEntry) {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" || (kind != "ip" && kind != "device") {
|
|
return false, nil
|
|
}
|
|
now := time.Now()
|
|
var entry model.BlacklistEntry
|
|
err := model.DB.
|
|
Where("tenant_id = ? AND kind = ? AND value = ?", tenantID, kind, value).
|
|
Where("expires_at IS NULL OR expires_at > ?", now).
|
|
Order("id desc").
|
|
First(&entry).Error
|
|
if err != nil {
|
|
return false, nil
|
|
}
|
|
return true, &entry
|
|
}
|
|
|
|
// CheckVisitorBlacklist 同时检查 IP 与设备。
|
|
func CheckVisitorBlacklist(tenantID uint, ip, deviceKey string) (blocked bool, kind string, reason string) {
|
|
if ok, e := IsBlacklisted(tenantID, "ip", ip); ok {
|
|
return true, "ip", e.Reason
|
|
}
|
|
if ok, e := IsBlacklisted(tenantID, "device", deviceKey); ok {
|
|
return true, "device", e.Reason
|
|
}
|
|
return false, "", ""
|
|
}
|