分类目录归档:PHP

必应今日美图的获取展示

前段时间学习了解了一个新的PHP Web框架,laravel “简洁,优雅,现代化”是这个框架的关键词,还在继续探索。

就想着用这个来实现一点好玩的事情。

必应搜索每天都更新一张图,而且图片质量高,不限制访问意味着不需要保留到本地,现在需要做的只是将每天的图片信息收集起来按日期的维度存储起来即可。

那么到哪里去获取bing每天的图片呢,最好的地方当然是bing官网了,直接查看源代码可以清楚的看到,图片地址是在id=”bgDiv” 的div 上面

这样就能找到我们想要的图片了,但是还没结束,我们还需要存储到图片名称,地理位置信息,作者,图片简介等。

查看源代码,可以看到详细信息在 id=”lap_w”的 div 中

需要注意的在源代码中不能直接搜索到有关图片信息的文字,因为此处是ajax加载而来的。那既然是ajax那就找找吧,查看xhr返回请求,除了js 文件,一些数据状态。也就只有这一个比较像了

打开就看到了所有我们想要的了。

http://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&nc=1477557984674&pid=hp&video=1

本来的思路是按源代码进行页面解析获取出信息,但是这样找到了官方API,那就不必费神解析去了。直接用吧。

当然了,也不必这么麻烦,直接在google 搜索必应 美图 API就能得到这个地址了。

那么我们就看看参数,能否获取到更多的历史数据

format=js&idx=0&n=1&nc=1477557984674&pid=hp&video=1

format: 数据返回格式,json

idx: 开始的天数,-1 代表今天,0 代表昨天,1代表前天

n: 表示从 idx 开始往前的天数,经测试目前最多只能达到9天;但idx 的变动可以获取到对应天数之前的数据;idx类似于 mysql 查询的offset数据,而n 则就像是pagesize

nc: 请求时的时间戳和毫秒数

pid: 不知

video: 1 是否显示视频地址,很多时候必应首页的是一个<video>视频播放,但也是有一张背景大图的

比如说这个

目前bing最大可查看api 地址是9天,也就是 idx=9即可。

现在每天的详细信息(城市,国家,经纬度)等信息还有一个接口提供

http://cn.bing.com/cnhp/coverstory/

但是不知道参数,也只能显示每天数据。

那接下来就很简单了,存储数据即可。最后的成品是:http://bing.plmeizi.com/ 展示了获取到的数据图片。

那么我要查询bing 所有的图片在哪去看呢。很简单还是官方:http://www.bing.com/gallery/ 比起我的那不知道好到哪里去了,非常详细,如果你想找某天或者某个城市的图还是去官方地址查找

最后感兴趣的可以参考下我的表结构。

 

