mirror of
https://github.com/TeaOSLab/EdgeCommon.git
synced 2025-11-03 12:20:27 +08:00
DNS自定义线路中增加CIDR、区域以及排除功能
This commit is contained in:
@@ -2,27 +2,71 @@ package configutils
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"math"
|
||||
"math/big"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IP2Long 将IP转换为整型
|
||||
// IPString2Long 将IP转换为整型
|
||||
// 注意IPv6没有顺序
|
||||
func IP2Long(ip string) uint64 {
|
||||
func IPString2Long(ip string) uint64 {
|
||||
if len(ip) == 0 {
|
||||
return 0
|
||||
}
|
||||
s := net.ParseIP(ip)
|
||||
if len(s) == 0 {
|
||||
var netIP = net.ParseIP(ip)
|
||||
if len(netIP) == 0 {
|
||||
return 0
|
||||
}
|
||||
return IP2Long(netIP)
|
||||
}
|
||||
|
||||
// IP2Long 将IP对象转换为整型
|
||||
func IP2Long(netIP net.IP) uint64 {
|
||||
if len(netIP) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
if strings.Contains(ip, ":") {
|
||||
return math.MaxUint32 + xxhash.Sum64(s)
|
||||
var b4 = netIP.To4()
|
||||
if b4 != nil {
|
||||
return uint64(binary.BigEndian.Uint32(b4.To4()))
|
||||
}
|
||||
return uint64(binary.BigEndian.Uint32(s.To4()))
|
||||
|
||||
var i = big.NewInt(0)
|
||||
i.SetBytes(netIP.To16())
|
||||
return i.Uint64()
|
||||
}
|
||||
|
||||
// IsIPv4 检查是否为IPv4
|
||||
func IsIPv4(netIP net.IP) bool {
|
||||
if len(netIP) == 0 {
|
||||
return false
|
||||
}
|
||||
return netIP.To4() != nil
|
||||
}
|
||||
|
||||
// IsIPv6 检查是否为IPv6
|
||||
func IsIPv6(netIP net.IP) bool {
|
||||
if len(netIP) == 0 {
|
||||
return false
|
||||
}
|
||||
return netIP.To4() == nil && netIP.To16() != nil
|
||||
}
|
||||
|
||||
// IPVersion 获取IP版本号
|
||||
func IPVersion(netIP net.IP) int {
|
||||
if len(netIP) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
if netIP.To4() != nil {
|
||||
return 4
|
||||
}
|
||||
|
||||
if netIP.To16() != nil {
|
||||
return 6
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// ParseCIDR 计算CIDR最大值
|
||||
|
||||
@@ -1,11 +1,41 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package configutils
|
||||
package configutils_test
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseCIDR(t *testing.T) {
|
||||
t.Log(ParseCIDR("192.168.1.1/32"))
|
||||
t.Log(ParseCIDR("192.168.1.1/24"))
|
||||
t.Log(ParseCIDR("192.168.1.1/16"))
|
||||
t.Log(configutils.ParseCIDR("192.168.1.1/32"))
|
||||
t.Log(configutils.ParseCIDR("192.168.1.1/24"))
|
||||
t.Log(configutils.ParseCIDR("192.168.1.1/16"))
|
||||
}
|
||||
|
||||
func TestIPString2Long(t *testing.T) {
|
||||
for _, ip := range []string{"127.0.0.1", "192.168.1.100", "::1", "fd00:6868:6868:0:10ac:d056:3bf6:7452", "fd00:6868:6868:0:10ac:d056:3bf6:7453", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "wrong ip"} {
|
||||
t.Log(fmt.Sprintf("%42s", ip), "=>", configutils.IPString2Long(ip))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPv4(t *testing.T) {
|
||||
t.Log(configutils.IsIPv4(net.ParseIP("192.168.1.100")))
|
||||
t.Log(configutils.IsIPv4(net.ParseIP("::1")))
|
||||
}
|
||||
|
||||
func TestIsIPv6(t *testing.T) {
|
||||
t.Log(configutils.IsIPv6(net.ParseIP("192.168.1.100")))
|
||||
t.Log(configutils.IsIPv6(net.ParseIP("::1")))
|
||||
}
|
||||
|
||||
func TestIPVersion(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
a.IsTrue(configutils.IPVersion(net.ParseIP("192.168.1.100")) == 4)
|
||||
a.IsTrue(configutils.IPVersion(net.ParseIP("1.2.3")) == 0)
|
||||
a.IsTrue(configutils.IPVersion(net.ParseIP("::1")) == 6)
|
||||
a.IsTrue(configutils.IPVersion(net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")) == 6)
|
||||
}
|
||||
|
||||
@@ -2,31 +2,107 @@
|
||||
|
||||
package dnsconfigs
|
||||
|
||||
import "github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net"
|
||||
)
|
||||
|
||||
type RouteRangeType = string
|
||||
|
||||
const (
|
||||
RouteRangeTypeIP RouteRangeType = "ipRange"
|
||||
RouteRangeTypeIP RouteRangeType = "ipRange" // IP范围
|
||||
RouteRangeTypeCIDR RouteRangeType = "cidr" // CIDR
|
||||
RouteRangeTypeRegion RouteRangeType = "region" // 区域
|
||||
)
|
||||
|
||||
func AllRouteRangeTypes() []*shared.Definition {
|
||||
return []*shared.Definition{
|
||||
{
|
||||
Name: "IP范围",
|
||||
Code: RouteRangeTypeIP,
|
||||
},
|
||||
{
|
||||
Name: "CIDR",
|
||||
Code: RouteRangeTypeCIDR,
|
||||
},
|
||||
{
|
||||
Name: "区域",
|
||||
Code: RouteRangeTypeRegion,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// RouteRegionResolver 解析IP接口
|
||||
type RouteRegionResolver interface {
|
||||
Resolve(ip net.IP) (countryId int64, provinceId int64, cityId int64, providerId int64)
|
||||
}
|
||||
|
||||
// RouteRangeInterface 线路范围接口
|
||||
type RouteRangeInterface interface {
|
||||
// Init 初始化
|
||||
Init() error
|
||||
Contains(ip uint64) bool
|
||||
|
||||
// Contains 判断是否包含
|
||||
Contains(ip net.IP) bool
|
||||
|
||||
// SetRegionResolver 设置IP解析接口
|
||||
SetRegionResolver(resolver RouteRegionResolver)
|
||||
|
||||
// IsExcluding 是否为排除
|
||||
IsExcluding() bool
|
||||
}
|
||||
|
||||
type BaseRouteRange struct {
|
||||
IsReverse bool `json:"isReverse"`
|
||||
|
||||
routeRegionResolver RouteRegionResolver
|
||||
}
|
||||
|
||||
func (this *BaseRouteRange) SetRegionResolver(resolver RouteRegionResolver) {
|
||||
this.routeRegionResolver = resolver
|
||||
}
|
||||
|
||||
func (this *BaseRouteRange) IsExcluding() bool {
|
||||
return this.IsReverse
|
||||
}
|
||||
|
||||
// RouteRangeIPRange IP范围配置
|
||||
// IPv4和IPv6不能混用
|
||||
type RouteRangeIPRange struct {
|
||||
BaseRouteRange
|
||||
|
||||
IPFrom string `json:"ipFrom"`
|
||||
IPTo string `json:"ipTo"`
|
||||
|
||||
ipFromLong uint64
|
||||
ipToLong uint64
|
||||
|
||||
ipVersion int // 4|6
|
||||
}
|
||||
|
||||
func (this *RouteRangeIPRange) Init() error {
|
||||
this.ipFromLong = configutils.IP2Long(this.IPFrom)
|
||||
this.ipToLong = configutils.IP2Long(this.IPTo)
|
||||
var ipFrom = net.ParseIP(this.IPFrom)
|
||||
var ipTo = net.ParseIP(this.IPTo)
|
||||
if ipFrom == nil {
|
||||
return errors.New("invalid ipFrom '" + this.IPFrom + "'")
|
||||
}
|
||||
if ipTo == nil {
|
||||
return errors.New("invalid ipTo '" + this.IPTo + "'")
|
||||
}
|
||||
|
||||
var ipFromVersion = configutils.IPVersion(ipFrom)
|
||||
var ipToVersion = configutils.IPVersion(ipTo)
|
||||
if ipFromVersion != ipToVersion {
|
||||
return errors.New("ipFrom and ipTo version are not same")
|
||||
}
|
||||
this.ipVersion = ipFromVersion
|
||||
|
||||
this.ipFromLong = configutils.IP2Long(ipFrom)
|
||||
this.ipToLong = configutils.IP2Long(ipTo)
|
||||
|
||||
if this.ipFromLong > this.ipToLong {
|
||||
this.ipFromLong, this.ipToLong = this.ipToLong, this.ipFromLong
|
||||
@@ -35,6 +111,152 @@ func (this *RouteRangeIPRange) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *RouteRangeIPRange) Contains(ip uint64) bool {
|
||||
return this.ipFromLong <= ip && this.ipToLong >= ip
|
||||
func (this *RouteRangeIPRange) Contains(netIP net.IP) bool {
|
||||
if len(netIP) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var version = configutils.IPVersion(netIP)
|
||||
if version != this.ipVersion {
|
||||
return false
|
||||
}
|
||||
|
||||
var ipLong = configutils.IP2Long(netIP)
|
||||
return ipLong >= this.ipFromLong && ipLong <= this.ipToLong
|
||||
}
|
||||
|
||||
// RouteRangeCIDR CIDR范围配置
|
||||
type RouteRangeCIDR struct {
|
||||
BaseRouteRange
|
||||
|
||||
CIDR string `json:"cidr"`
|
||||
|
||||
cidr *net.IPNet
|
||||
}
|
||||
|
||||
func (this *RouteRangeCIDR) Init() error {
|
||||
_, ipNet, err := net.ParseCIDR(this.CIDR)
|
||||
if err != nil {
|
||||
return errors.New("parse cidr failed: " + err.Error())
|
||||
}
|
||||
|
||||
this.cidr = ipNet
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *RouteRangeCIDR) Contains(netIP net.IP) bool {
|
||||
if netIP == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if this.cidr == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return this.cidr.Contains(netIP)
|
||||
}
|
||||
|
||||
// RouteRangeRegion 区域范围
|
||||
// country:ID, province:ID, city:ID, isp:ID
|
||||
type RouteRangeRegion struct {
|
||||
BaseRouteRange
|
||||
|
||||
Regions []*routeRegion `json:"regions"`
|
||||
}
|
||||
|
||||
func (this *RouteRangeRegion) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *RouteRangeRegion) Contains(netIP net.IP) bool {
|
||||
if this.routeRegionResolver == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(this.Regions) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
countryId, provinceId, cityId, providerId := this.routeRegionResolver.Resolve(netIP)
|
||||
if countryId <= 0 && provinceId <= 0 && cityId <= 0 && providerId <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, region := range this.Regions {
|
||||
if region.Id <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch region.Type {
|
||||
case "country":
|
||||
if region.Id == countryId {
|
||||
return true
|
||||
}
|
||||
case "province":
|
||||
if region.Id == provinceId {
|
||||
return true
|
||||
}
|
||||
case "city":
|
||||
if region.Id == cityId {
|
||||
return true
|
||||
}
|
||||
case "isp":
|
||||
if region.Id == providerId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type routeRegion struct {
|
||||
Type string `json:"type"` // country|province|city|isp
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// InitRangesFromJSON 从JSON中初始化线路范围
|
||||
func InitRangesFromJSON(rangesJSON []byte) (ranges []RouteRangeInterface, err error) {
|
||||
if len(rangesJSON) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var rangeMaps = []maps.Map{}
|
||||
err = json.Unmarshal(rangesJSON, &rangeMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, rangeMap := range rangeMaps {
|
||||
var rangeType = rangeMap.GetString("type")
|
||||
paramsJSON, err := json.Marshal(rangeMap.Get("params"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var r RouteRangeInterface
|
||||
|
||||
switch rangeType {
|
||||
case RouteRangeTypeIP:
|
||||
r = &RouteRangeIPRange{}
|
||||
case RouteRangeTypeCIDR:
|
||||
r = &RouteRangeCIDR{}
|
||||
case RouteRangeTypeRegion:
|
||||
r = &RouteRangeRegion{}
|
||||
default:
|
||||
return nil, errors.New("invalid route line type '" + rangeType + "'")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(paramsJSON, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = r.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ranges = append(ranges, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
100
pkg/dnsconfigs/route_ranges_test.go
Normal file
100
pkg/dnsconfigs/route_ranges_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package dnsconfigs
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRouteRangeIPRange_Contains(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
// ipv4
|
||||
{
|
||||
var r = &RouteRangeIPRange{
|
||||
IPFrom: "192.168.1.100",
|
||||
IPTo: "192.168.3.200",
|
||||
}
|
||||
err := r.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a.IsFalse(r.Contains(net.ParseIP("aaa")))
|
||||
a.IsTrue(r.Contains(net.ParseIP("192.168.1.200")))
|
||||
a.IsTrue(r.Contains(net.ParseIP("192.168.3.200")))
|
||||
a.IsFalse(r.Contains(net.ParseIP("192.168.4.1")))
|
||||
a.IsFalse(r.Contains(net.ParseIP("::1")))
|
||||
}
|
||||
|
||||
// ipv6
|
||||
{
|
||||
var prefix = "1:2:3:4:5:6"
|
||||
var r = &RouteRangeIPRange{
|
||||
IPFrom: prefix + ":1:8",
|
||||
IPTo: prefix + ":5:10",
|
||||
}
|
||||
err := r.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a.IsFalse(r.Contains(net.ParseIP("aaa")))
|
||||
a.IsTrue(r.Contains(net.ParseIP(prefix + ":3:4")))
|
||||
a.IsTrue(r.Contains(net.ParseIP(prefix + ":5:9")))
|
||||
a.IsTrue(r.Contains(net.ParseIP(prefix + ":5:10")))
|
||||
a.IsTrue(r.Contains(net.ParseIP(prefix + ":4:8")))
|
||||
a.IsFalse(r.Contains(net.ParseIP(prefix + ":5:11")))
|
||||
}
|
||||
|
||||
{
|
||||
var r = &RouteRangeCIDR{
|
||||
CIDR: "192.168.2.1/24",
|
||||
}
|
||||
err := r.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a.IsFalse(r.Contains(net.ParseIP("aaa")))
|
||||
a.IsTrue(r.Contains(net.ParseIP("192.168.2.1")))
|
||||
a.IsTrue(r.Contains(net.ParseIP("192.168.2.254")))
|
||||
a.IsTrue(r.Contains(net.ParseIP("192.168.2.100")))
|
||||
a.IsFalse(r.Contains(net.ParseIP("192.168.3.1")))
|
||||
a.IsFalse(r.Contains(net.ParseIP("192.168.1.1")))
|
||||
}
|
||||
|
||||
// reverse ipv4
|
||||
{
|
||||
var r = &RouteRangeIPRange{
|
||||
IPFrom: "192.168.1.100",
|
||||
IPTo: "192.168.3.200",
|
||||
IsReverse: true,
|
||||
}
|
||||
err := r.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a.IsFalse(r.Contains(net.ParseIP("aaa")))
|
||||
a.IsFalse(r.Contains(net.ParseIP("192.168.1.200")))
|
||||
a.IsFalse(r.Contains(net.ParseIP("192.168.3.200")))
|
||||
a.IsTrue(r.Contains(net.ParseIP("192.168.4.1")))
|
||||
}
|
||||
|
||||
// reverse cidr
|
||||
{
|
||||
var r = &RouteRangeCIDR{
|
||||
CIDR: "192.168.2.1/24",
|
||||
IsReverse: true,
|
||||
}
|
||||
err := r.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a.IsFalse(r.Contains(net.ParseIP("aaa")))
|
||||
a.IsFalse(r.Contains(net.ParseIP("192.168.2.1")))
|
||||
a.IsFalse(r.Contains(net.ParseIP("192.168.2.254")))
|
||||
a.IsFalse(r.Contains(net.ParseIP("192.168.2.100")))
|
||||
a.IsTrue(r.Contains(net.ParseIP("192.168.3.1")))
|
||||
a.IsTrue(r.Contains(net.ParseIP("192.168.1.1")))
|
||||
}
|
||||
}
|
||||
@@ -120,12 +120,12 @@ var AllDefaultChinaProvinceRoutes = []*Route{
|
||||
{
|
||||
Name: "河北省",
|
||||
Code: "china:province:heibei",
|
||||
AliasNames: []string{"河北省"},
|
||||
AliasNames: []string{"河北省", "河北"},
|
||||
},
|
||||
{
|
||||
Name: "山西省",
|
||||
Code: "china:province:shanxi",
|
||||
AliasNames: []string{"山西省"},
|
||||
AliasNames: []string{"山西省", "山西"},
|
||||
},
|
||||
{
|
||||
Name: "内蒙古自治区",
|
||||
@@ -135,17 +135,17 @@ var AllDefaultChinaProvinceRoutes = []*Route{
|
||||
{
|
||||
Name: "辽宁省",
|
||||
Code: "china:province:liaoning",
|
||||
AliasNames: []string{"辽宁省"},
|
||||
AliasNames: []string{"辽宁省", "辽宁"},
|
||||
},
|
||||
{
|
||||
Name: "吉林省",
|
||||
Code: "china:jilin",
|
||||
AliasNames: []string{"吉林省"},
|
||||
AliasNames: []string{"吉林省", "吉林"},
|
||||
},
|
||||
{
|
||||
Name: "黑龙江省",
|
||||
Code: "china:province:heilongjiang",
|
||||
AliasNames: []string{"黑龙江省"},
|
||||
AliasNames: []string{"黑龙江省", "黑龙江"},
|
||||
},
|
||||
{
|
||||
Name: "上海市",
|
||||
@@ -155,52 +155,52 @@ var AllDefaultChinaProvinceRoutes = []*Route{
|
||||
{
|
||||
Name: "江苏省",
|
||||
Code: "china:province:jiangsu",
|
||||
AliasNames: []string{"江苏省"},
|
||||
AliasNames: []string{"江苏省", "江苏"},
|
||||
},
|
||||
{
|
||||
Name: "浙江省",
|
||||
Code: "china:province:zhejiang",
|
||||
AliasNames: []string{"浙江省"},
|
||||
AliasNames: []string{"浙江省", "浙江"},
|
||||
},
|
||||
{
|
||||
Name: "安徽省",
|
||||
Code: "china:province:anhui",
|
||||
AliasNames: []string{"安徽省"},
|
||||
AliasNames: []string{"安徽省", "安徽"},
|
||||
},
|
||||
{
|
||||
Name: "福建省",
|
||||
Code: "china:province:fujian",
|
||||
AliasNames: []string{"福建省"},
|
||||
AliasNames: []string{"福建省", "福建"},
|
||||
},
|
||||
{
|
||||
Name: "江西省",
|
||||
Code: "china:province:jiangxi",
|
||||
AliasNames: []string{"江西省"},
|
||||
AliasNames: []string{"江西省", "江西"},
|
||||
},
|
||||
{
|
||||
Name: "山东省",
|
||||
Code: "china:province:shandong",
|
||||
AliasNames: []string{"山东省"},
|
||||
AliasNames: []string{"山东省", "山东"},
|
||||
},
|
||||
{
|
||||
Name: "河南省",
|
||||
Code: "china:province:henan",
|
||||
AliasNames: []string{"河南省"},
|
||||
AliasNames: []string{"河南省", "河南"},
|
||||
},
|
||||
{
|
||||
Name: "湖北省",
|
||||
Code: "china:province:hubei",
|
||||
AliasNames: []string{"湖北省"},
|
||||
AliasNames: []string{"湖北省", "湖北"},
|
||||
},
|
||||
{
|
||||
Name: "湖南省",
|
||||
Code: "china:province:hunan",
|
||||
AliasNames: []string{"湖南省"},
|
||||
AliasNames: []string{"湖南省", "湖南"},
|
||||
},
|
||||
{
|
||||
Name: "广东省",
|
||||
Code: "china:province:guangdong",
|
||||
AliasNames: []string{"广东省"},
|
||||
AliasNames: []string{"广东省", "广东"},
|
||||
},
|
||||
{
|
||||
Name: "广西壮族自治区",
|
||||
@@ -210,7 +210,7 @@ var AllDefaultChinaProvinceRoutes = []*Route{
|
||||
{
|
||||
Name: "海南省",
|
||||
Code: "china:province:hainan",
|
||||
AliasNames: []string{"海南省"},
|
||||
AliasNames: []string{"海南省", "海南"},
|
||||
},
|
||||
{
|
||||
Name: "重庆市",
|
||||
@@ -220,17 +220,17 @@ var AllDefaultChinaProvinceRoutes = []*Route{
|
||||
{
|
||||
Name: "四川省",
|
||||
Code: "china:province:sichuan",
|
||||
AliasNames: []string{"四川省"},
|
||||
AliasNames: []string{"四川省", "四川"},
|
||||
},
|
||||
{
|
||||
Name: "贵州省",
|
||||
Code: "china:province:guizhou",
|
||||
AliasNames: []string{"贵州省"},
|
||||
AliasNames: []string{"贵州省", "贵州"},
|
||||
},
|
||||
{
|
||||
Name: "云南省",
|
||||
Code: "china:province:yunnan",
|
||||
AliasNames: []string{"云南省"},
|
||||
AliasNames: []string{"云南省", "云南"},
|
||||
},
|
||||
{
|
||||
Name: "西藏自治区",
|
||||
@@ -240,17 +240,17 @@ var AllDefaultChinaProvinceRoutes = []*Route{
|
||||
{
|
||||
Name: "陕西省",
|
||||
Code: "china:province:shaanxi",
|
||||
AliasNames: []string{"陕西省"},
|
||||
AliasNames: []string{"陕西省", "陕西"},
|
||||
},
|
||||
{
|
||||
Name: "甘肃省",
|
||||
Code: "china:province:gansu",
|
||||
AliasNames: []string{"甘肃省"},
|
||||
AliasNames: []string{"甘肃省", "甘肃"},
|
||||
},
|
||||
{
|
||||
Name: "青海省",
|
||||
Code: "china:province:qinghai",
|
||||
AliasNames: []string{"青海省"},
|
||||
AliasNames: []string{"青海省", "青海"},
|
||||
},
|
||||
{
|
||||
Name: "宁夏回族自治区",
|
||||
@@ -275,7 +275,7 @@ var AllDefaultChinaProvinceRoutes = []*Route{
|
||||
{
|
||||
Name: "台湾省",
|
||||
Code: "china:province:tw",
|
||||
AliasNames: []string{"台湾省"},
|
||||
AliasNames: []string{"台湾省", "台湾"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user