package service import ( "net/url" "strings" ) // 来源分类常量 const ( RefKindDirect = "direct" RefKindInternal = "internal" RefKindSearch = "search" RefKindSocial = "social" RefKindExternal = "external" ) // searchHosts 搜索引擎域名(按「等于或以 .子域结尾」匹配) var searchHosts = []string{"google.com", "bing.com", "baidu.com", "sogou.com", "so.com", "sm.cn", "duckduckgo.com", "yandex.com", "yandex.ru", "yahoo.com"} // socialHosts 社交/社区域名特征 var socialHosts = []string{ "weibo.com", "weibo.cn", "zhihu.com", "zhuanlan.zhihu.com", "twitter.com", "x.com", "t.me", "telegram.me", "douyin.com", "xiaohongshu.com", "bilibili.com", "b23.tv", "qq.com", "tieba.baidu.com", "v2ex.com", "juejin.cn", "csdn.net", "segmentfault.com", "github.com", "gitee.com", "reddit.com", "facebook.com", "instagram.com", "discord.com", } // hostMatches 按「等于或以 .子域结尾」匹配 host 与域名列表 func hostMatches(host string, list []string) bool { for _, d := range list { if host == d || strings.HasSuffix(host, "."+d) { return true } } return false } // ClassifyReferrer 解析并归类 Referer:返回 (来源 host, 来源类别)。 // 空 → direct;本站 → internal;搜索引擎 → search;社交 → social;其余 → external;解析失败按 direct。 func ClassifyReferrer(raw, siteHost string) (string, string) { raw = strings.TrimSpace(raw) if raw == "" { return "", RefKindDirect } u, err := url.Parse(raw) if err != nil || u.Host == "" { return "", RefKindDirect } host := strings.ToLower(u.Host) if i := strings.LastIndex(host, ":"); i >= 0 && !strings.Contains(host, "]") { host = host[:i] // 去端口 } if siteHost != "" && (host == siteHost || siteHost == host) { return host, RefKindInternal } if hostMatches(host, searchHosts) { return host, RefKindSearch } if hostMatches(host, socialHosts) { return host, RefKindSocial } return host, RefKindExternal }