CREATE TABLE `bi_picture` (
`pi_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`pi_copyr_crc32` bigint(12) DEFAULT '0' COMMENT '简介版权信息crc32',
`pi_startdate` int(8) NOT NULL DEFAULT '0' COMMENT '展示开始时间',
`pi_enddate` int(8) NOT NULL DEFAULT '0' COMMENT '展示开始时间',
`pi_url` varchar(255) NOT NULL COMMENT '必应主图',
`pi_copyright` varchar(255) NOT NULL COMMENT '主图信息',
`pi_copyrightlink` varchar(255) NOT NULL COMMENT '主图必应搜索url',
`pi_hash` varchar(32) DEFAULT '' COMMENT 'hash信息',
`pi_title` varchar(255) DEFAULT '' COMMENT '图片名称,每天信息接口',
`pi_addr` varchar(255) DEFAULT '' COMMENT '图片地理位置',
`pi_para1` varchar(500) DEFAULT '' COMMENT '图片描述1',
`pi_para2` varchar(500) DEFAULT '' COMMENT '图片描述2',
`pi_provider` varchar(32) DEFAULT '' COMMENT '图片提供者',
`pi_thumb` varchar(255) DEFAULT '' COMMENT '缩略图地址imageUrl',
`pi_searchpic` varchar(255) DEFAULT '' COMMENT '必应搜索图片地址imageUrl',
`pi_country` varchar(32) DEFAULT '' COMMENT '国家',
`pi_city` varchar(32) DEFAULT '' COMMENT '城市',
`pi_longitude` decimal(10,6) NOT NULL DEFAULT '0.000000' COMMENT '经度',
`pi_latitude` decimal(10,6) NOT NULL DEFAULT '0.000000' COMMENT '纬度',
`last_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`pi_id`),
KEY `pi_copyr_crc32` (`pi_copyr_crc32`),
KEY `pi_enddate` (`pi_enddate`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='必应今日美图信息表';

 

opcache PHP新的字节码缓存扩展

字节码缓存组件 Zend Optimizer+ 现在更改名字为 Zend opcache了。且在php 5.5版本后,会集成到php的官方组件中,也就没有必要安装其他的APC,eAccelerator等了。。

APC与Opcache都是字节码缓存也就是,PHP在被编译的时候,首先会把php代码转换为字节码,字节码然后被执行。

php文件第二次执行时,同样还是会重新转换为字节码,但是很多时候,文件内容几乎是一样的,比如静态HTML文件,生成后内容许久都不会改变,用户访问请求直接由服务器读取响应给客户端浏览器。都不用经过PHP进行解析构建了。

内存中的字节码数据,可以直接缓存进行二次编译。这样程序就会快一些,cpu的消耗也少了。

详细介绍看这两篇

新一代 PHP 加速插件 Zend Opcache

php的 zend opcache VS apc 性能比较

我主要是用来测试了一下phpcmsV9.5.4 的默认index.php主页,没有数据内容,但有sql查询操作

测试是Apache2.2.6 32Bit 服务器,PHP 5.4.26 ts,mysql 5.6.16 64Bit

ab 版本 This is ApacheBench, Version 2.3 <$Revision: 655654 $>

请求数:1000

并发数:10

没有加载opcache测试

第一次测试

p

吞吐率 38.46 rps,耗时26.001 s,每个请求耗时260.015 ms

第二次测试

p2

吞吐率有所提高 40.73 rps,耗时 24.554 s,每个请求耗时245.544 ms

加载opcache进行测试

opcache版本 php_opcache-7.0.3-5.4-ts-vc9-x86

opcache配置信息


opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1

第一次配置opcache好后 cache 状态

cstat

 

cache hits 命中数 1

cache misses 未命中数 1

使用内存225.2Kb

opcache第一次测试

p_c1

吞吐率提升到49.11rps,总耗时20.361 s,每个请求耗时下降到203.612 ms

在phpinfo中可查看opcache的命中数量情况

cstat_c

命中数量已达到43957,内存使用2.31Mb。

opcache第二次测试

p_c2

吞吐率提升到47.87rps,总耗时20.888 s,每个请求耗时下降到208.882 ms

opcache之后的两次压测数据变化不大,每个请求耗时在1ms差距内,吞吐率也在1~2 rps

但与之前未启用opcache时,总耗时少了4 ~ 6 s,每个请求耗时少了40 ~ 60 ms。吞吐率也提升了 8%。

这都是在一行代码未改的情况下有效性能提升。

 

php文件被编译为字节码进行内存缓存,如果在生成环境中,代码和内容变量都是比较固定的

缓存起来的内容就可以高速的返回,用户会得到较快的响应。

但是在本地开发是,建议不要开启opcache,否则就得不到最新的值

例如:


<?php
 header('Content-type:text/html; charset=utf-8');
 
 $str = 'abc';
 echo $str; // 输出abc
?>

赋予$str 一个新的值



<?php
 header('Content-type:text/html; charset=utf-8');
 
 $str = 'new abc';
 echo $str; // 输出的还是 abc
?>

这是因为$str 已经被编译为字节码了,再次访问时,内存里面还是可以找到对应的缓存,就没有进行重新编译,返回的值也就还是之前的值 abc

不过,opcache有一个参数可以用来设置缓存时间长度

[bash]

opcache.force_restart_timeout (default "180")

[/bash]

默认时间为180秒,还是建议本地不用开启opcache

 

注意:官方给了一个Note,如果opcache要与xdebug同时加载,那么opcache需要在xdebug之前进行加载。

但是我本地测试了一下,xdebug先加载,再加载opcache,也正常启动了,xdebug,opcache都加载成功,opcache缓存也正常。

不过还是按照官方的建议来安装加载,否则可能会出现奇怪的错误吧。

 

官方Github https://github.com/zendtech/ZendOptimizerPlus

 

PHP opcache pecl http://pecl.php.net/package/ZendOpcache

Window opcache dll扩展 http://windows.php.net/downloads/pecl/releases/opcache/7.0.3/

解决win7本地php链接mysql延迟缓慢问题

最近在新机器上面,要配置一个WAMP环境,因为个人比较喜欢都是自己搭建环境。

在配置完成后,打开一个项目页面,明显的感觉到延迟和顿感。但是不是很明白哪里有问题,也继续就这样用着。

某天,项目数据库需要连接到同事的机器上面去访问,配置后,发现访问飞速,代码还是那些代码。就因为数据库换了,所以变得很快。

所以我确信是PHP连接mysql数据库的问题了,没有瞎猜和细想。直接google了,就看到这样一篇文章

问题就是:由于localhost 没有指定 127.0.0.1 ip地址,请求localhost时直接返回的 ipv6格式地址 ::1 。然后去尝试监听 ::1 上面的mysql 3306端口,但是mysql 的监听端口是在ipv4 127.0.0.1 上。

在地址 ::1 找寻 3306 端口没有找到,才会在 ipv4 的地址上面去寻找。所有就有了这样的一个延迟。

按上面文章的说法,一看hosts文件果然如此。

localhost 确实被注释着,也没有 localhost 的地址指向。


# 127.0.0.1 localhost
# ::1 localhost

本地ping测试
[修改之前]
得到ipv6 ::1 地址


[修改之后]
得到ipv4 127.0.0.1 地址

再次切换到本地数据库,重新请求瞬间秒开页面了。

中国天气网城市代码

找了很多天气城市代码,有些都不准确,在这里http://aigudao.net/?post=105看到一个比较准确的。

转换成数组放这里了


<?php

// By leil.plmeizi.com 2014-4-23
$citycode = array (
 101010100 => '北京市',
 101010200 => '海淀区',
 101010300 => '朝阳区',
 101010400 => '顺义区',
 101010500 => '怀柔区',
 101010600 => '通州区',
 101010700 => '昌平区',
 101010800 => '延庆县',
 101010900 => '丰台区',
 101011000 => '石景山区',
 101011100 => '大兴区',
 101011200 => '房山区',
 101011300 => '密云县',
 101011400 => '门头沟区',
 101011500 => '平谷区',
 101020100 => '上海市',
 101020200 => '闵行区',
 101020300 => '宝山区',
 101020500 => '嘉定区',
 101020700 => '金山区',
 101020800 => '青浦区',
 101020900 => '松江区',
 101021000 => '奉贤区',
 101021100 => '崇明县',
 101021200 => '徐家汇区',
 101021300 => '浦东区',
 101020600 => '浦东南汇',
 101030100 => '天津市',
 101030200 => '武清区',
 101030300 => '宝坻区',
 101030400 => '东丽区',
 101030500 => '西青区',
 101030600 => '北辰区',
 101030700 => '宁河县',
 101030800 => '汉沽区',
 101030900 => '静海县',
 101031000 => '津南区',
 101031100 => '塘沽区',
 101031200 => '大港区',
 101031400 => '蓟县',
 101040100 => '重庆市',
 101040200 => '永川区',
 101040300 => '合川区',
 101040400 => '南川区',
 101040500 => '江津区',
 101040600 => '万盛区',
 101040700 => '渝北区',
 101040800 => '北碚区',
 101040900 => '巴南区',
 101041000 => '长寿区',
 101041100 => '黔江区',
 101041300 => '万州区',
 101041400 => '涪陵区',
 101041500 => '开县',
 101041600 => '城口县',
 101041700 => '云阳县',
 101041800 => '巫溪县',
 101041900 => '奉节县',
 101042000 => '巫山县',
 101042100 => '潼南县',
 101042200 => '垫江县',
 101042300 => '梁平县',
 101042400 => '忠县',
 101042500 => '石柱县',
 101042600 => '大足县',
 101042700 => '荣昌县',
 101042800 => '铜梁县',
 101042900 => '璧山县',
 101043000 => '丰都县',
 101043100 => '武隆县',
 101043200 => '彭水县',
 101043300 => '綦江县',
 101043400 => '酉阳县',
 101043600 => '秀山县',
 101050101 => '哈尔滨市',
 101050102 => '双城市',
 101050103 => '呼兰县',
 101050104 => '阿城区',
 101050105 => '宾县',
 101050106 => '依兰县',
 101050107 => '巴彦县',
 101050108 => '通河县',
 101050109 => '方正县',
 101050110 => '延寿县',
 101050111 => '尚志市',
 101050112 => '五常市',
 101050113 => '木兰县',
 101050201 => '齐齐哈尔市',
 101050202 => '讷河市',
 101050203 => '龙江县',
 101050204 => '甘南县',
 101050205 => '富裕县',
 101050206 => '依安县',
 101050207 => '拜泉县',
 101050208 => '克山县',
 101050209 => '克东县',
 101050210 => '泰来县',
 101050301 => '牡丹江市',
 101050302 => '海林市',
 101050303 => '穆棱市',
 101050304 => '林口县',
 101050305 => '绥芬河市',
 101050306 => '宁安市',
 101050307 => '东宁县',
 101050401 => '佳木斯市',
 101050402 => '汤原县',
 101050403 => '抚远县',
 101050404 => '桦川县',
 101050405 => '桦南县',
 101050406 => '同江市',
 101050407 => '富锦市',
 101050501 => '绥化市',
 101050502 => '肇东市',
 101050503 => '安达市',
 101050504 => '海伦市',
 101050505 => '明水县',
 101050506 => '望奎县',
 101050507 => '兰西县',
 101050508 => '青冈县',
 101050509 => '庆安县',
 101050510 => '绥棱县',
 101050601 => '黑河市',
 101050602 => '嫩江县',
 101050603 => '孙吴县',
 101050604 => '逊克县',
 101050605 => '五大连池市',
 101050606 => '北安市',
 101050701 => '大兴安岭',
 101050702 => '塔河县',
 101050703 => '漠河县',
 101050704 => '呼玛',
 101050705 => '呼中',
 101050706 => '新林',
 101050708 => '加格达奇',
 101050801 => '伊春市',
 101050802 => '乌伊岭区',
 101050803 => '五营区',
 101050804 => '铁力市',
 101050805 => '嘉荫县',
 101050901 => '大庆市',
 101050902 => '林甸县',
 101050903 => '肇州县',
 101050904 => '肇源县',
 101050905 => '杜尔伯特县',
 101051002 => '七台河市',
 101051003 => '勃利县',
 101051101 => '鸡西市',
 101051102 => '虎林市',
 101051103 => '密山市',
 101051104 => '鸡东县',
 101051201 => '鹤岗市',
 101051202 => '绥滨县',
 101051203 => '萝北县',
 101051301 => '双鸭山市',
 101051302 => '集贤县',
 101051303 => '宝清县',
 101051304 => '饶河县',
 101051305 => '友谊县',
 101060101 => '长春市',
 101060102 => '农安县',
 101060103 => '德惠市',
 101060104 => '九台市',
 101060105 => '榆树市',
 101060106 => '双阳区',
 101060201 => '吉林市',
 101060202 => '舒兰市',
 101060203 => '永吉县',
 101060204 => '蛟河市',
 101060205 => '磐石市',
 101060206 => '桦甸市',
 101060301 => '延吉市',
 101060302 => '敦化市',
 101060303 => '安图县',
 101060304 => '汪清县',
 101060305 => '和龙市',
 101060307 => '龙井市',
 101060308 => '珲春市',
 101060309 => '图们市',
 101060401 => '四平市',
 101060402 => '双辽市',
 101060403 => '梨树县',
 101060404 => '公主岭市',
 101060405 => '伊通县',
 101060501 => '通化市',
 101060502 => '梅河口市',
 101060503 => '柳河县',
 101060504 => '辉南县',
 101060505 => '集安市',
 101060506 => '通化县',
 101060601 => '白城市',
 101060602 => '洮南市',
 101060603 => '大安市',
 101060604 => '镇赉县',
 101060605 => '通榆县',
 101060701 => '辽源市',
 101060702 => '东丰县',
 101060703 => '东辽县',
 101060801 => '松原市',
 101060802 => '乾安县',
 101060803 => '前郭县',
 101060804 => '长岭县',
 101060805 => '扶余县',
 101060901 => '白山市',
 101060902 => '靖宇县',
 101060903 => '临江市',
 101060904 => '东岗市',
 101060905 => '长白县',
 101060906 => '抚松县',
 101060907 => '江源区',
 101070101 => '沈阳市',
 101070103 => '辽中县',
 101070104 => '康平县',
 101070105 => '法库县',
 101070106 => '新民市',
 101070201 => '大连市',
 101070202 => '瓦房店市',
 101070203 => '金州区',
 101070204 => '普兰店市',
 101070205 => '旅顺市',
 101070206 => '长海县',
 101070207 => '庄河市',
 101070301 => '鞍山市',
 101070302 => '台安县',
 101070303 => '岫岩县',
 101070304 => '海城市',
 101070401 => '抚顺市',
 101070402 => '新宾县',
 101070403 => '清原县',
 101070404 => '章党县',
 101070501 => '本溪市',
 101070502 => '本溪县',
 101070504 => '桓仁县',
 101070601 => '丹东市',
 101070602 => '凤城市',
 101070603 => '宽甸县',
 101070604 => '东港市',
 101070701 => '锦州市',
 101070702 => '凌海市',
 101070704 => '义县',
 101070705 => '黑山县',
 101070706 => '北镇市',
 101070801 => '营口市',
 101070802 => '大石桥市',
 101070803 => '盖州市',
 101070901 => '阜新市',
 101070902 => '彰武县',
 101071001 => '辽阳市',
 101071002 => '辽阳县',
 101071003 => '灯塔市',
 101071004 => '弓长岭区',
 101071101 => '铁岭市',
 101071102 => '开原市',
 101071103 => '昌图县',
 101071104 => '西丰县',
 101071105 => '调兵山市',
 101071201 => '朝阳市',
 101071203 => '凌源市',
 101071204 => '喀左',
 101071205 => '北票市',
 101071207 => '建平县',
 101071301 => '盘锦市',
 101071302 => '大洼县',
 101071303 => '盘山县',
 101071401 => '葫芦岛市',
 101071402 => '建昌县',
 101071403 => '绥中县',
 101071404 => '兴城市',
 101080101 => '呼和浩特市',
 101080102 => '土左旗',
 101080103 => '托克托县',
 101080104 => '和林格尔县',
 101080105 => '清水河县',
 101080106 => '呼市郊区',
 101080107 => '武川县',
 101080201 => '包头市',
 101080202 => '白云鄂博',
 101080203 => '满都拉',
 101080204 => '土右旗',
 101080205 => '固阳县',
 101080206 => '达茂旗',
 101080207 => '希拉穆仁',
 101080301 => '乌海',
 101080401 => '集宁区',
 101080402 => '卓资县',
 101080403 => '化德县',
 101080404 => '商都县',
 101080406 => '兴和县',
 101080407 => '凉城县',
 101080408 => '察右前旗',
 101080409 => '察右中旗',
 101080410 => '察右后旗',
 101080411 => '四子王旗',
 101080412 => '丰镇市',
 101080501 => '通辽市',
 101080502 => '舍伯吐',
 101080503 => '科左中旗',
 101080504 => '科左后旗',
 101080505 => '青龙山',
 101080506 => '开鲁县',
 101080507 => '库伦旗',
 101080508 => '奈曼旗',
 101080509 => '扎鲁特旗',
 101080511 => '巴雅尔吐胡硕',
 101081108 => '霍林郭勒市',
 101080601 => '赤峰市',
 101080603 => '阿鲁旗',
 101080604 => '浩尔吐',
 101080605 => '巴林左旗',
 101080606 => '巴林右旗',
 101080607 => '林西县',
 101080608 => '克什克腾旗',
 101080609 => '翁牛特旗',
 101080610 => '岗子',
 101080611 => '喀喇沁旗',
 101080612 => '八里罕',
 101080613 => '宁城县',
 101080614 => '敖汉旗',
 101080615 => '宝国吐',
 101080701 => '鄂尔多斯市',
 101080703 => '达拉特旗',
 101080704 => '准格尔旗',
 101080705 => '鄂前旗',
 101080706 => '河南旗',
 101080707 => '伊克乌素',
 101080708 => '鄂托克旗',
 101080709 => '杭锦旗',
 101080710 => '乌审旗',
 101080711 => '伊金霍洛旗',
 101080712 => '乌审召',
 101080713 => '东胜区',
 101080801 => '临河区',
 101080802 => '五原县',
 101080803 => '磴口县',
 101080804 => '乌前旗',
 101080805 => '大佘太',
 101080806 => '乌中旗',
 101080807 => '乌后旗',
 101080808 => '海力素',
 101080809 => '那仁宝力格',
 101080810 => '杭锦后旗',
 101080901 => '锡林浩特市',
 101080903 => '二连浩特市',
 101080904 => '阿巴嘎旗',
 101080906 => '苏左旗',
 101080907 => '苏右旗',
 101080908 => '朱日和',
 101080909 => '东乌旗',
 101080910 => '西乌旗',
 101080911 => '太仆寺旗',
 101080912 => '镶黄旗',
 101080913 => '正镶白旗',
 101080914 => '正蓝旗',
 101080915 => '多伦县',
 101080916 => '博克图',
 101080917 => '乌拉盖',
 101081001 => '海拉尔',
 101081002 => '小二沟',
 101081003 => '阿荣旗',
 101081004 => '莫力达瓦旗',
 101081005 => '鄂伦春旗',
 101081006 => '鄂温克旗',
 101081007 => '陈旗',
 101081008 => '新左旗',
 101081009 => '新右旗',
 101081010 => '满洲里市',
 101081011 => '牙克石市',
 101081012 => '扎兰屯市',
 101081014 => '额尔古纳市',
 101081015 => '根河市',
 101081016 => '图里河',
 101081101 => '乌兰浩特市',
 101081102 => '阿尔山市',
 101081103 => '科右中旗',
 101081104 => '胡尔勒',
 101081105 => '扎赉特旗',
 101081106 => '索伦',
 101081107 => '突泉县',
 101081109 => '科右前旗',
 101080510 => '高力板',
 101081201 => '阿左旗',
 101081202 => '阿右旗',
 101081203 => '额济纳旗',
 101081204 => '拐子湖',
 101081205 => '吉兰太',
 101081206 => '锡林高勒',
 101081207 => '头道湖',
 101081208 => '中泉子',
 101081209 => '诺尔贡',
 101081210 => '雅布赖',
 101081211 => '乌斯泰',
 101081212 => '孪井滩',
 101090101 => '石家庄市',
 101090102 => '井陉区',
 101090103 => '正定县',
 101090104 => '栾城县',
 101090105 => '行唐县',
 101090106 => '灵寿县',
 101090107 => '高邑县',
 101090108 => '深泽县',
 101090109 => '赞皇县',
 101090110 => '无极县',
 101090111 => '平山县',
 101090112 => '元氏县',
 101090113 => '赵县',
 101090114 => '辛集市',
 101090115 => '藁城市',
 101090116 => '晋州市',
 101090117 => '新乐市',
 101090118 => '鹿泉市',
 101090201 => '保定市',
 101090202 => '满城县',
 101090203 => '阜平县',
 101090204 => '徐水县',
 101090205 => '唐县',
 101090206 => '高阳县',
 101090207 => '容城县',
 101090209 => '涞源县',
 101090210 => '望都县',
 101090211 => '安新县',
 101090212 => '易县',
 101090214 => '曲阳县',
 101090215 => '蠡县',
 101090216 => '顺平县',
 101090217 => '雄县',
 101090218 => '涿州市',
 101090219 => '定州市',
 101090220 => '安国市',
 101090221 => '高碑店市',
 101090222 => '涞水县',
 101090223 => '定兴县',
 101090224 => '清苑县',
 101090225 => '博野县',
 101090301 => '张家口市',
 101090302 => '宣化区',
 101090303 => '张北县',
 101090304 => '康保县',
 101090305 => '沽源县',
 101090306 => '尚义县',
 101090307 => '蔚县',
 101090308 => '阳原县',
 101090309 => '怀安县',
 101090310 => '万全县',
 101090311 => '怀来县',
 101090312 => '涿鹿县',
 101090313 => '赤城县',
 101090314 => '崇礼县',
 101090402 => '承德市',
 101090403 => '承德县',
 101090404 => '兴隆县',
 101090405 => '平泉县',
 101090406 => '滦平县',
 101090407 => '隆化县',
 101090408 => '丰宁县',
 101090409 => '宽城县',
 101090410 => '围场县',
 101090501 => '唐山市',
 101090502 => '丰南区',
 101090503 => '丰润区',
 101090504 => '滦县',
 101090505 => '滦南县',
 101090506 => '乐亭县',
 101090507 => '迁西县',
 101090508 => '玉田县',
 101090509 => '唐海县',
 101090510 => '遵化市',
 101090511 => '迁安市',
 101090512 => '曹妃甸',
 101090601 => '廊坊市',
 101090602 => '固安县',
 101090603 => '永清县',
 101090604 => '香河县',
 101090605 => '大城县',
 101090606 => '文安县',
 101090607 => '大厂县',
 101090608 => '霸州市',
 101090609 => '三河市',
 101090701 => '沧州市',
 101090702 => '青县',
 101090703 => '东光县',
 101090704 => '海兴县',
 101090705 => '盐山县',
 101090706 => '肃宁县',
 101090707 => '南皮县',
 101090708 => '吴桥县',
 101090709 => '献县',
 101090710 => '孟村县',
 101090711 => '泊头市',
 101090712 => '任丘市',
 101090713 => '黄骅市',
 101090714 => '河间市',
 101090716 => '沧县',
 101090801 => '衡水市',
 101090802 => '枣强县',
 101090803 => '武邑县',
 101090804 => '武强县',
 101090805 => '饶阳县',
 101090806 => '安平市',
 101090807 => '故城县',
 101090808 => '景县',
 101090809 => '阜城县',
 101090810 => '冀州市',
 101090811 => '深州市',
 101090901 => '邢台市',
 101090902 => '临城县',
 101090904 => '内丘县',
 101090905 => '柏乡县',
 101090906 => '隆尧县',
 101090907 => '南和县',
 101090908 => '宁晋县',
 101090909 => '巨鹿县',
 101090910 => '新河县',
 101090911 => '广宗县',
 101090912 => '平乡县',
 101090913 => '威县',
 101090914 => '清河县',
 101090915 => '临西县',
 101090916 => '南宫市',
 101090917 => '沙河市',
 101090918 => '任县',
 101091001 => '邯郸市',
 101091002 => '峰峰矿区',
 101091003 => '临漳县',
 101091004 => '成安县',
 101091005 => '大名县',
 101091006 => '涉县',
 101091007 => '磁县',
 101091008 => '肥乡县',
 101091009 => '永年县',
 101091010 => '邱县',
 101091011 => '鸡泽县',
 101091012 => '广平县',
 101091013 => '馆陶县',
 101091014 => '魏县',
 101091015 => '曲周县',
 101091016 => '武安市',
 101091101 => '秦皇岛市',
 101091102 => '青龙县',
 101091103 => '昌黎县',
 101091104 => '抚宁县',
 101091105 => '卢龙县',
 101091106 => '北戴河',
 101100101 => '太原市',
 101100102 => '清徐县',
 101100103 => '阳曲县',
 101100104 => '娄烦县',
 101100105 => '古交市',
 101100106 => '尖草坪区',
 101100107 => '小店区',
 101100201 => '大同市',
 101100202 => '阳高县',
 101100203 => '大同县',
 101100204 => '天镇县',
 101100205 => '广灵县',
 101100206 => '灵丘县',
 101100207 => '浑源县',
 101100208 => '左云县',
 101100301 => '阳泉市',
 101100302 => '盂县',
 101100303 => '平定县',
 101100401 => '晋中市',
 101100402 => '榆次区',
 101100403 => '榆社县',
 101100404 => '左权县',
 101100405 => '和顺县',
 101100406 => '昔阳县',
 101100407 => '寿阳县',
 101100408 => '太谷县',
 101100409 => '祁县',
 101100410 => '平遥县',
 101100411 => '灵石县',
 101100412 => '介休市',
 101100501 => '长治市',
 101100502 => '黎城县',
 101100503 => '屯留县',
 101100504 => '潞城市',
 101100505 => '襄垣县',
 101100506 => '平顺县',
 101100507 => '武乡县',
 101100508 => '沁县',
 101100509 => '长子县',
 101100510 => '沁源县',
 101100511 => '壶关县',
 101100601 => '晋城市',
 101100602 => '沁水县',
 101100603 => '阳城县',
 101100604 => '陵川县',
 101100605 => '高平市',
 101100606 => '泽州县',
 101100701 => '临汾市',
 101100702 => '曲沃县',
 101100703 => '永和县',
 101100704 => '隰县',
 101100705 => '大宁县',
 101100706 => '吉县',
 101100707 => '襄汾县',
 101100708 => '蒲县',
 101100709 => '汾西县',
 101100710 => '洪洞县',
 101100711 => '霍州市',
 101100712 => '乡宁县',
 101100713 => '翼城县',
 101100714 => '侯马市',
 101100715 => '浮山县',
 101100716 => '安泽县',
 101100717 => '古县',
 101100801 => '运城市',
 101100802 => '临猗县',
 101100803 => '稷山县',
 101100804 => '万荣县',
 101100805 => '河津市',
 101100806 => '新绛县',
 101100807 => '绛县',
 101100808 => '闻喜县',
 101100809 => '垣曲县',
 101100810 => '永济市',
 101100811 => '芮城县',
 101100812 => '夏县',
 101100813 => '平陆县',
 101100901 => '朔州市',
 101100902 => '平鲁区',
 101100903 => '山阴县',
 101100904 => '右玉县',
 101100905 => '应县',
 101100906 => '怀仁县',
 101101001 => '忻州市',
 101101002 => '定襄县',
 101101003 => '五台县',
 101101004 => '河曲县',
 101101005 => '偏关县',
 101101006 => '神池县',
 101101007 => '宁武县',
 101101008 => '代县',
 101101009 => '繁峙县',
 101101010 => '五台山',
 101101011 => '保德县',
 101101012 => '静乐县',
 101101013 => '岢岚县',
 101101014 => '五寨县',
 101101015 => '原平市',
 101101100 => '吕梁市',
 101101101 => '离石区',
 101101102 => '临县',
 101101103 => '兴县',
 101101104 => '岚县',
 101101105 => '柳林县',
 101101106 => '石楼县',
 101101107 => '方山县',
 101101108 => '交口县',
 101101109 => '中阳县',
 101101110 => '孝义市',
 101101111 => '汾阳市',
 101101112 => '文水县',
 101101113 => '交城县',
 101110101 => '西安市',
 101110102 => '长安区',
 101110103 => '临潼区',
 101110104 => '蓝田县',
 101110105 => '周至县',
 101110106 => '户县',
 101110107 => '高陵县',
 101110200 => '咸阳市',
 101110201 => '三原县',
 101110202 => '礼泉县',
 101110203 => '永寿县',
 101110204 => '淳化县',
 101110205 => '泾阳县',
 101110206 => '武功县',
 101110207 => '乾县',
 101110208 => '彬县',
 101110209 => '长武县',
 101110210 => '旬邑县',
 101110211 => '兴平市',
 101110300 => '延安市',
 101110301 => '延长县',
 101110302 => '延川县',
 101110303 => '子长县',
 101110304 => '宜川县',
 101110305 => '富县',
 101110306 => '志丹县',
 101110307 => '安塞县',
 101110308 => '甘泉县',
 101110309 => '洛川县',
 101110310 => '黄陵县',
 101110311 => '黄龙县',
 101110312 => '吴起县',
 101110401 => '榆林市',
 101110402 => '府谷县',
 101110403 => '神木县',
 101110404 => '佳县',
 101110405 => '定边县',
 101110406 => '靖边县',
 101110407 => '横山县',
 101110408 => '米脂县',
 101110409 => '子洲县',
 101110410 => '绥德县',
 101110411 => '吴堡县',
 101110412 => '清涧县',
 101110413 => '榆阳区',
 101110501 => '渭南市',
 101110502 => '华县',
 101110503 => '潼关县',
 101110504 => '大荔县',
 101110505 => '白水县',
 101110506 => '富平县',
 101110507 => '蒲城县',
 101110508 => '澄城县',
 101110509 => '合阳县',
 101110510 => '韩城市',
 101110511 => '华阴市',
 101110601 => '商洛市',
 101110602 => '洛南县',
 101110603 => '柞水县',
 101110604 => '商州',
 101110605 => '镇安县',
 101110606 => '丹凤县',
 101110607 => '商南县',
 101110608 => '山阳县',
 101110701 => '安康市',
 101110702 => '紫阳县',
 101110703 => '石泉县',
 101110704 => '汉阴县',
 101110705 => '旬阳县',
 101110706 => '岚皋县',
 101110707 => '平利县',
 101110708 => '白河县',
 101110709 => '镇坪县',
 101110710 => '宁陕县',
 101110801 => '汉中市',
 101110802 => '略阳县',
 101110803 => '勉县',
 101110804 => '留坝县',
 101110805 => '洋县',
 101110806 => '城固县',
 101110807 => '西乡县',
 101110808 => '佛坪县',
 101110809 => '宁强县',
 101110810 => '南郑县',
 101110811 => '镇巴县',
 101111101 => '杨凌市',
 101110901 => '宝鸡市',
 101110903 => '千阳县',
 101110904 => '麟游县',
 101110905 => '岐山县',
 101110906 => '凤翔县',
 101110907 => '扶风县',
 101110908 => '眉县',
 101110909 => '太白县',
 101110910 => '凤县',
 101110911 => '陇县',
 101110912 => '陈仓区',
 101111001 => '铜川市',
 101111002 => '耀县',
 101111003 => '宜君县',
 101111004 => '耀州',
 101120101 => '济南市',
 101120102 => '长清区',
 101120103 => '商河县',
 101120104 => '章丘市',
 101120105 => '平阴县',
 101120106 => '济阳县',
 101120201 => '青岛市',
 101120202 => '崂山',
 101120204 => '即墨市',
 101120205 => '胶州市',
 101120206 => '胶南市',
 101120207 => '莱西市',
 101120208 => '平度市',
 101120301 => '淄博市',
 101120302 => '淄川区',
 101120303 => '博山区',
 101120304 => '高青县',
 101120305 => '周村区',
 101120306 => '沂源县',
 101120307 => '桓台县',
 101120308 => '临淄区',
 101120401 => '德州市',
 101120402 => '武城县',
 101120403 => '临邑县',
 101120404 => '陵县',
 101120405 => '齐河县',
 101120406 => '乐陵市',
 101120407 => '庆云县',
 101120408 => '平原县',
 101120409 => '宁津县',
 101120410 => '夏津县',
 101120411 => '禹城市',
 101120501 => '烟台市',
 101120502 => '莱州市',
 101120503 => '长岛县',
 101120504 => '蓬莱市',
 101120505 => '龙口市',
 101120506 => '招远市',
 101120507 => '栖霞市',
 101120508 => '福山区',
 101120509 => '牟平区',
 101120510 => '莱阳市',
 101120511 => '海阳市',
 101120601 => '潍坊市',
 101120602 => '青州市',
 101120603 => '寿光市',
 101120604 => '临朐县',
 101120605 => '昌乐县',
 101120606 => '昌邑市',
 101120607 => '安丘市',
 101120608 => '高密市',
 101120609 => '诸城市',
 101120701 => '济宁市',
 101120702 => '嘉祥县',
 101120703 => '微山县',
 101120704 => '鱼台县',
 101120705 => '兖州市',
 101120706 => '金乡县',
 101120707 => '汶上县',
 101120708 => '泗水县',
 101120709 => '梁山县',
 101120710 => '曲阜市',
 101120711 => '邹城市',
 101120801 => '泰安市',
 101120802 => '新泰市',
 101120804 => '肥城市',
 101120805 => '东平县',
 101120806 => '宁阳县',
 101120901 => '临沂市',
 101120902 => '莒南县',
 101120903 => '沂南县',
 101120904 => '苍山县',
 101120905 => '临沭县',
 101120906 => '郯城县',
 101120907 => '蒙阴县',
 101120908 => '平邑县',
 101120909 => '费县',
 101120910 => '沂水县',
 101121001 => '菏泽市',
 101121002 => '鄄城县',
 101121003 => '郓城县',
 101121004 => '东明县',
 101121005 => '定陶县',
 101121006 => '巨野县',
 101121007 => '曹县',
 101121008 => '成武县',
 101121009 => '单县',
 101121101 => '滨州市',
 101121102 => '博兴县',
 101121103 => '无棣县',
 101121104 => '阳信县',
 101121105 => '惠民县',
 101121106 => '沾化县',
 101121107 => '邹平县',
 101121201 => '东营市',
 101121202 => '河口',
 101121203 => '垦利县',
 101121204 => '利津县',
 101121205 => '广饶县',
 101121301 => '威海市',
 101121302 => '文登市',
 101121303 => '荣成市',
 101121304 => '乳山市',
 101121305 => '成山头',
 101121306 => '石岛',
 101121401 => '枣庄市',
 101121402 => '薛城区',
 101121403 => '峄城区',
 101121404 => '台儿庄区',
 101121405 => '滕州市',
 101121501 => '日照市',
 101121502 => '五莲县',
 101121503 => '莒县',
 101121601 => '莱芜市',
 101121701 => '聊城市',
 101121702 => '冠县',
 101121703 => '阳谷县',
 101121704 => '高唐县',
 101121705 => '茌平县',
 101121706 => '东阿县',
 101121707 => '临清市',
 101121709 => '莘县',
 101130101 => '乌鲁木齐市',
 101130103 => '小渠子',
 101130105 => '达坂城',
 101130108 => '乌鲁木齐牧试站',
 101130109 => '天池',
 101130110 => '白杨沟',
 101130201 => '克拉玛依市',
 101130202 => '乌尔禾区',
 101130203 => '白碱滩区',
 101130301 => '石河子市',
 101130302 => '炮台',
 101130303 => '莫索湾',
 101130401 => '昌吉市',
 101130402 => '呼图壁县',
 101130403 => '米泉市',
 101130404 => '阜康市',
 101130405 => '吉木萨尔',
 101130406 => '奇台县',
 101130407 => '玛纳斯县',
 101130408 => '木垒县',
 101130409 => '蔡家湖',
 101130501 => '吐鲁番市',
 101130502 => '托克逊县',
 101130504 => '鄯善县',
 101130601 => '库尔勒市',
 101130602 => '轮台县',
 101130603 => '尉犁县',
 101130604 => '若羌县',
 101130605 => '且末县',
 101130606 => '和静县',
 101130607 => '焉耆县',
 101130608 => '和硕县',
 101130610 => '巴音布鲁',
 101130611 => '铁干里克',
 101130612 => '博湖县',
 101130613 => '塔中',
 101130614 => '巴仑台',
 101130701 => '阿拉尔市',
 101130801 => '阿克苏市',
 101130802 => '乌什县',
 101130803 => '温宿县',
 101130804 => '拜城县',
 101130805 => '新和县',
 101130806 => '沙雅县',
 101130807 => '库车县',
 101130808 => '柯坪县',
 101130809 => '阿瓦提县',
 101130901 => '喀什市',
 101130902 => '英吉沙县',
 101130903 => '塔什库尔干',
 101130904 => '麦盖提县',
 101130905 => '莎车县',
 101130906 => '叶城县',
 101130907 => '泽普县',
 101130908 => '巴楚县',
 101130909 => '岳普湖县',
 101130910 => '伽师县',
 101130911 => '疏附县',
 101130912 => '疏勒县',
 101131001 => '伊宁市',
 101131002 => '察布查尔',
 101131003 => '尼勒克县',
 101131004 => '伊宁县',
 101131005 => '巩留县',
 101131006 => '新源县',
 101131007 => '昭苏县',
 101131008 => '特克斯县',
 101131009 => '霍城县',
 101131010 => '霍尔果斯',
 101131011 => '奎屯市',
 101131101 => '塔城市',
 101131102 => '裕民县',
 101131103 => '额敏县',
 101131104 => '和布克赛尔',
 101131105 => '托里县',
 101131106 => '乌苏市',
 101131107 => '沙湾县',
 101131201 => '哈密市',
 101131203 => '巴里坤县',
 101131204 => '伊吾县',
 101131301 => '和田市',
 101131302 => '皮山县',
 101131303 => '策勒县',
 101131304 => '墨玉县',
 101131305 => '洛浦县',
 101131306 => '民丰县',
 101131307 => '于田县',
 101131401 => '阿勒泰市',
 101131402 => '哈巴河县',
 101131405 => '吉木乃县',
 101131406 => '布尔津县',
 101131407 => '福海县',
 101131408 => '富蕴县',
 101131409 => '青河县',
 101131501 => '阿图什市',
 101131502 => '乌恰县',
 101131503 => '阿克陶县',
 101131504 => '阿合奇县',
 101131601 => '博乐市',
 101131602 => '温泉县',
 101131603 => '精河县',
 101131606 => '阿拉山口',
 101140101 => '拉萨市',
 101140102 => '当雄县',
 101140103 => '尼木县',
 101140104 => '林周县',
 101140105 => '堆龙德庆',
 101140106 => '曲水县',
 101140107 => '达孜县',
 101140108 => '墨竹工卡',
 101140201 => '日喀则市',
 101140202 => '拉孜县',
 101140203 => '南木林县',
 101140204 => '聂拉木县',
 101140205 => '定日县',
 101140206 => '江孜县',
 101140207 => '帕里县',
 101140208 => '仲巴县',
 101140209 => '萨嘎县',
 101140210 => '吉隆县',
 101140211 => '昂仁县',
 101140212 => '定结县',
 101140213 => '萨迦县',
 101140214 => '谢通门县',
 101140216 => '岗巴县',
 101140217 => '白朗县',
 101140218 => '亚东县',
 101140219 => '康马县',
 101140220 => '仁布县',
 101140301 => '山南市',
 101140302 => '贡县嘎',
 101140303 => '扎囊县',
 101140304 => '加查县',
 101140305 => '浪卡子县',
 101140306 => '错那县',
 101140307 => '隆子县',
 101140308 => '泽当县',
 101140309 => '乃东县',
 101140310 => '桑日县',
 101140311 => '洛扎县',
 101140312 => '措美县',
 101140313 => '琼结县',
 101140314 => '曲松县',
 101140401 => '林芝市',
 101140402 => '波密县',
 101140403 => '米林县',
 101140404 => '察隅县',
 101140405 => '工布江达',
 101140406 => '朗县',
 101140407 => '墨脱县',
 101140501 => '昌都市',
 101140502 => '丁青县',
 101140503 => '边坝县',
 101140504 => '洛隆县',
 101140505 => '左贡县',
 101140506 => '芒康县',
 101140507 => '类乌齐县',
 101140508 => '八宿县',
 101140509 => '江达县',
 101140510 => '察雅县',
 101140511 => '贡觉县',
 101140601 => '那曲县',
 101140602 => '尼玛县',
 101140603 => '嘉黎县',
 101140604 => '班戈县',
 101140605 => '安多县',
 101140606 => '索县',
 101140607 => '聂荣县',
 101140608 => '巴青县',
 101140609 => '比如县',
 101140610 => '双湖县',
 101140701 => '阿里',
 101140702 => '改则县',
 101140703 => '申扎',
 101140704 => '狮泉河',
 101140705 => '普兰县',
 101140706 => '札达县',
 101140707 => '噶尔县',
 101140708 => '日土县',
 101140709 => '革吉县',
 101140710 => '措勤县',
 101150101 => '西宁市',
 101150102 => '大通县',
 101150103 => '湟源县',
 101150104 => '湟中县',
 101150201 => '海东市',
 101150202 => '乐都县',
 101150203 => '民和县',
 101150204 => '互助县',
 101150205 => '化隆县',
 101150206 => '循化县',
 101150207 => '冷湖县',
 101150208 => '平安县',
 101150301 => '黄南市',
 101150302 => '尖扎县',
 101150303 => '泽库县',
 101150304 => '河南县',
 101150305 => '同仁县',
 101150401 => '海南市',
 101150404 => '贵德县',
 101150406 => '兴海县',
 101150407 => '贵南县',
 101150408 => '同德县',
 101150409 => '共和县',
 101150501 => '果洛县',
 101150502 => '班玛县',
 101150503 => '甘德县',
 101150504 => '达日县',
 101150505 => '久治县',
 101150506 => '玛多县',
 101150507 => '多县',
 101150508 => '玛沁县',
 101150601 => '玉树市',
 101150602 => '称多县',
 101150603 => '治多县',
 101150604 => '杂多县',
 101150605 => '囊谦县',
 101150606 => '曲麻莱县',
 101150701 => '海西市',
 101150708 => '天峻县',
 101150709 => '乌兰县',
 101150712 => '茫崖',
 101150713 => '大柴旦',
 101150716 => '德令哈市',
 101150801 => '海北市',
 101150802 => '门源县',
 101150803 => '祁连县',
 101150804 => '海晏县',
 101150806 => '刚察县',
 101160101 => '兰州市',
 101160102 => '皋兰县',
 101160103 => '永登县',
 101160104 => '榆中县',
 101161401 => '嘉峪关市',
 101160201 => '定西市',
 101160202 => '通渭县',
 101160203 => '陇西县',
 101160204 => '渭源县',
 101160205 => '临洮县',
 101160206 => '漳县',
 101160207 => '岷县',
 101160208 => '安定区',
 101160301 => '平凉市',
 101160302 => '泾川县',
 101160303 => '灵台县',
 101160304 => '崇信县',
 101160305 => '华亭县',
 101160306 => '庄浪县',
 101160307 => '静宁县',
 101160308 => '崆峒区',
 101160401 => '西峰区',
 101160403 => '环县',
 101160404 => '华池县',
 101160405 => '合水县',
 101160406 => '正宁县',
 101160407 => '宁县',
 101160408 => '镇原县',
 101160409 => '庆城县',
 101160501 => '武威市',
 101160502 => '民勤县',
 101160503 => '古浪县',
 101160505 => '天祝县',
 101160601 => '金昌市',
 101160602 => '永昌县',
 101160701 => '张掖市',
 101160702 => '肃南县',
 101160703 => '民乐县',
 101160704 => '临泽县',
 101160705 => '高台县',
 101160706 => '山丹县',
 101160801 => '酒泉市',
 101160803 => '金塔县',
 101160804 => '阿克塞',
 101160805 => '瓜州县',
 101160806 => '肃北县',
 101160807 => '玉门市',
 101160808 => '敦煌市',
 101160901 => '天水市',
 101160903 => '清水县',
 101160904 => '秦安县',
 101160905 => '甘谷县',
 101160906 => '武山县',
 101160907 => '张家川县',
 101160908 => '麦积区',
 101161001 => '武都区',
 101161002 => '成县',
 101161003 => '文县',
 101161004 => '宕昌县',
 101161005 => '康县',
 101161006 => '西和县',
 101161007 => '礼县',
 101161008 => '徽县',
 101161009 => '两当县',
 101161101 => '临夏市',
 101161102 => '康乐县',
 101161103 => '永靖县',
 101161104 => '广河县',
 101161105 => '和政县',
 101161106 => '东乡',
 101161107 => '积石山县',
 101161201 => '合作市',
 101161202 => '临潭县',
 101161203 => '卓尼县',
 101161204 => '舟曲县',
 101161205 => '迭部县',
 101161206 => '玛曲县',
 101161207 => '碌曲县',
 101161208 => '夏河县',
 101161301 => '白银市',
 101161302 => '靖远县',
 101161303 => '会宁县',
 101161304 => '平川区',
 101161305 => '景泰县',
 101170101 => '银川市',
 101170102 => '永宁县',
 101170103 => '灵武市',
 101170104 => '贺兰县',
 101170201 => '石嘴山市',
 101170202 => '惠农县',
 101170203 => '平罗县',
 101170204 => '陶乐县',
 101170301 => '吴忠市',
 101170302 => '同心县',
 101170303 => '盐池县',
 101170306 => '青铜峡市',
 101170401 => '固原市',
 101170402 => '西吉县',
 101170403 => '隆德县',
 101170404 => '泾源县',
 101170406 => '彭阳县',
 101170501 => '中卫市',
 101170502 => '中宁县',
 101170504 => '海原县',
 101180101 => '郑州市',
 101180102 => '巩义市',
 101180103 => '荥阳市',
 101180104 => '登封市',
 101180105 => '新密市',
 101180106 => '新郑市',
 101180107 => '中牟县',
 101180108 => '上街',
 101180201 => '安阳市',
 101180202 => '汤阴县',
 101180203 => '滑县',
 101180204 => '内黄县',
 101180205 => '林州市',
 101180301 => '新乡市',
 101180302 => '获嘉县',
 101180303 => '原阳县',
 101180304 => '辉县市',
 101180305 => '卫辉市',
 101180306 => '延津县',
 101180307 => '封丘县',
 101180308 => '长垣县',
 101180401 => '许昌市',
 101180402 => '鄢陵县',
 101180403 => '襄城县',
 101180404 => '长葛市',
 101180405 => '禹州市',
 101180501 => '平顶山市',
 101180502 => '郏县',
 101180503 => '宝丰县',
 101180504 => '汝州市',
 101180505 => '叶县',
 101180506 => '舞钢市',
 101180507 => '鲁山县',
 101180508 => '石龙区',
 101180601 => '信阳市',
 101180602 => '息县',
 101180603 => '罗山县',
 101180604 => '光山县',
 101180605 => '新县',
 101180606 => '淮滨县',
 101180607 => '潢川县',
 101180608 => '固始县',
 101180609 => '商城县',
 101180701 => '南阳市',
 101180702 => '南召县',
 101180703 => '方城县',
 101180704 => '社旗县',
 101180705 => '西峡县',
 101180706 => '内乡县',
 101180707 => '镇平县',
 101180708 => '淅川县',
 101180709 => '新野县',
 101180710 => '唐河县',
 101180711 => '邓州市',
 101180712 => '桐柏县',
 101180801 => '开封市',
 101180802 => '杞县',
 101180803 => '尉氏县',
 101180804 => '通许县',
 101180805 => '兰考县',
 101180901 => '洛阳市',
 101180902 => '新安县',
 101180903 => '孟津县',
 101180904 => '宜阳县',
 101180905 => '洛宁县',
 101180906 => '伊川县',
 101180907 => '嵩县',
 101180908 => '偃师市',
 101180909 => '栾川县',
 101180910 => '汝阳县',
 101180911 => '吉利区',
 101181001 => '商丘市',
 101181003 => '睢县',
 101181004 => '民权县',
 101181005 => '虞城县',
 101181006 => '柘城县',
 101181007 => '宁陵县',
 101181008 => '夏邑县',
 101181009 => '永城市',
 101181101 => '焦作市',
 101181102 => '修武县',
 101181103 => '武陟县',
 101181104 => '沁阳市',
 101181106 => '博爱县',
 101181107 => '温县',
 101181108 => '孟州市',
 101181201 => '鹤壁市',
 101181202 => '浚县',
 101181203 => '淇县',
 101181301 => '濮阳市',
 101181302 => '台前县',
 101181303 => '南乐县',
 101181304 => '清丰县',
 101181305 => '范县',
 101181401 => '周口市',
 101181402 => '扶沟县',
 101181403 => '太康县',
 101181404 => '淮阳县',
 101181405 => '西华县',
 101181406 => '商水县',
 101181407 => '项城市',
 101181408 => '郸城县',
 101181409 => '鹿邑县',
 101181410 => '沈丘县',
 101181501 => '漯河市',
 101181502 => '临颍县',
 101181503 => '舞阳县',
 101181601 => '驻马店市',
 101181602 => '西平县',
 101181603 => '遂平县',
 101181604 => '上蔡县',
 101181605 => '汝南县',
 101181606 => '泌阳县',
 101181607 => '平舆县',
 101181608 => '新蔡县',
 101181609 => '确山县',
 101181610 => '正阳县',
 101181701 => '三门峡市',
 101181702 => '灵宝市',
 101181703 => '渑池县',
 101181704 => '卢氏县',
 101181705 => '义马市',
 101181706 => '陕县',
 101181801 => '济源市',
 101190101 => '南京市',
 101190102 => '溧水县',
 101190103 => '高淳县',
 101190104 => '江宁区',
 101190105 => '六合区',
 101190106 => '江浦',
 101190107 => '浦口区',
 101190201 => '无锡市',
 101190202 => '江阴市',
 101190203 => '宜兴市',
 101190204 => '锡山区',
 101190301 => '镇江市',
 101190302 => '丹阳市',
 101190303 => '扬中市',
 101190304 => '句容市',
 101190305 => '丹徒区',
 101190401 => '苏州市',
 101190402 => '常熟市',
 101190403 => '张家港市',
 101190404 => '昆山市',
 101190405 => '吴中',
 101190407 => '吴江市',
 101190408 => '太仓市',
 101190501 => '南通市',
 101190502 => '海安县',
 101190503 => '如皋市',
 101190504 => '如东县',
 101190507 => '启东市',
 101190508 => '海门市',
 101190509 => '通州区',
 101190601 => '扬州市',
 101190602 => '宝应县',
 101190603 => '仪征市',
 101190604 => '高邮市',
 101190605 => '江都区',
 101190606 => '邗江区',
 101190701 => '盐城市',
 101190702 => '响水县',
 101190703 => '滨海县',
 101190704 => '阜宁县',
 101190705 => '射阳县',
 101190706 => '建湖县',
 101190707 => '东台市',
 101190708 => '大丰市',
 101190709 => '盐都区',
 101190801 => '徐州市',
 101190802 => '铜山区',
 101190803 => '丰县',
 101190804 => '沛县',
 101190805 => '邳州市',
 101190806 => '睢宁县',
 101190807 => '新沂市',
 101190901 => '淮安市',
 101190902 => '金湖县',
 101190903 => '盱眙县',
 101190904 => '洪泽县',
 101190905 => '涟水县',
 101190906 => '淮阴区',
 101190908 => '楚州区',
 101191001 => '连云港市',
 101191002 => '东海县',
 101191003 => '赣榆县',
 101191004 => '灌云县',
 101191005 => '灌南县',
 101191101 => '常州市',
 101191102 => '溧阳市',
 101191103 => '金坛市',
 101191104 => '武进区',
 101191201 => '泰州市',
 101191202 => '兴化市',
 101191203 => '泰兴市',
 101191204 => '姜堰市',
 101191205 => '靖江市',
 101191301 => '宿迁市',
 101191302 => '沭阳县',
 101191303 => '泗阳县',
 101191304 => '泗洪县',
 101191305 => '宿豫区',
 101200101 => '武汉市',
 101200102 => '蔡甸区',
 101200103 => '黄陂区',
 101200104 => '新洲区',
 101200105 => '江夏区',
 101200106 => '东西湖区',
 101200201 => '襄阳市',
 101200202 => '襄州区',
 101200203 => '保康县',
 101200204 => '南漳县',
 101200205 => '宜城市',
 101200206 => '老河口市',
 101200207 => '谷城县',
 101200208 => '枣阳市',
 101200301 => '鄂州市',
 101200302 => '梁子湖',
 101200401 => '孝感市',
 101200402 => '安陆市',
 101200403 => '云梦县',
 101200404 => '大悟县',
 101200405 => '应城市',
 101200406 => '汉川市',
 101200407 => '孝昌县',
 101200501 => '黄冈市',
 101200502 => '红安县',
 101200503 => '麻城市',
 101200504 => '罗田县',
 101200505 => '英山县',
 101200506 => '浠水县',
 101200507 => '蕲春县',
 101200508 => '黄梅县',
 101200509 => '武穴市',
 101200510 => '团风县',
 101200601 => '黄石市',
 101200602 => '大冶市',
 101200603 => '阳新县',
 101200604 => '铁山区',
 101200605 => '下陆区',
 101200606 => '西塞山区',
 101200701 => '咸宁市',
 101200702 => '赤壁市',
 101200703 => '嘉鱼县',
 101200704 => '崇阳县',
 101200705 => '通城县',
 101200706 => '通山县',
 101200801 => '荆州市',
 101200802 => '江陵县',
 101200803 => '公安县',
 101200804 => '石首市',
 101200805 => '监利县',
 101200806 => '洪湖市',
 101200807 => '松滋市',
 101201406 => '沙市区',
 101200901 => '宜昌市',
 101200902 => '远安县',
 101200903 => '秭归县',
 101200904 => '兴山县',
 101200906 => '五峰县',
 101200907 => '当阳市',
 101200908 => '长阳县',
 101200909 => '宜都市',
 101200910 => '枝江市',
 101200911 => '三峡',
 101200912 => '夷陵区',
 101201001 => '恩施市',
 101201002 => '利川市',
 101201003 => '建始县',
 101201004 => '咸丰县',
 101201005 => '宣恩县',
 101201006 => '鹤峰县',
 101201007 => '来凤县',
 101201008 => '巴东县',
 101201101 => '十堰市',
 101201102 => '竹溪县',
 101201103 => '郧西县',
 101201104 => '郧县',
 101201105 => '竹山县',
 101201106 => '房县',
 101201107 => '丹江口市',
 101201108 => '茅箭区',
 101201109 => '张湾区',
 101201201 => '神农架',
 101201301 => '随州市',
 101201302 => '广水市',
 101201401 => '荆门市',
 101201402 => '钟祥市',
 101201403 => '京山县',
 101201404 => '掇刀区',
 101201405 => '沙洋县',
 101201501 => '天门市',
 101201601 => '仙桃市',
 101201701 => '潜江市',
 101210101 => '杭州市',
 101210102 => '萧山区',
 101210103 => '桐庐县',
 101210104 => '淳安县',
 101210105 => '建德市',
 101210106 => '余杭区',
 101210107 => '临安市',
 101210108 => '富阳市',
 101210201 => '湖州市',
 101210202 => '长兴县',
 101210203 => '安吉县',
 101210204 => '德清县',
 101210301 => '嘉兴市',
 101210302 => '嘉善县',
 101210303 => '海宁市',
 101210304 => '桐乡市',
 101210305 => '平湖市',
 101210306 => '海盐县',
 101210401 => '宁波市',
 101210403 => '慈溪市',
 101210404 => '余姚市',
 101210405 => '奉化市',
 101210406 => '象山县',
 101210408 => '宁海县',
 101210410 => '北仑区',
 101210411 => '鄞州区',
 101210412 => '镇海区',
 101210501 => '绍兴市',
 101210502 => '诸暨市',
 101210503 => '上虞市',
 101210504 => '新昌县',
 101210505 => '嵊州市',
 101210601 => '台州市',
 101210603 => '玉环县',
 101210604 => '三门县',
 101210605 => '天台县',
 101210606 => '仙居县',
 101210607 => '温岭市',
 101210609 => '洪家',
 101210610 => '临海市',
 101210611 => '椒江区',
 101210612 => '黄岩区',
 101210613 => '路桥区',
 101210701 => '温州市',
 101210702 => '泰顺县',
 101210703 => '文成县',
 101210704 => '平阳县',
 101210705 => '瑞安市',
 101210706 => '洞头县',
 101210707 => '乐清市',
 101210708 => '永嘉县',
 101210709 => '苍南县',
 101210801 => '丽水市',
 101210802 => '遂昌县',
 101210803 => '龙泉市',
 101210804 => '缙云县',
 101210805 => '青田县',
 101210806 => '云和',
 101210807 => '庆元县',
 101210808 => '松阳县',
 101210809 => '景宁县',
 101210901 => '金华市',
 101210902 => '浦江县',
 101210903 => '兰溪市',
 101210904 => '义乌市',
 101210905 => '东阳市',
 101210906 => '武义县',
 101210907 => '永康市',
 101210908 => '磐安县',
 101211001 => '衢州市',
 101211002 => '常山县',
 101211003 => '开化县',
 101211004 => '龙游县',
 101211005 => '江山市',
 101211006 => '衢江区',
 101211101 => '舟山市',
 101211102 => '嵊泗县',
 101211104 => '岱山县',
 101211105 => '普陀区',
 101211106 => '定海区',
 101220101 => '合肥市',
 101220102 => '长丰县',
 101220103 => '肥东县',
 101220104 => '肥西县',
 101220201 => '蚌埠市',
 101220202 => '怀远县',
 101220203 => '固镇县',
 101220204 => '五河县',
 101220301 => '芜湖市',
 101220302 => '繁昌县',
 101220303 => '芜湖县',
 101220304 => '南陵县',
 101220401 => '淮南市',
 101220402 => '凤台县',
 101220403 => '潘集',
 101220501 => '马鞍山市',
 101220502 => '当涂县',
 101220601 => '安庆市',
 101220602 => '枞阳县',
 101220603 => '太湖县',
 101220604 => '潜山县',
 101220605 => '怀宁县',
 101220606 => '宿松县',
 101220607 => '望江县',
 101220608 => '岳西县',
 101220609 => '桐城市',
 101220701 => '宿州市',
 101220702 => '砀山县',
 101220703 => '灵璧县',
 101220704 => '泗县',
 101220705 => '萧县',
 101220801 => '阜阳市',
 101220802 => '阜南县',
 101220803 => '颍上县',
 101220804 => '临泉县',
 101220805 => '界首市',
 101220806 => '太和县',
 101220901 => '亳州市',
 101220902 => '涡阳县',
 101220903 => '利辛县',
 101220904 => '蒙城县',
 101221001 => '黄山市',
 101221002 => '黄山区',
 101221003 => '屯溪区',
 101221004 => '祁门县',
 101221005 => '黟县',
 101221006 => '歙县',
 101221007 => '休宁县',
 101221008 => '黄山风景区',
 101221101 => '滁州市',
 101221102 => '凤阳县',
 101221103 => '明光市',
 101221104 => '定远县',
 101221105 => '全椒县',
 101221106 => '来安县',
 101221107 => '天长市',
 101221201 => '淮北市',
 101221202 => '濉溪县',
 101221301 => '铜陵市',
 101221401 => '宣城市',
 101221402 => '泾县',
 101221403 => '旌德县',
 101221404 => '宁国市',
 101221405 => '绩溪县',
 101221406 => '广德县',
 101221407 => '郎溪县',
 101221501 => '六安市',
 101221502 => '霍邱县',
 101221503 => '寿县',
 101221505 => '金寨县',
 101221506 => '霍山县',
 101221507 => '舒城县',
 101221601 => '巢湖市',
 101221602 => '庐江县',
 101221603 => '无为县',
 101221604 => '含山县',
 101221605 => '和县',
 101221701 => '池州市',
 101221702 => '东至县',
 101221703 => '青阳县',
 101221704 => '九华山',
 101221705 => '石台县',
 101230101 => '福州市',
 101230102 => '闽清县',
 101230103 => '闽侯县',
 101230104 => '罗源县',
 101230105 => '连江县',
 101230107 => '永泰县',
 101230108 => '平潭县',
 101230110 => '长乐市',
 101230111 => '福清市',
 101230201 => '厦门市',
 101230202 => '同安区',
 101230301 => '宁德市',
 101230302 => '古田县',
 101230303 => '霞浦县',
 101230304 => '寿宁县',
 101230305 => '周宁县',
 101230306 => '福安市',
 101230307 => '柘荣县',
 101230308 => '福鼎市',
 101230309 => '屏南县',
 101230401 => '莆田市',
 101230402 => '仙游县',
 101230403 => '秀屿港',
 101230404 => '涵江区',
 101230405 => '秀屿区',
 101230406 => '荔城区',
 101230407 => '城厢区',
 101230501 => '泉州市',
 101230502 => '安溪县',
 101230504 => '永春县',
 101230505 => '德化县',
 101230506 => '南安市',
 101230507 => '崇武',
 101230508 => '惠安县',
 101230509 => '晋江市',
 101230510 => '石狮市',
 101230601 => '漳州市',
 101230602 => '长泰县',
 101230603 => '南靖县',
 101230604 => '平和县',
 101230605 => '龙海市',
 101230606 => '漳浦县',
 101230607 => '诏安县',
 101230608 => '东山县',
 101230609 => '云霄县',
 101230610 => '华安县',
 101230701 => '龙岩市',
 101230702 => '长汀县',
 101230703 => '连城县',
 101230704 => '武平县',
 101230705 => '上杭县',
 101230706 => '永定县',
 101230707 => '漳平市',
 101230801 => '三明市',
 101230802 => '宁化县',
 101230803 => '清流县',
 101230804 => '泰宁县',
 101230805 => '将乐县',
 101230806 => '建宁县',
 101230807 => '明溪县',
 101230808 => '沙县',
 101230809 => '尤溪县',
 101230810 => '永安市',
 101230811 => '大田县',
 101230901 => '南平市',
 101230902 => '顺昌县',
 101230903 => '光泽县',
 101230904 => '邵武市',
 101230905 => '武夷山市',
 101230906 => '浦城县',
 101230907 => '建阳市',
 101230908 => '松溪县',
 101230909 => '政和县',
 101230910 => '建瓯市',
 101240101 => '南昌市',
 101240102 => '新建县',
 101240103 => '南昌县',
 101240104 => '安义县',
 101240105 => '进贤县',
 101240201 => '九江市',
 101240202 => '瑞昌市',
 101240203 => '庐山区',
 101240204 => '武宁县',
 101240205 => '德安县',
 101240206 => '永修县',
 101240207 => '湖口县',
 101240208 => '彭县泽',
 101240209 => '星子县',
 101240210 => '都昌县',
 101240212 => '修水县',
 101240301 => '上饶市',
 101240302 => '鄱阳县',
 101240303 => '婺源县',
 101240305 => '余干县',
 101240306 => '万年县',
 101240307 => '德兴市',
 101240308 => '上饶县',
 101240309 => '弋阳县',
 101240310 => '横峰县',
 101240311 => '铅山县',
 101240312 => '玉山县',
 101240313 => '广丰县',
 101240401 => '抚州市',
 101240402 => '广昌县',
 101240403 => '乐安县',
 101240404 => '崇仁县',
 101240405 => '金溪县',
 101240406 => '资溪县',
 101240407 => '宜黄县',
 101240408 => '南城县',
 101240409 => '南丰县',
 101240410 => '黎川县',
 101240411 => '东乡县',
 101240501 => '宜春市',
 101240502 => '铜鼓县',
 101240503 => '宜丰县',
 101240504 => '万载县',
 101240505 => '上高县',
 101240506 => '靖安县',
 101240507 => '奉新县',
 101240508 => '高安市',
 101240509 => '樟树市',
 101240510 => '丰城市',
 101240601 => '吉安市',
 101240602 => '吉安县',
 101240603 => '吉水县',
 101240604 => '新干县',
 101240605 => '峡江县',
 101240606 => '永丰县',
 101240607 => '永新县',
 101240608 => '井冈山市',
 101240609 => '万安县',
 101240610 => '遂川县',
 101240611 => '泰和县',
 101240612 => '安福县',
 101240613 => '宁冈县',
 101240701 => '赣州市',
 101240702 => '崇义县',
 101240703 => '上犹县',
 101240704 => '南康市',
 101240705 => '大余县',
 101240706 => '信丰县',
 101240707 => '宁都县',
 101240708 => '石城县',
 101240709 => '瑞金市',
 101240710 => '于都县',
 101240711 => '会昌县',
 101240712 => '安远县',
 101240713 => '全南县',
 101240714 => '龙南县',
 101240715 => '定南县',
 101240716 => '寻乌县',
 101240717 => '兴国县',
 101240718 => '赣县',
 101240801 => '景德镇市',
 101240802 => '乐平市',
 101240803 => '浮梁县',
 101240901 => '萍乡市',
 101240902 => '莲花县',
 101240903 => '上栗县',
 101240904 => '安源区',
 101240905 => '芦溪县',
 101240906 => '湘东区',
 101241001 => '新余市',
 101241002 => '分宜县',
 101241101 => '鹰潭市',
 101241102 => '余江县',
 101241103 => '贵溪市',
 101250101 => '长沙市',
 101250102 => '宁乡县',
 101250103 => '浏阳市',
 101250104 => '马坡岭',
 101250105 => '望城县',
 101250201 => '湘潭市',
 101250202 => '韶山市',
 101250203 => '湘乡市',
 101250301 => '株洲市',
 101250302 => '攸县',
 101250303 => '醴陵市',
 101250305 => '茶陵县',
 101250306 => '炎陵县',
 101250401 => '衡阳市',
 101250402 => '衡山县',
 101250403 => '衡东县',
 101250404 => '祁东县',
 101250405 => '衡阳县',
 101250406 => '常宁市',
 101250407 => '衡南县',
 101250408 => '耒阳市',
 101250409 => '南岳县',
 101250501 => '郴州市',
 101250502 => '桂阳县',
 101250503 => '嘉禾县',
 101250504 => '宜章县',
 101250505 => '临武县',
 101250507 => '资兴市',
 101250508 => '汝城县',
 101250509 => '安仁县',
 101250510 => '永兴县',
 101250511 => '桂东县',
 101250512 => '苏仙区',
 101250601 => '常德市',
 101250602 => '安乡县',
 101250603 => '桃源县',
 101250604 => '汉寿县',
 101250605 => '澧县',
 101250606 => '临澧县',
 101250607 => '石门县',
 101250608 => '津市市',
 101250700 => '益阳市',
 101250701 => '赫山区',
 101250702 => '南县',
 101250703 => '桃江县',
 101250704 => '安化县',
 101250705 => '沅江市',
 101250801 => '娄底市',
 101250802 => '双峰县',
 101250803 => '冷水江市',
 101250805 => '新化县',
 101250806 => '涟源市',
 101250901 => '邵阳市',
 101250902 => '隆回县',
 101250903 => '洞口县',
 101250904 => '新邵县',
 101250905 => '邵东县',
 101250906 => '绥宁县',
 101250907 => '新宁县',
 101250908 => '武冈市',
 101250909 => '城步县',
 101250910 => '邵阳县',
 101251001 => '岳阳市',
 101251002 => '华容县',
 101251003 => '湘阴县',
 101251004 => '汨罗市',
 101251005 => '平江县',
 101251006 => '临湘市',
 101251101 => '张家界市',
 101251102 => '桑植县',
 101251103 => '慈利县',
 101251104 => '武陵源区',
 101251201 => '怀化市',
 101251203 => '沅陵县',
 101251204 => '辰溪县',
 101251205 => '靖州县',
 101251206 => '会同县',
 101251207 => '通道县',
 101251208 => '麻阳县',
 101251209 => '新晃县',
 101251210 => '芷江县',
 101251211 => '溆浦县',
 101251212 => '中方县',
 101251213 => '洪江市',
 101251401 => '永州市',
 101251402 => '祁阳县',
 101251403 => '东安县',
 101251404 => '双牌县',
 101251405 => '道县',
 101251406 => '宁远县',
 101251407 => '江永县',
 101251408 => '蓝山县',
 101251409 => '新田县',
 101251410 => '江华县',
 101251411 => '冷水滩区',
 101251501 => '吉首市',
 101251502 => '保靖县',
 101251503 => '永顺县',
 101251504 => '古丈县',
 101251505 => '凤凰县',
 101251506 => '泸溪县',
 101251507 => '龙山县',
 101251508 => '花垣县',
 101260101 => '贵阳市',
 101260102 => '白云区',
 101260103 => '花溪区',
 101260104 => '乌当区',
 101260105 => '息烽县',
 101260106 => '开阳县',
 101260107 => '修文县',
 101260108 => '清镇市',
 101260109 => '小河区',
 101260110 => '云岩区',
 101260111 => '南明区',
 101260201 => '遵义市',
 101260202 => '遵义县',
 101260203 => '仁怀市',
 101260204 => '绥阳县',
 101260205 => '湄潭县',
 101260206 => '凤冈县',
 101260207 => '桐梓县',
 101260208 => '赤水市',
 101260209 => '习水县',
 101260210 => '道真县',
 101260211 => '正安县',
 101260212 => '务川县',
 101260213 => '余庆县',
 101260214 => '汇川县',
 101260215 => '红花岗',
 101260301 => '安顺市',
 101260302 => '普定县',
 101260303 => '镇宁县',
 101260304 => '平坝县',
 101260305 => '紫云县',
 101260306 => '关岭县',
 101260401 => '都匀市',
 101260402 => '贵定县',
 101260403 => '瓮安县',
 101260404 => '长顺县',
 101260405 => '福泉市',
 101260406 => '惠水县',
 101260407 => '龙里县',
 101260408 => '罗甸县',
 101260409 => '平塘县',
 101260410 => '独山县',
 101260411 => '三都县',
 101260412 => '荔波县',
 101260501 => '凯里市',
 101260502 => '岑巩县',
 101260503 => '施秉县',
 101260504 => '镇远县',
 101260505 => '黄平县',
 101260507 => '麻江县',
 101260508 => '丹寨县',
 101260509 => '三穗县',
 101260510 => '台江县',
 101260511 => '剑河县',
 101260512 => '雷山县',
 101260513 => '黎平县',
 101260514 => '天柱县',
 101260515 => '锦屏县',
 101260516 => '榕江县',
 101260517 => '从江县',
 101260601 => '铜仁市',
 101260602 => '江口县',
 101260603 => '玉屏县',
 101260604 => '万山县',
 101260605 => '思南县',
 101260607 => '印江县',
 101260608 => '石阡县',
 101260609 => '沿河县',
 101260610 => '德江县',
 101260611 => '松桃县',
 101260701 => '毕节市',
 101260702 => '赫章县',
 101260703 => '金沙县',
 101260704 => '威宁县',
 101260705 => '大方县',
 101260706 => '纳雍县',
 101260707 => '织金县',
 101260708 => '黔西县',
 101260801 => '水城',
 101260802 => '六枝',
 101260804 => '盘县',
 101260901 => '兴义市',
 101260902 => '晴隆县',
 101260903 => '兴仁县',
 101260904 => '贞丰县',
 101260905 => '望谟县',
 101260907 => '安龙县',
 101260908 => '册亨县',
 101260909 => '普安县',
 101270101 => '成都市',
 101270102 => '龙泉驿区',
 101270103 => '新都县',
 101270104 => '温江区',
 101270105 => '金堂县',
 101270106 => '双流县',
 101270107 => '郫县',
 101270108 => '大邑县',
 101270109 => '蒲江县',
 101270110 => '新津县',
 101270111 => '都江堰市',
 101270112 => '彭州市',
 101270113 => '邛崃市',
 101270114 => '崇州市',
 101270201 => '攀枝花市',
 101270202 => '仁和区',
 101270203 => '米易县',
 101270204 => '盐边县',
 101270301 => '自贡市',
 101270302 => '富顺县',
 101270303 => '荣县',
 101270401 => '绵阳市',
 101270402 => '三台县',
 101270403 => '盐亭县',
 101270404 => '安县',
 101270405 => '梓潼县',
 101270406 => '北川县',
 101270407 => '平武县',
 101270408 => '江油市',
 101270501 => '南充市',
 101270502 => '南部县',
 101270503 => '营山县',
 101270504 => '蓬安县',
 101270505 => '仪陇县',
 101270506 => '西充县',
 101270507 => '阆中市',
 101270601 => '达州市',
 101270602 => '宣汉县',
 101270603 => '开江县',
 101270604 => '大竹县',
 101270605 => '渠县',
 101270606 => '万源市',
 101270607 => '达川县',
 101270608 => '达县',
 101270701 => '遂宁市',
 101270702 => '蓬溪县',
 101270703 => '射洪县',
 101270801 => '广安市',
 101270802 => '岳池县',
 101270803 => '武胜县',
 101270804 => '邻水县',
 101270805 => '华蓥市',
 101270901 => '巴中市',
 101270902 => '通江县',
 101270903 => '南江县',
 101270904 => '平昌县',
 101271001 => '泸州市',
 101271003 => '泸县',
 101271004 => '合江县',
 101271005 => '叙永县',
 101271006 => '古蔺县',
 101271007 => '纳溪县',
 101271101 => '宜宾市',
 101271103 => '宜宾县',
 101271104 => '南溪县',
 101271105 => '江安县',
 101271106 => '长宁县',
 101271107 => '高县',
 101271108 => '珙县',
 101271109 => '筠连县',
 101271110 => '兴文县',
 101271111 => '屏山县',
 101271201 => '内江市',
 101271202 => '东兴区',
 101271203 => '威远县',
 101271204 => '资中县',
 101271205 => '隆昌县',
 101271301 => '资阳市',
 101271302 => '安岳县',
 101271303 => '乐至县',
 101271304 => '简阳市',
 101271401 => '乐山市',
 101271402 => '犍为县',
 101271403 => '井研县',
 101271404 => '夹江县',
 101271405 => '沐川县',
 101271406 => '峨边县',
 101271407 => '马边县',
 101271408 => '峨眉',
 101271409 => '峨眉山市',
 101271501 => '眉山市',
 101271502 => '仁寿县',
 101271503 => '彭山县',
 101271504 => '洪雅县',
 101271505 => '丹棱县',
 101271506 => '青神县',
 101271601 => '凉山',
 101271603 => '木里县',
 101271604 => '盐源县',
 101271605 => '德昌县',
 101271606 => '会理县',
 101271607 => '会东县',
 101271608 => '宁南县',
 101271609 => '普格县',
 101271610 => '西昌市',
 101271611 => '金阳县',
 101271612 => '昭觉县',
 101271613 => '喜德县',
 101271614 => '冕宁县',
 101271615 => '越西县',
 101271616 => '甘洛县',
 101271617 => '雷波县',
 101271618 => '美姑县',
 101271619 => '布拖县',
 101271701 => '雅安市',
 101271702 => '名山县',
 101271703 => '荥经县',
 101271704 => '汉源县',
 101271705 => '石棉县',
 101271706 => '天全县',
 101271707 => '芦山县',
 101271708 => '宝兴县',
 101271801 => '甘孜县',
 101271802 => '康定县',
 101271803 => '泸定县',
 101271804 => '丹巴县',
 101271805 => '九龙县',
 101271806 => '雅江县',
 101271807 => '道孚县',
 101271808 => '炉霍县',
 101271809 => '新龙县',
 101271810 => '德格县',
 101271811 => '白玉县',
 101271812 => '石渠县',
 101271813 => '色达县',
 101271814 => '理塘县',
 101271815 => '巴塘县',
 101271816 => '乡城县',
 101271817 => '稻城县',
 101271818 => '得荣县',
 101271901 => '阿坝县',
 101271902 => '汶川县',
 101271903 => '理县',
 101271904 => '茂县',
 101271905 => '松潘县',
 101271906 => '九寨沟县',
 101271907 => '金川县',
 101271908 => '小金县',
 101271909 => '黑水县',
 101271910 => '马尔康县',
 101271911 => '壤塘县',
 101271912 => '若尔盖县',
 101271913 => '红原县',
 101272001 => '德阳市',
 101272002 => '中江县',
 101272003 => '广汉市',
 101272004 => '什邡市',
 101272005 => '绵竹市',
 101272006 => '罗江县',
 101272101 => '广元市',
 101272102 => '旺苍县',
 101272103 => '青川县',
 101272104 => '剑阁县',
 101272105 => '苍溪县',
 101280101 => '广州市',
 101280102 => '番禺区',
 101280103 => '从化市',
 101280104 => '增城市',
 101280105 => '花都区',
 101280201 => '韶关',
 101280202 => '乳源县',
 101280203 => '始兴县',
 101280204 => '翁源县',
 101280205 => '乐昌市',
 101280206 => '仁化县',
 101280207 => '南雄市',
 101280208 => '新丰县',
 101280209 => '曲江区',
 101280210 => '浈江区',
 101280211 => '武江区',
 101280301 => '惠州市',
 101280302 => '博罗县',
 101280303 => '惠阳市',
 101280304 => '惠东县',
 101280305 => '龙门县',
 101280401 => '梅州市',
 101280402 => '兴宁市',
 101280403 => '蕉岭县',
 101280404 => '大埔县',
 101280406 => '丰顺县',
 101280407 => '平远县',
 101280408 => '五华县',
 101280409 => '梅县',
 101280501 => '汕头市',
 101280502 => '潮阳区',
 101280503 => '澄海区',
 101280504 => '南澳县',
 101280601 => '深圳市',
 101280701 => '珠海市',
 101280702 => '斗门区',
 101280703 => '金湾区',
 101280800 => '佛山市',
 101280801 => '顺德市',
 101280802 => '三水市',
 101280803 => '南海市',
 101280804 => '高明市',
 101280901 => '肇庆市',
 101280902 => '广宁县',
 101280903 => '四会市',
 101280905 => '德庆县',
 101280906 => '怀集县',
 101280907 => '封开县',
 101280908 => '高要市',
 101281001 => '湛江市',
 101281002 => '吴川市',
 101281003 => '雷州市',
 101281004 => '徐闻县',
 101281005 => '廉江市',
 101281006 => '赤坎区',
 101281007 => '遂溪县',
 101281008 => '坡头区',
 101281009 => '霞山区',
 101281010 => '麻章区',
 101281101 => '江门市',
 101281103 => '开平市',
 101281104 => '新会',
 101281105 => '恩平市',
 101281106 => '台山市',
 101281107 => '蓬江',
 101281108 => '鹤山市',
 101281109 => '江海',
 101281201 => '河源市',
 101281202 => '紫金县',
 101281203 => '连平县',
 101281204 => '和平县',
 101281205 => '龙川县',
 101281206 => '东源县',
 101281301 => '清远市',
 101281302 => '连南县',
 101281303 => '连州市',
 101281304 => '连山县',
 101281305 => '阳山县',
 101281306 => '佛冈县',
 101281307 => '英德市',
 101281308 => '清新县',
 101281401 => '云浮市',
 101281402 => '罗定市',
 101281403 => '新兴县',
 101281404 => '郁南县',
 101281406 => '云安县',
 101281501 => '潮州市',
 101281502 => '饶平县',
 101281503 => '潮安县',
 101281601 => '东莞市',
 101281701 => '中山市',
 101281801 => '阳江市',
 101281802 => '阳春市',
 101281803 => '阳东县',
 101281804 => '阳西县',
 101281901 => '揭阳市',
 101281902 => '揭西县',
 101281903 => '普宁市',
 101281904 => '惠来县',
 101281905 => '揭东县',
 101282001 => '茂名市',
 101282002 => '高州市',
 101282003 => '化州市',
 101282004 => '电白县',
 101282005 => '信宜市',
 101282006 => '茂港区',
 101282101 => '汕尾市',
 101282102 => '海丰县',
 101282103 => '陆丰市',
 101282104 => '陆河县',
 101290101 => '昆明市',
 101290103 => '东川区',
 101290104 => '寻甸县',
 101290105 => '晋宁县',
 101290106 => '宜良县',
 101290107 => '石林县',
 101290108 => '呈贡区',
 101290109 => '富民县',
 101290110 => '嵩明县',
 101290111 => '禄劝县',
 101290112 => '安宁市',
 101290113 => '太华山',
 101290201 => '大理市',
 101290202 => '云龙县',
 101290203 => '漾濞县',
 101290204 => '永平县',
 101290205 => '宾川县',
 101290206 => '弥渡县',
 101290207 => '祥云县',
 101290208 => '巍山县',
 101290209 => '剑川县',
 101290210 => '洱源县',
 101290211 => '鹤庆县',
 101290212 => '南涧县',
 101290301 => '红河县',
 101290302 => '石屏县',
 101290303 => '建水县',
 101290304 => '弥勒县',
 101290305 => '元阳县',
 101290306 => '绿春县',
 101290307 => '开远市',
 101290308 => '个旧市',
 101290309 => '蒙自市',
 101290310 => '屏边县',
 101290311 => '泸西县',
 101290312 => '金平县',
 101290313 => '河口县',
 101290401 => '曲靖市',
 101290402 => '沾益县',
 101290403 => '陆良县',
 101290404 => '富源县',
 101290405 => '马龙县',
 101290406 => '师宗县',
 101290407 => '罗平县',
 101290408 => '会泽县',
 101290409 => '宣威市',
 101290501 => '保山市',
 101290503 => '龙陵县',
 101290504 => '施甸县',
 101290505 => '昌宁县',
 101290506 => '腾冲县',
 101290601 => '文山市',
 101290602 => '西畴县',
 101290603 => '马关县',
 101290604 => '麻栗坡县',
 101290605 => '砚山县',
 101290606 => '丘北县',
 101290607 => '广南县',
 101290608 => '富宁县',
 101290701 => '玉溪市',
 101290702 => '澄江县',
 101290703 => '江川县',
 101290704 => '通海县',
 101290705 => '华宁县',
 101290706 => '新平县',
 101290707 => '易门县',
 101290708 => '峨山县',
 101290709 => '元江县',
 101290801 => '楚雄市',
 101290802 => '大姚县',
 101290803 => '元谋县',
 101290804 => '姚安县',
 101290805 => '牟定县',
 101290806 => '南华县',
 101290807 => '武定县',
 101290808 => '禄丰县',
 101290809 => '双柏县',
 101290810 => '永仁县',
 101290901 => '普洱市',
 101290902 => '景谷县',
 101290903 => '景东县',
 101290904 => '澜沧县',
 101290906 => '墨江县',
 101290907 => '江城县',
 101290908 => '孟连县',
 101290909 => '西盟县',
 101290911 => '镇沅县',
 101290912 => '宁洱县',
 101291001 => '昭通市',
 101291002 => '鲁甸县',
 101291003 => '彝良县',
 101291004 => '镇雄县',
 101291005 => '威信县',
 101291006 => '巧家县',
 101291007 => '绥江县',
 101291008 => '永善县',
 101291009 => '盐津县',
 101291010 => '大关县',
 101291011 => '水富县',
 101291101 => '临沧市',
 101291102 => '沧源县',
 101291103 => '耿马县',
 101291104 => '双江县',
 101291105 => '凤庆县',
 101291106 => '永德县',
 101291107 => '云县',
 101291108 => '镇康县',
 101291201 => '怒江',
 101291203 => '福贡县',
 101291204 => '兰坪县',
 101291205 => '泸水县',
 101291206 => '六库',
 101291207 => '贡山县',
 101291301 => '香格里拉县',
 101291302 => '德钦县',
 101291303 => '维西县',
 101291304 => '中甸县',
 101291401 => '丽江市',
 101291402 => '永胜县',
 101291403 => '华坪县',
 101291404 => '宁蒗县',
 101291501 => '德宏市',
 101291503 => '陇川县',
 101291504 => '盈江县',
 101291506 => '瑞丽市',
 101291507 => '梁河县',
 101291508 => '潞西市',
 101291601 => '景洪市',
 101291603 => '勐海县',
 101291605 => '勐腊县',
 101300101 => '南宁市',
 101300103 => '邕宁区',
 101300104 => '横县',
 101300105 => '隆安县',
 101300106 => '马山县',
 101300107 => '上林县',
 101300108 => '武鸣县',
 101300109 => '宾阳县',
 101300201 => '崇左市',
 101300202 => '天等县',
 101300203 => '龙州县',
 101300204 => '凭祥市',
 101300205 => '大新县',
 101300206 => '扶绥县',
 101300207 => '宁明县',
 101300301 => '柳州市',
 101300302 => '柳城县',
 101300304 => '鹿寨县',
 101300305 => '柳江县',
 101300306 => '融安县',
 101300307 => '融水县',
 101300308 => '三江县',
 101300401 => '来宾市',
 101300402 => '忻城县',
 101300403 => '金秀县',
 101300404 => '象州县',
 101300405 => '武宣县',
 101300406 => '合山市',
 101300501 => '桂林市',
 101300503 => '龙胜县',
 101300504 => '永福县',
 101300505 => '临桂县',
 101300506 => '兴安县',
 101300507 => '灵川县',
 101300508 => '全州县',
 101300509 => '灌阳县',
 101300510 => '阳朔县',
 101300511 => '恭城县',
 101300512 => '平乐县',
 101300513 => '荔蒲县',
 101300514 => '资源县',
 101300601 => '梧州市',
 101300602 => '藤县',
 101300604 => '苍梧县',
 101300605 => '蒙山县',
 101300606 => '岑溪市',
 101300701 => '贺州市',
 101300702 => '昭平县',
 101300703 => '富川县',
 101300704 => '钟山县',
 101300801 => '贵港市',
 101300802 => '桂平市',
 101300803 => '平南县',
 101300901 => '玉林市',
 101300902 => '博白县',
 101300903 => '北流市',
 101300904 => '容县',
 101300905 => '陆川县',
 101300906 => '兴业县',
 101301001 => '百色市',
 101301002 => '那坡县',
 101301003 => '田阳县',
 101301004 => '德保县',
 101301005 => '靖西县',
 101301006 => '田东县',
 101301007 => '平果县',
 101301008 => '隆林县',
 101301009 => '西林县',
 101301010 => '乐业县',
 101301011 => '凌云县',
 101301012 => '田林县',
 101301101 => '钦州市',
 101301102 => '浦北县',
 101301103 => '灵山县',
 101301201 => '河池市',
 101301202 => '天峨县',
 101301203 => '东兰县',
 101301204 => '巴马县',
 101301205 => '环江县',
 101301206 => '罗城县',
 101301207 => '宜州市',
 101301208 => '凤山县',
 101301209 => '南丹县',
 101301210 => '都安县',
 101301211 => '大化县',
 101301301 => '北海市',
 101301302 => '合浦县',
 101301303 => '涠洲岛',
 101301401 => '防城港市',
 101301402 => '上思县',
 101301403 => '东兴市',
 101301405 => '防城区',
 101310101 => '海口市',
 101310201 => '三亚市',
 101310202 => '东方市',
 101310203 => '临高县',
 101310204 => '澄迈县',
 101310205 => '儋州市',
 101310206 => '昌江县',
 101310207 => '白沙县',
 101310208 => '琼中县',
 101310209 => '定安县',
 101310210 => '屯昌县',
 101310211 => '琼海市',
 101310212 => '文昌市',
 101310214 => '保亭县',
 101310215 => '万宁市',
 101310216 => '陵水县',
 101310217 => '西沙',
 101310220 => '南沙岛',
 101310221 => '乐东县',
 101310222 => '五指山市',
 101320101 => '香港',
 101320103 => '新界',
 101330101 => '澳门',
 101330102 => '氹仔岛',
 101330103 => '路环岛',
 101231001 => '钓鱼岛',
 101340101 => '台北市',
 101340102 => '桃园县',
 101340103 => '新竹市',
 101340104 => '宜兰县',
 101340201 => '高雄市',
 101340202 => '嘉义市',
 101340203 => '台南市',
 101340204 => '台东县',
 101340205 => '屏东县',
 101340401 => '台中市',
 101340402 => '苗栗县',
 101340403 => '彰化县',
 101340404 => '南投县',
 101340405 => '花莲县',
 101340406 => '云林县',
 102010100 => '首尔',
 103010100 => '东京',
 103020100 => '大阪',
 103040100 => '札幌',
 103050100 => '福冈',
 103090100 => '京都',
 104010100 => '新加坡',
 105010100 => '吉隆坡',
 106010100 => '曼谷',
 107010100 => '河内',
 107020100 => '胡志明市',
 108010100 => '仰光',
 111010100 => '雅加达',
 111080100 => '登巴萨',
 112010100 => '德黑兰',
 113010100 => '新德里',
 113030100 => '孟买',
 113090100 => '斯利那加',
 114010100 => '伊斯兰堡',
 114030100 => '卡拉奇',
 114040100 => '白沙瓦',
 115010100 => '塔什干',
 117010100 => '科伦坡',
 118010100 => '喀布尔',
 118030100 => '坎大哈',
 120010100 => '斯里巴加湾',
 121010100 => '巴林',
 124010100 => '阿布扎比',
 124020100 => '迪拜',
 127010100 => '平壤',
 130010100 => '阿斯塔纳',
 132010100 => '乌兰巴托',
 136010100 => '马尼拉',
 138010100 => '利雅得',
 139010100 => '大马士革',
 301010100 => '开罗',
 302010100 => '开普敦',
 302020100 => '约翰内斯堡',
 303010100 => '突尼斯-迦太基',
 304020100 => '拉各斯',
 305010100 => '阿尔及尔',
 317010100 => '内罗毕',
 321020100 => '卡萨布兰卡',
 327010100 => '达喀尔',
 332010100 => '达累斯萨拉姆',
 334010100 => '卢萨卡',
 401010100 => '华盛顿特区',
 401020101 => '迈阿密',
 401020104 => '奥兰多',
 401030101 => '亚特兰大',
 401040101 => '洛杉矶',
 401040102 => '圣弗朗西斯科',
 401060100 => '波士顿',
 401070101 => '芝加哥',
 401100101 => '西雅图',
 401110101 => '纽约',
 401120108 => '休斯敦',
 401370100 => '拉斯维加斯',
 401480100 => '火奴鲁鲁',
 404010100 => '渥太华',
 404030100 => '温哥华',
 404040100 => '多伦多',
 404130100 => '埃德蒙顿',
 404140100 => '卡尔加里',
 404220100 => '温尼伯',
 404230100 => '魁北克',
 404240100 => '蒙特利尔',
 406010100 => '哈瓦那',
 411010100 => '墨西哥城',
 416010100 => '加拉加斯',
 501010100 => '布宜诺斯艾利斯',
 502010100 => '巴西利亚',
 502220100 => '里约热内卢',
 502250100 => '圣保罗',
 503010100 => '圣地亚哥',
 504010100 => '波哥大',
 510070100 => '利马',
 201010100 => '伦敦',
 201050100 => '曼彻斯特',
 202010100 => '巴黎',
 202100100 => '马赛',
 203010100 => '柏林',
 203020100 => '法兰克福',
 203050100 => '汉堡',
 204010100 => '罗马',
 204040100 => '米兰',
 205010100 => '阿姆斯特丹',
 206010100 => '马德里',
 206020100 => '巴塞罗那',
 207010100 => '哥本哈根',
 208010100 => '莫斯科',
 210020100 => '日内瓦',
 210030100 => '苏黎世',
 211010100 => '斯德哥尔摩',
 214010100 => '里斯本',
 215020100 => '伊斯坦布尔',
 216010100 => '布鲁塞尔',
 217010100 => '维也纳',
 218010100 => '雅典',
 222010100 => '赫尔辛基-万塔',
 601020101 => '悉尼',
 601030101 => '布里斯班',
 601040101 => '阿德莱德',
 601060101 => '墨尔本',
 601070101 => '珀斯',
 606010100 => '惠灵顿',
 606020100 => '奥克兰',
 606030100 => '克莱斯特彻奇',
);

页游日志分析的流程

新的工作已经有快5个月了,有必要记录一下这个工作内容是哪些。

这是一份通过PHP程序对日志数据进行解析与分析得到结果协助运营人员的分析工作。

程序数据分析cms系统的目标用户(使用者)是公司内部运营人员以及合作方的游戏运营人员。程序本身而言与我之前的工作还是有很多不同的,没有市场调研部门,没有运营部门,没有设计部门,只有自己程序开发部,连前端也没用,有一位游戏p图的。程序两人完成所有工作需求。简而言之对比以前得到的资源是少之又少,工作性质也是枯燥无味居多。

工作流程是,游戏中写文本日志到服务器本地→(应该是通过文件同步系统同步到分析服务器)→PHP程序每小时运行一次解析分析日志→存入Mysql→PHP后台cms读取Mysql数据→运营人员使用查看。

完整的步骤就是这样

架构

网页游戏中有很多动作,比如购买,比如抽奖,比如参加活动,比如是否消费,消费了多少金币元宝,消费的数量等等。而游戏中主要统计的数据就是用户相关的留存率,用户活跃度,新增用户数,每天用户登录次数等等;还有充值相关的充值详细记录,各服充值简表,活跃用户平均收入(ARUP),充值用户平均收入(ARUUP),当天充值总额等等;还有道具活动相关的,道具消耗度,活动的参与人数消费数据等等,其中几乎每个需求中需要到的几项数据统计包括,时间服务器ID人数次数消费额度消耗数量消费占当天总额占比 ,而这些数据都从游戏日志里面来得到,比如玩家登陆的动作,对应的是这样一条日志

23:01:02 | 18140 |RoleLogin | 0C429E0A73EA0C20A85CD3B1E03A1C81 | 67_201308291943399606777822 | 请深坑 | 31 | 35091 | 27794000 | 29200000 | 13688000 | 0 | 1050 | 0 | 0 | null | 50 | 4 | 127.0.0.1 | 0 | 0

其中比如登陆人数,登陆次数,登陆时段等等统计都可以从这里得到。

那么程序需要做的是把这些日志都存入数据库,通常意义来说一条日志对应的是一个数据表,但是有时候相近的日志可以放在一个表中。解析日志存放数据完成后就开始进行需求的检索分析(比如统计人数,次数,消耗数量等),完成后既写入一个统计信息的表里面,这样做的意义是在于不必要去查几十万行的日志表,只用检索统计信息表就足够了,运营人员会很快的得到结果。

比如登陆登出,有些字段信息很多都是一致的,那么可以存入一张数据表Login,只需加入一个type字段用来标识login或者logou。作为一款在大平台上投放的网页游戏,人数还是有那么多的,对应的全服玩家登录日志信息会很大,不可能全部放在一张数据表中,那么对应就需要分表了,分表策略很简单,按天分表或者按月分表,数据量很少的情况下选择不分表。这样的分表方式有个好处,第一清晰明了,按天分表每天的数据都可见可查,有具体的时间线;第二程序处理方便,数据表每天自动生成,数据分析读取都按天进行依次遍历检索得到结果。第三日志格式写入方式决定的,日志都是按月做文件夹,天小时为文件名,很好的进行了区分。这里就不适合分区分表的存储,首先不知道分区总表的数量,其次程序检索不是很方便。

一条日志对应的就是一个table class,也就是一个table model,他们都有共同的父类logmainTable。

每个table model主要的处理方法有run()检测当天是否存在表,statis()数据表的分析且写入tj_table(统计信息表)。

入口文件是采用php cli模式进行运行,其中的主要参数就是startdate/enddate/del/ 就是在这个时间区间中的表进行分析,如加上del参数则那就是全部重新进行解析(即从日志里面读取记录在此写入mysql)。

日志解析流程

讲日志结构存储为mysql对应的记录的过程。

日志分析流程

在日志解析完成入库后。接下来会立刻执行日志分析,也就是说这是一个动作,两个步骤依次执行的。

需求分析流程

假如来了一个工作需求,比如游戏新开了一个活动,运营的人想着知道这个活动的收益情况以及用户人数、次数信息。运营的通知游戏那边写一个日志type,接下来的事情就是由我们来做了。那么工作的流程就是这样

推荐一下在线图片制作网站 http://www.processon.com/ 挺好用的,各种关系图,原型图都能画。用起来很方便,随时自动保存。还支持下载导出和在线分享。

MongoDB实现mysql主键autoincrement

MongoDB新建表默认有一个_id字段来作为autoincrement自增实现,而这个_id字段类型是objectid类型(objectid 是12字节的BSON类型)。ObjectId的详细解释。

而现在因为数据库迁移,将项目中原mysql的一些locations表移植到mongodb上面。对于locations 使用mongodb 2d loc 索引能够更快的进行数据检索,同时避免原mysql需要经过复杂的经纬度查询。

那么数据迁移后主要的要求有

1、数据库表的迁移不会影响表与表之间的关系,继续保持表之间的主外键关系。

2、lat 与 lng 字段在mongodb 新表中合并为一个字段loc,同时对loc加以2d索引。

对于第二点主要影响是在代码之中,可以看看这里。就不做详细描述了。

那么现在主要要做的事情就是,维持表与表的关系与主外键关系。我们以event 与 对应的 event_locations为例

event中有一个外键location_id,location_id就是event_locations的主键id;而现在mongodb中新建event_locations表,同时要保持event_locations与event的主外键引用关系。

因为mongodb表中默认有着_id这样一个主键,那现在我们的数据迁移以及mongodb event_locations表结构构建有两种选择

1、event_locations 主键以_id,即将原mysql event_locations 的id 替换为 _id。也就是如下表结构


{ "_id" : ObjectId("51243bdb383ca83413000000"),  "topic_id" : 103874, "name" : "xixi aaahah", "address" : "xxxbbbb cccccc", "loc" : [ 30.11, 112.02 ], "created" : 1361329114, "updated" : 0 }

优缺点分析

优点就是 使用默认的_id主键值,速度快检索方便。_id自增不必维护,有mongodb自行完成。

缺点很明显 event中的location_id外键,原来类型是int,现在改用_id objectid类型作为主键id,location_id类型必须要更改为char(32)了。且程序代码还需要一定的更改。

最大的缺点就是目前项目已经上线,直接更改数据库表可能会带来不可预知的问题;对于运营维护可能带来很大压力。且对于目前event已有的location_id数据需要进行同步更新。

2、保持原来的event_locations表结构,使用id继续维持与event location_id的主外键关系。


{ "_id" : ObjectId("51243bdb383ca83413000000"), "id" : 2354, "topic_id" : 103874, "name" : "xixi aaahah", "address" : "xxxbbbb cccccc", "loc" : [ 30.11, 112.02 ], "created" : 1361329114, "updated" : 0 }

优缺点分析

优点 不必去更改event location_id的数据类型,且location_id的数据不必修改;只用将event_locations数据完整导入到新表中即可。

缺点 要维持id的自增属性,而mongodb默认是_id才有自增属性的,所以我们要用某种方法实现id的自增实现。_id会占用存储空间(默认的_id原本可以删除的,但考虑到以后可能会用到就保存了)。

我现在采用的是第2种方式,主要原因如下

1、项目已上线,且已经有了用户数据;对于直接更改数据表与操作数据会带来很大的风险。强烈不推荐。

2、event_locations数据几乎不用同步,只需要单向导入到mongodb 新表就好。

3、程序代码变动最小,目前程序代码更改就是新的loc(lng+lat)存储与id的自增实现。而第一种除了这些,还需要把所有的id替换为_id,数据类型也要从int变为string。

在确定了解决方法之后那么现在主要的难题就是怎么实现id的自增?

目前也有两种方法

1、传统实现,每次在event_locations insert之前,全表id desc排序得到最大的id值,对id值+1既得到了最新的id值从而实现了自增。

2、使用mongodb的findAndModify()方法实现,独立一个ids表用来记录所有可能需要id自增的值。

同样简要说明下这两种实现id自增的方法优劣。

第一种方法

优点 不用构建ids表(工具表),免于维护

缺点 每次都要进行全表索引排序且查找到最大id那条记录,在进行+1

第二种

优点 findandmodify() 原子操作(了解原子操作请查看这里),查找并修改,直接返回当前操作的这个文档内容。

缺点 要构建维护ids表,即项目中所有表可能需要自增的表都有一条记录存在于其中。

无论采用哪种方法都要修改程序代码,即id autoincrement部分需要程序实现。我采用的是第二种方法。

主要考虑还是性能问题,避免每次insert之前都要对event_locations进行全部的索引排序。而维护ids这样的表会很简单且性能上忽略不计,详细表结构如下


{ "_id" : ObjectId("5147ca1a08c2c1bc12abdb4c"), "id" : 2356, "tablename" : "topic" }

// _id 默认主键,mongodb自增

// id  表中自增主键值

// tablename 表名

获取下一个id的核心代码,继承了mongodb的ids Model。其中一个方法专门来做这个事情。


/**
* 构建ids用于存储所有表中需要自增的id值,结合findandmodify()来返回对应表最新的id
* @param string $tablename 表名
* @return int
*/
public function get_next_id($tablename = 'topic'){

// db.ids.findAndModify({update:{$inc: {id:1}},query:{tablename:'topic'},new:true});
$data = array(
'findAndModify' => 'ids',
'update' => array( '$inc' => array( 'id' => 1)),
'query' => array('tablename' => $tablename),
'new' => true
);
$idrecord = $this->db()->command( $data );
$newid = $idrecord['value'];

return $newid['id'];
}

// 等同于db.ids.findAndModify({query: {tablename: 'topic'}, update: {$inc: {id: 1}}});

这样在获取到最新的id值后,insert event_locations 时需要将id加入到sql语句中(原先id是autoincrement,id字段没有在sql中)。之后也就是同样的业务流程了。

扩展:

有时候可能会遇到一些奇怪的需求,比如一张表里面有两个自增id要实现。。。如此,上面的表结构就不能实现了。

<pre>// _id 默认主键,mongodb自增
// fieldname  表中自增字段名
// fieldval 表中自增id值
// tablename 表名</pre>

如此就行了哦

有时候也会碰到一些奇怪的需求,比如要从指定的值如200开始id自增。

那么其实用这样一句就可以了

db.ids.findAndModify({query: {tablename: 'topic'}, update: {id: 200,tablename: 'topic'}});
代码实现上就是注意要update时要把tablename也要传递进去了,因为update会更新所以的记录,如果不写上tablename那么返回的文档就只有一个id字段了。。。有需要的自己扩展下吧

需要注意的是event_locations id现在不是主键也没有任何索引,除非你加上了;所以这里还是强烈推荐加上索引

db.event_locations.ensureIndex({id: 1});

参考

在MongoDB中模拟Auto Increment

Create an Auto-Incrementing Sequence Field

findandmodify命令

“笔记”项目缓存应用

有感于Web应用的缓存设计模式这篇文章里面的缓存使用设计。

其实缓存方面确实可以大做文章,看看这位大牛的理念

  • 以减少数据库服务器磁盘IO为最终目的,而不是减少发送到数据库的SQL条数。实际上使用ORM,会显著增加SQL条数,有时候会成倍增加SQL。
  • 数据库schema设计的取向是尽量设计 细颗粒度 的表,表和表之间用外键关联,颗粒度越细,缓存对象的单位越小,缓存的应用场景越广泛
  • 尽量避免多表关联查询,尽量拆成多个表单独的主键查询,尽量多制造 n + 1 条查询,不要害怕“臭名昭著”的 n + 1 问题,实际上 n + 1 才能有效利用ORM缓存

拆分n+1条查询的方式,看起来似乎非常违反大家的直觉,但实际上这是真理,我实践经验证明:数据库服务器的瓶颈往往是磁盘IO,而不是SQL并发数量。因此 拆分n+1条查询本质上是以增加n条SQL语句为代价,简化复杂SQL,换取数据库服务器磁盘IO的降低 当然这样做以后,对于ORM来说,有额外的好处,就是可以高效的使用缓存了。

这里我看到了这样的一个新概念,就是“数据库的瓶颈往往是磁盘IO,而不是SQL的并发数量”;这个说法我也不知道是否正确的,没有经历过百万千万数据级别的研究与维护;不过可以学习一下。

确实如同该博主所说,对于一个没有太多访问量的网站有些过度设计,不过如果用户或数据量突然飙升情况下可以以较少的代码更改,实现更高的承载力;当然了,在网站最初设计时,这些因素也应当考虑进去;之前我做的mpf就是没有考虑这些问题,导致高峰时期(1w+ 并发)都访问一个专题页面内容,而次次几乎都会读取数据库服务器,差点导致宕机,后来对于专题内呈现的非动态数据内容都进行了memcached缓存,这样应该好多了。

从数据读取已经ORM有效性来说,最小化schema表设计确实很有效果

1、主键查询mysql表会buffer数据缓存。

2、ORM关系很透明明了

3、ORM model对象可以直接进行数据缓存(目前公司有一个项目就是如此)

如一个微博系统,微博一张表,微博附属信息一张表,微博插入的音乐,图像,视频,评论登录都是独立一张表,全部用id与微博主表关联起来。

按照column拆表实现细粒度对象缓存

数据库的瓶颈往往在磁盘IO上,所以应该尽量避免对大表的扫描。传统的拆表是按照row去拆分,保持表的体积不会过大,但是缺点是造成应用代码复杂度很高;使用ORM缓存的办法,则是按照column进行拆表,原则一般是:

  • 将大字段拆分出来,放在一个单独的表里面,表只有主键和大字段,外键放在主表当中
  • 将不参与where条件和统计查询的字段拆分出来,放在独立的表中,外键放在主表当中

按照column拆表本质上是一个去关系化的过程。主表只保留参与关系运算的字段,将非关系型的字段剥离到关联表当中,关联表仅允许主键查询,以Key-Value DB的方式来访问。因此这种缓存设计模式本质上是一种SQLDB和NoSQLDB的混合架构设计

数据库设计分表这个应该很常见了,常用字段一个表,非常用字段可以另一个表;同时大数据字段单独一表。

数据一致性问题

使用缓存,必须要考虑数据一致性问题。写入保持同步即可,在读取时需要首先检索cache里面有没有这样的数据,有着读取,无则读取数据并写入缓存;

主要麻烦的地方就是在于update与delete,在原数据信息更新时,缓存数据也必须更新了,否则数据会不一致。不论是内存缓存还是文件脚本缓存或是html页面缓存都是这样的。区别在于是程序主动更新还是用户访问的被动更新。

有些开源cms 采用的就是用户被动访问更新,有人访问;采取cache中去读取更新。不过我更喜欢程序主动更新。如上文博主采用的以文章update更新时间+id作为key来存储缓存信息,在文章原数据update之后,update更新时间一定会被修改,那么读取时cache中就没有了这样的文章内容了,但是原来的文章数据缓存还是存在的,只能等待cache服务器进行自动抛弃更新。这样的话cache中就可能会多很多冗余数据了,对于cache服务器检索查询一定有影响吧。

我的选择就是在文章数据更新时直接update cache或者是不update cache 但是一定把原文章 cache数据清除掉。

在用户删除文章时,先对cache里面的数据进行清除,然后进行数据库的原数据删除。

今天抽空把daynote.sinaapp.com 中的markdown转换后的数据那一层进行缓存,缓存时间是7200s,对于此类非公开的私密日记。用户访问查看后一般都会选择离开,不会时时刻刻进行刷新数据请求,7200s数据缓存搓搓有余了。

原先的xhzd.sinaapp.com也是使用大量的memcache缓存,其中列表都使用了;但是考虑到sae上面的memcache额度,字详细那一块没有缓存。目前几乎没什么访问量,也就没有进行缓存操作了。如果访问量大,那么可以把几乎整个字典都存入了缓存了。

SAE上面使用memcache的方法请看这里

mongodb初学习

目前学习使用mongodb,主要是一个项目中使用到了地理位置服务相关的内容。比如发布一个活动,客户端自动将当前位置获取到一同存储到后端;可以利用数据库中的地理位置信息实现如“附近的活动,附近的人;在地图上展现附近的活动、人”等功能。

原先的这些location功能实现是一并采用mysql建表实现的。

[mysql]

CREATE TABLE `i_event_locations` (
`event_id` int(10) unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘活动id’,
`name` varchar(255) NOT NULL DEFAULT ” COMMENT ‘Name’,
`address` varchar(255) NOT NULL COMMENT ‘Address’,
`lat` double NOT NULL DEFAULT ‘0’ COMMENT ‘纬度’,
`lng` double NOT NULL DEFAULT ‘0’ COMMENT ‘经度’,
`created` int(10) unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘创建时间’,
`updated` int(10) unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘更新时间’,
UNIQUE KEY `event_id` (`event_id`) USING BTREE
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT=’Location Table’

[/mysql]

表结构就是如此,需要说明的是活动event与location是一对一的关系,event_id就是活动id即是外键也是主键。

这样的结构在后端业务中怎么实现呢,主要思路就是通过传入经纬度得到一个表达式distance,在event与location进行join联表查询时按distance进行desc的排序,得到最近的event list;如果需要限制在10km之间,那么就需要加上where distance < 10000


/**
* 返回计算距离的SQL语句表达式
* @param $lat
* @param $lng
*/
private function get_distance_exp($lat, $lng){
return DB::expr("(2 * 6378.137* ASIN(SQRT(POW(SIN(PI() * ($lat - lat)/360), 2) + COS(PI() * {$lat} / 180)
* COS(lat * PI() / 180) * POW(SIN(PI() * ({$lng} - lng) / 360), 2))))*1000");
}

