缘起

一直用 golang 写后端。

通信行业经常需要查询特定手机号码的归属地的省份、城市信息。我一直用xluohome/phonedata这个库。

这个库提供了一个二进制文件 phone.dat 以及一个读取和查询该文件的 go package,文件只有 4M,查询速度很快。但也因为这个文件是二进制的,如果项目作者没有及时更新号码库,或者工作中需要添加或修改一些号码归属地特殊规则的时候会很麻烦。

最近遇到了修改 phone.dat 以增加支持的号段映射规则的需求。于是干脆 fork 了这个 repo ,然后参考当年修改游戏数据文件的思路,在 fork 出的项目里写了个解包-打包工具。这样就可以随意修改 phone.dat 了。

我的项目地址

https://github.com/seedjyh/phonedata

需要自行编译 cmd/phonedatatool/main.go。

使用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22

:: 解包:可以将二进制文件 phone.dat 解包到一个名为 abc 的目录。里面有三个文本文件。

D:\seedjyh\phonedata>phonedatatool.exe -unpack -i phone.dat -o tmp
Unpack completed.

:: 然后需要手动修改三个文本文件。修改完成后……

:: 打包:可以将目录 abc 里的文本文件打包成二进制文件 phone.2.dat
D:\seedjyh\phonedata>phonedatatool.exe -pack -i tmp -o phone.2.dat
Pack completed.

:: 查询号码:测试打包后的二进制文件是否正常。

D:\seedjyh\phonedata>phonedatatool.exe -query -i phone.2.dat -number 13336061916
PhoneNum:  13336061916
AreaZone:  0571
CardType:  中国电信
City:  杭州
ZipCode:  310000
Province:  浙江
Query completed.

phone.dat 文件格式

这个二进制文件分为四个部分,如下:

数据内容 长度(字节数)
版本号 4
索引区偏移量 4
记录区 大概 10K
索引区 最大,约 4M

版本号部分就是四个文本数字,比如 2108

索引区偏移量是一个 LittleEndian 的 32 位整数,描述索引区的起点。这是因为记录区的长度不定,需要有一个信息来划分记录区和索引区。

记录区是 370 个 \0 结尾的字符串,UTF-8 编码。每个字符串长这样: 浙江|杭州|310000|0571\0 ,用 | 分开,依次是省名、城市名、邮编、长途区号。

索引区有 454336 个条目,每个条目严格 9 字节。其中前四个字节是 LittleEndian 的 32 位整数,用于描述手机号码前 7 位;中间四个字节也是 LittleEndian 的 32 位整数,用于描述该手机号码对应的「记录区字符串」的文件偏移量。最后那个字节表示卡类型。例如索引区第一条的 9 个字节依次是

字节下标 0 1 2 3 4 5 6 7 8
值(十六进制) 20 D6 13 00 4E 1A 00 00 02

前四个字节 0x0013D620 对应十进制 1300000,即号码前 7 位。

中间四个字节 0x00001A4E 对应十进制 6734,表示这个号段的归属地信息从整个文件的第 6734 字节开始(直到 \0 结尾)。

最后那个字节 2 表示是联通号码。卡类型编码如下:

卡片类型码 卡片类型
1 中国移动
2 中国联通
3 中国电信
4 电信虚拟运营商
5 联通虚拟运营商
6 移动虚拟运营商