Total Complexity | 59 |
Total Lines | 433 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like QqWryService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use QqWryService, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
31 | class QqWryService extends Service |
||
32 | { |
||
33 | /** |
||
34 | * QQWry.Dat文件指针 |
||
35 | * @var resource |
||
36 | */ |
||
37 | private $fp; |
||
38 | |||
39 | /** |
||
40 | * 第一条IP记录的偏移地址 |
||
41 | * |
||
42 | * @var int |
||
43 | */ |
||
44 | private $firstIp; |
||
45 | |||
46 | /** |
||
47 | * 最后一条IP记录的偏移地址 |
||
48 | * @var int |
||
49 | */ |
||
50 | private $lastIp; |
||
51 | |||
52 | /** |
||
53 | * IP记录的总条数(不包含版本信息记录) |
||
54 | * @var int |
||
55 | */ |
||
56 | private $totalIp; |
||
57 | |||
58 | /** |
||
59 | * 不存在 |
||
60 | * @var string |
||
61 | */ |
||
62 | private $unknown = '未知'; |
||
63 | |||
64 | /** |
||
65 | * IP数据库文件存放位置 |
||
66 | * @var mixed |
||
67 | */ |
||
68 | private $ipPath = ''; |
||
69 | |||
70 | /** |
||
71 | * 构造函数,打开 QQWry.Dat 文件并初始化类中的信息 |
||
72 | * @param App $app |
||
73 | * @throws DtaException |
||
74 | */ |
||
75 | public function __construct(App $app) |
||
76 | { |
||
77 | $this->ipPath = config('dtapp.ip_path', ''); |
||
78 | if (empty($this->ipPath)) { |
||
79 | throw new DtaException('请检查配置文件是否配置了IP数据库文件存放位置'); |
||
80 | } |
||
81 | $this->fp = 0; |
||
|
|||
82 | if (($this->fp = fopen($this->ipPath . 'qqwry.dat', 'rb')) !== false) { |
||
83 | $this->firstIp = $this->getLong(); |
||
84 | $this->lastIp = $this->getLong(); |
||
85 | $this->totalIp = ($this->lastIp - $this->firstIp) / 7; |
||
86 | } |
||
87 | parent::__construct($app); |
||
88 | } |
||
89 | |||
90 | /** |
||
91 | * 设置未知的返回字段 |
||
92 | * @param string $unknown |
||
93 | * @return QqWryService |
||
94 | */ |
||
95 | public function setUnknown(string $unknown = '未知') |
||
96 | { |
||
97 | $this->unknown = $unknown; |
||
98 | return $this; |
||
99 | } |
||
100 | |||
101 | /** |
||
102 | * 获取省信息 |
||
103 | * @param string $ip |
||
104 | * @return mixed |
||
105 | * @throws DtaException |
||
106 | */ |
||
107 | public function getProvince(string $ip = '') |
||
108 | { |
||
109 | return $this->getLocation($ip)['state']; |
||
110 | } |
||
111 | |||
112 | /** |
||
113 | * 获取城市信息 |
||
114 | * @param string $ip |
||
115 | * @return mixed |
||
116 | * @throws DtaException |
||
117 | */ |
||
118 | public function getCity(string $ip = '') |
||
119 | { |
||
120 | return $this->getLocation($ip)['city']; |
||
121 | } |
||
122 | |||
123 | /** |
||
124 | * 获取地区信息 |
||
125 | * @param string $ip |
||
126 | * @return mixed |
||
127 | * @throws DtaException |
||
128 | */ |
||
129 | public function getArea(string $ip = '') |
||
130 | { |
||
131 | return $this->getLocation($ip)['area']; |
||
132 | } |
||
133 | |||
134 | /** |
||
135 | * 获取运营商信息 |
||
136 | * @param string $ip |
||
137 | * @return mixed |
||
138 | * @throws DtaException |
||
139 | */ |
||
140 | public function getExtend(string $ip = '') |
||
141 | { |
||
142 | return $this->getLocation($ip)['extend']; |
||
143 | } |
||
144 | |||
145 | /** |
||
146 | * 根据所给 IP 地址或域名返回所在地区信息 |
||
147 | * @param string $ip |
||
148 | * @return mixed|null |
||
149 | * @throws DtaException |
||
150 | */ |
||
151 | public function getLocation(string $ip = '') |
||
152 | { |
||
153 | if (empty($ip)) { |
||
154 | $ip = get_ip(); |
||
155 | } |
||
156 | if (strpos($ip, 'http://') === 0) { |
||
157 | $ip = substr($ip, 7); |
||
158 | $ip = gethostbyname($ip); |
||
159 | } |
||
160 | static $locationData = []; |
||
161 | if (!isset($locationData[$ip])) { |
||
162 | if (!$this->fp) { |
||
163 | // 如果数据文件没有被正确打开,则直接返回错误 |
||
164 | throw new DtaException('数据库文件不存在!'); |
||
165 | } |
||
166 | $location['ip'] = $ip; // 将输入的域名转化为IP地址 |
||
167 | $ip = $this->packIp($location['ip']); // 将输入的IP地址转化为可比较的IP地址 |
||
168 | // 不合法的IP地址会被转化为255.255.255.255 |
||
169 | // 对分搜索 |
||
170 | $l = 0; // 搜索的下边界 |
||
171 | $u = $this->totalIp; // 搜索的上边界 |
||
172 | $findip = $this->lastIp; // 如果没有找到就返回最后一条IP记录(QQWry.Dat的版本信息) |
||
173 | while ($l <= $u) { // 当上边界小于下边界时,查找失败 |
||
174 | $i = floor(($l + $u) / 2); // 计算近似中间记录 |
||
175 | fseek($this->fp, $this->firstIp + $i * 7); |
||
176 | $beginip = strrev(fread($this->fp, 4)); // 获取中间记录的开始IP地址 |
||
177 | // strrev函数在这里的作用是将little-endian的压缩IP地址转化为big-endian的格式 |
||
178 | // 以便用于比较,后面相同。 |
||
179 | if ($ip < $beginip) { // 用户的IP小于中间记录的开始IP地址时 |
||
180 | $u = $i - 1; // 将搜索的上边界修改为中间记录减一 |
||
181 | } else { |
||
182 | fseek($this->fp, $this->getLong3()); |
||
183 | $endip = strrev(fread($this->fp, 4)); // 获取中间记录的结束IP地址 |
||
184 | if ($ip > $endip) { // 用户的IP大于中间记录的结束IP地址时 |
||
185 | $l = $i + 1; // 将搜索的下边界修改为中间记录加一 |
||
186 | } else { // 用户的IP在中间记录的IP范围内时 |
||
187 | $findip = $this->firstIp + $i * 7; |
||
188 | break; // 则表示找到结果,退出循环 |
||
189 | } |
||
190 | } |
||
191 | } |
||
192 | //获取查找到的IP地理位置信息 |
||
193 | fseek($this->fp, $findip); |
||
194 | $location['beginip'] = long2ip($this->getLong()); // 用户IP所在范围的开始地址 |
||
195 | $offset = $this->getLong3(); |
||
196 | fseek($this->fp, $offset); |
||
197 | $location['endip'] = long2ip($this->getLong()); // 用户IP所在范围的结束地址 |
||
198 | $byte = fread($this->fp, 1); // 标志字节 |
||
199 | switch (ord($byte)) { |
||
200 | case 1: // 标志字节为1,表示国家和区域信息都被同时重定向 |
||
201 | $countryOffset = $this->getLong3(); // 重定向地址 |
||
202 | fseek($this->fp, $countryOffset); |
||
203 | $byte = fread($this->fp, 1); // 标志字节 |
||
204 | switch (ord($byte)) { |
||
205 | case 2: // 标志字节为2,表示国家信息又被重定向 |
||
206 | fseek($this->fp, $this->getLong3()); |
||
207 | $location['all'] = $this->getString(); |
||
208 | fseek($this->fp, $countryOffset + 4); |
||
209 | $location['extend'] = $this->getExtendString(); |
||
210 | break; |
||
211 | default: // 否则,表示国家信息没有被重定向 |
||
212 | $location['all'] = $this->getString($byte); |
||
213 | $location['extend'] = $this->getExtendString(); |
||
214 | break; |
||
215 | } |
||
216 | break; |
||
217 | case 2: // 标志字节为2,表示国家信息被重定向 |
||
218 | fseek($this->fp, $this->getLong3()); |
||
219 | $location['all'] = $this->getString(); |
||
220 | fseek($this->fp, $offset + 8); |
||
221 | $location['extend'] = $this->getExtendString(); |
||
222 | break; |
||
223 | default: // 否则,表示国家信息没有被重定向 |
||
224 | $location['all'] = $this->getString($byte); |
||
225 | $location['extend'] = $this->getExtendString(); |
||
226 | break; |
||
227 | } |
||
228 | // CZ88.NET表示没有有效信息 |
||
229 | if (trim($location['all']) === 'CZ88.NET') { |
||
230 | $location['all'] = $this->unknown; |
||
231 | } |
||
232 | if (trim($location['extend']) === 'CZ88.NET') { |
||
233 | $location['extend'] = ''; |
||
234 | } |
||
235 | $location['all'] = iconv("gb2312", "UTF-8//IGNORE", $location['all']); |
||
236 | $location['extend'] = iconv("gb2312", "UTF-8//IGNORE", $location['extend']); |
||
237 | $location['extend'] = $location['extend'] ?? ''; |
||
238 | $parseData = $this->parseLocation($location['all']); |
||
239 | $location['state'] = $parseData[0]; |
||
240 | $location['city'] = $parseData[1]; |
||
241 | $location['area'] = $parseData[2]; |
||
242 | |||
243 | // 全部地址 |
||
244 | $res['location_all'] = $location['all']; |
||
245 | // 运营商 |
||
246 | $res['isp']['name'] = $location['extend']; |
||
247 | // IP |
||
248 | $res['ip']['ipv4'] = $location['ip']; |
||
249 | $res['ip']['beginip'] = $location['beginip']; |
||
250 | $res['ip']['endip'] = $location['endip']; |
||
251 | $res['ip']['trueip'] = ip2long($location['ip']); |
||
252 | $res['ip']['ipv6'] = $this->getNormalizedIP($location['ip']); |
||
253 | $getAdCodeLatLng = $this->getNameAdCodeLatLng($location['state'], $location['city'], $location['area']); |
||
254 | // 省份 |
||
255 | $res['province'] = $getAdCodeLatLng['province']; |
||
256 | // 城市 |
||
257 | $res['city'] = $getAdCodeLatLng['city']; |
||
258 | // 地区 |
||
259 | $res['district'] = $getAdCodeLatLng['district']; |
||
260 | $locationData[$ip] = $location; |
||
261 | } |
||
262 | return $res; |
||
263 | } |
||
264 | |||
265 | /** |
||
266 | * 解析省市区县 |
||
267 | * @param $location |
||
268 | * @return array |
||
269 | * @example '江苏省苏州市吴江市' , '江苏省苏州市吴中区' , '江苏省苏州市昆山市' , '黑龙江省鸡西市' , '广西桂林市' , '陕西省西安市户县' , '河南省开封市通许县' ,'内蒙古呼伦贝尔市海拉尔区','甘肃省白银市平川区','孟加拉','上海市' , '北京市朝阳区' ,'美国' ,'香港' , 俄罗斯' ,'IANA' |
||
270 | */ |
||
271 | private function parseLocation($location) |
||
272 | { |
||
273 | $state = $city = $area = $this->unknown; |
||
274 | if (preg_match('/^(.+省)?(新疆|内蒙古|宁夏|西藏|广西|香港|澳门)?(.+市)?(.+市)?(.+(县|区))?/', $location, $preg)) { |
||
275 | if (count($preg) == 4) { //匹配 "浙江省杭州市" |
||
276 | $state = $preg[1] ? $preg[1] : ($preg[2] ? $preg[2] : $preg[3]); |
||
277 | $city = $preg[3]; |
||
278 | } else if (count($preg) == 7) { //匹配 "浙江省杭州市江干区" |
||
279 | $state = $preg[1] ? $preg[1] : ($preg[2] ? $preg[2] : $preg[3]); |
||
280 | $city = $preg[3]; |
||
281 | $area = $preg[5]; |
||
282 | } else if (count($preg) == 3) { //匹配 "香港" |
||
283 | $state = $preg[1] ? $preg[1] : $preg[2]; |
||
284 | $city = $state; |
||
285 | } else if (count($preg) == 2) { //匹配 "浙江省" |
||
286 | $state = $preg[1] ? $preg[1] : $this->unknown; |
||
287 | } |
||
288 | } |
||
289 | return [$state, $city, $area]; |
||
290 | } |
||
291 | |||
292 | /** |
||
293 | * 返回读取的长整型数 |
||
294 | * @return mixed |
||
295 | */ |
||
296 | private function getLong() |
||
297 | { |
||
298 | //将读取的little-endian编码的4个字节转化为长整型数 |
||
299 | $result = unpack('Vlong', fread($this->fp, 4)); |
||
300 | return $result['long']; |
||
301 | } |
||
302 | |||
303 | /** |
||
304 | * 返回读取的3个字节的长整型数 |
||
305 | * @return mixed |
||
306 | */ |
||
307 | private function getLong3() |
||
308 | { |
||
309 | //将读取的little-endian编码的3个字节转化为长整型数 |
||
310 | $result = unpack('Vlong', fread($this->fp, 3) . chr(0)); |
||
311 | return $result['long']; |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * 返回压缩后可进行比较的IP地址 |
||
316 | * @param $ip |
||
317 | * @return false|string |
||
318 | */ |
||
319 | private function packIp($ip) |
||
324 | } |
||
325 | |||
326 | /** |
||
327 | * 返回读取的字符串 |
||
328 | * |
||
329 | * @access private |
||
330 | * @param string $data |
||
331 | * @return string |
||
332 | */ |
||
333 | private function getString($data = "") |
||
334 | { |
||
335 | $char = fread($this->fp, 1); |
||
336 | while (ord($char) > 0) { // 字符串按照C格式保存,以\0结束 |
||
337 | $data .= $char; // 将读取的字符连接到给定字符串之后 |
||
338 | $char = fread($this->fp, 1); |
||
339 | } |
||
340 | return $data; |
||
341 | } |
||
342 | |||
343 | /** |
||
344 | * 返回地区信息 |
||
345 | * @return string |
||
346 | */ |
||
347 | private function getExtendString() |
||
348 | { |
||
349 | $byte = fread($this->fp, 1); // 标志字节 |
||
350 | switch (ord($byte)) { |
||
351 | case 0: // 没有区域信息 |
||
352 | $area = ""; |
||
353 | break; |
||
354 | case 1: |
||
355 | case 2: // 标志字节为1或2,表示区域信息被重定向 |
||
356 | fseek($this->fp, $this->getLong3()); |
||
357 | $area = $this->getString(); |
||
358 | break; |
||
359 | default: // 否则,表示区域信息没有被重定向 |
||
360 | $area = $this->getString($byte); |
||
361 | break; |
||
362 | } |
||
363 | return $area; |
||
364 | } |
||
365 | |||
366 | /** |
||
367 | * 析构函数,用于在页面执行结束后自动关闭打开的文件。 |
||
368 | */ |
||
369 | public function __destruct() |
||
370 | { |
||
371 | if ($this->fp) { |
||
372 | fclose($this->fp); |
||
373 | } |
||
374 | $this->fp = 0; |
||
375 | } |
||
376 | |||
377 | /** |
||
378 | * ipv4转换ipv6 |
||
379 | * @param $ip |
||
380 | * @return bool|false|string|string[]|null |
||
381 | */ |
||
382 | protected function getNormalizedIP($ip) |
||
399 | } |
||
400 | |||
401 | /** |
||
402 | * 解析CODE |
||
403 | * @param $province_name |
||
404 | * @param $city_name |
||
405 | * @param $district_name |
||
406 | * @return array |
||
407 | */ |
||
408 | private function getNameAdCodeLatLng($province_name, $city_name, $district_name) |
||
464 | ]; |
||
465 | } |
||
466 | } |
||
467 |
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..