就是上面这个东东,我也没看明白是什么原理。总之它返回的是以米为单位的一个数据值。

弊端确实有,需要联表查询且还要计算表达式可能会比较慢。(详细性能测试暂没有进行)

确实需要了解下性能方面的问题了,不能只听别人说mysql这样处理很慢就觉得慢,听别人说mongodb用的快就用;

mongodb是文档化schema free(自由格式)的数据库,开源有商业团队支持,高速发展,应用广泛;与mysql不存在谁取代谁的问题,只是什么环境下哪种数据库更适合。

回到那个地理位置检索的问题,将i_event_locations转变为mongodb collection后,可以根据建立的loc二维索引进行当前location 经纬度的检索,这样就得到了所有的event_id值,在通过这些event_id的到符合需求条件的event活动;

这样也就不用进行联表查询以及地理位置的表达式运算(这部分目前直接交予索引处理了);

MongoDB的特性 来自blog.nosqlfan.com

  • 简单的查询语句,没有Join操作
  • 文档型存储,其数据是用二进制的Json格式Bson存储的。其数据就像Ruby的hashes,或者Python的字典,或者PHP的数组
  • Sharding,MongoDB提供auto-sharding实现数据的扩展性
  • GridFS,MongoDB的提供的文件存储API
  • 数组索引,你可以对文档中的某个数组属性建立索引
  • MapReduce,可以用于进行复杂的统计和并行计算
  • 高性能,通过使用mmap和定时fsync的方法,避免了频繁IO,使其性能更高

