308 lines
9.1 KiB
Go
308 lines
9.1 KiB
Go
package service
|
||
|
||
import (
|
||
"strconv"
|
||
"strings"
|
||
)
|
||
|
||
// countryZh ISO2 → 中文国名(展示用;存储仍用 ISO)
|
||
var countryZh = map[string]string{
|
||
"CN": "中国", "HK": "中国香港", "MO": "中国澳门", "TW": "中国台湾",
|
||
"US": "美国", "JP": "日本", "KR": "韩国", "SG": "新加坡",
|
||
"GB": "英国", "DE": "德国", "FR": "法国", "RU": "俄罗斯",
|
||
"CA": "加拿大", "AU": "澳大利亚", "IN": "印度", "TH": "泰国",
|
||
"VN": "越南", "MY": "马来西亚", "ID": "印度尼西亚", "PH": "菲律宾",
|
||
"NL": "荷兰", "IT": "意大利", "ES": "西班牙", "BR": "巴西",
|
||
"MX": "墨西哥", "SE": "瑞典", "CH": "瑞士", "PL": "波兰",
|
||
"TR": "土耳其", "SA": "沙特阿拉伯", "AE": "阿联酋", "NZ": "新西兰",
|
||
"IE": "爱尔兰", "BE": "比利时", "AT": "奥地利", "NO": "挪威",
|
||
"DK": "丹麦", "FI": "芬兰", "PT": "葡萄牙", "CZ": "捷克",
|
||
"UA": "乌克兰", "IL": "以色列", "ZA": "南非", "AR": "阿根廷",
|
||
}
|
||
|
||
type cnRegionMeta struct {
|
||
Name string
|
||
ISO string
|
||
}
|
||
|
||
// cnRegionMap 中国省级英文名/别名 → 中文名 + 地图 ISO
|
||
var cnRegionMap = map[string]cnRegionMeta{
|
||
"beijing": {Name: "北京", ISO: "BJ"},
|
||
"peking": {Name: "北京", ISO: "BJ"},
|
||
"tianjin": {Name: "天津", ISO: "TJ"},
|
||
"shanghai": {Name: "上海", ISO: "SH"},
|
||
"chongqing": {Name: "重庆", ISO: "CQ"},
|
||
"chungking": {Name: "重庆", ISO: "CQ"},
|
||
"hebei": {Name: "河北", ISO: "HE"},
|
||
"shanxi": {Name: "山西", ISO: "SX"},
|
||
"liaoning": {Name: "辽宁", ISO: "LN"},
|
||
"jilin": {Name: "吉林", ISO: "JL"},
|
||
"heilongjiang": {Name: "黑龙江", ISO: "HL"},
|
||
"jiangsu": {Name: "江苏", ISO: "JS"},
|
||
"zhejiang": {Name: "浙江", ISO: "ZJ"},
|
||
"anhui": {Name: "安徽", ISO: "AH"},
|
||
"fujian": {Name: "福建", ISO: "FJ"},
|
||
"jiangxi": {Name: "江西", ISO: "JX"},
|
||
"shandong": {Name: "山东", ISO: "SD"},
|
||
"henan": {Name: "河南", ISO: "HA"},
|
||
"hubei": {Name: "湖北", ISO: "HB"},
|
||
"hunan": {Name: "湖南", ISO: "HN"},
|
||
"guangdong": {Name: "广东", ISO: "GD"},
|
||
"guangxi": {Name: "广西", ISO: "GX"},
|
||
"hainan": {Name: "海南", ISO: "HI"},
|
||
"sichuan": {Name: "四川", ISO: "SC"},
|
||
"guizhou": {Name: "贵州", ISO: "GZ"},
|
||
"yunnan": {Name: "云南", ISO: "YN"},
|
||
"xizang": {Name: "西藏", ISO: "XZ"},
|
||
"tibet": {Name: "西藏", ISO: "XZ"},
|
||
"shaanxi": {Name: "陕西", ISO: "SN"},
|
||
"shaanxi province": {Name: "陕西", ISO: "SN"},
|
||
"gansu": {Name: "甘肃", ISO: "GS"},
|
||
"qinghai": {Name: "青海", ISO: "QH"},
|
||
"ningxia": {Name: "宁夏", ISO: "NX"},
|
||
"xinjiang": {Name: "新疆", ISO: "XJ"},
|
||
"xinjiang uygur": {Name: "新疆", ISO: "XJ"},
|
||
"inner mongolia": {Name: "内蒙古", ISO: "NM"},
|
||
"nei mongol": {Name: "内蒙古", ISO: "NM"},
|
||
"guangxi zhuangzu": {Name: "广西", ISO: "GX"},
|
||
"ningxia huizu": {Name: "宁夏", ISO: "NX"},
|
||
"hong kong": {Name: "香港", ISO: "HK"},
|
||
"macau": {Name: "澳门", ISO: "MO"},
|
||
"macao": {Name: "澳门", ISO: "MO"},
|
||
"taiwan": {Name: "台湾", ISO: "TW"},
|
||
// 台湾各县市(IP2Location 常直接给县市名作为 region)
|
||
"changhua": {Name: "台湾", ISO: "TW"},
|
||
"chiayi": {Name: "台湾", ISO: "TW"},
|
||
"hsinchu": {Name: "台湾", ISO: "TW"},
|
||
"hualien": {Name: "台湾", ISO: "TW"},
|
||
"kaohsiung": {Name: "台湾", ISO: "TW"},
|
||
"keelung": {Name: "台湾", ISO: "TW"},
|
||
"kinmen": {Name: "台湾", ISO: "TW"},
|
||
"lienchiang": {Name: "台湾", ISO: "TW"},
|
||
"miaoli": {Name: "台湾", ISO: "TW"},
|
||
"nantou": {Name: "台湾", ISO: "TW"},
|
||
"new taipei": {Name: "台湾", ISO: "TW"},
|
||
"penghu": {Name: "台湾", ISO: "TW"},
|
||
"pingtung": {Name: "台湾", ISO: "TW"},
|
||
"taichung": {Name: "台湾", ISO: "TW"},
|
||
"tainan": {Name: "台湾", ISO: "TW"},
|
||
"taipei": {Name: "台湾", ISO: "TW"},
|
||
"taitung": {Name: "台湾", ISO: "TW"},
|
||
"taoyuan": {Name: "台湾", ISO: "TW"},
|
||
"yilan": {Name: "台湾", ISO: "TW"},
|
||
"yunlin": {Name: "台湾", ISO: "TW"},
|
||
}
|
||
|
||
// cityZh 定义见 geo_zh_cities.go(LITE 中国城市全表 + 同音复合键)
|
||
|
||
// asnOrgZh 常见 ASN / AS 组织名 → 中文运营商
|
||
var asnOrgZh = map[uint]string{
|
||
4134: "中国电信",
|
||
4812: "中国电信",
|
||
4837: "中国联通",
|
||
4808: "中国联通",
|
||
9808: "中国移动",
|
||
56040: "中国移动",
|
||
56041: "中国移动",
|
||
56042: "中国移动",
|
||
4538: "教育网",
|
||
23910: "教育网",
|
||
58539: "教育网",
|
||
7497: "教育网",
|
||
}
|
||
|
||
var asnOrgKeywordZh = []struct {
|
||
kw string
|
||
zh string
|
||
}{
|
||
{"chinanet", "中国电信"},
|
||
{"china telecom", "中国电信"},
|
||
{"chinatelecom", "中国电信"},
|
||
{"unicom", "中国联通"},
|
||
{"china unicom", "中国联通"},
|
||
{"mobile", "中国移动"},
|
||
{"china mobile", "中国移动"},
|
||
{"cernet", "教育网"},
|
||
{"education", "教育网"},
|
||
}
|
||
|
||
// CountryLabelZh ISO2 → 中文国名(未知则返回原码)
|
||
func CountryLabelZh(iso string) string {
|
||
iso = strings.ToUpper(strings.TrimSpace(iso))
|
||
if iso == "" {
|
||
return ""
|
||
}
|
||
if v, ok := countryZh[iso]; ok {
|
||
return v
|
||
}
|
||
return iso
|
||
}
|
||
|
||
// ApplyGeoZh 就地补充中文省/市/运营商;country 保持 ISO2
|
||
func ApplyGeoZh(g *GeoInfo) {
|
||
if g == nil {
|
||
return
|
||
}
|
||
g.Country = normalizeCountryCode(g.Country)
|
||
if g.Country == "CN" || g.Country == "HK" || g.Country == "MO" || g.Country == "TW" {
|
||
mapCNRegionCity(g)
|
||
}
|
||
if g.ASN > 0 {
|
||
if zh, ok := asnOrgZh[g.ASN]; ok {
|
||
g.ASOrg = zh
|
||
} else if g.ASOrg != "" {
|
||
g.ASOrg = mapASOrgZh(g.ASOrg)
|
||
}
|
||
} else if g.ASOrg != "" {
|
||
g.ASOrg = mapASOrgZh(g.ASOrg)
|
||
}
|
||
g.Region = truncateRunes(strings.TrimSpace(g.Region), 64)
|
||
g.City = truncateRunes(strings.TrimSpace(g.City), 64)
|
||
g.ASOrg = truncateRunes(strings.TrimSpace(g.ASOrg), 128)
|
||
}
|
||
|
||
func mapCNRegionCity(g *GeoInfo) {
|
||
if g.RegionISO == "" {
|
||
if meta, ok := lookupCNRegion(g.Region); ok {
|
||
g.Region = meta.Name
|
||
g.RegionISO = meta.ISO
|
||
}
|
||
} else if g.Region != "" {
|
||
if meta, ok := lookupCNRegion(g.Region); ok {
|
||
if g.RegionISO == "" {
|
||
g.RegionISO = meta.ISO
|
||
}
|
||
g.Region = meta.Name
|
||
}
|
||
} else if g.RegionISO != "" {
|
||
for _, meta := range cnRegionMap {
|
||
if meta.ISO == strings.ToUpper(g.RegionISO) {
|
||
g.Region = meta.Name
|
||
break
|
||
}
|
||
}
|
||
}
|
||
if g.City != "" {
|
||
if zh, ok := lookupCityZh(g.RegionISO, g.Region, g.City); ok {
|
||
g.City = zh
|
||
}
|
||
}
|
||
// 直辖市:BIN 有时只给城市不给省
|
||
if g.Region == "" && g.City != "" {
|
||
switch g.City {
|
||
case "北京", "上海", "天津", "重庆":
|
||
g.Region = g.City
|
||
if meta, ok := cnRegionMap[strings.ToLower(g.City)]; ok {
|
||
g.RegionISO = meta.ISO
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func lookupCNRegion(name string) (cnRegionMeta, bool) {
|
||
key := strings.ToLower(strings.TrimSpace(name))
|
||
if key == "" {
|
||
return cnRegionMeta{}, false
|
||
}
|
||
if meta, ok := cnRegionMap[key]; ok {
|
||
return meta, true
|
||
}
|
||
// 去掉常见后缀再试
|
||
for _, suffix := range []string{" province", " sheng", " autonomous region", " municipality", " zhuangzu", " huizu", " uygur"} {
|
||
if strings.HasSuffix(key, suffix) {
|
||
if meta, ok := cnRegionMap[strings.TrimSuffix(key, suffix)]; ok {
|
||
return meta, true
|
||
}
|
||
}
|
||
}
|
||
return cnRegionMeta{}, false
|
||
}
|
||
|
||
// lookupCityZh 将 IP2Location 英文城市名映射为中文。
|
||
// 同音城市优先用「省ISO|城市」复合键;并尝试去掉 city/shi 后缀与撇号变体。
|
||
func lookupCityZh(regionISO, region, name string) (string, bool) {
|
||
key := normalizeCityKey(name)
|
||
if key == "" {
|
||
return "", false
|
||
}
|
||
iso := strings.ToLower(strings.TrimSpace(regionISO))
|
||
if iso == "" {
|
||
if meta, ok := lookupCNRegion(region); ok {
|
||
iso = strings.ToLower(meta.ISO)
|
||
}
|
||
}
|
||
candidates := cityKeyCandidates(key)
|
||
if iso != "" {
|
||
for _, c := range candidates {
|
||
if zh, ok := cityZh[iso+"|"+c]; ok {
|
||
return zh, true
|
||
}
|
||
}
|
||
}
|
||
for _, c := range candidates {
|
||
if zh, ok := cityZh[c]; ok {
|
||
return zh, true
|
||
}
|
||
}
|
||
return "", false
|
||
}
|
||
|
||
func normalizeCityKey(name string) string {
|
||
key := strings.ToLower(strings.TrimSpace(name))
|
||
if key == "" || key == "-" {
|
||
return ""
|
||
}
|
||
return key
|
||
}
|
||
|
||
func cityKeyCandidates(key string) []string {
|
||
seen := map[string]struct{}{}
|
||
var out []string
|
||
add := func(s string) {
|
||
s = strings.TrimSpace(s)
|
||
if s == "" {
|
||
return
|
||
}
|
||
if _, ok := seen[s]; ok {
|
||
return
|
||
}
|
||
seen[s] = struct{}{}
|
||
out = append(out, s)
|
||
}
|
||
add(key)
|
||
add(strings.ReplaceAll(key, "'", ""))
|
||
for _, suffix := range []string{" city", " shi"} {
|
||
if strings.HasSuffix(key, suffix) {
|
||
base := strings.TrimSuffix(key, suffix)
|
||
add(base)
|
||
add(strings.ReplaceAll(base, "'", ""))
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
func mapASOrgZh(org string) string {
|
||
l := strings.ToLower(strings.TrimSpace(org))
|
||
if l == "" {
|
||
return org
|
||
}
|
||
for _, rule := range asnOrgKeywordZh {
|
||
if strings.Contains(l, rule.kw) {
|
||
return rule.zh
|
||
}
|
||
}
|
||
// "AS4134 Chinanet" 等形式
|
||
if strings.HasPrefix(l, "as") {
|
||
num := strings.TrimPrefix(l, "as")
|
||
if i := strings.IndexByte(num, ' '); i >= 0 {
|
||
num = num[:i]
|
||
}
|
||
if n, err := strconv.ParseUint(num, 10, 32); err == nil {
|
||
if zh, ok := asnOrgZh[uint(n)]; ok {
|
||
return zh
|
||
}
|
||
}
|
||
}
|
||
return org
|
||
}
|