MongoDB的优点

  • 高性能,速度非常快(如果你的内存足够的话)
  • 没有固定的表结构,不用为了修改表结构而进行数据迁移
  • 查询语言简单,容易上手
  • 使用Sharding实现水平扩展
  • 部署方便

使用MongoDB,你得记住以下几点:

  • MongoDB 假设你有大磁盘空间
  • MongoDB 假设你的内存也足够大于放下你的热数据
  • MongoDB 假设你是部署在64位系统上的(32位有2G的限制,试用还可以)
  • MongoDB 假设你的系统是little-endian的
  • MongoDB 假设你有多台机器(并不专注于单机可靠性)
  • MongoDB 假设你希望用安全换性能,同时允许你用性能换安全

MongoDB在下面领域不太擅长

  • 不太稳定,特别是auto-sharding目前还有很多问题
  • 不支持SQL,这意味着你很多通过SQL接口的工具不再适用
  • 持久化,MongoDB单机可靠性不太好,宕机可能丢失一段时间的数据
  • 相关文档比较少,新功能都有这个问题
  • 相关人才比较难找,这也是新功能的问题之一

安装mongodb

要学习使用,首先下载mongodb

windows 32位平台下也可以安装但是数据容量最大只有2G,可以作为测试学习之用,不推荐作为工作生产环境。

Linux 64位则最大可以达到128T,所以推荐linux平台上部署。

mongodb下载后解压到目录后

进入bin里面执行下面命令

–dbpath 是mongodb数据库文档存储目录,–logpath 是mongodb的日志文件,–install 则是将mongodb安装到系统服务里面去。要了解更多命令执行mongod –help查看。

执行完成后检查mongodb.log要是没有错误信息,则通过http://localhost:27017可以看到

You are trying to access MongoDB on the native driver port. For http diagnostic access, add 1000 to the port number
则说明成功了。而访问http://localhost:28017 可以查看mongodb的监控信息。

注意这里第一次安装没有成功时,mongodb_data里面新生成的的mongod.lock 可能会被锁住,再次安装时会产生

dbexit: really exiting now

这时需要删除掉mongod.lock,重新进行安装。

注意在windows xp 下面安装时可能会出现“无法定位程序输入点 InterlockedCompareExchange64 于动态链接库KERNEL32.dll上”

原因就是这样

“Mongodb最新的开发分支已经不再支持xp,也就是说vista是最陈旧支持的客户端,windows server2003是最陈旧的windows服务器版本,但是2.0的分支任然支持着xp,V2.0.6是最新也是最后一个新版本支持xp。因此最好还 是在 linux 下开发吧。”

我的windows xp安装版本是

db version v2.0.6, pdfile version 4.5
Wed Feb 27 22:24:02 git version: e1c0cbc25863f6356aa4e31375add7bb49fb05bc

所以还是推荐最新平台最新版本吧。

MongoDB shell命令

MongoDB自带一个JavaScript shell, 以命令行与MongoDB实例交互。

可以执行管理操作,检查运行实例等

启动Shell:

$ ./mongo

可以运行任何JavaScript程序,还可以利用JavaScript的标准库。所以所有的对象方法都是区分大小写的,输入命令时要注意!

可以定义和调用JavaScript函数。

可以使用多行命令。

show dbs; — 查看数据库

show databases;  –查看数据库

use dbname; –选择数据库

show collections; –查看表

show tables; –查看表

db.tablename.find(); — tablename的数据查询,

db.tablename.find({a: ‘k’}); — 对a字段进行数据k的匹配检索查询

db.tablename.find({a:’k’}).sort({id: -1}); –对a字段进行数据k的匹配检索查询且按照id进行倒叙排列,反之1则是正序排列了

db.tablename.find({a:’k’}).limit(5); –对a字段进行数据k的匹配检索查询只返回最初5条记录

db.tablename.find({a:’k’}).skip(2).limit(5); –对a字段进行数据k的匹配检索查询从第二条开始返回5条记录。skip()限制返回记录的开始点,就等于是mysql里面的offset

db.tablename.save({a: ‘haha’, id:’22}); — 添加新的记录

db.tablename.update({id: 22},  {$set: {a: ‘xixi’}}); — 对id为22的字段a进行修改;即第一个参数是query(也就是mysql中的where),第二个参数修改内容集合,还有第三个参数就是update时的一些设置;

db.tablename.remove({id:22}, 1); — 删除id为22的记录,第一个参数为where,第二个参数为limit,比如为1则只删除查询结果中的1条

db.tablename.drop(); –删除当前的表,drop()不同于remove(),remove只是清空数据,drop直接数据连同表删除了

db.dropDatabase(); –删除当前的数据库

mongodb crud manual

db.location.ensureIndex({loc: 2d}) –对location构建2d 地理位置的索引,{id: 1}设置为1则是正序索引,-1倒序索引;1(ascending),-1(descending)

db.location.getIndexes(); –查看location的索引。

db.location.getIndexKeys(); –查看location的索引keys。

db.location.dropIndex(‘loc’); –按索引名字删除索引,注意这里不是字段的名字

db.location.count(); –查询表中记录数

 

mongodb数据导出,不是进入到mongo shell里面去,

mongodump -d databasename -o D:/dirname -u username -p  –等于是mysql的mysqldump命令一样。

有导出当然有导入咯。

mongorestore -d test D:\mongodb_bak\test -u admin -p –还原test数据库

mongodb不推荐使用mongoexport与mongoimport来进行导出与导入;而是推荐使用mongodump与mongorestore来进行。

 

 

mongodb权限验证

mongodb 默认是无权限验证就可访问的。所以需要开启auth用户验证,需要在mongod安装时加上 –auth参数

Control Access to MongoDB Instances with Authentication

加入–auth参数安装好mongodb后;可以使用下面的进行用户设置

— 超级管理员,所有数据库都能访问

use admin

db.addUser(“username”,”password”);

— 普通管理员

user exampledb

db.addUser(“alex”,”alex”);

alex只能对exampledb进行查看管理

— 只读管理员,登录后只有读的权限

user exampledb

db.addUser(“alex”,”alex”,true);

 

对比之前的无认证环境,可以看到现在都出现了need login的信息内容了。

那首先就login

db.auth(“username”,”password”);  返回的是1,表示这个用户匹配上了,也就是登录成功了。

 

还有一种就是keyFile的方式,

Specify the path to a key file to store authentication information. This option is only useful for the connection between replica set members.

指定的密钥文件的路径存储身份验证信息。此选项只适用于副本集成员之间的连接。(没明白这个副本集成员之间的链接是什么意思)

同样是在安装时指定 mongod –keyFile filepath

keyFile 必须小于1kb,只是包含base64的字符;密钥文件没有组且具有 “world” permissions on UNIX systems

+++++

unix world permissions

unix的世界权限是个啥呢?Unix Permissions

找到了一篇解答

The third group of permission bits corresponds to the world permissions. These are the permissions granted to everyone.

也就是每一个人都具有这个权限,任何人都有这个权限。

+++++

在windows system上面不检测权限。

更多请看:Security Considerations for Replica Sets

 

 

 

更多命令看这里

学习资料推荐

MongoDB Manual

MongoDB资料汇总专题

MySQL和MongoDB设计实例对比

Php Mongodb Manual

MongoDB php driver

Php mongodb-odm

Kohanaframework mongodb module

从PHP客户端看MongoDB通信协议

mysql表分区创建使用学习

表分区的测试使用,主要内容来自于其他博客文章以及mysql5.1的参考手册

mysql测试版本:mysql5.5.28

mysql物理存储文件(有mysql配置的datadir决定存储路径)格式简介

数据库engine为MYISAM

frm表结构文件,myd表数据文件,myi表索引文件。

INNODB engine对应的表物理存储文件

innodb的数据库的物理文件结构为:

.frm文件

.ibd文件和.ibdata文件:

这两种文件都是存放innodb数据的文件,之所以用两种文件来存放innodb的数据,是因为innodb的数据存储方式能够通过配置来决定是使用共享表空间存放存储数据,还是用独享表空间存放存储数据。

独享表空间存储方式使用.ibd文件,并且每个表一个ibd文件

共享表空间存储方式使用.ibdata文件,所有表共同使用一个ibdata文件

创建分区

分区的一些优点包括:

·         与单个磁盘或文件系统分区相比,可以存储更多的数据。

·         对于那些已经失去保存意义的数据,通常可以通过删除与那些数据有关的分区,很容易地删除那些数据。相反地,在某些情况下,添加新数据的过程又可以通过为那些新数据专门增加一个新的分区,来很方便地实现。

通常和分区有关的其他优点包括下面列出的这些。MySQL 分区中的这些功能目前还没有实现,但是在我们的优先级列表中,具有高的优先级;我们希望在5.1的生产版本中,能包括这些功能。

·         一些查询可以得到极大的优化,这主要是借助于满足一个给定WHERE 语句的数据可以只保存在一个或多个分区内,这样在查找时就不用查找其他剩余的分区。因为分区可以在创建了分区表后进行修改,所以在第一次配置分区方案时还不曾这么做时,可以重新组织数据,来提高那些常用查询的效率。

·         涉及到例如SUM() 和 COUNT()这样聚合函数的查询,可以很容易地进行并行处理。这种查询的一个简单例子如 “SELECT salesperson_id, COUNT(orders) as order_total FROM sales GROUP BY salesperson_id;”。通过“并行”, 这意味着该查询可以在每个分区上同时进行,最终结果只需通过总计所有分区得到的结果。

·         通过跨多个磁盘来分散数据查询,来获得更大的查询吞吐量。

简而言之就是 数据管理优化,查询更快,数据查询并行

检测mysql是否支持分区


mysql&gt; show variables like
&quot;%partition%&quot;;
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| have_partitioning | YES   |
+-------------------+-------+
1 row in set

RANGE 分区:基于属于一个给定连续区间的列值,把多行分配给分区。


DROP TABLE IF EXISTS `p_range`;

CREATE TABLE `p_range` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` char(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE (id)
(PARTITION p0 VALUES LESS THAN (8) ENGINE = MyISAM) */;

range分区就是 partition by range(id) 表示按id 1-7的数据存储在p0分区;如果id大于7了则数据不能写入了,因为没有对应的数据分区来存储;
所以这时在创建分区时需要使用maxvalues关键字了

PARTITION BY RANGE (id)
(
PARTITION p0 VALUES LESS THAN (8),
PARTITION p1 VALUES LESS THAN MAXVALUE)

这样就表示,所有id大于7的数据记录存在在p1分区里。

RANGE分区在如下场合特别有用:

·         当需要删除“旧的”数据时。如果你使用上面最近的那个例子给出的分区方案,你只需简单地使用 “ALTER TABLE employees DROP PARTITION p0;”来删除所有在1991年前就已经停止工作的雇员相对应的所有行。对于有大量行的表,这比运行一个如“DELETE FROM employees WHERE YEAR(separated) <= 1990;”这样的一个DELETE查询要有效得多。

·         想要使用一个包含有日期或时间值,或包含有从一些其他级数开始增长的值的列。

·         经常运行直接依赖于用于分割表的列的查询。例如,当执行一个如“SELECT COUNT(*) FROM employees WHERE YEAR(separated) = 2000 GROUP BY store_id;”这样的查询时,MySQL可以很迅速地确定只有分区p2需要扫描,这是因为余下的分区不可能包含有符合该WHERE子句的任何记录。

LIST 分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择。

list分区可以理解为按一个键的id区间进行数据存储,比如类型表 1,2,3,4的所有记录存储在p0里面,5,6,7,8存在在p1分区里面

这里与range分区一样,如果现在有条记录typeid是9,那么这条记录是不能存入的;

需要注意的是:LIST分区没有类似如“VALUES LESS THAN MAXVALUE”这样的包含其他值在内的定义。将要匹配的任何值都必须在值列表中找到。


DROP TABLE IF EXISTS `p_list`;

CREATE TABLE `p_list` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`typeid` mediumint(10) NOT NULL DEFAULT '0',
`typename` char(20) DEFAULT NULL,
PRIMARY KEY (`id`,`typeid`)
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY LIST (typeid)
(PARTITION p0 VALUES IN (1,2,3,4) ENGINE = MyISAM,
PARTITION p1 VALUES IN (5,6,7,8) ENGINE = MyISAM) */;

HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL 中有效的、产生非负整数值的任何表达式。

HASH分区主要用来确保数据在预先确定数目的分区中平均分布。在RANGE和LIST分区中,必须明确指定一个给定的列值或列值集合应该保存在哪个分区中;而在HASH分区中,MySQL 自动完成这些工作,你所要做的只是基于将要被哈希的列值指定一个列值或表达式,以及指定被分区的表将要被分割成的分区数量。

要使用HASH分区来分割一个表,要在CREATE TABLE 语句上添加一个“PARTITION BY HASH (expr)”子句,其中“expr”是一个返回一个整数的表达式。它可以仅仅是字段类型为MySQL 整型的一列的名字。此外,你很可能需要在后面再添加一个“PARTITIONS num”子句,其中num 是一个非负的整数,它表示表将要被分割成分区的数量。如果没有包括一个PARTITIONS子句,那么分区的数量将默认为1。


DROP TABLE IF EXISTS `p_hash`;

CREATE TABLE `p_hash` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`storeid` mediumint(10) NOT NULL DEFAULT '0',
`storename` char(255) DEFAULT NULL,
PRIMARY KEY (`id`,`storeid`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY HASH (storeid)
PARTITIONS 4 */;

InnoDB引擎

简单点说就是数据的存入可以按 partition by hash(expr); 这里的expr可以是键名也可以是表达式比如YEAR(time),如果是表达式的情况下

“但是应当记住,每当插入或更新(或者可能删除)一行,这个表达式都要计算一次;这意味着非常复杂的表达式可能会引起性能问题,尤其是在执行同时影响大量行的运算(例如批量插入)的时候。 ”

在执行删除、写入、更新时这个表达式都会计算一次。

数据的分布采用基于用户函数结果的模数来确定使用哪个编号的分区。换句话,对于一个表达式“expr”,将要保存记录的分区编号为N ,其中“N = MOD(expr, num)”。

比如上面的storeid 为10;那么 N=MOD(10,4) ;N是等于2的,那么这条记录就存储在p2的分区里面。

如果插入一个表达式列值为’2005-09-15’的记录到表中,那么保存该条记录的分区确定如下:MOD(YEAR(‘2005-09-01’),4)  =  MOD(2005,4)  =  1 ;就存储在p1分区里面了。

“MySQL 5.1 还支持一个被称为“linear hashing(线性哈希功能)”的变量,它使用一个更加复杂的算法来确定新行插入到已经分区了的表中的位置。

线性哈希分区和常规哈希分区在语法上的唯一区别在于,在“PARTITION BY” 子句中添加“LINEAR”关键字;线性哈希功能使用的一个线性的2的幂(powers-of-two)运算法则

按照线性哈希分区的优点在于增加、删除、合并和拆分分区将变得更加快捷,有利于处理含有极其大量(1000GB)数据的表。

它的缺点在于,与使用常规HASH分区得到的数据分布相比,各个分区间数据的分布不大可能均衡。”

KEY 分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL 服务器提供其自身的哈希函数。必须有一列或多列包含整数值。


DROP TABLE IF EXISTS `p_key`;

CREATE TABLE `p_key` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`keyname` char(20) DEFAULT NULL,
`keyval` varchar(1000) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=12 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY KEY (id)
PARTITIONS 4 */;

按照KEY进行分区类似于按照HASH分区,除了HASH分区使用的用户定义的表达式,而KEY分区的 哈希函数是由MySQL 服务器提供。MySQL 簇(Cluster)使用函数MD5()来实现KEY分区;对于使用其他存储引擎的表,服务器使用其自己内部的 哈希函数,这些函数是基于与PASSWORD()一样的运算法则。

“CREATE TABLE … PARTITION BY KEY”的语法规则类似于创建一个通过HASH分区的表的规则。它们唯一的区别在于使用的关键字是KEY而不是HASH,并且KEY分区只采用一个或多个列名的一个列表。

与hash的区别就是,hash使用用户定义的表达式如YEAR(time) ;而key分区则是由mysql服务器提供的。同样KEY也是可以使用linear线性key的,与hash linear是相同的算法。

子分区:是分区表中每个分区的再次分割。


DROP TABLE IF EXISTS `p_subpartition`;

CREATE TABLE `p_subpartition` (
`id` int(10) DEFAULT NULL,
`title` char(255) NOT NULL,
`createtime` date NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8
/*!50100

PARTITION BY RANGE (YEAR(createtime))
SUBPARTITION BY HASH (MONTH(createtime))
(PARTITION p0 VALUES LESS THAN (2012)
(SUBPARTITION s1 ENGINE = MyISAM,
SUBPARTITION s2 ENGINE = MyISAM),
PARTITION p1 VALUES LESS THAN (2013)
(SUBPARTITION s3 ENGINE = MyISAM,
SUBPARTITION s4 ENGINE = MyISAM),
PARTITION p2 VALUES LESS THAN MAXVALUE
(SUBPARTITION s5 ENGINE = MyISAM,
SUBPARTITION s6 ENGINE = MyISAM)) */;

可以看到p_subpartition有三个分区p0,p1,p2;而这三个分区每一个又进一步分为2个分区。那么整个表都就分为6个小分区;

可以看到代表p_sobpartitionp0.myd的文件消失了,取代的是p_subpartition#p#p0#sp#s1.myd

在MySQL 5.1中,对于已经通过RANGE或LIST分区了的表再进行子分区是可能的。

子分区是分区表中每个分区的再次分割,子分区既可以使用HASH希分区,也可以使用KEY分区。这 也被称为复合分区(composite partitioning)。

1,如果一个分区中创建了子分区,其他分区也要有子分区

2,如果创建了了分区,每个分区中的子分区数必有相同

3,同一分区内的子分区,名字不相同,不同分区内的子分区名子可以相同(5.1.50不适用)

分区注意点

1、重新分区时,如果原分区里面存在maxvalue则新的分区里面也必须包含maxvalue否则就错误。
alter table p_range2x
reorganize partition p1,p2
into (partition p0 values less than (5), partition p1 values less than maxvalue);

[Err] 1520 – Reorganize of range partitions cannot change total ranges except for last partition where it can extend the range

2、分区删除时,数据也同样会被删除

alter table p_range drop partition p0;

3、如果range分区列表里面没有maxvalue则如有新数据大于现在分区range数据值那么这个数据是无法写入到数据库表的。

4、修改表名不需要 删除分区后在进行更改,修改表名后分区存储myd myi对应也会自动更改。

如果希望从所有分区删除所有的数据,但是又保留表的定义和表的分区模式,使用TRUNCATE TABLE命令。(请参见13.2.9节,“TRUNCATE语法”)。

如果希望改变表的分区而又不丢失数据,使用“ALTER TABLE … REORGANIZE PARTITION”语句。参见下面的内容,或者在13.1.2节,“ALTER TABLE语法” 中参考关于REORGANIZE PARTITION的信息。

5、对表进行分区时,不论采用哪种分区方式如果表中存在主键那么主键必须在分区列中。表分区的局限性。

6、list方式分区没有类似于range那种 less than maxvalue的写法,也就是说list分区表的所有数据都必须在分区字段的值列表集合中。

7、在MySQL 5.1版中,同一个分区表的所有分区必须使用同一个存储引擎;例如,不能对一个分区使用MyISAM,而对另一个使用InnoDB。

8、分区的名字是不区分大小写的,myp1与MYp1是相同的。

分区的管理

range与list分区的改变动作不能适用于hash与key方式的分区。删除与添加动作是都能使用的。

以下面的例子


DROP TABLE IF EXISTS `p_list`;

CREATE TABLE `p_list` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`typeid` mediumint(10) NOT NULL DEFAULT '0',
`typename` char(20) DEFAULT NULL,
PRIMARY KEY (`id`,`typeid`)
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY LIST (typeid)
(PARTITION p0 VALUES IN (1,2,3,4) ENGINE = MyISAM,
PARTITION p1 VALUES IN (5,6,7,8) ENGINE = MyISAM) */;

range与list分区的管理
删除分区
ALTER TABLE tr DROP PARTITION p1;
需要注意的是删除分区后,该分区的所有数据都没有了。同时删除后存在一个重大影响也就是typeid为5,6,7,8的记录是不能写入到该表了的!

清空数据

如果想要保留表结构与分区结构可以使用 TRUNCATE TABLE 清空表

更改分区保留数据

ALTER TABLE tbl_name REORGANIZE PARTITION partition_list INTO (partition_definitions);

如果想保留数据进行分区的更改

ALTER TABLE p_list REORGANIZE PARTITION p0 INTO (
 PARTITION s0 VALUES IN(1,2),
 PARTITION s1 VALUES IN(3,4),
);

这样就能进行分区的合并了,那怎么进行拆分呢

ALTER TABLE p_list REORGANIZE PARTITION s0,s1 INTO (
 PARTITION p0 VALUES IN(1,2,3,4),
); 使用 REORGANIZE PARTITION进行数据的合并与拆分,数据是没有丢失的。
在使用REORGANIZE进行重新分区时,需要注意几点:
1、用来确定新分区模式的PARTITION子句使用与用在CREATE TABLE中确定分区模式的PARTITION子句相同的规则。(partition 分区子句必须与创建原分区时的规则相同)
2、partition_definitions 列表中分区的合集应该与在partition_list 中命名分区的合集占有相同的区间或值集合。 (不管是合并还是拆分,s0,s1到p0;p0到s0,s1 里面的区间或者值都必须相同)
3、对于按照RANGE分区的表,只能重新组织相邻的分区;不能跳过RANGE分区。(比如按range年份 p0 1990,p1 2000 ,p2 2013三个分区;在合并时partition p0,p2 into()
   这样是不行的,因为这两个分区不是相邻的分区;)
4、不能使用REORGANIZE PARTITION来改变表的分区类型;也就是说,例如,不能把RANGE分区变为HASH分区,反之亦然。也不能使用该命令来改变分区表达式或列。

增加分区
ALTER TABLE p_list ADD PARTITION (PARTITION p2 VALUES IN (9, 10, 11));
但是不能使用
ALTER TABLE p_list ADD PARTITION (PARTITION p2 VALUES IN (9, 14));
这样mysql 会产生错误1465 (HY000): 在LIST分区中,同一个常数的多次定义
hash与key分区的管理在改变分区设置方面,按照HASH分区或KEY分区的表彼此非常相似,但是它们又与按照RANGE或LIST分区的表在很多方面有差别。
关于添加和删除按照RANGE或LIST进行分区的表的分区
不能使用与从按照RANGE或LIST分区的表中删除分区相同的方式,来从HASH或KEY分区的表中删除分区。但是,可以使用“ALTER TABLE ... COALESCE PARTITION”命令来合并HASH或KEY分区。
DROP TABLE IF EXISTS `p_hash`;

CREATE TABLE `p_hash` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`storeid` mediumint(10) NOT NULL DEFAULT '0',
`storename` char(255) DEFAULT NULL,
PRIMARY KEY (`id`,`storeid`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8
/*!50100 PARTITION BY HASH (storeid)
PARTITIONS 4 */;

如p_hash的分区数为4个; 要减少分区数为2个 ALTER TABLE p_hash COALESCE PARTITION 2 对于按照HASH,KEY,LINEAR HASH,或LINEAR KEY分区的表, COALESCE能起到同样的作用。

COALESCE不能用来增加分区的数量,如果你尝试这么做,结果会出现类似于下面的错误:

mysql> ALTER TABLE clients COALESCE PARTITION 18;
错误1478 (HY000): 不能移动所有分区,使用DROP TABLE代替

要增加顾客表的分区数量从12到18,使用“ALTER TABLE … ADD PARTITION”,具体如下:

ALTER TABLE clients ADD PARTITION PARTITIONS 18;

注释:“ALTER TABLE … REORGANIZE PARTITION”不能用于按照HASH或HASH分区的表。

分区维护

重建分区

这和先删除保存在分区中的所有记录,然后重新插入它们,具有同样的效果。它可用于整理分区碎片。

ALTER TABLE t1 REBUILD PARTITION (p0, p1);

优化分区

如果从分区中删除了大量的行,或者对一个带有可变长度的行(也就是说,有VARCHAR,BLOB,或TEXT类型的列)作了许多修改,

可以使用“ALTER TABLE … OPTIMIZE PARTITION”来收回没有使用的空间,并整理分区数据文件的碎片。

ALTER TABLE t1 OPTIMIZE PARTITION (p0, p1);

分析分区
读取并保存分区的键分布
ALTER TABLE t1 ANALYZE PARTITION (p3);

修补分区: 修补被破坏的分区。
ALTER TABLE t1 REPAIR PARTITION (p0,p1);

检查分区
可以使用几乎与对非分区表使用CHECK TABLE 相同的方式检查分区。
ALTER TABLE trb3 CHECK PARTITION (p1);
这个命令可以告诉你表t1的分区p1中的数据或索引是否已经被破坏。如果发生了这种情况,使用“ALTER TABLE ... REPAIR PARTITION”来修补该分区。

获取分区信息

在mysql服务器信息数据库里面的partitions存放着服务器所有表的分区信息。


-- explain partitions命令
explain partitions select * from p_hash
+----+-------------+--------+-------------+------+---------------+------+---------+------+------+-------+
| id | select_type | table  | partitions  | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+--------+-------------+------+---------------+------+---------+------+------+-------+
|  1 | SIMPLE      | p_hash | p0,p1,p2,p3 | ALL  | NULL          | NULL | NULL    | NULL |   10 |       |
+----+-------------+--------+-------------+------+---------------+------+---------+------+------+-------+

-- 获取到p_list表的分区详细信息。

select * from information_schema.`PARTITIONS` where TABLE_NAME = 'p_list';
-- 分区的创建信息
show create table p_list;

 

更多阅读:

mysql5.5 reference manmul partition

mysql表分区
mysql分区功能详细介绍以及实例

discuzx2系列之session的实现机制

discuzx2系列文章

discuzx2系列之群组积分排行

我们知道discuzx是没有采用php默认的session机制的,而是自己采用数据库表自己进行了构建一套session用户状态识别机制;而不是简单的把php session采用存入数据库的方式。

直接看代码

几个顶级的control入口是引用了下面的class_core.php;也就是discuzx核心类文件。


require './source/class/class_core.php';
$discuz = & discuz_core::instance();

$discuz->init();

可以再discuz_core类里面的,初始化了一系列模块


function init() {
if(!$this->initated) {
$this->_init_db();
$this->_init_memory();
$this->_init_user();
$this->_init_session();    // 初始化session
$this->_init_setting();
$this->_init_mobile();
$this->_init_cron();
$this->_init_misc();
$this->_init_connect();
}
$this->initated = true;
}

那就来看看_init_session()  这个函数的主要作用初始化session,检测已有session或者创建新的session作为当前连接的唯一标识
其中附加功能包括,当前连接用户ip的白名单,黑名单检测;确定用户的详细属性信息,如是新登陆的用户则更新用户的状态信息和在线统计信息


function _init_session() {
$this->session = new discuz_session();  // 实例化数据库session类

if($this->init_session)    {
// $this->var 就是超全局变量$_G 可以看到$this->var = & $_G;是在 _init_env()中引用赋值的
// 将目前cookie为sid的值和用户个人信息传入init;discuz_session 的init方法始终会返回一个实例对象,来表示当前的用户或游客
$this->session->init($this->var['cookie']['sid'], $this->var['clientip'], $this->var['uid']);

$this->var['sid'] = $this->session->sid;
$this->var['session'] = $this->session->var; // 当前用户对象的session数据

// 如果当前返回sid 不等于浏览器中名为sig cookie的值,则设置新的sid值1天后过期
if($this->var['sid'] != $this->var['cookie']['sid']) {
dsetcookie('sid', $this->var['sid'], 86400);
}
// $isnew 表示是否为网站新的用户,也就是新创建的session
if($this->session->isnew) {
// 是新来访用户,检测用户的ip是否在系统的可访问名单($_G['setting']['ipaccess'])或是否在系统的黑名单(common_banned禁止访问表;cache_ipbanned.php)中
if(ipbanned($this->var['clientip'])) {
// ip不在可访问名单或者ip在系统的黑名单中;则将用户移入 groupid=6的“禁止ip”组;游客的groupid是7
$this->session->set('groupid', 6);
}
}

// 如果是“禁止ip”组,显示系统提示信息
// "抱歉,您的 IP 地址不在被允许,或您的账号被禁用,无法访问本站点"
if($this->session->get('groupid') == 6) {
$this->var['member']['groupid'] = 6;
sysmessage('user_banned');
}
// 存在当前登录用户;用户是新登录的或者lastactivity最后活动时间在十分钟以前了
if($this->var['uid'] && ($this->session->isnew || ($this->session->get('lastactivity') + 600) &lt; TIMESTAMP)) {
// 重新设置lastactivity,接下来在footer.html中调用updatesession();时更新当前用户session记录信息 主要作用就是更新lastactivity;避免按在线时间$_G['setting']['onlinehold']过期被清除掉
$this->session->set('lastactivity', TIMESTAMP);
if($this->session->isnew) {
// 新的登录用户,更新用户的状态
DB::update('common_member_status', array('lastip' => $this->var['clientip'], 'lastvisit' => TIMESTAMP), "uid='".$this->var['uid']."'");
}
}

}
}

discuz_session类;主要作用对session进行操作,对应的是common_session记录表。其中$discuz->session 返回的就是浏览器当前连接用户的session实例对象了。($discuz就是discuz_core的引用实例)


class discuz_session {

var $sid = null;
var $var;
var $isnew = false;
var $newguest = array('sid' => 0, 'ip1' => 0, 'ip2' => 0, 'ip3' => 0, 'ip4' => 0,
'uid' => 0, 'username' => '', 'groupid' => 7, 'invisible' => 0, 'action' => 0,
'lastactivity' => 0, 'fid' => 0, 'tid' => 0, 'lastolupdate' => 0, 'country' => 0, 'email' => ''); //add country ,email

var $old =  array('sid' =>  '', 'ip' =>  '', 'uid' =>  0);

// 实例化,初始化session默认数据
function discuz_session($sid = '', $ip = '', $uid = 0) {
$this->old = array('sid' =>  $sid, 'ip' =>  $ip, 'uid' =>  $uid);
$this->var = $this->newguest;
if(!empty($ip)) {
$this->init($sid, $ip, $uid);
}
}

function set($key, $value) {
if(isset($this->newguest[$key])) {
$this->var[$key] = $value;
} elseif ($key == 'ip') {
$ips = explode('.', $value);
$this->set('ip1', $ips[0]);
$this->set('ip2', $ips[1]);
$this->set('ip3', $ips[2]);
$this->set('ip4', $ips[3]);
}
}

function get($key) {
if(isset($this->newguest[$key])) {
return $this->var[$key];
} elseif ($key == 'ip') {
return $this->get('ip1').'.'.$this->get('ip2').'.'.$this->get('ip3').'.'.$this->get('ip4');
}
}
// 初始化返回session实例对象
function init($sid, $ip, $uid) {
$this->old = array('sid' =>  $sid, 'ip' =>  $ip, 'uid' =>  $uid);
$session = array();
// cookie sid存在;读取记录
if($sid) {
$session = DB::fetch_first("SELECT * FROM ".DB::table('common_session').
" WHERE sid='$sid' AND CONCAT_WS('.', ip1,ip2,ip3,ip4)='$ip'");
}
// 不存在或存在session记录但与新的uid值不匹配
if(empty($session) || $session['uid'] != $uid) {
// 重新创建session记录
$session = $this->create($ip, $uid);
}

$this->var = $session;
$this->sid = $session['sid'];
}

// 返回新设置的session对象
function create($ip, $uid) {

$this->isnew = true;
$this->var = $this->newguest;
$this->set('sid', random(6));
$this->set('uid', $uid);
$this->set('ip', $ip);

//写入区域信息
include_once libfile('function/misc');
$ipaddress = convertip($ip);
//$ipaddress = mb_convert_encoding($ipaddress, 'UTF-8', 'GB2312');
$country = mb_substr($ipaddress, 2, 2, 'UTF-8');  // 获取两个字存入数据库

include_once libfile('local', 'include');
if (array_key_exists($country, $local_name)) { //如果数据在数组中
$country = $local_name[$country];
} else {// 如果不是则随机获取一个
$country = $local_name[array_rand($local_name)];
}
$this->set('country', $country);

$uid && $this->set('invisible', getuserprofile('invisible'));
$this->set('lastactivity', time());
$this->sid = $this->var['sid'];

return $this->var;
}

// 清除session记录,也就是清除掉已过期的连接
function delete() {

global $_G;
// onlinehold 系统设置的用户在线时间(分钟) 15
// 这里的$_G['setting']['onlinehold']已经换算成秒了;build_cache_setting()
$onlinehold = $_G['setting']['onlinehold'];
// 游客活动时间 1分钟
$guestspan = 60;
$onlinehold = time() - $onlinehold;
$guestspan = time() - $guestspan;

$condition = " sid='{$this->sid}' ";
$condition .= " OR lastactivity&lt;$onlinehold ";  // 活动时间距离现在超过了15分钟了
$condition .= " OR (uid='0' AND ip1='{$this->var['ip1']}' AND ip2='{$this->var['ip2']}' AND ip3='{$this->var['ip3']}' AND ip4='{$this->var['ip4']}' AND lastactivity>$guestspan) "; // 同一ip,访问活动时间在1分钟以内
$condition .= $this->var['uid'] ? " OR (uid='{$this->var['uid']}') " : ''; // 存在uid 也可按uid删除
DB::delete('common_session', $condition);
}

// 更新记录
function update() {
global $_G;
if($this->sid !== null) {

$data = daddslashes($this->var);
// 新创建用户session记录;且对符合提交的session记录进行清除
if($this->isnew) {
$this->delete();
DB::insert('common_session', $data, false, false, true);
} else {
// 更新session记录
DB::update('common_session', $data, "sid='$data[sid]'");
}
$_G['session'] = $data;
// 更新sid过期时间,等于是每次刷新都会重新更新
dsetcookie('sid', $this->sid, 86400);
}
}

// 在线人数统计 登录用户/游客统计
function onlinecount($type = 0) {
$condition = $type == 1 ? ' WHERE uid>0 ' : ($type == 2 ? ' WHERE invisible=1 ' : '');
return DB::result_first("SELECT count(*) FROM ".DB::table('common_session').$condition);
}

}

对应的php session中的有效期,回收机制就是delete()方法;写入与读取对应的就是update();那用户在浏览器端刷新后是怎么进行session的验证与返回的呢?session开启后,用户每次请求服务器都会进行数据验证与返回的;所以discuzx每次请求都会执行这个update()

discuzx采用的是页面载入模板的形式,对应的 template/default/common/footer.htm是每个页面都可能会加载的;有一些公共内容在这里,其中就有用户的session更新函数调用

<!–{eval updatesession();}–>

updatesession()函数


function updatesession($force = false) {

global $_G;
static $updated = false;

if(!$updated) {
// 登录用户
if($_G['uid']) {
// 获取当前用户最后活动时间且authcode编码后写入到浏览器cookie中
if($_G['cookie']['ulastactivity']) {
$ulastactivity = authcode($_G['cookie']['ulastactivity'], 'DECODE');
} else {
$ulastactivity = getuserprofile('lastactivity');
// 有效期长达一年
dsetcookie('ulastactivity', authcode($ulastactivity, 'ENCODE'), 31536000);
}
}
// 实例化核心类
$discuz = & discuz_core::instance();
// $discuz->session 当前这个用户的session对象

// 用户在线时间更新时长(分钟) 也就是在线用户多久更新一次自己的“在线时间”
// Discuz! 可统计每个用户总共和当月的在线时间,本设置用以设定更新用户在线时间的时间频率。例如设置为 10,则用户每在线 10 分钟更新一次记录。本设置值越小,则统计越精确,但消耗资源越大。建议设置为 5~30 范围内,0 为不记录用户在线时间
$oltimespan = $_G['setting']['oltimespan'];
// session记录中的“用户在线时间更新”的最后时间
$lastolupdate = $discuz->session->var['lastolupdate'];

// 登录用户 且存在“在线时间更新”频率 且时间段已过则进行用户的个人在线时间更新。
// 等于是用户在线时间是按每次10分钟,这样叠加的;间隔越小越准确。
if($_G['uid'] && $oltimespan && TIMESTAMP - ($lastolupdate ? $lastolupdate : $ulastactivity) > $oltimespan * 60)      {
DB::query("UPDATE ".DB::table('common_onlinetime')."
SET total=total+'$oltimespan', thismonth=thismonth+'$oltimespan', lastupdate='" . TIMESTAMP . "'
WHERE uid='{$_G['uid']}'");
if(!DB::affected_rows()) {
DB::insert('common_onlinetime', array(
'uid' => $_G['uid'],
'thismonth' => $oltimespan,
'total' => $oltimespan,
'lastupdate' => TIMESTAMP,
));
}
// 重新设置 “用户在线时间更新”的最后更新时间
$discuz->session->set('lastolupdate', TIMESTAMP);
}
foreach($discuz->session->var as $k => $v) {
if(isset($_G['member'][$k]) && $k != 'lastactivity') {
$discuz->session->set($k, $_G['member'][$k]);
}
}

foreach($_G['action'] as $k => $v) {
$discuz->session->set($k, $v);
}

// 这里进行session状态更新
$discuz->session->update();

$updated = true;
// 更新当前登录用户的统计信息与用户状态信息
if($_G['uid'] && TIMESTAMP - $ulastactivity > 21600) {
if($oltimespan && TIMESTAMP - $ulastactivity > 43200) {
$total = DB::result_first("SELECT total FROM ".DB::table('common_onlinetime')." WHERE uid='$_G[uid]'");
// 换算为分钟写入用户个人统计信息表
DB::update('common_member_count', array('oltime' => round(intval($total) / 60)), "uid='$_G[uid]'", 1);
}
dsetcookie('ulastactivity', authcode(TIMESTAMP, 'ENCODE'), 31536000);
DB::update('common_member_status', array('lastip' => $_G['clientip'], 'lastactivity' => TIMESTAMP, 'lastvisit' => TIMESTAMP), "uid='$_G[uid]'", 1);
}
}
return $updated;
}

session在程序中的流程。用户访问页面,初始化discuz_session实例;读取客户端cookie sid 查找对应的session;已存在则返回记录信息,设置cookie sid的过期时间;不存在则创建新的session记录,也就是一个新的用户,在创建新用户前,对session记录表按系统设置的用户在线时间、sid、uid、同一ip1分钟内活动记录这几个条件进行清理;(很关键的一点是只有在创建新用户session记录时,才会进行session记录的删除整理) 登陆用户与session的关系这里是一个很简单的实现,也就是

_init_session(); // 也就是$this->var[‘uid’] 是否是有值得,也就是$_G[‘uid’]是否存在。uid等用户个人信息赋值的过程是在login成功后调用setloginstatus($member,$lifetime);函数(function_member.php)

$this->session->init($this->var[‘cookie’][‘sid’], $this->var[‘clientip’], $this->var[‘uid’]);

这样用户uid就直接与session记录关联起来了。同时登陆用户会进行“个人在线时间信息”“用户个人状态”的数据更新处理。

common_session表结构

CREATE TABLE pre_common_session (
sid char(6) NOT NULL default ” COMMENT ‘sid’,
ip1 tinyint(3) unsigned NOT NULL default ‘0’ COMMENT ‘IP段’,
ip2 tinyint(3) unsigned NOT NULL default ‘0’ COMMENT ‘IP段’,
ip3 tinyint(3) unsigned NOT NULL default ‘0’ COMMENT ‘IP段’,
ip4 tinyint(3) unsigned NOT NULL default ‘0’ COMMENT ‘IP段’,
uid mediumint(8) unsigned NOT NULL default ‘0’ COMMENT ‘会员id’,
username char(15) NOT NULL default ” COMMENT ‘会员名’,
groupid smallint(6) unsigned NOT NULL default ‘0’ COMMENT ‘会员组’,
invisible tinyint(1) NOT NULL default ‘0’ COMMENT ‘是否隐身登录’,
`action` tinyint(1) unsigned NOT NULL default ‘0’ COMMENT ‘当前动作’,
lastactivity int(10) unsigned NOT NULL default ‘0’ COMMENT ‘最后活动时间’,
lastolupdate int(10) unsigned NOT NULL default ‘0’ COMMENT ‘在线时间最后更新’,
fid mediumint(8) unsigned NOT NULL default ‘0’ COMMENT ‘论坛id’,
tid mediumint(8) unsigned NOT NULL default ‘0’ COMMENT ‘主题id’,
UNIQUE KEY sid (sid),
KEY uid (uid)
) ENGINE=MEMORY DEFAULT CHARSET=utf8 COMMENT=’会员认证表’;

需要注意的是session表采用的是memory引擎,优点是常驻内存存取速度快,方便操作与管理;缺点则是如果服务器重启则数据都被清空,只读或读为主的访问模式(短时间用户激增写入量会很大)

更多了解:

MySQL Memory存储引擎:优势及性能测试

一天一笔记

上班也有三年多了,大概是从10年时候有一个对当天工作情况记录的习惯;都是记录在笔记本上面的,去年写完了一本,今年也买了一个新的笔记本,在9月份的时候突然笔没有墨水了,又多次搞忘记了买笔芯;所以转念一想为什么不自己搭建一个网上笔记记录的平台呢?

目前部署在sina sae平台上面想看的请点http://daynote.sinaapp.com

首页(图片都还是本地测试数据)

于是就计划开始做了,要最快的做出来;所以使用的是同事@ljyf5593用kohanaframework写的一个cms,后台功能很完善,用户、权限、后台工具等等都直接采用,也就是只需要做下前台以及数据的展示就ok了。

要说记笔记这样的事情,其实有很多现成的软件可用;各种云平台,云笔记等等,同事就是用的盛大麦库云笔记,如果你还想极客一点可以使用各种云平台的开放api开发一个自己的云笔记应用。不过我觉得目前自己用的这个就挺好;直接web页面访问,也不用安装各种客户端,也不想去折腾云平台api了。所以就先用着呗。

要说自己认为的亮点,我觉得有这么几个

#标记管理

不用搞成每一个note笔记前都放置一个checkbox,在建一个checkbox submit进行删除、导出、发送等其他操作。且用户点击note即选择,取消则再点击一次就ok。结果集操作nav可以随着用户的结果记录是否存在而自动的显示与隐藏。js很简单,看源码ok。

#导出,email发送

在用户选择标记后,note集合可以用来删除,导出,email发送。其中导出就是导出为excel到线下。

导出

本来想把同一天的所有不同时段内容进行分组的,不过我觉得phpexcel进行数据处理真的太难搞了。所以就直接一条笔记对应一条记录。kohana 使用phpexcel真的很方便,直接加module就好了。github上就有,开源的贡献者真伟大。

email发送

标记标签后发送到邮箱,就是着过样子啦。。。ps: title 上面已经修改好了striptags

吐槽:sae其实真的很强大了,但是也为了安全运维等因素导致sae环境与lamp环境还是有区别的。比如禁止本地io,.htaccess采用了config.yaml。所以导致一些类库代码在 sae上面跑不起来,也没有一个具体的详细错误,对于排除起来真的好麻烦。所以目前显线上sae平台上面,导出,email发送都是空白空白。。。还得找个时间单独的把phpexcel与email模块进行sae测试下,进行具体的代码排查。

=== 2013年1月4日21:34:54 更新===

phpexcel在sae上能正常数据导出了,原因是这样的,phpexcel默认是采用Excel2007进行数据导出的,所以也是采用的Excel2007实现类。而Excel2007实现类需要zip扩展来实现的(也就是zipArchive类,不过可以用zlib来实现对于zip读写,不过成本就太大了)。官方也考虑到了可能不会支持zip扩展的情况,于是就使用xml实现了PHPExcel_Settings::PCLZIP的PHPExcel_Shared_ZipArchive类,不过在sae环境上测试时提示”XMLWriter class  not found “也就是需要xmlwriter的扩展支持(PHP 5 >= 5.1.2, PECL xmlwriter >= 0.1.0) sae上面没有支持。

//writer/Excel2007.php
$zipClass = PHPExcel_Settings::getZipClass();
$objZip = new $zipClass();

本来想替换另一种实现或者是直接csv导出的,又看到phpexcel里面还可用Excel5进行数据导出;抱着试一试的态度看看,测试正常数据导出!这说明Excel5没有采用xml进行excel创建,详细还没看代码。

改为Excel5数据导出很简单


$objWriter = PHPExcel_IOFactory::createWriter($this->_spreadsheet, 'Excel5'); // 第二参数把Excel2007改为Excel5就行
$objWriter->save('php://output');
exit;

#支持markdown语法

markdown很流行,开源平台,大公司各种支持。不过markdown听闻版本分化严重啊,希望能有一个统一标准。确实很好用,如果没有一个格式体现,全是一段段文字看起来很累。https://github.com/michelf/php-markdown

#笔记note编写自动保存

这个很简单,直接setIntval 15秒自动保存,就是要区分创建与修改的动作。如果是创建则自动写入数据库,把note创建成功的noteid返回到表单,这时点击发布时实际上市修改了。如果你没有点击发布而是刷新了页面,那么这时还只是草稿,草稿数则+1;

#note笔记归档

用户笔记可以按照月份、年份进行排序实现。便于用户进行自我梳理查找。我觉得这里的作用应该大于笔记搜索;让用户可以看看自我的成长经历。个人就这挫设计,其实有更好的ui 设计,比如现在很流行的时间线。

其他还想实现的功能,比如可以让textarea支持table的(tabIndent.js)不过有时候我经常在touch,移动设备上面写,table这时现的很多余所以暂时没有加上去。还有同步发送到微博,不过貌似笔记是比较私密的事情,当然也有可以公开的暂时也没有加。

有喜欢的可以注册用下哟。

一个有关当前时间的sql排序实现

在项目中遇到了这样的一个需求

“在用户参与的一个活动列表中,获取当前时间中刚刚开始了(也就是刚才开始的,不是最先开始)的一条活动;如果当前时间没有活动开始,则获取将要开始的一条活动记录”且必须在一次数据库查询操作中完成。

也就是必须要在数据库中做活动的状态区分。

现在具象化一点的理解这个需求,有这样一个表t_time

里面只有三个字段

id,starttime,endtime

我们假定现在每一条记录就是一个活动。

现在可以把需求抽象的分析为“当前时间大于starttime且小于endtime的按desc到序排列获取第一条记录,当前时间小于starttime且小于endtime的按asc正序排列获取第一条记录”且同样是要在一条sql语句中完成。

于是开始进行sql编写,在sql要完成两种状态的辨别,那就肯定是需要条件分支类似的语句控制。

mysql中的条件语句分支,常用的就是if ,case when 语句。

在找到if then 的用法后,开始第一次if then语句的测试


if (SELECT * FROM `t_time` where starttime < UNIX_TIMESTAMP() and endtime >UNIX_TIMESTAMP())
then ( SELECT * FROM `t_time` where starttime < UNIX_TIMESTAMP() and endtime >UNIX_TIMESTAMP() order by starttime desc )
else if(SELECT * FROM t_time WHERE starttime > UNIX_TIMESTAMP() and endtime > UNIX_TIMESTAMP())
then ( SELECT * FROM `t_time` where starttime > UNIX_TIMESTAMP() and endtime >UNIX_TIMESTAMP() order by starttime asc )
end if;

结果是;华丽丽的报错啊!!!

Err] 1064 – You have an error in your SQL syntax;

考虑到语法没有问题啊。mysql真是有待学习啊,在网上搜索后了解到“if then endif只能在procedure或是function里用”,手册也没看,就乱来。而且不能为了这么一个处理还单独去创建一个存储过程或函数。

 

再查看case when语句,这次是能在sql中直接执行的。


select * from t_time where endtime > UNIX_TIMESTAMP()
and (case when starttime < UNIX_TIMESTAMP() then starttime < UNIX_TIMESTAMP() else starttime > UNIX_TIMESTAMP() end) order by starttime desc;

结果

但是还是没有达到预期效果,case when 没有起到作用,加上case when语句别名后的结果

可以看到case when 返回的是boolean结果;再次无效

 

于是继续查看手册还有没有办法,if()函数也能进行分支判断,可以一试。

IF(expr1,expr2,expr3)
如果 expr1 是TRUE (expr1 <> 0 and expr1 <> NULL),则 IF()的返回值为expr2; 否则返回值则为 expr3。IF() 的返回值为数字值或字符串值,具体情况视其所在语境而定。

if()函数可以返回数值或者是字符


SELECT *,if(starttime < UNIX_TIMESTAMP(), 'starttime < UNIX_TIMESTAMP()', 'starttime > UNIX_TIMESTAMP()') as t
FROM `t_time` where endtime > UNIX_TIMESTAMP() and t order by starttime desc;

 

 

结果还是语法错误,[Err] 1054 – Unknown column ‘t’ in ‘where clause’

把 and t去掉;sql语句是能正常执行的。结果如下

if()函数确实是执行了,但是返回的字符 别名为t,sql语句还是不认识。这个没搞明白。
无奈,只好求教于公司同事w;在简单说明后,他说项目里面有一处地方用到了这个,已经有了一个sql语句实现了;原来这个项目里面已经处理过这样的业务需求了,他说那时候第一次研究出来的,因为这个项目我是半路进来的,前面的开发都没参与;但隐约记得那一段时间他们组里的人都在说“写出一个牛x的sql排序了”,不过因为各种原因,我没去了解。哎,总归还是遇上了。

于是请w去讲解下这个处理方式。

在查看到项目源代码后,他说简而言之就是一个排序的实现,sql里面还是要进行两个状态的判断;

就是通过if()函数构建一个线性的排序


SELECT *,if( starttime < UNIX_TIMESTAMP(), starttime - UNIX_TIMESTAMP(), (starttime - UNIX_TIMESTAMP()) + 2000000) as t
FROM `t_time` where endtime > UNIX_TIMESTAMP() order by t asc;

分析if()函数的作用。

为true
也就是已经开始记录的排序,执行 starttime – UNIX_TIMESTAMP() 按得到的值;starttime asc排序这样就得到了“最先开始的活动记录,也就是按starttime asc排序”列表
为false
执行 (starttime – UNIX_TIMESTAMP())+20000000
starttime – UNIX_TIMESTAMP()这样得到了 “starttime距离现在最近的,也就是马上将要开始的starttime”记录列表;由于 “未开始记录的”starttime – UNIX_TIMESTAMP() 得到的值可能小于“已开始记录的”starttime – UNIX_TIMESTAMP() 的值,这种情况下排序就混乱了,所以要加上一个很大的值20000000避免这种情况,这样就能保证未开始记录的starttime永远大于已开始记录的 starttime值,这样未开始的值记录排在了已开始记录之后了。

( 图上的时间是假定的,就是为了说明已经开始的记录是哪几条。其实执行的时间也是算得出来的:-) )

这里我们的需求就快要实现了,可以看到已开始记录是按starttime  asc排序的。也就是得到的是“最先开始的活动记录”,而我们要的是“刚刚开始的活动记录”,实现这个其实很简单了

starttime < UNIX_TIMESTAMP() ,那么starttime – UNIX_TIMESTAMP() 则是一定为负数的;starttime越大,得出的绝对值越小。反之starttime越小,得出的绝对值则越大,但加上负号,则是starttime越小的,得出的负值越小了,最后sql语句都是asc排序的。结果集里面已开始的记录也就是asc排序的。

 

所以如果要starttime 正序desc排序,那就是 UNIX_TIMESTAMP() – starttime 表达式了,starttime越大的,得到的值越小;starttime越小的,得到值越大;因为最后sql语句是asc排序,结果集里面已开始的记录从排列上来说是desc倒序的。


SELECT *,if( starttime < UNIX_TIMESTAMP(), UNIX_TIMESTAMP() - starttime, (starttime - UNIX_TIMESTAMP()) + 20000000) as t
FROM `t_time` where endtime > UNIX_TIMESTAMP() order by t asc;

可以看到id为2的记录排到第一了。

 

这个业务需求在与当前时间有关且记录是否过期进行排序的列表中常应用到,一次请求后数据处理过程中;可以用endtime与当前时间进行对比,对于已过期的记录进行标记,这样显示时可以很明了了。

 

这个之前在ppc上面提问题了的,查看可以点击这里

 

Apache Solr使用简介

在一个项目中需要使用全文检索功能,架构w推荐了Apache Solr一个独立的企业级别搜索服务器。于是就顺势了解下这个solr搜索服务器。

简介

官网地址:http://lucene.apache.org/solr/

solr是基于lucene的一个搜索服务器,lucene也是Apache的一个开源项目;

Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,即它不是一个完整的全文检

索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。

Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起

完整的全文检索引擎。–来自百度百科

可以简单的说Lucene就是一个搜索引擎架构,而Solr则是一个搜索服务器的实现。

solr是使用java开发实现,目前已经到4.0版本了,运行需要java1.6或以上版本;基于http get进行xml/json/cvs/binary等格式类型的数据传输。solr的数据都是以document对象进行存储,每一个document都由许多field组成,且每一个document都有一个默认的uniqueid,默认的是id。

solr的安装很简单,下载完成后解压,进入solr/example目录

user:~/solr/example java -jar start.jar

输入http://ip:8983/solr/即可看到solr服务器的管理界面。

solr需要一个java服务器,solr服务器里面自带了jetty服务器所以不需要另立服务器,如果有tomcat也可以直接新开一个端口用来运行。

Dashboard是solr服务器的搭建路径,版本,运行环境,机器的物理内存,File Descriptor Count ,jvm内存使用情况等

Logging 则是运行的记录,包括错误,警告等

core admin则是 Schema 的管理界面,可以查看某个Schema的情况,同事还能新建,删除,优化等

java properties 即运行solr的java的所有详细参数

thread dump 运行线程的详细情况,各种数据跟踪

collection1 则是建立的Schema,可以查看Schema的记录数,版本数;还有如ping,query,config等管理操作

solr与php的结合实现

php的solr接口实现已现有代码实现了。项目地址:http://code.google.com/p/solr-php-client/

不过这里需要注意的是因为solr现在是4.0版本了,该php client实现里面还有几个需要注意的,如service.php 中实现add接口的addDocument方法,4.0版本的add属性参数已有改变,废弃了其中的allowDups,overwritePending,overwriteCommitted这几个参数所以需要将这几个参数去掉,否则solr服务器会报错。

一个很简单的例子

<?php

require_once 'Apache/Solr/Service.php';

$transportIntance = new Apache_Solr_HttpTransport_Curl();
//$transportIntance = new Apache_Solr_HttpTransport_CurlNoReuse();
// 如果这里有不同的Schema如ASchema与BSchema,则传入的第三个参数就是'/solr/ASchema/'与'/solr/BSchema/'
$solr = new Apache_Solr_Service('192.168.1.10', '8983', '/solr/');

// 检测solr服务是否可用
if($solr->ping()){
$solr->deleteByQuery('*:*');

//
// Create two documents to represent two auto parts.
// In practice, documents would likely be assembled from a
//   database query.
// 这里需要注意的是,使用的field必须存在于Schema中,否则document写入不到solr服务器,且检索时会产生
  // org.apache.solr.common.SolrException: ERROR: [doc=50cdce141f792] unknown field 'partno'的错误
  // 这是需要将partno等schema里面没有的field全部都加入到apache-solr-4.0.0/example/solr/collection1/conf/schema.xml <fields>里面
  // 写入到schema的 field 需要注意name与attribute,详细请看http://wiki.apache.org/solr/SchemaXml
  // 保存完成后重启服务器
$parts = array(
'spark_plug' => array(
'partno' => 1,
'name' => 'Spark plugdw',
//      'model' => array( 'Boxster', '924' ),
'year' => array( 1999, 2000 ),
'price' => 25.00,
'inStock' => true,
),
'windshield' => array(
'partno' => 2,
'name' => 'Windshield',
//      'model' => array('911', '111'), //multiple values encountered for non multiValued field model:
'year' => array( 1999, 2000 ),
'price' => 15.00,
'inStock' => false,
)
);

$documents = array();

foreach ( $parts as $item => $fields ) {
$part = new Apache_Solr_Document();
$part->id = uniqid();
foreach ( $fields as $key => $value ) {
if ( is_array( $value ) ) {
foreach ( $value as $datum ) {
$part->setMultiValue( $key, $datum );
}
}
else {
$part->$key = $value;
}
}

$documents[] = $part;
}

//var_dump($documents);

// Load the documents into the index

try {
$solr->addDocuments( $documents );
    // 这里需要注意在加入document后必须要commit提交到检索索引里面,否则最新写入的内容不会被检索到
$solr->commit();
   // optimize索引优化这里会重建索引,每次adddocument时不必要进行,应该放到用户搜索很少的时刻定时执行
   // 如每周末的24点执行,crontab也是一个选择
$solr->optimize();
}
catch ( Exception $e ) {
echo $e->getMessage();
}

// Run some queries. Provide the raw path, a starting offset
//   for result documents, and the maximum number of result
//   documents to return. You can also use a fourth parameter
//   to control how results are sorted and highlighted,
//   among other options.
//
$offset = 0;
$limit = 10;

$queries = array(
'partno:1',
'name:*w*'
);

foreach ( $queries as $query ) {
$response = $solr->search( $query, $offset, $limit );

if ( $response->getHttpStatus() == 200 ) {
// print_r( $response->getRawResponse() );

if ( $response->response->numFound > 0 ) {
echo "$query <br /><br/>";

foreach ( $response->response->docs as $doc ) {
echo "$doc->partno $doc->name <br />";
}

echo '<br />';
}
}
else {
echo $response->getHttpStatusMessage();
}
}

}else {
die('no connection solr service...');
}

?>

solr的query语法

根据不同的需要进行选择。

/**
* query语法
* 查询语法 http://lucene.apache.org/core/3_6_0/queryparsersyntax.html
* 1) title:”The Right Way” AND text:go // 标题的值匹配,AND text:go 表示对title默认的值为go的项进行筛选,但不是必须的
* 2) mod_date:[20020101 TO 20030101] //mod_date日期在20020101与20030101之间的
* 3) title:{Aida TO Carmen}     // 标题在Aida 与Carmen之间的集合,但不包括Aida Carmen
* 4) content:”jakarta apache” AND “Apache Lucene” // 搜索的字段必须包含 jakarta apache与Apache Lucene;可以使用&&代替AND
* 5) content:+jakarta lucene     // 必须包含jakarta 或可能包含lucene
* 6) content:”jakarta apache” jakarta // 结果中包含jakarta apache或jakarta ;或使用content:”jakarta apache” OR jakarta。可以使用||代替OR
* 7) content:”jakarta apache” NOT “Apache Lucene” // 包含jakarta apache,但不包含Apache Lucene;可以使用! 代替NOT
* 8) content:”jakarta apache” -“Apache Lucene” // 包含jakarta apache,但不包括Apache Lucene;与NOT区别还没明白
* 9) content:(jakarta OR apache) AND website // 使用括号实现子查询,包含jakarta OR apache以及website
* 10) title:(+return +”pink panther”)        // 使用括号将多个子句合并为一个字段,标题中必须包含单词return以及短语”pink panther”
* 11) title:tes? // 使用? * 来实现内容的搜索
* 12) 支持转移字符的查询,需转移字符包括+ – && || ! ( ) { } [ ] ^ ” ~ * ? : \ 可以使用\来转义字符,如title:\(1\+1\)\:2
* 13) title:roam~ // ~表示模糊查询,romam~将查找到foam或roams得单词;roam~0.8表示将得到roam 80%的相似度单词
* 14) content:”jakarta apache”~10 // 临近搜索,检索相隔10个单词的”apache”和”jakarta”
*/

solr的搜索是自动进行分词的,当然这是指英文的空格分词;如果中文需要分词怎么办呢?当然也有有心人已经做出来了,就是这个mmseg4j 作者博客上的简介 作者对于lucene solr很有研究,值得一看。

更多阅读

Solr Tutorial

Apache Solr Wiki

淘宝网综合业务平台团队博客 solr文章

Lucene/Solr Optimize相关总结

Solr监控

discuzx2系列之群组积分排行

很长一段时间内都在忙着一个用discuzX2二次开发的项目。刚开始接触discuzx,查看里面的代码的时候感觉就像渣渣一样。虽然功能很强大,但是觉得代码真是太混乱了。不过随着后来项目定制的深入以及慢慢对discuzx的了解,discuzx真的很强大。尽管有些代码不是很规范,比如全局变量的滥用,函数里面require文件,输入数据的检测,模板里面的逻辑混乱可阅读性不高,整体架构只是类mvc还是很传统的函数编程等等还有很多。不过discuzx确实是一款成功的论坛类cms的系统,是一款很好的产品,可扩展性高,数据承载量大,功能强大,快速高效。现在就介绍下遇到的问题以及对这些问题的处理方法。

discuzx的资料:

discuzx的技术文库 http://dev.discuz.org/wiki

discuzx主站 http://www.discuz.net/

现在discuzx已经更新到2.5了。听说3.0现在正在研发中,2.5版本的整体架构变更比较大,很多之前直接写sql的以及用函数的地方都使用类库来替代了,不过代码还没有细看详细了解自行看源码。

这次要记录的内容是discuzx群组模块里面的一个功能扩展,这个功能就是群组的积分排行以及群组的当月积分排行。

可以看出群组排行可以按两项排行,一是总积分排行,二是当月积分排行。

一群组总积分排行,这个其实很简单。

我们知道用户创建的群组数据都存储在forum_forum表中,我们通过查看数据字典可以很轻松的知道该表中的字段所代表的意义。

如上图commoncredits就是“群组公共积分”,那么现在就可以很轻松的按这个字段的值进行desc排序了。不过群组的公共积分是怎样计算的呢?下面在详细了解。

二群组当月积分的总排行

这个会比较麻烦,因为discuzx木有这个功能啊(侧面说明discuzx很强大,很多功能都不需要进行改动直接拿来用)。不过我们可以马上进行扩展。

首先就是要搞明白群组的“公共积分”是怎么得来的,不管是总积分排行还是当月积分排行,数据更新的来源都是同一处。不过现在无处下手不知道积分增加的代码在哪,不过积分增加一定是与用户在群组内的操作有关的,比如发帖,回复,回帖等。所以只好监控表数据,通过回帖操作,发现总积分commoncredits数据+1,不过第二次操作时数据就没有变化了,那就说明回帖的时候一定进行了数据的更新。

可以查找到帖子回复的代码文件是source/include/post/post_newreply.php

不过再次查找就是麻烦了,一段一段的读代码时间也太长了,所以简单的办法来了,直接source搜索commoncredits,commoncredits数据变更一定是有“commoncredits=commoncredits+1”的sql语句了,于是搜之就出现结果了,就是function_grouplog.php这个文件里面,只有这一个函数。然后再用“updategroupcreditlog”在post_newreply.php里面搜索是在哪调用的就知道了

我们还可以具体了解到updategroupcreditlog是在哪些的动作中调用的。

可以看到发帖、回帖、发布商品动作都会对群组的公共积分进行增加。

那么接下来要做的事就是了解updategroupcreditlog函数了。

查看源代码可以了解到这个函数对于增加积分的行为。使用cookie可以避免用户每次回帖或发帖都检测数据库,同时在服务器端forum_groupcreditslog中保存用户今天在该论坛是否回帖的数据是很有必要的。因为这样可以规避用户更改客户端时间或删除cookie信息来为增加群组的积分。接下来就是通过检查$_G是否存在当前群组的信息;然后再检测群组的等级,判断群组是否足够积分进行升级。


<?php

/**
 *      [Discuz!] (C)2001-2099 Comsenz Inc.
 *      This is NOT a freeware, use is subject to license terms
 *
 *      $Id: function_grouplog.php 16644 2010-09-11 03:33:30Z monkey $
 */

if(!defined('IN_DISCUZ')) {
 exit('Access Denied');
 }

function updategroupcreditlog($fid, $uid) {
 global $_G;
 if(empty($fid) || empty($uid)) {
 return false;
 }
 $today = date('Ymd', TIMESTAMP);
 $month = date('Ym', TIMESTAMP);

$updategroupcredit = getcookie('groupcredit_'.$fid);
 if($updategroupcredit < $today) {
 $status = DB::result_first("SELECT logdate FROM ".DB::table('forum_groupcreditslog')." WHERE fid='$fid' AND uid='$uid' AND logdate='$today'");
 if(empty($status)) {

DB::query("UPDATE ".DB::table('forum_forum')." SET commoncredits=commoncredits+1 WHERE fid='$fid'");
 DB::query("REPLACE INTO ".DB::table('forum_groupcreditslog')." (fid, uid, logdate) VALUES ('$fid', '$uid', '$today')");
 if(empty($_G['forum']) || empty($_G['forum']['level'])) {
 $forum = DB::fetch_first("SELECT name, level, commoncredits FROM ".DB::table('forum_forum')." WHERE fid='$fid'");
 } else {
 $_G['forum']['commoncredits'] ++;
 $forum = &$_G['forum'];
 }
 if(empty($_G['grouplevels'])) {
 loadcache('grouplevels');
 }
 $grouplevel = $_G['grouplevels'][$forum['level']];

if($grouplevel['type'] == 'default' && !($forum['commoncredits'] >= $grouplevel['creditshigher'] && $forum['commoncredits'] < $grouplevel['creditslower'])) {
 $levelid = DB::result_first("SELECT levelid FROM ".DB::table('forum_grouplevel')." WHERE type='default' AND creditshigher<='$forum[commoncredits]' AND creditslower>'$forum[commoncredits]' LIMIT 1");
 if(!empty($levelid)) {
 DB::query("UPDATE ".DB::table('forum_forum')." SET level='$levelid' WHERE fid='$fid'");
 $groupfounderuid = DB::result_first("SELECT founderuid FROM ".DB::table('forum_forumfield')." WHERE fid='$fid' LIMIT 1");
 notification_add($groupfounderuid, 'system', 'grouplevel_update', array(
 'groupname' => '<a href="forum.php?mod=group&fid='.$fid.'">'.$forum['name'].'</a>',
 'newlevel' => $_G['grouplevels'][$levelid]['leveltitle']
 ));
 }
 }
// 当月积分排行的处理
// 当前月份财富记录判断
 $monthstatus = DB::result_first("SELECT *  FROM ".DB::table('forum_groupmonthcredits')." WHERE fid='$fid' AND month='$month'");
 // 已存在当月财富记录
 if( $monthstatus){
 DB::query("UPDATE ".DB::table('forum_forum')." SET monthcommoncredits=monthcommoncredits+1 WHERE fid='$fid'");
 DB::query("UPDATE ".DB::table('forum_groupmonthcredits')." SET credit=credit+1 WHERE fid='$fid' AND month = {$month}");
 }else {
 // 需创建新的记录
 DB::query("INSERT INTO ".DB::table('forum_groupmonthcredits')." (fid, month, credit) VALUES ('$fid', '$month', '1')");
 DB::query("UPDATE ".DB::table('forum_forum')." SET monthcommoncredits=1,creditmonth = {$month} WHERE fid='$fid'");
 }

}
 dsetcookie('groupcredit_'.$fid, $today, 86400);
 }
 }


那么我们的目的明确了,接下来就是要分析处理了。

记录群组每月的增长财富值,并按当前月份的财富值进行排行。

群组表forum_forum新加入字段
monthcommoncredits // 当前月份财富值,积分值
creditmonth          // 财富值的当前月份,便于群组排序是与当前月份进行匹配

第一步
用户每天发帖,群组财富积分会增长。

第二部
借此机会可以实现每月的财富积分记录
新建一个数据表“群组每月的财富积分记录”

groupmonthcredits
fid  // 群组id
month // 月份
credit // 当月积分
可以用来记录统计某个群组的每个月的积分,对应可以扩展实现群组的当月积分趋势
普通用户if(empty($status)) 今天第一次进行发帖时
群组总积分+1
判断 groupmonthcredits 表的当前月份的财富积分记录是否存在?
如存在 则当前月份财富积分 credit 继续+1,群组表中的公共积分 monthcommoncredits 继续+1
否则不存在,说明现在进入新的月份,在 groupmonthcredits 创建新的当前月财富积分记录,credit默认为1(因为用户今天在群组内的第一次操作有财富积分+1奖励)。
同时forum_forum表中的当月财富积分 monthcommoncredits 更新为1,将当前财富月份 creditmonth 更新为当前的月份
第三步
群组按当前月份财富额排行,可以直接使用群组表 forum_forum 中的monthcommoncredits 进行desc排序。
但这里会存在一个bug,就是某群组8月的“当月财富积分”为1000,排名第一,这时候进入9月
但群组中的任何一位成员都没有进行发帖、回复等操作。于是该群组的财富积分还是 1000 ,还是高居榜首,原因就是第二步中的所有当月财富积分的判断都需要在用户进行发帖,回复等操作下进行处理。这时候要解决这个问题就需要用到 creditmonth 财富值月份字段了。只查询与当前月份匹配的群组记录

这样就可以避免了。尽管这样的可能性比较小,但是还需要做考虑。
对于财富积分的增长,始终放在用户手中。

[转]Apache的Order Allow,Deny 详解

原文地址:http://www.cnblogs.com/top5/archive/2009/09/22/1571709.html

对于.htaccess的用法不胜了解,常用的重定向、规则重写、ErrorDocument也都是复制粘贴了。对于一些files filesmatch ,Order Deny,Allow的使用不是很明白。转载一篇自我学习。

 

Allow和Deny可以用于apache的conf文件或者.htaccess文件中(配合Directory, Location, Files等),用来控制目录和文件的访问授权。

所以,最常用的是:
Order Deny,Allow
Allow from All

注意“Deny,Allow”中间只有一个逗号,也只能有一个逗号,有空格都会出错;单词的大小写不限。上面设定的含义是先设定“先检查禁 止设定,没有禁止的全部允许”,而第二句没有Deny,也就是没有禁止访问的设定,直接就是允许所有访问了。这个主要是用来确保或者覆盖上级目录的设置, 开放所有内容的访问权。

按照上面的解释,下面的设定是无条件禁止访问:
Order Allow,Deny
Deny from All

如果要禁止部分内容的访问,其他的全部开放:
Order Deny,Allow
Deny from ip1 ip2
或者
Order Allow,Deny
Allow from all
Deny from ip1 ip2

apache会按照order决定最后使用哪一条规则,比如上面的第二种方式,虽然第二句allow允许了访问,但由于在order中 allow不是最后规则,因此还需要看有没有deny规则,于是到了第三句,符合ip1和ip2的访问就被禁止了。注意,order决定的“最后”规则非 常重要,下面是两个错误的例子和改正方式:

Order Deny,Allow
Allow from all
Deny from domain.org
错误:想禁止来自domain.org的访问,但是deny不是最后规则,apache在处理到第二句allow的时候就已经匹配成功,根本就不会去看第三句。
解决方法:Order Allow,Deny,后面两句不动,即可。

Order Allow,Deny
Allow from ip1
Deny from all
错误:想只允许来自ip1的访问,但是,虽然第二句中设定了allow规则,由于order中deny在后,所以会以第三句deny为准,而第三句的范围中又明显包含了ip1(all include ip1),所以所有的访问都被禁止了。
解决方法一:直接去掉第三句。
解决方法二:
Order Deny,Allow
Deny from all
Allow from ip1

 

下面是测试过的例子:
——————————–
Order deny,allow
allow from all
deny from 219.204.253.8
#全部都可以通行
——————————-
Order deny,allow
deny from 219.204.253.8
allow from all
#全部都可以通行
——————————-
Order allow,deny
deny from 219.204.253.8
allow from all
#只有219.204.253.8不能通行
——————————-
Order allow,deny
allow from all
deny from 219.204.253.8
#只有219.204.253.8不能通行
——————————-
——————————-
Order allow,deny
deny from all
allow from 219.204.253.8
#全部都不能通行
——————————-
Order allow,deny
allow from 219.204.253.8
deny from all
#全部都不能通行
——————————-
Order deny,allow
allow from 219.204.253.8
deny from all
#只允许219.204.253.8通行
——————————-
Order deny,allow
deny from all
allow from 219.204.253.8
#只允许219.204.253.8通行
——————————-
——————————–
Order deny,allow
#全部都可以通行(默认的)
——————————-
Order allow,deny
#全部都不能通行(默认的)
——————————-
Order allow,deny
deny from all
#全部都不能通行
——————————-
Order deny,allow
deny from all
#全部都不能通行
——————————-
对于上面两种情况,如果换成allow from all,则全部都可以通行!
——————————-
Order deny,allow
deny from 219.204.253.8
#只有219.204.253.8不能通行
——————————-
Order allow,deny
deny from 219.204.253.8
#全部都不能通行
——————————-
Order allow,deny
allow from 219.204.253.8
#只允许219.204.253.8通行
——————————-
Order deny,allow
allow from 219.204.253.8
#全部都可以通行
——————————-
——————————-
order deny,allow
allow from 218.20.253.2
deny from 218.20
#代表拒绝218.20开头的IP,但允许218.20.253.2通过;而其它非218.20开头的IP也都允许通过。
——————————-
order allow,deny
allow from 218.20.253.2
deny from 218.20
#和上面的差不多,只是掉换的order语句中的allow、deny先后顺序,但最终结果表示全部都拒绝!

phpunit的安装,测试

什么是phpunit(来自百度百科

PHPUnit是一个轻量级的PHP测试框架。它是在PHP5下面对JUnit3系列版本的完整移植,是xUnit测试框架家族的一员(它们都基于模式先锋Kent Beck的设计)。

单元测试是几个现代敏捷开发方法的基础,使得PHPUnit成为许多大型PHP项目的关键工具。这个工具也可以被Xdebug扩展用来生成代码覆盖率报告 ,并且可以与phing集成来自动测试,最后它还可以和Selenium整合来完成大型的自动化集成测试。

安装部署有两种方式

方法一: 使用Pear

运行 pear channel-discover pear.phpunit.de;   pear install phpunit/PHPUnit

方法二 :手动安装   1 从http://pear.phpunit.de/get/下载软件包并解压   2 把解压后的目录加入php.ini中的include_path   3 将脚本pear-phpunit改名为phpunit   4 将phpunit脚本中的@php_bin@替换成php可执行脚本的路径   5 为phpunit脚本增加可执行权限并加入$PATH   6 将PHPUnit/Runner/Version.php中的@package_version@替换成3位   PHPUnit版本号

这里我们使用的Pear方式进行安装。以windows下环境为例。

在建立一个完整的php工作开发环境后,phpunit的的部署首先需要xdebug与pear的支持,才能安装phpunit。

1、安装xdebug

[Xdebug]

extension=php_xdebug.dll

xdebug.profiler_enable=on

xdebug.trace_output_dir=”E:/php/xdebug”

xdebug.profiler_output_dir=”E:/php/xdebug”

在安装xdebug后,重启apache,在phpinfo中即可看到了xdebug的信息。

接下来cmd进入php的安装目录,如E:/wamp/php/

输入pear,会提示“pear不是内部或外部的命令,也不是可运行的程序…”,这说明系统还没有安装pear,如果出现如下的命令方式

则说明pear安装成功了,可以直接去安装phpunit了。

在这里需要注意的是,xdebug的的加载方式必须以zend的方式进行加载;而extension是以php的加载方式进行实现的

所以在最后安装成功phpunit后,如果xdebug还是以php方式进行加载的话,系统会提示如下信息

zend_extension 加载是以zend引擎的方式进行模块加载的,详细信息如下

{

根据 PHP 版本,zend_extension 指令可以是以下之一:

zend_extension (non ZTS, non debug build)
zend_extension_ts ( ZTS, non debug build)
zend_extension_debug (non ZTS, debug build)
zend_extension_debug_ts ( ZTS, debug build)

ZTS:ZEND Thread Safety

可通过phpinfo()查看ZTS是否启用,从而决定用zend_extension还是zend_extension_ts。既查看phpinfo综合信息处的状态

extension意为基于php引擎的扩展;zend_extension意为基于zend引擎的扩展

注:php是基于zend引擎的

不同的扩展安装后,在php.ini里是用extension还是zend_extension,是取决于该扩展,有的扩展可能只能用zend_extension,如xdebug,也有的扩展可以用extension或zend_extension,如mmcache。

注:上面的结论不保证准确。
zend_extension加载php扩展时需用全路径,而extension加载时可以用相对extension_dir的路径。

}

这里我的 Thread Safety是启用状态,即线程安全版本,所以使用zend_extension_ts来加载。且xdebug必须使用全路径,这点很重要;不然添加到php.ini后,重启apache不会报错,但xdebug信息也是没有的。

2、安装pear

还是在php安装目录中,cmd输入如下命令

go-pear

连续输入两次yes,在直接按Enter。然后就自动从服务器上下载pear安装包,进行自动安装了。

再次输入pear命令,则会出现commands列表了。说pear安装成功了。

3、安装phpunit

继续cmd命令输入

pear channel-discover pear.phpunit.de

pear channel-discover pear.symfony-project.com

pear install phpunit/PHPUnit

这时可能会提示缺少依赖包内容,如下图

phpunit package

我们可以使用

pear install Image_GraphViz

pear install log

pear install YAML

进行依赖包得依次安装。安装完成后,继续输入

pear install phpunit/PHPUnit

耐心等待,可能有点慢,直到下载安装成功。

至此,在cmd中键入phpunit –version,提示phpunit版本信息则表示安装成功可以进入单元测试阶段了。

4、结合netbeans 使用phpunit进行单元测试

主要有两步,一、对某个类或者整个项目创建phpunit单元测试;二、单击源文件或项目进行单元测试。详细进行请进入使用 PHPUnit 和 Selenium 进行测试阅读。

其中对需要测试的类添加@assert 标注需要注意,点击这里详细了解,在netbeans中可以直接“创建phpunit单元测试”生成测试类;

<?php
class Calculator
{
/**
* @assert (0, 0) == 0
* @assert (0, 1) == 1
* @assert (1, 0) == 1
* @assert (1, 1) == 2
* @assert (1, 2) == 4
*/
public function add($a, $b)
{
return $a + $b;
}

// 添加用来测试“代码覆盖率”的
public function addnow($a, $b)
{
return $a + $b;
}
}
?>


同时也可以使用命令来完成

phpunit –skeleton-test Calculator

注意,Calculator是类名,不是文件名。

会在同目录下自动生成Calculator的测试类,CalculatorTest.php;

CalculatorTest.php类详细内容


<?php
require_once 'PHPUnit/Framework.php';

require_once 'D:\www\phpunit\Calculator.php';

/**
* Test class for Calculator.
* Generated by PHPUnit on 2012-09-25 at 10:17:34.
*/
class CalculatorTest extends PHPUnit_Framework_TestCase
{
/**
* @var Calculator
*/
protected $object;

/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
$this->object = new Calculator;
}

/**
* Tears down the fixture, for example, closes a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
}

/**
* Generated from @assert (0, 0) == 0.
*/
public function testAdd()
{
$this->assertEquals(
0,
$this->object->add(0, 0)
);
}

/**
* Generated from @assert (0, 1) == 1.
*/
public function testAdd2()
{
$this->assertEquals(
1,
$this->object->add(0, 1)
);
}

/**
* Generated from @assert (1, 0) == 1.
*/
public function testAdd3()
{
$this->assertEquals(
1,
$this->object->add(1, 0)
);
}

/**
* Generated from @assert (1, 1) == 2.
*/
public function testAdd4()
{
$this->assertEquals(
2,
$this->object->add(1, 1)
);
}

/**
* Generated from @assert (1, 2) == 4.
*/
public function testAdd5()
{
$this->assertEquals(
4,
$this->object->add(1, 2)
);
}

/**
* @todo Implement testAddnow().
*/
public function testAddnow()
{
// Remove the following lines when you implement this test.
$this->markTestIncomplete(
'This test has not been implemented yet.'
);
}
}
?>

这时就可以测试这个测试类了。

phpunit CalculatorTest.php unittest

结果信息如下

点号表示测试通过,没有错误;

F表示其中一个测试结果有误。错误信息直观展示了。

I表示测试没有实现执行

对比netbeans的执行更加抽象一点,如下为netbeans执行的测试结果。

这个只是一个简单的示例,更详细的服务于项目级别的对于每一个独立的模块都需要写新的独立测试类来进行测试。

phpunit安装成功后,会在c盘建立一个php5文件夹。其中可以找到PHPUnit的接测试例子

C:\php5\pear\docs\PHPUnit\PHPUnit\Samples

(说实话money例子挺复杂的)很容易绕晕了。

参考信息:

phpunit官方手册

PHP Warning: Xdebug MUST be loaded as a Zend extension in Unknown on line 0 解决办法 

PHP下安装phpunit 及xdebug

PHPUnit安装 部分配图来源此博客,鸣谢

使用 PHPUnit 和 Selenium 进行测试

PHPUnit是一个轻量级的PHP测试框架。它是在PHP5下面对JUnit3系列版本的完整移植,是xUnit测试框架家族的一员(它们都基于模式先锋Kent Beck的设计)。
单元测试是几个现代敏捷开发方法的基础,使得PHPUnit成为许多大型PHP项目的关键工具。这个工具也可以被Xdebug扩展用来生成代码覆盖率报告 ,并且可以与phing集成来自动测试,最后它还可以和Selenium整合来完成大型的自动化集成测试。

windows7 IE的保护模式对于js form.submit()自动执行提交的影响

近期在完成一个项目充值支付业务逻辑时,需要完成用户在网站上提交充值订单,网站后端处理充值订单信息,发布“网站正在跳转,进入充值平台”的提示信息,跳转至充值平台进行充值的这样一个流程。

因为直接采用的是discuzX进行二次开发,在xp、2003等各浏览器下业务逻辑测试都没有问题。就是在交付客户测试中,发现客户的IE9浏览器不能正常的充值支付;发现是停顿在了跳转消息发布的那一步中了,也就是说后端返回自建的form表单,并没有正确的提交。


// 对discuzx的提交进行了更改,没有采用ajax提交;而是直接订单form表单新页面提交,后端直接echo数据。

echo '<form id="payform" action="'.$requesturl.'" method="post"></form><script type="text/javascript" reload="1">$(\'payform\').submit();</script>';
dexit();

确实是个问题了,刚开始以为是订单form新页面提交后,新产生的此页面渲染模式有关,从而建立完整的文档结构


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">

...

但是效果还是没好转,只是在完成提示信息;自己form表单还是没有进行自提交。再次google查看

不过在查看的过程中发现了这样一个问题,就是在xp或2003的系统上是能够正常跳转使用的;但在win7 ie下则不可以了,但是其他浏览器则行,就算是IE内核,但非ie官方的浏览器也是正常的。

于是对比2003与win7下的IE8设置,发现了下图独特的地方。

多了一个“启动保护模式”选择框!
另一位同事尝试的取消这个勾选框后,重启IE 再次测试就能正常的跳转了。
至此问题明了了,不过这个选择框是windows7上系列浏览器的默认勾选状态;所以不可能要求windows7的用户去更改此项设置,且会招致用户的反感。
于是在想办法的过程中参考了其他网站的api支付跳转实现效果,发现用户充值订单表单提交后,在跳转信息的提示过程中,程序后端并没有直接跳转至支付平台,而是再次跳转至网站本地后端的一个页面,而这个跳转的过程是使用window.location.href来实现的,而目标页面则直接通过header location跳转到了目标支付平台,也就是充值表单数据最终是以get方式发布至支付平台。(安全性方面不知道是否降低了,待验证)
代码结构再次更改后,2003下测试正常;在windows7下测试也能执行正常的跳转了!为此,新添加一个php页面用来处理添加充值订单,header页面跳转。而原form接受页面只是进行数据的收集以及跳转信息的提示。
在业务逻辑构造的过程中,查看了discuzx2.5最新版本;支付充值这块地方的代码还是没有变化还是采用自建from表单提交的方式实现的。于是在discuz官网论坛上面去搜索“IE9 充值”果然有很多用户的帖子,
而官方团队对此的回复是“该问题我们已经发现,这问题与环境有关,且不好重现,之前装有安全软件的一些用户也出现空白现象,往后我们再排查下是否是产品本身的兼容性问题。 感谢您的反馈1”
不过已经有用户发现了这样的问题了,并且有了对应的解答
知识普及:
IE保护模式原理简述

IE保护模式(Protected Mode),以前也叫做IE低权利(Lowcenters)。IE保护模式可能是Windows Vista里最严厉的安全措施,比我们熟知的UAC(用户帐户控制)还要严厉。事实上,IE保护模式依赖于Windows Vista的以下三大特性:

(1)UAC(用户帐户控制)

相信大家已经很熟悉了,可以让管理员帐户自动获得一个标准用户的访问令牌,以减少WindowsVista系统的受攻击面。

(2)MIC(强制完整性控制)

这是WindowsVista安全架构中新增加的一种检测机制。大家知道,Windows2000/XP安全体系里,安全子系统把进程的访问令牌和 资源的访问控制列表进行匹配比较,以确认该进程是否具有访问该资源的权限。而在WindowsVista下,除了遵守传统的安全控制机制外,还必须检查进 程和资源对象的完整性级别,完整性级别低的进程,不能写入完整性级别高的资源对象。

(3)UIPI(用户界面特权隔离)

完整性级别低的进程,不能向完整性级别高的进程发送Window消息。

windows IE浏览器安全设置的注意事项:

在这里,我们必须了解以下三点注意事项:

1.只有IE浏览器才能享用保护模式带来的好处,第三方的浏览器、或者以IE为内核的一些外挂浏览器无法获得保护模式的功能。

2.只有确认安全的网站,才能添加到可信站点区域。错将问题站点添加到可信区域,将会导致系统安全的严重受损。

3.最好能够对旧版插件进行改进,以确保插件的兼容性,这样可以大大提高系统安全。

扩展阅读

Internet Explorer 保护模式有何功能?

IE8浏览器安全设置 更好的安全浏览

关闭“保护模式”后,IE9还有没有安全?

yui compressor Error missing variable name identifier is a reserved word

yui compressor 是一个很好用的css、js文件压缩工具。在昨天使用过程中出现了这样的问题。

missing variable name 缺少变量名称,以及接下来的identifier is a reserved word 定义了保留字。google搜索之,在stackoverflow看到这样一篇文章  Javascript YUICompressor error

不过还好提示有行号,31:11 ,31行,看看我这段的代码片段是

[javascript]

var goto = ”; // 第31行
if( location.hash){
goto = location.hash;
}else{
var hash = goto = "index";
hash = ‘#’ + hash;
location.replace( location.href + hash);
jQuery.cookie(‘wall_hash’, ‘index’, {
expires: new Date( 2100, 10, 1),
path: ‘/’
});
}
$.constrDom( goto);

[/javascript]

可以看到关键字是goto

第一回答的即“使用了私有的保留字”链接指向 reserved word 不过并没有看到goto的关键字,不过还是尝试修改了,修改后再次执行 yui compressor后,顺利执行,完成压缩!

再次搜索goto javascript保留字看到了这样的一个表格

可以清楚的看到goto关键确实存在,不过为什么firefox MDN为什么没有写入goto关键字呢?

不过还是自己无知了,对于javascript未来的保留字不是很熟悉,虽然在javascript执行过程中没有问题,但是还是要尽量的规避这些保留字了。

paypal支付开发解析(二)

在注册完成开发账户,虚拟的买家账户以及卖家账户后,即可准备开始开发,不过在此需要明白IPN与PDT是什么。

IPN(Instant Payment Notify) 即时付款传输

什么事即时付款通知,pdf文档里面说得很明白,一张图更加的有助于理解。

其中1到5这个动作过程就是即时付款通知的整个流程。详细解释如下

注意这里的IPN,是由paypal服务器主动把数据post到你指定的url页面,这里的url怎么设置?即IPN是如何启用的呢?

pdf文档里面说明了有两种方法,一是在卖家paypal登录账户后,在“用户信息”右边列表栏“即时付款通知习惯设定”点击那个“选择框” 输入通知的url地址

二是在表单中加入name为notify_url的隐藏域地址来启用IPN。

在这里我们采用第二种方法。

直观的示例代码:

// paypal发送至的$_POST数据
if(!empty($_POST)) {
$notify = $_POST;
$location = TRUE;
} else {
exit('Access Denied');
}
// 加上cmd命令 数据原样返回
$tmpAr = array_merge($_POST, array("cmd" => "_notify-validate"));
$postFieldsAr = array();
foreach ($tmpAr as $name => $value) {
$postFieldsAr[] = "$name=$value";
}
// $custom 自定义值
$customs = explode( '|', $notify['custom']);
// 订单编号
$orderid = $customs[1];
// 使用curl 发送至paypal服务器,返回VERIFIED或者是INVALID
$ppResponseAr = PPHttpPost(PAYPAL_URL, implode("&", $postFieldsAr), false);

$notifydata= array();
if( strcmp($ppResponseAr['httpResponse'], 'VERIFIED') == 0){
         // 数据验证成功,检测处理的订单数据,如订单状态是否完成,卖家商户邮箱是否一致,此次订单货币是否与表单提交一致等
if( ($notify['payment_status'] == 'Completed') && ($notify['receiver_email'] == DEFAULT_EMAIL_ADDRESS) && ($notify['mc_currency'] == 'USD') ){
$notifydata= array(
'validator'    => TRUE,
'status'    => 'completed',
'order_no'     => $orderid,
'price'     => $notify['mc_gross'] ? $notify['mc_gross'] : $notify['payment_gross'],
'trade_no'    => $notify['txn_id'],
'notify'    => 'success',
'location'    => $location
);
}
}else if( strcmp($ppResponseAr['httpResponse'], 'INVALID') == 0) {
$notifydata= array(
'validator'    => FALSE,
'notify'    => 'fail',
'location'    => $location
);
}

    /**
     * 结合discuzx的具体业务逻辑实现数据内容的更新
     */

// 订单数据paypal验证成功情况下
if($notifydata['validator']) {

$orderid = $notifydata['order_no'];
$postprice = $notifydata['price'];
$order = DB::fetch_first("SELECT o.*, m.username FROM ".DB::table('forum_order')." o LEFT JOIN ".DB::table('common_member')." m USING (uid) WHERE o.orderid='$orderid'");
if($order && floatval($postprice) == floatval($order['price']) ) {
        // 更新订单状态
if($order['status'] == 1) {
DB::query("UPDATE ".DB::table('forum_order')." SET status='2', buyer='$notifydata[trade_no]\tpaypal', paytype='paypal', confirmdate='$_G[timestamp]' WHERE orderid='$orderid'");
            // 更新用户的虚拟货币
updatemembercount($order['uid'], array($_G['setting']['creditstrans'] => $order['amount']), 1, 'AFD', $order['uid']);
updatecreditbyaction($action, $uid = 0, $extrasql = array(), $needle = '', $coef = 1, $update = 1, $fid = 0);
            // 清理过期的订单
DB::query("DELETE FROM ".DB::table('forum_order')." WHERE submitdate<'$_G[timestamp]'-60*86400");

$submitdate = dgmdate($order['submitdate']);
$confirmdate = dgmdate(TIMESTAMP);
            // 向用户发送充值成功的消息
notification_add($order['uid'], 'credit', 'addfunds_d', array(
'orderid' => $order['orderid'],
'price' => $order['price'],
'value' => $_G['setting']['extcredits'][$_G['setting']['creditstrans']]['title'].' '.$order['amount'].' '.$_G['setting']['extcredits'][$_G['setting']['creditstrans']]['unit']
), 1);
}

}

}

// 充值成功,跳转
if($notifydata['location']) {
$url = rawurlencode('home.php?mod=spacecp&ac=credit');

dheader('location: '.$_G['siteurl'].'forum.php?mod=misc&action=paysucceed');
} else {
    // 充值失败,显示提示信息
exit($notifydata['notify']);
}

至此IPN作用完成,同时整个充值过程也就完成了,不过用户这时还停留在paypal的支付完成的页面,不会自动跳转至商户或者用户已充值记录页面。这时,PDT的作用就出现了。

PDT(Payment Data Transfer)付款数据传输

同样PDT示意图