[转载]10 Best JavaScript And HTML5 Presentation Frameworks | Devzum - Its all about Design & Development

mikel阅读(1397)

[转载]10 Best JavaScript And HTML5 Presentation Frameworks | Devzum – Its all about Design & Development.

JavaScript and HTML5 frameworks play a big role in creating presentations for modern browsers. They embed features into web pages, and provide an effective way to present information. Generally, manual embedding takes great amount of time and effort. It’s really complicated and beginners cannot avail adequate results without applying efficient techniques. Presentations frameworks on other side feature great results without any issue or complication. They deliver best web development results with maximum speed and accuracy.

HTML5 is becoming very popular among modern designers and web developers. Its feature rich and lag free performance plays a big part in enhancing functionality and efficiency of a website. You can easily modify the look and feel of your website by selecting picking a perfect framework. Undoubtedly, it is a perfect web alternative to MS power point and keynote.

Therefore, check out top JavaScript and HTML5 frameworks, and pick the best one to present your task with the help of these slide presentation frameworks.

1) HTML5 Slides

top best html5 and javascript presentation framework html5

Slides is an efficient presentation framework for web development. Provided by Google, this framework gives a link which can be used to get the template with utmost ease and comfort. All one has to do is copy the code for the sample slides, and fill them up with new content.

2) Reveal.js 

top best html5 and javascript presentation framework - revealjs

Reveal.js is a renowned framework for web development. It creates great slides which can be positioned horizontally as well as vertically. By using it, you can easily add a perfect combination of various effects and 3D slide transitions to your presentation. A large number of web developers are using this effective and reliable framework.

3) Impress.js 

top best html5 and javascript presentation framework - impressjs 

Another efficient framework is Impress.js. It’s an amazing framework in which slides are arranged in 3D place, and are defined in “div” elements with adequate data attributes controlling the x, y, z locations and rotations. By using it, you can easily design some affluent, stunning and original slide shows.

4) Deck.js 

top best html5 and javascript presentation framework - deckjs

If you want a reliable framework to create great web presentations, deck.js can cater your needs. It’s one of the oldest HTML5 presentations, and carries stunning and effective features to show your slide. Its documented API also helps a lot in creating user defined extensions.

5) HTML Slide Presentation 

top best html5 and javascript presentation framework html-presentation 

6) CSSS 

top best html5 and javascript presentation framework - csss

This CSS based slide show system is extremely popular among designers and developers. It can be easily used to create and change presentations on all browsers. After using this framework, the quality of your presentation can improve a lot.

7) Fathom.js 

top best html5 and javascript presentation framework - fathomjs

Fathom.jb features an effective way to create presentations with HTML and styles with CSS. It is a great framework, and is powered by JQuery. As compared to other frameworks, it is much better because of high speed, and accurate performance.

8) DZSlides 

top best html5 and javascript presentation framework -  dzslide

DZ slides is a one file HTML system, and helps a lot in building slides in HTML5 and CSS3. By using this framework, you can easily include any text, image, or video in your slide, and can make it mobile friendly too.

9) Jmpress.js 

top best html5 and javascript presentation framework - jmpressjs

Jmpress.js also features an effective way to create great presentations. Used by a large number of designers, it not only delivers effective results, but is also much extremely easy in use. After using this framework, you’ll see great results in quick time.

10) HTML Slidy 

top best html5 and javascript presentation framework - html-slidy

The task of creating web presentations can also be handled with HTML Slidy. It’s a great framework and creates accessible slideshows in HTML and XHTML. The performance of this framework is extremely good for responsive web development.

[转载]EncodingAESKey - 方倍工作室 - 博客园

mikel阅读(1290)

[转载]EncodingAESKey – 方倍工作室 – 博客园.

关键字:EncodingAESKey

公众平台消息体签名及加解密方案概述
1.新增消息体签名验证,用于公众平台和公众账号验证消息体的正确性

2.针对推送给微信公众账号的普通消息和事件消息,以及推送给设备公众账号的设备消息进行加密

3.公众账号对密文消息的回复也要求加密

开发者需注意,公众账号主动调用API的情况将不受影响。
启用加解密功能(即选择兼容模式或安全模式)后,公众平台服务器在向公众账号服务器配置地址(可在“开发者中心”修改)推送消息时,URL 将新增加两个参数(加密类型和消息体签名),并以此来体现新功能。加密算法采用AES,具体的加解密流程和方案请看接入指引、技术方案和示例代码。
为了配合消息加密功能的上线,并帮助开发者适配新特性,公众平台提供了3种加解密的模式供开发者选择,即明文模式、兼容模式、安全模式(可在“开发者中心”选择相应模式),选择兼容模式和安全模式前,需在开发者中心填写消息加解密密钥EncodingAESKey。

明文模式:维持现有模式,没有适配加解密新特性,消息体明文收发,默认设置为明文模式

兼容模式:公众平台发送消息内容将同时包括明文和密文,消息包长度增加到原来的3倍左右;公众号回复明文或密文均可,不影响现有消息收发;开发者可在此模式下进行调试

安全模式(推荐):公众平台发送消息体的内容只含有密文,公众账号回复的消息体也为密文,建议开发者在调试成功后使用此模式收发消息
什么是EncodingAESKey? 微信公众平台采用AES对称加密算法对推送给公众帐号的消息体对行加密,EncodingAESKey则是加密所用的秘钥。公众帐号用此秘钥对收到的密文消息体进行解密,回复消息体也用此秘钥加密。

 

此外,微信公众平台为开发者提供了5种语言的示例代码(包括C++、php、Java、Python和C#版本,http://mp.weixin.qq.com/wiki/downloads/SampleCode.zip )。 请开发者查看接入指引开发者FAQ来接入消息体签名及加解密功能,若关注技术实现,可查看技术方案

 

该文档讲述如何使用示例代码接入加解密,参考本文档并使用示例代码,加解密的接入将非常简单。若想进一步的了解细节,请查看技术方案。 微信公众平台提供了C++、php、Java、Python和C# 5种语言的示例代码,每种语言的类名和接口名均一致,下面以C++为例说明:

目录

[隐藏]

函数说明

构造函数

   // @param sToken: 公众平台上,开发者设置的Token
   // @param sEncodingAESKey: 公众平台上,开发者设置的EncodingAESKey
   // @param sAppid: 公众号的appid
   WXBizMsgCrypt(const std::string &sToken, 
                   const std::string &sEncodingAESKey, 
                   const std::string &sAppid);

解密函数

   // 检验消息的真实性,并且获取解密后的明文
   // @param sMsgSignature: 签名串,对应URL参数的msg_signature
   // @param sTimeStamp: 时间戳,对应URL参数的timestamp
   // @param sNonce: 随机串,对应URL参数的nonce
   // @param sPostData: 密文,对应POST请求的数据
   // @param sMsg: 解密后的明文,当return返回0时有效
   // @return: 成功0,失败返回对应的错误码
   int DecryptMsg(const std::string &sMsgSignature,
                   const std::string &sTimeStamp,
                   const std::string &sNonce,
                   const std::string &sPostData,
                   std::string &sMsg);

加密函数

   //将公众号回复用户的消息加密打包
   // @param sReplyMsg:公众号待回复用户的消息,xml格式的字符串
   // @param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp
   // @param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce
   // @param sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,当return返回0时有效
   // return:成功0,失败返回对应的错误码
   int EncryptMsg(const std::string &sReplyMsg,
                   const std::string &sTimeStamp,
                   const std::string &sNonce,
                   std::string &sEncryptMsg);

使用方法

在安全模式或兼容模式下,url上会新增两个参数encrypt_type和msg_signature。encrypt_type表示加密类 型,msg_signature:表示对消息体的签名。 url上无encrypt_type参数或者其值为raw时表示为不加密;encrypt_type为aes时,表示aes加密(暂时只有raw和aes 两种值)。公众帐号开发者根据此参数来判断微信公众平台发送的消息是否加密。

兼容模式和安全模式加解密的方法完全一样,兼容模式的xml消息体比安全模式多了几个明文字段,具体请查看《消息加解密详细技术方案》。
实例化对象

使用构造函数,实例化一个对象,传入公众帐号的token, appid, EncodingAESKey。
解密

安全模式或者兼容模式下,公众号收到以下带密文消息体(“……”表示兼容模式下的明文字段):

encrypt_msg = 
<xml>
	<ToUserName><![CDATA[gh_10f6c3c3ac5a]]></ToUserName>
	……
	<Encrypt><![CDATA[hQM/NS0ujPGbF+/8yVe61E3mUVWVO1izRlZdyv26zrVUSE3zUEBdcXITxjbjiHH38kexVdpQLCnRfbrqny1yGvgqqKTGKxJWWQ9D5WiiUKxavHRNzYVzAjYkp7esNGy7HJcl/P3BGarQF3+AWyNQ5w7xax5GbOwiXD54yri7xmNMHBOHapDzBslbnTFiEy+8sjSl4asNbn2+ZVBpqGsyKDv0ZG+DlSlXlW+gNPVLP+YxeUhJcyfp91qoa0FJagRNlkNul4mGz+sZXJs0WF7lPx6lslDGW3J66crvIIx/klpl0oa/tC6n/9c8OFQ9pp8hrLq7B9EaAGFlIyz5UhVLiWPN97JkL6JCfxVooVMEKcKRrrlRDGe8RWVM3EW/nxk9Ic37lYY5j97YZfq375AoTBdGDtoPFZsvv3Upyut1i6G0JRogUsMPlyZl9B8Pl/wcA7k7i4LYMr2yK4SxNFrBUw==]]></Encrypt>
</xml>

调用DecryptMsg接口,传入收到的url上的参数:msg_signature(注意:不是signature,而是msg_signature), timestamp, nonce和encrypt_msg,若调用成功,sMsg则为输出结果,其内容为如下的明文的xml消息体:

<xml>
	<ToUserName><![CDATA[gh_10f6c3c3ac5a]]></ToUserName>
	<FromUserName><![CDATA[oyORnuP8q7ou2gfYjqLzSIWZf0rs]]></FromUserName>
 	<CreateTime>1411035097</CreateTime>
	<MsgType><![CDATA]></MsgType>
	<Content><![CDATA[this is a test message]]></Content>
	<MsgId>6060349595123187712</MsgId>
</xml>

公众帐号处理消息

生成需要回复给微信公众平台的xml消息体,假设回复以下内容:

res_msg = 
<xml>    
	<ToUserName><![CDATA[oyORnuP8q7ou2gfYjqLzSIWZf0rs]]></ToUserName>
	<FromUserName><![CDATA[gh_10f6c3c3ac5a]]></FromUserName>
	<CreateTime>1411034505</CreateTime>
	<MsgType><![CDATA]></MsgType>
	<Content><![CDATA[Welcome to join us!]]></Content>
	<FuncFlag>0</FuncFlag>
</xml>

回包加密

调用EncryptMsg接口,传入需要回复给微信公众平台的res_msg, timestamp, nonce,若加密成功,则sEncryptMsg为密文消息体,内容如下:

<xml>
  	<Encrypt><![CDATA[LDFAmKFr7U/RMmwRbsR676wjym90byw7+hhh226e8bu6KVYy00HheIsVER4eMgz/VBtofSaeXXQBz6fVdkN2CzBUaTtjJeTCXEIDfTBNxpw/QRLGLqqMZHA3I+JiBxrrSzd2yXuXst7TdkVgY4lZEHQcWk85x1niT79XLaWQog+OnBV31eZbXGPPv8dZciKqGo0meTYi+fkMEJdyS8OE7NjO79vpIyIw7hMBtEXPBK/tJGN5m5SoAS6I4rRZ8Zl8umKxXqgr7N8ZOs6DB9tokpvSl9wT9T3E62rufaKP5EL1imJUd1pngxy09EP24O8Th4bCrdUcZpJio2l11vE6bWK2s5WrLuO0cKY2GP2unQ4fDxh0L4ePmNOVFJwp9Hyvd0BAsleXA4jWeOMw5nH3Vn49/Q/ZAQ2HN3dB0bMA+6KJYLvIzTz/Iz6vEjk8ZkK+AbhW5eldnyRDXP/OWfZH2P3WQZUwc/G/LGmS3ekqMwQThhS2Eg5t4yHv0mAIei07Lknip8nnwgEeF4R9hOGutE9ETsGG4CP1LHTQ4fgYchOMfB3wANOjIt9xendbhHbu51Z4OKnA0F+MlgZomiqweT1v/+LUxcsFAZ1J+Vtt0FQXElDKg+YyQnRCiLl3I+GJ/cxSj86XwClZC3NNhAkVU11SvxcXEYh9smckV/qRP2Acsvdls0UqZVWnPtzgx8hc8QBZaeH+JeiaPQD88frNvA==]]></Encrypt>
	<MsgSignature><![CDATA[8d9521e63f84b2cd2e0daa124eb7eb0c34b6204a]]></MsgSignature>
	<TimeStamp>1411034505</TimeStamp>
	<Nonce><![CDATA[1351554359]]></Nonce>
</xml>

注意事项

  • EncodingAESKey长度固定为43个字符,从a-z,A-Z,0-9共62个字符中选取。 公众帐号可以在公众平台的开发者中心的服务器配置修改
  • 出于安全考虑,公众平台网站提供了修改EncodingAESKey的功能(在EncodingAESKey可能泄漏时进行修改),所以建议公众 账号保存当前的和上一次的EncodinAESKey,若当前EncodingAESKey解密失败,则尝试用上一次的EncodingAESKey的解 密。回包时,用哪个Key解密成功,则用此Key加密对应的回包
  • 兼容模式消息体同时存在明文和密文,消息体会增至以前的3倍左右,开发者注意检查系统,防止因消息变长和URL参数增加而出现接收错误
  • 如果url上无encrypt_type参数或者其值为raw,则回复明文,否则回复密文。兼容模式期间公众账号回复明文或密文均可(不要两种类型都回)

函数错误返回码

函数返回码 说明
0 处理成功
-40001 校验签名失败
-40002 解析xml失败
-40003 计算签名失败
-40004 不合法的AESKey
-40005 校验AppID失败
-40006 AES加密失败
-40007 AES解密失败
-40008 公众平台发送的xml不合法
-40009 Base64编码失败
-40010 Base64解码失败
-40011 公众帐号生成回包xml失败

 

示例代码下载

微信公众平台为开发者提供了5种语言的示例代码(包括C++、php、Java、Python和C#版本) 点击下载 http://mp.weixin.qq.com/wiki/downloads/SampleCode.zip

 

阅读须知

1. EncodingAESKey长度固定为43个字符,从a-z,A-Z,0-9共62个字符中选取,公众帐号可以在公众平台的开发者中心的服务器配置修改;

2. AES密钥:AESKey=Base64_Decode(EncodingAESKey + “=”),EncodingAESKey尾部填充一个字符的“=”, 用Base64_Decode生成32个字节的AESKey;

3. AES采用CBC模式,秘钥长度为32个字节,数据采用PKCS#7填充;PKCS#7:K为秘钥字节数(采用32),buf为待加密的内容,N为其字节 数。Buf需要被填充为K的整数倍。在buf的尾部填充(K-N%K)个字节,每个字节的内容是(K- N%K);

尾部填充
01 if ( N%K==(K-1))
0202 if ( N%K==(K-2))
030303 if ( N%K==(K-3))
KK….KK (K个字节) if ( N%K==0)

具体详见:http://tools.ietf.org/html/rfc2315

5. 出于安全考虑,公众平台网站提供了修改EncodingAESKey的功能(在EncodingAESKey可能泄漏时进行修改),所以建议公众账号保存 当前的和上一次的EncodingAESKey,若当前EncodingAESKey生成的AESKey解密失败,则尝试用上一次的AESKey的解密。回包时,用哪个AESKey解密成功,则用此AESKey加密对应的回包

6. 兼容模式消息体同时存在明文和密文,消息体会增至以前的3倍左右,开发者注意检查系统,防止因消息变长和URL参数增加而出现接收错误

7. 微信团队提供了多种语言的示例代码(包括php、Java、C++、Python、C#),请开发者尽量使用示例代码。(http://mp.weixin.qq.com/wiki/downloads/SampleCode.zip )
下面以普通文本消息为例,详细说明公众平台对消息体加解密的方法和流程,其它普通消息和事件消息的加解密可以此类推。

公众账号接收用户消息

消息体加密

现有消息为明文,格式如下:

msg = 
<xml>
	<ToUserName><![CDATA[toUser]]></ToUserName>
	<FromUserName><![CDATA[fromUser]]></FromUserName> 
	<CreateTime>1348831860</CreateTime>
	<MsgType><![CDATA]></MsgType>
	<Content><![CDATA[this is a test]]></Content>
	<MsgId>1234567890123456</MsgId>
</xml>

兼容模式期间同时保留明文和密文,消息格式如下:

new_msg=
<xml>
	<ToUserName><![CDATA[toUser]]></ToUserName>
	<FromUserName><![CDATA[fromUser]]></FromUserName> 
	<CreateTime>1348831860</CreateTime>
	<MsgType><![CDATA]></MsgType>
	<Content><![CDATA[this is a test]]></Content>
	<MsgId>1234567890123456</MsgId>
	<Encrypt><![CDATA[msg_encrypt]]</Encrypt>
</xml>

安全模式下,消息体只有密文,格式如下:

new_msg=
<xml> 
	<ToUserName><![CDATA[toUser]]</ToUserName>
       <Encrypt><![CDATA[msg_encrypt]]</Encrypt>
</xml>

其中,msg_encrypt = Base64_Encode( AES_Encrypt[ random(16B) + msg_len(4B) + msg + $AppId] )

AES加密的buf由16个字节的随机字符串、4个字节的msg_len(网络字节序)、msg和$AppId组成,其中msg_len为msg的长度,$AppId为公众帐号的AppId

AESKey =Base64_Decode(EncodingAESKey + “=”),32个字节

url上增加参数encrypt_type,encrypt_type的值为raw时表示为不加密,encrypt_type的值为aes时,表示aes加密(暂时只有raw和aes两种值),无encrypt_type参数同样表示不加密
消息体签名

为了验证消息体的合法性,公众平台新增消息体签名,开发者可用以验证消息体的真实性,并对验证通过的消息体进行解密

在url上增加参数:msg_signature

msg_signature=sha1(sort(Token、timestamp、nonce, msg_encrypt))

参数 说明
Token 公众平台上,开发者设置的Token
timestamp URL上原有参数,时间戳
nonce URL上原有参数,随机数
msg_encrypt 前文描述密文消息体

消息体验证和解密

开发者先验证消息体签名的正确性,验证通过后,再对消息体进行解密。
验证方式

1. 开发者计算签名,dev_msg_signature=sha1(sort(Token、timestamp、nonce, msg_encrypt))

2. 比较dev_msg_signature和URL上带的msg_signature是否相等,相等则表示验证通过
解密方式如下:

1. aes_msg=Base64_Decode(msg_encrypt)

2. rand_msg=AES_Decrypt(aes_msg)

3. 验证尾部$AppId是否是自己的AppId,相同则表示消息没有被篡改,这里进一步加强了消息签名验证

4. 去掉rand_msg头部的16个随机字节,4个字节的msg_len,和尾部的$AppId即为最终的xml消息体

 

公众账号向用户回复消息

如果url上无encrypt_type或者其值为raw,则回复明文,否则按照上述的加密算法加密回复密文。兼容模式期间公众账号回复明文或密文均可(不要两种类型都回)

回复消息体的签名与加密

现有消息格式:

msg=
<xml>
	 <ToUserName><![CDATA[toUser]]></ToUserName>
	 <FromUserName><![CDATA[fromUser]]></FromUserName>
	 <CreateTime>12345678</CreateTime>
	 <MsgType><![CDATA]></MsgType>
	 <Content><![CDATA[你好]]></Content>
</xml>

加密后消息格式:

new_msg=
<xml>
	<Encrypt><![CDATA[msg_encrypt]]></Encrypt>
	<MsgSignature><![CDATA[msg_signature]]></MsgSignature>
	<TimeStamp>timestamp</TimeStamp>
	<Nonce><![CDATA[nonce]]></Nonce>
</xml> 

其中,msg_encrypt=Base64_Encode(AES_Encrypt [random(16B)+ msg_len(4B) + msg + $AppId])

random(16B)为16字节的随机字符串;msg_len为msg长度,占4个字节(网络字节序),$AppId为公众账号的AppId

AESKey =Base64_Decode(EncodingAESKey + “=”),32个字节

msg_signature=sha1(sort(Token、timestamp、nonce, msg_encrypt))

timestamp、nonce回填请求中的值或者重新生成均可

 

 

消息加解密功能开发者FAQ

Q 为什么要上线消息加密功能?

A 为了更好的保护用户和公众账号的信息安全。
Q 接入消息加解密功能复杂吗?

A 开发者接入消息加解密功能并不复杂,微信团队提供了5种语言的示例代码(包括C++、php、Python、Java和C#),对于使用这个5种语言的开 发者,只需根据《消息加解密接入指引》,参考示例代码,调用微信公众平台提供的函数即可;而对于其他语言的开发者,需根据《消息加解密详细技术方案》编写 相关代码。
Q 消息加密功能将带来哪些重要变化?

A 有如下几个方面:

  • 选择明文模式时,收发消息的方式和原先相同,但安全系数较低,微信团队推荐开发者在兼容模式下开发调试,并升级到安全模式;
  • 选择兼容模式时,消息包同时包括明文和密文,消息包的长度会相应增加到原来的3倍左右,开发者需检查系统,做好预留,防止因消息变长而接收出错;
  • 兼容模式和安全模式下,公众平台服务器向公众账号服务器配置地址URL推送消息时,将会增加两个参数;
  • 安全模式下,内容为纯密文,请提前做好接收消息的解密工作和回复消息的加密工作。

Q 什么是EncodingAESKey?

A 微信公众平台采用AES对称加密算法对推送给公众帐号的消息体对行加密,EncodingAESKey则是加密所用的秘钥。公众帐号用此秘钥对收到的密文消息体进行解密,回复消息体也用此秘钥加密。
Q 开发者如何判断消息是否被加密?什么情况下需要对回包进行加密?

A 请开发者根据URL参数来判断:url上无encrypt_type参数或者其值为raw,表示消息体仅含有明文,公众账号回复明文。encrypt_type为aes则表示消息体含有密文,公众账号回复密文(兼容模式期间回复明文或密文均可)。
Q 公众账号开发者上线加解密版本后,还需要保留明文解包和回包逻辑吗?

A 暂时先保留之前的逻辑,根据参数判断,也做成兼容模式比较好。
Q 常见错误举例

A 常见错误原因如:

  • xml格式不对:如<TimeStamp>写成了<Timestamp > (s小写了且p和>中间有空格)
  • 公众平台网站提供了修改EncodingAESKey的功能,公众账号需要保存当前的和上一次的EncodingAESKey,若当前的 EncodingAESKey解密失败,则尝试用上一次的EncodingAESKey解密。回包时,用哪个Key解密成功,则用此Key加密对应的回包
  • 调用DecryptMsg解密时,传入的是url上的msg_signature,而不是signature
  • Java要求jdk 1.6及1.6以上
  • 异常java.security.InvalidKeyException:illegal Key Size的解决方案:在官方网站下载JCE无限制权限策略文件(JDK7的下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html 下 载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt,如果安装了JRE,将两个 jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件;如果安装了JDK,将两个jar文件放到%JDK_HOME% \jre\lib\security目录下覆盖原来文件

 

[转载]爬虫-微信公众平台消息获取 - 推酷

mikel阅读(1072)

[转载]爬虫-微信公众平台消息获取 – 推酷.

帮朋友抓取微信公众平台的用户评论信息。

下面只说核心的部分,怎么获取评论信息。

查看HTML代码,没有发现关于评论部分的标签。看来是用JS动态生成的,但是查找ajax请求也没有找到哪里有返回数据。

最后搜索一下,原来是在这里,很直白的写在了JS里:

<script type="text/javascript">
    wx.cgiData = {
      total_count : 91,
      latest_msg_id : '200325222',
      count : "20"*1 || 20,
      day : "7",
      frommsgid : "",
      can_search_msg : "1",
      offset : "",
      action : "",
      keyword : "",
      list : ({"msg_item":[{"id":200322761,"type":1,"fakeid":"593656935","nick_name":"Suang 1","date_time":1398854675,"content":"记得帮我查一下是不是这个电话!","source":"","msg_status":4,"has_reply":0,"refuse_reason":"","multi_item":[],"to_uin":3071594631,"send_stat":{"total":0,"succ":0,"fail":0}},{"id":200322760,"type":2,"fakeid":"593656935","nick_name":"Suang 1","date_time":1398854664,"source":"","msg_status":4,"has_reply":0,"refuse_reason":"","multi_item":[],"to_uin":3071594631,"send_stat":{"total":0,"succ":0,"fail":0}},{"id":200322759,"type":1,"fakeid":"593656935","nick_name":"Suang 1","date_time":1398854659,"content":"勐璇,我看到那人了!","source":"","msg_status":4,"has_reply":0,"refuse_reason":"","multi_item":[],"to_uin":3071594631,"send_stat":{"total":0,"succ":0,"fail":0}},{"id":200322344,"type":2,"fakeid":"1994400010","nick_name":"ABC的CBA","date_time":1398839849,"source":"","msg_status":4,"has_reply":0,"refuse_reason":"","multi_item":[],"to_uin":3071594631,"send_stat":{"total":0,"succ":0,"fail":0}},{"id":200321209,"type":1,"fakeid":"1591078101","nick_name":"倚(纺织服装)","date_time":1398788906,"content":"\/::<","source":"","msg_status":4,"has_reply":0,"refuse_reason":"","multi_item":[],"to_uin":3071594631,"send_stat":{"total":0,"succ":0,"fail":0}},{"id":200321206,"type":2,"fakeid":"1591078101","nick_name":"倚(纺织服装)","date_time":1398788859,"source":"","msg_status":4,"has_reply":1,"refuse_reason":"","multi_item":[],"to_uin":3071594631,"send_stat":{"total":0,"succ":0,"fail":0}},

用的是JSON格式,代码太乱,放在Eclipse里格式化一下,消息列表大概就是这个样了:

{"msg_item" :[ {
  "id" : 200322761,
  "type" : 1,
  "fakeid" : "593656935",
  "nick_name" : "Suang 1",
  "date_time" : 1398854675,
  "content" : "记得帮我查一下是不是这个电话!",
  "source" : "",
  "msg_status" : 4,
  "has_reply" : 0,
  "refuse_reason" : "",
  "multi_item" : [],
  "to_uin" : 3071594631,
  "send_stat" : {
    "total" : 0,
    "succ" : 0,
    "fail" : 0
  }
}, {
  "id" : 200322760,
  "type" : 2,
  "fakeid" : "593656935",
  "nick_name" : "Suang 1",
  "date_time" : 1398854664,
  "source" : "",
  "msg_status" : 4,
  "has_reply" : 0,
  "refuse_reason" : "",
  "multi_item" : [],
  "to_uin" : 3071594631,
  "send_stat" : {
    "total" : 0,
    "succ" : 0,
    "fail" : 0
  }
}
]
}

上面就是 json字符串 中 msg_item 所对应的列表里的对象。

可以看出这个是一个数组,每个评论是里面的一个对象。怎么生成对于的Java类呢 ?

这里有一个在线的工具:http://jsongen.byingtondesign.com/

可以根据JSON 字符串,生成对应的java类:

类1

import java.util.List;

public class MessageList{
  private List<Message> msg_item;

  public List<Message> getMsg_item() {
    return msg_item;
  }

  public void setMsg_item(List<Message> msgItem) {
    msg_item = msgItem;
  }
  
}

类2。部分字段没有用,删掉了

public class Message {

  private String content;
  private long date_time;
  private String fakeid;
  private int has_reply;
  private long id;
  private int msg_status;
  private String nick_name;
  private String refuse_reason;
  private String source;
  private long to_uin;
  private int type;
// get set 略去
}

下面来做个测试。用google的 Gson 来进行处理,把json字符串解析为 java对象。

//jsonstr 为 msg_item 的json字符串
    MessageList msgList = new Gson().fromJson(jsonstr, MessageList.class);
    System.out.println(msgList.getMsg_item().size());

解析成功。所有的对象都在 msgList里了

[转载]dede程序重复安装出现dir的页面如何处理 - 站长圈

mikel阅读(873)

[转载]dede程序重复安装出现dir的页面如何处理 – 站长圈.

安装dede(织梦CMS)系统的时候,如果之前安装过一次,此时打开 install 安装路径的时候会出现dir的提示,这个时候可以按照一下步骤操作:

1.删除install下的index.html文件;

2.删除install下install.lock文件;

3.将install下的index.php.bak文件改成index.php;

4.正常安装一下,搞定!

[转载]微信协议简单调研笔记 - 聂永的博客 - BlogJava

mikel阅读(888)

[转载]微信协议简单调研笔记 – 聂永的博客 – BlogJava.

前言

微信可调研点很多,这里仅仅从协议角度进行调研,会涉及到微信协议交换、消息收发等。所谓“弱水三千,只取一瓢”吧。

杂七杂八的,有些长,可直接拉到最后看结论好了。

一。微信协议概览

微信传输协议,官方公布甚少,在微信技术总监所透漏PPT《微信之道—至简》文档中,有所体现。

纯个人理解:

因张小龙做邮箱Foxmail起家,继而又做了QQ Mail等,QQ Mail是国内第一个支持Exchange ActiveSync协议的免费邮箱,基于其从业背景,微信从一开始就采取基于ActiveSync的修改版状态同步协议Sync,也就再自然不过了。

一句话:增量式、按序、可靠的状态同步传输的微信协议。

大致交换简图如下:

Image(9)

如何获取新数据呢:

  1. 服务器端通知,客户端获取
  2. 客户端携带最新的SyncKey,发起数据请求
  3. 服务器端生成最新的SyncKey连同最新数据发送给客户端
  4. 基于版本号机制同步协议,可确保数据增量、有序传输
  5. SyncKey,由服务器端序列号生成器生成,一旦有新消息产生,将会产生最新的SyncKey。类似于版本号

服务器端通知有状态更新,客户端主动获取自从上次更新之后有变动的状态数据,增量式,顺序式。

二。微信Web端简单调试

在线版本微信:

https://webpush.weixin.qq.com/

通过Firefox + Firebug组合调试,也能证实了微信大致通过交换SyncKey方式获取新数据的论述。

1. 发起GET长连接检测是否存在新的需要同步的数据

会携带上最新SyncKey

https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?callback=jQuery18306073923335455973_1393208247730&r=1393209241862&sid=s7c%2FsxpGRSihgZAA&uin=937355&deviceid=e542565508353877&synckey=1_620943725%7C2_620943769%7C3_620943770%7C11_620942796%7C201_1393208420%7C202_1393209127%7C1000_1393203219&_=1393209241865

返回内容:

 window.synccheck={retcode:"0",selector:"2"}

selector值大于0,表示有新的消息需要同步。

据目测,心跳周期为27秒左右。

2. 一旦有新数据,客户端POST请求主动获取同步的数据

https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=s7c%2FsxpGRSihgZAA&r=1393208447375

携带消息体:

{"BaseRequest":{"Uin":937355,"Sid":"s7c/sxpGRSihgZAA"},"SyncKey":{"Count":6,"List":[{"Key":1,"Val":620943725},{"Key":2,"Val":620943767},{"Key":3,"Val":620943760},{"Key":11,"Val":620942796},{"Key":201,"Val":1393208365},{"Key":1000,"Val":1393203219}]},"rr":1393208447374}

会携带上最新的SyncKey,会返回复杂结构体JSON内容。

但浏览端收取到消息之后,如何通知服务器端已确认收到了?Web版本微信,没有去做。

在以往使用过程中,曾发现WEB端有丢失消息的现象,但属于偶尔现象。但Android微信客户端(只要登陆连接上来之后)貌似就没有丢失过。

3. 发送消息流程

  1. 发起一个POST提交,用于提交用户需要发送的消息

    https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?sid=lQ95vHR52DiaLVqo&r=1393988414386

发送内容:

{"BaseRequest":{"Uin":937355,"Sid":"lQ95vHR52DiaLVqo","Skey":"A6A1ECC6A7DE59DEFF6A05F226AA334DECBA457887B25BC6","DeviceID":"e937227863752975"},"Msg":{"FromUserName":"yongboy","ToUserName":"hehe057854","Type":1,"Content":"hello","ClientMsgId":1393988414380,"LocalID":1393988414380}}

相应内容:

{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
}
,
"MsgID": 1020944348,
"LocalID": "1393988414380"
}
  1. 再次发起一个POST请求,用于申请最新SyncKey

    https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=lQ95vHR52DiaLVqo&r=1393988414756

发送内容:

{"BaseRequest":{"Uin":937355,"Sid":"lQ95vHR52DiaLVqo"},"SyncKey":{"Count":6,"List":[{"Key":1,"Val":620944310},{"Key":2,"Val":620944346},{"Key":3,"Val":620944344},{"Key":11,"Val":620942796},{"Key":201,"Val":1393988357},{"Key":1000,"Val":1393930108}]},"rr":1393988414756}

响应的(部分)内容:

"SKey": "8F8C6A03489E85E9FDF727ACB95C93C2CDCE9FB9532FC15B"  
  1. 终止GET长连接,使用最新SyncKey再次发起一个新的GET长连接

    https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?callback=JQuery1830245810089652082181393988305564&r=1393988415015&sid=lQ95vHR52DiaLVqo&uin=937355&deviceid=e937227863752975&synckey=1620944310%7C2620944348%7C3620944344%7C11620942796%7C2011393988357%7C10001393930108&=1393988415016

三。微信Android简单分析

Windows桌面端Android虚拟机中运行最新版微信(5.2),通过tcpdump/Wireshark组合封包分析,以下为分析结果。

0. 初始连接记录

简单记录微信启动之后请求:

11:20:35 dns查询 
dns.weixin.qq.com
返回一组IP地址

11:20:35 DNS查询
long.weixin.qq.com
返回一组IP地址,本次通信中,微信使用了最后一个IP作为TCP长连接的连接地址。

11:20:35
http://dns.weixin.qq.com/cgi-bin/micromsg-bin/newgetdns?uin=0&clientversion=620888113&scene=0&net=1
用于请求服务器获得最优IP路径。服务器通过结算返回一个xml定义了域名:IP对应列表。仔细阅读,可看到微信已经开始了国际化的步伐:香港、加拿大、韩国等。
具体文本,请参考:https://gist.github.com/yongboy/9341884

11:20:35
获取到long.weixin.qq.com最优IP,然后建立到101.227.131.105的TCP长连接

11:21:25
POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/getprofile HTTP/1.1  (application/octet-stream)
返回一个名为“micromsgresp.dat”的附件,估计是未阅读的离线消息

11:21:31
POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/whatsnews HTTP/1.1  (application/octet-stream)
大概是资讯、订阅更新等

中间进行一些资源请求等,类似于
GET http://wx.qlogo.cn/mmhead/Q3auHgzwzM7NR4TYFcoNjbxZpfO9aiaE7RU5lXGUw13SMicL6iacWIf2A/96
图片等一些静态资源都会被分配到wx.qlogo.cn域名下面

不明白做什么用途
POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/downloadpackage HTTP/1.1  (application/octet-stream)
输出为micromsgresp.dat文件

11:21:47
GET http://support.weixin.qq.com/cgi-bin/mmsupport-bin/reportdevice?channel=34&deviceid=A952001f7a840c2a&clientversion=620888113&platform=0&lang=zh_CN&installtype=0 HTTP/1.1 
返回chunked分块数据

11:21:49
POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/reportstrategy HTTP/1.1  (application/octet-stream)

1. 心跳频率约为5分钟

上次使用Wireshark分析有误(得出18分钟结论),再次重新分析,心跳频率在5分钟左右。

2. 登陆之后,会建立一个长连接,端口号为8080

简单目测为HTTP,初始以为是双通道HTTP,难道是自定义的用于双通道通信的HTTP协议吗,网络上可见资料都是模棱两可、语焉不详。

具体查看长连接初始数据通信,没有发现任何包含”HTTP”字样的数据,以为是微信自定义的TCP/HTTP通信格式。据分析,用于可能用于获取数据、心跳交换消息等用途吧。这个后面会详谈微信是如何做到的。

2.0 初始消息传输

个人资料、离线未阅读消息部分等通过 POST HTTP短连接单独获取。

2.1 二进制简单分析

抽取微信某次HTTP协议方式通信数据,16进制表示,每两个靠近的数字为一个byte字节:

2014-03-03_15h07_30

微信协议可能如下:

一个消息包 = 消息头 + 消息体

消息头固定16字节长度,消息包长度定义在消息头前4个字节中。

单纯摘取第0000行为例,共16个字节的头部:

00 00 00 10 00 10 00 01 00 00 00 06 00 00 00 0f

16进制表示,每两个紧挨着数字代表一个byte字节。

微信消息包格式: 1. 前4字节表示数据包长度,可变 值为16时,意味着一个仅仅包含头部的完整的数据包(可能表示着预先定义好的业务意义),后面可能还有会别的消息包 2. 2个字节表示头部长度,固定值,0x10 = 16 3. 2个字节表示谢意版本,固定值,0x01 = 1 4. 4个字节操作说明数字,可变 5. 序列号,可变 6. 头部后面紧跟着消息体,非明文,加密形式 7. 一个消息包,最小16 byte字节

通过上图(以及其它数据多次采样)分析:

  1. 0000 – 0040为单独的数据包
  2. 0050行为下一个数据包的头部,前四个字节值为0xca = 202,表示包含了从0050-0110共202个字节数据
  3. 一次数据发送,可能包含若干子数据包
  4. 换行符\n,16进制表示为0x0a,在00f0行,包含了两个换行符号
  5. 一个数据体换行符号用于更细粒度的业务数据分割 是否蒙对,需要问问做微信协议的同学
  6. 所有被标记为HTTP协议通信所发送数据都包含换行符号
2.2 动手试试猜想,模拟微信TCP长连接

开始很不解为什么会出现如此怪异的HTTP双通道长连接请求,难道基于TCP通信,然后做了一些手脚?很常规的TCP长连接,传输数据时(不是所有数据传输),被wireshark误认为HTTP长连接。这个需要做一个实验证实一下自己想法,设想如下:

写一个Ping-Pong客户端、服务器端程序,然后使用Wireshark看一下结果,是否符合判断。

Java版本的请求端,默认请求8080端口:

C语言版本的服务器程序,收到什么发送什么,没有任何逻辑,默认绑定8080端口:

这里有一个现场图:

2014-03-03_14h53_19

可以尝试稍微改变输出内容,去除换行符“\n”,把端口换成9000,试试看,就会发现Wireshark输出不同的结果来。

2.3 结论是什么呢?

若使用原始TCP进行双向通信,则需要满足以下条件,可以被类似于Wireshark协议拦截器误认为是HTTP长连接:

  1. 使用80/8080端口(81/3128/8000经测试无效) 也许8080一般被作为WEB代理服务端口,微信才会享用这个红利吧。
  2. 输出的内容中,一定要包含换行字符”\n”

因此,可以定性为微信使用了基于8080端口TCP长连接,一旦数据包中含有换行”\n”符号,就会被Wireshark误认为HTTP协议。可能微信是无心为之吧。

3. 新消息获取方式

  1. TCP长连接接收到服务器通知有新消息需要获取
  2. APP发起一个HTTP POST请求获取新状态消息,会带上当前SyncKey 地址为:http://short.weixin.qq.com/cgi-bin/micromsg-bin/reportstrategy HTTP/1.1,看不到明文
  3. APP获取到新的消息,会再次发起一次HTTP POST请求,告诉服务器已确认收到,同时获取最新SyncKey 地址为:http://short.weixin.qq.com/cgi-bin/micromsg-bin/kvreport,看不到明文
  4. 接受一个消息,TCP长连接至少交互两次,客户端发起两次HTTP POST请求
    具体每次交互内容是什么,有些模糊
  5. 服务器需要支持:状态消息获取标记,状态消息确认收取标记。只有被确认收到,此状态消息才算是被正确消费掉
  6. 多个不同设备同一账号同时使用微信,同一个状态消息会会被同时分发到多个设备上

此时消息请求截图如下:

2014-03-03_15h58_15

4. 发送消息方式

发送消息走已经建立的TCP长连接通道,发送消息到服务器,然后接受确认信息等,产生一次交互。

小伙伴接收到信息阅读也都会收到服务器端通知,产生一次交互等。

可以确定,微信发送消息走TCP长连接方式,因为不对自身状态数据产生影响,应该不交换SyncKey。

  • 在低速网络下,大概会看到消息发送中的提示,属于消息重发机制
  • 网络不好有时客户端会出现发送失败的红色感叹号
  • 已发送到服务器但未收到确认的消息,客户端显示红色感叹号,再次重发,服务器作为重复消息处理,反馈确认
  • 上传图片,会根据图片大小,分割成若干部分(大概1.5K被划分为一部分),同一时间点,客户端会发起若干次POST请求,各自上传成功之后,服务器大概会合并成一个完整图片,返回一个缩略图,显示在APP聊天窗口内。APP作为常规的文字消息发送到服务器端
  • 上传音频,则单独走TCP通道,一个两秒的录制音频,客户端录制完毕,分为两块传输,一块最大1.5K左右,服务端响应一条数据通知确认收到。共三次数据传输。
    音频和纯文字信息一致,都是走TCP长连接,客户端发送,服务器端确认。

四。微信协议小结

  1. 发布的消息对应一个ID(只要单个方向唯一即可,服务器端可能会根ID判断重复接收),消息重传机制确保有限次的重试,重试失败给予用户提示,发送成功会反馈确认,客户端只有收到确认信息才知道发送成功。发送消息可能不会产生新SyncKey。
  2. 基于版本号(SynKey)的状态消息同步机制,增量、有序传输需求水到渠成。长连接通知/短连接获取、确认等,交互方式简单,确保了消息可靠谱、准确无误到达。
  3. 客户端/服务器端都会存储消息ID处理记录,避免被重复消费客户端获取最新消息,但未确认,服务器端不会认为该消息被消费掉。下次客户端会重新获取,会查询当前消息是否被处理过。根据一些现象猜测。
  4. 总体上看,微信协议跨平台(TCP或HTPP都可呈现,处理方式可统一),通过“握手”同步,很可靠,无论哪一个平台都可以支持的很好
  5. 微信协议最小成本为16字节,大部分时间若干个消息包和在一起,批量传输。微信协议说不上最简洁,也不是最节省流量,但是非常成功的。
  6. 若服务器检测到一些不确定因素,可能会导致微启用安全套接层SSL协议进行常规的TCP长连接传输。短连接都没有发生变化

以上,根据有限资料和数据拦截观察总结得出,啰啰嗦嗦,勉强凑成一篇,会存在一些不正确之处,欢迎给予纠正。在多次

五。附录

Microsoft Exchange Active Sync协议,简称EAS,分为folderrsync(同步文件夹目录,即邮箱内有哪几个文件夹)和sync(每个文件夹内有哪些文档)两部分。

某网友总结的协议一次回话大致示范:

Client:   synckey=0 //第一次key为0
Server:  newsynckey=1235434    //第一次返回新key
Client:   synckey=1235434   //使用新key查询
Server:  newsynckey=1647645,data=*****//第一次查询,得到新key和数据
Client:   synckey=1647645
Server:  newsynckey=5637535,data=null //第二次查询,无新消息
Client:   synckey=5637535
Server: newsynckey=8654542, data=****//第三次查询,增量同步
  • 上页中的相邻请求都是隔固定时间的,如两分钟
  • 客户端每次使用旧key标记自己的状态,服务端每次将新key和增量数据一起返回。
  • key是递增的,但不要求连续
  • 请求的某个参数决定服务器是否立即返回

[转载]微信文章和公众号排名查询工具 – 勇哥实验室

mikel阅读(1568)

[转载]微信文章和公众号排名查询工具 – 勇哥实验室.

 

概述

闲来无事,研究了下搜狗的微信搜索,顺手写了两个工具:查询文章的关键词排名和公众号的关键词排名。用到的知识很简单:分析搜狗微信搜索参数和正则匹配。点击下面链接可直接访问工具:
微信文章排名查询
微信公众号排名查询

实现思路

1.分析搜狗微信搜索的主要参数,说明如下:
query:查询的关键词
type:查询类型:1-查询公众号;2-查询文章
p:当前页码
num:查询每页的结果数,默认为10
_ast:当前请求的时间
2.为保障查询参数的正确,我首先模拟登陆首页,获得其它意义不明确的参数,和自定义的查询参数还有相关cookies数据一起,作为一个完整请求发送。
3.用正则表达式匹配排名数据即可。
为保证能检索到排名,需要确认一个文章的识别码(公众号识别码就是微信号),具体如下:
识别码

/**
* @params $keyword 关键词
* @params $id 身份标识
* @params $type 搜索类型,1:公众号;2:搜索文章
* @return int 0-无排名;非零值表示当前排名
*/
function getRank($keyword, $id, $type=2)
{
//模擬從首頁登陸
$client = new HttpClient('weixin.sogou.com');
if(!$client-&gt;get('/'))
{
die('查询发生错误');
}

$param = array(
'query'=&gt;$keywords,
'type'=&gt;$type,
'p'=&gt;1,
'num'=&gt;100,
'_ast'=&gt;time(),
);
//获取額外参数
preg_match_all('/<input name="([^" type="hidden" value="([^" />content, $out);
foreach($out[1] as $k=&gt;$v)
{
$param[$v] = $out[2][$k];
}

$re = array();
if($client-&gt;get('/weixin',$param))
{
if($type==2) //查找文章排名
{
//匹配字串为
<h4><a id="sogou_vr_11002601_title_2" href="http://mp.weixin.qq.com/s?__biz=MzA5ODE2OTYwMA==&amp;mid=200270633&amp;idx=4&amp;sn=db2642f26640871b1747e92fcd8070af&amp;3rd=MzA3MDU4NTYzMw==&amp;scene=6#rd" target="_blank">夏养小常识 让你轻松<em><!--red_beg-->祛湿<!--red_end--></em>又健脾</a></h4>
$patten = '/
<h4>.*<a href="([^" target="_blank">]+&gt;(.+)&lt;\/a&gt;.*&lt;\/h4&gt;/Us';
preg_match_all($patten, $client-&gt;content, $result);
if(!empty($result[1]))
{
foreach($result[1] as $k=&gt;$val)
{
if(strpos($val, $id)!==false)
{
return $k+1;
}
}
}
}
else //查找公众号排名
{
/**匹配字串示例
</a><a id="sogou_vr_11002301_box_25" class="wx-rb bg-blue wx-rb_v1" href="/gzh?openid=oIWsFt_TJmVxf3hcRW7af1Gxi3lU" target="_blank"></a></h4>
<div class="img-box"><img style="visibility: hidden;" src="http://img01.sogoucdn.com/app/a/100520090/oIWsFt_TJmVxf3hcRW7af1Gxi3lU" alt="" /></div>
<div class="txt-box">
<h3>移动<em><!--red_beg-->互联网<!--red_end--></em>中心</h3>
<h4>微信号:yidongwang2003</h4>
<p class="s-p3"><span class="sp-tit">功能介绍:</span><span class="sp-txt">移动<em><!--red_beg-->互联网<!--red_end--></em>中心官方平台主要专注于移动<em><!--red_beg-->互联网<!--red_end--></em>领域,分享行业内资讯、产品、运营、营销、创业等最新资讯;广泛发布正能量及移动<em><!--red_beg-->互联网<!--red_end--></em>合作信息,力求给您的工作及生活带来真正的帮助!中国人必定帐号!</span></p>
<p class="s-p3"><span class="sp-tit"><script>// <![CDATA[
authnamewrite('1')
// ]]></script>认证:</span><span class="sp-txt">来自腾讯微博认证资料:移动互联网中心官方微博! @移动互联网中心</span></p>

</div>
<div class="v-box"><img src="http://img03.sogoucdn.com/app/a/100520081/tUy9svLEHsxhh_PRnxmR" alt="" width="70" height="70" />
微信扫描关注</div>
*/
$patten = '/微信号:([^&lt;]+)&lt;\/span&gt;/U';
preg_match_all($patten, $client-&gt;content, $result);
if(!empty($result[1]))
{
$k = array_search($id, $result[1]);
if($k!==false)
{
return $k+1;
}
}
}
}

return 0;
}

其中$client是httpclient类,主要用户发送http请求。代码如下:

class HttpClient
{
// Request vars
var $host;
var $port;
var $path;
var $method;
var $postdata = '';
var $cookies = array();
var $referer;
var $accept = 'text/xml,application/xml,application/xhtml+xml,text/html,text/plain,image/png,image/jpeg,image/gif,*/*';
var $accept_encoding = 'gzip';
var $accept_language = 'en-us';
var $user_agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36';
// Options
var $timeout = 20;
var $use_gzip = true;
var $persist_cookies = true; // If true, received cookies are placed in the $this-&gt;cookies array ready for the next request
// Note: This currently ignores the cookie path (and time) completely. Time is not important,
// but path could possibly lead to security problems.
var $persist_referers = true; // For each request, sends path of last request as referer
var $debug = false;
var $handle_redirects = true; // Auaomtically redirect if Location or URI header is found
var $max_redirects = 5;
var $headers_only = false; // If true, stops receiving once headers have been read.
// Basic authorization variables
var $username;
var $password;
// Response vars
var $status;
var $headers = array();
var $content = '';
var $errormsg;
// Tracker variables
var $redirect_count = 0;
var $cookie_host = '';
function HttpClient($host, $port = 80)
{
$this-&gt;host = $host;
$this-&gt;port = $port;
}
function get($path, $data = false)
{
$this-&gt;path = $path;
$this-&gt;method = 'GET';
if ($data)
{
$this-&gt;path .= '?' . $this-&gt;buildQueryString($data);
}
return $this-&gt;doRequest();
}
function post($path, $data)
{
$this-&gt;path = $path;
$this-&gt;method = 'POST';
$this-&gt;postdata = $this-&gt;buildQueryString($data);
return $this-&gt;doRequest();
}
function buildQueryString($data)
{
$querystring = '';
if (is_array($data))
{
// Change data in to postable data
foreach ($data as $key =&gt; $val)
{
if (is_array($val))
{
foreach ($val as $val2)
{
$querystring .= urlencode($key) . '=' . urlencode($val2) . '&amp;';
}
} else
{
$querystring .= urlencode($key) . '=' . urlencode($val) . '&amp;';
}
}
$querystring = substr($querystring, 0, -1); // Eliminate unnecessary &amp;
} else
{
$querystring = $data;
}
return $querystring;
}
function doRequest()
{
// Performs the actual HTTP request, returning true or false depending on outcome
if (!$fp = @fsockopen($this-&gt;host, $this-&gt;port, $errno, $errstr, $this-&gt;timeout))
{
// Set error message
switch ($errno)
{
case - 3:
$this-&gt;errormsg = 'Socket creation failed (-3)';
case - 4:
$this-&gt;errormsg = 'DNS lookup failure (-4)';
case - 5:
$this-&gt;errormsg = 'Connection refused or timed out (-5)';
default:
$this-&gt;errormsg = 'Connection failed (' . $errno . ')';
$this-&gt;errormsg .= ' ' . $errstr;
$this-&gt;debug($this-&gt;errormsg);
}
return false;
}
socket_set_timeout($fp, $this-&gt;timeout);
$request = $this-&gt;buildRequest();
$this-&gt;debug('Request', $request);
fwrite($fp, $request);
// Reset all the variables that should not persist between requests
$this-&gt;headers = array();
$this-&gt;content = '';
$this-&gt;errormsg = '';
// Set a couple of flags
$inHeaders = true;
$atStart = true;
// Now start reading back the response
while (!feof($fp))
{
$line = fgets($fp, 4096);
if ($atStart)
{
// Deal with first line of returned data
$atStart = false;
if (!preg_match('/HTTP\/(\\d\\.\\d)\\s*(\\d+)\\s*(.*)/', $line, $m))
{
$this-&gt;errormsg = "Status code line invalid: " . htmlentities($line);
$this-&gt;debug($this-&gt;errormsg);
return false;
}
$http_version = $m[1]; // not used
$this-&gt;status = $m[2];
$status_string = $m[3]; // not used
$this-&gt;debug(trim($line));
continue;
}
if ($inHeaders)
{
if (trim($line) == '')
{
$inHeaders = false;
$this-&gt;debug('Received Headers', $this-&gt;headers);
if ($this-&gt;headers_only)
{
break; // Skip the rest of the input
}
continue;
}
if (!preg_match('/([^:]+):\\s*(.*)/', $line, $m))
{
// Skip to the next header
continue;
}
$key = strtolower(trim($m[1]));
$val = trim($m[2]);
// Deal with the possibility of multiple headers of same name
if (isset($this-&gt;headers[$key]))
{
if (is_array($this-&gt;headers[$key]))
{
$this-&gt;headers[$key][] = $val;
} else
{
$this-&gt;headers[$key] = array($this-&gt;headers[$key], $val);
}
} else
{
$this-&gt;headers[$key] = $val;
}
continue;
}
// We're not in the headers, so append the line to the contents
$this-&gt;content .= $line;
}
fclose($fp);
// If data is compressed, uncompress it
if (isset($this-&gt;headers['content-encoding']) &amp;&amp; $this-&gt;headers['content-encoding'] ==
'gzip')
{
$this-&gt;debug('Content is gzip encoded, unzipping it');
$this-&gt;content = substr($this-&gt;content, 10); // See http://www.php.net/manual/en/function.gzencode.php
$this-&gt;content = gzinflate($this-&gt;content);
}
// If $persist_cookies, deal with any cookies
//if ($this-&gt;persist_cookies &amp;&amp; isset($this-&gt;headers['set-cookie']) &amp;&amp; $this-&gt;host == $this-&gt;cookie_host) {
if ($this-&gt;persist_cookies &amp;&amp; isset($this-&gt;headers['set-cookie']))
{
$cookies = $this-&gt;headers['set-cookie'];
if (!is_array($cookies))
{
$cookies = array($cookies);
}
foreach ($cookies as $cookie)
{
if (preg_match('/([^=]+)=([^;]+);/', $cookie, $m))
{
$this-&gt;cookies[$m[1]] = $m[2];
}
}
// Record domain of cookies for security reasons
//$this-&gt;cookie_host = $this-&gt;host;
}
// If $persist_referers, set the referer ready for the next request
if ($this-&gt;persist_referers)
{
$this-&gt;debug('Persisting referer: ' . $this-&gt;getRequestURL());
$this-&gt;referer = $this-&gt;getRequestURL();
}
// Finally, if handle_redirects and a redirect is sent, do that
if ($this-&gt;handle_redirects) {
if (++$this-&gt;redirect_count &gt;= $this-&gt;max_redirects) {
$this-&gt;errormsg = 'Number of redirects exceeded maximum ('.$this-&gt;max_redirects.')';
$this-&gt;debug($this-&gt;errormsg);
$this-&gt;redirect_count = 0;
return false;
}
$location = isset($this-&gt;headers['location']) ? $this-&gt;headers['location'] : '';
$uri = isset($this-&gt;headers['uri']) ? $this-&gt;headers['uri'] : '';
if ($location || $uri) {
$url = parse_url($location.$uri);
// This will FAIL if redirect is to a different site
return $this-&gt;get($url['path']);
}
}
return true;
}
function buildRequest()
{
$headers = array();
$headers[] = "{$this-&gt;method} {$this-&gt;path} HTTP/1.0"; // Using 1.1 leads to all manner of problems, such as "chunked" encoding
$headers[] = "Host: {$this-&gt;host}";
$headers[] = "User-Agent: {$this-&gt;user_agent}";
$headers[] = "Accept: {$this-&gt;accept}";
if ($this-&gt;use_gzip)
{
$headers[] = "Accept-encoding: {$this-&gt;accept_encoding}";
}
$headers[] = "Accept-language: {$this-&gt;accept_language}";
if ($this-&gt;referer)
{
$headers[] = "Referer: {$this-&gt;referer}";
}
// Cookies
if ($this-&gt;cookies)
{
$cookie = 'Cookie: ';
foreach ($this-&gt;cookies as $key =&gt; $value)
{
$cookie .= "$key=$value; ";
}
$headers[] = $cookie;
}
// Basic authentication
if ($this-&gt;username &amp;&amp; $this-&gt;password)
{
$headers[] = 'Authorization: BASIC ' . base64_encode($this-&gt;username . ':' . $this-&gt;
password);
}
// If this is a POST, set the content type and length
if ($this-&gt;postdata)
{
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
$headers[] = 'Content-Length: ' . strlen($this-&gt;postdata);
}
$request = implode("
", $headers) . "

" . $this-&gt;postdata;
return $request;
}
function getStatus()
{
return $this-&gt;status;
}
function getContent()
{
return $this-&gt;content;
}
function getHeaders()
{
return $this-&gt;headers;
}
function getHeader($header)
{
$header = strtolower($header);
if (isset($this-&gt;headers[$header]))
{
return $this-&gt;headers[$header];
} else
{
return false;
}
}
function getError()
{
return $this-&gt;errormsg;
}
function getCookies()
{
return $this-&gt;cookies;
}
function getRequestURL()
{
$url = 'http://' . $this-&gt;host;
if ($this-&gt;port != 80)
{
$url .= ':' . $this-&gt;port;
}
$url .= $this-&gt;path;
return $url;
}
function setReferee($string)
{
$this-&gt;referer = $string;
}
// Setter methods
function setUserAgent($string)
{
$this-&gt;user_agent = $string;
}
function setAuthorization($username, $password)
{
$this-&gt;username = $username;
$this-&gt;password = $password;
}
function setCookies($array)
{
$this-&gt;cookies = $array;
}
// Option setting methods
function useGzip($boolean)
{
$this-&gt;use_gzip = $boolean;
}
function setPersistCookies($boolean)
{
$this-&gt;persist_cookies = $boolean;
}
function setPersistReferers($boolean)
{
$this-&gt;persist_referers = $boolean;
}
function setHandleRedirects($boolean)
{
$this-&gt;handle_redirects = $boolean;
}
function setMaxRedirects($num)
{
$this-&gt;max_redirects = $num;
}
function setHeadersOnly($boolean)
{
$this-&gt;headers_only = $boolean;
}
function setDebug($boolean)
{
$this-&gt;debug = $boolean;
}
// "Quick" static methods
function quickGet($url)
{
$bits = parse_url($url);
$host = $bits['host'];
$port = isset($bits['port']) ? $bits['port'] : 80;
$path = isset($bits['path']) ? $bits['path'] : '/';
if (isset($bits['query']))
{
$path .= '?' . $bits['query'];
}
$client = new HttpClient($host, $port);
if (!$client-&gt;get($path))
{
return false;
} else
{
return $client-&gt;getContent();
}
}
function quickPost($url, $data)
{
$bits = parse_url($url);
$host = $bits['host'];
$port = isset($bits['port']) ? $bits['port'] : 80;
$path = isset($bits['path']) ? $bits['path'] : '/';
$client = new HttpClient($host, $port);
if (!$client-&gt;post($path, $data))
{
return false;
} else
{
return $client-&gt;getContent();
}
}
function debug($msg, $object = false)
{
if ($this-&gt;debug)
{
print '
<div style="border: 1px solid red; padding: 0.5em; margin: 0.5em;"><strong>HttpClient Debug:</strong> ' .$msg;
if ($object)
{
ob_start();
print_r($object);
$content = htmlentities(ob_get_contents());
ob_end_clean();
print $content;
}
print '</div>
';
}
}
}

备注:微信排名变动比较大,所以排名的准确性比百度要低好多。本示例仅供参考!

转载请注明:勇哥实验室 » 微信文章和公众号排名查询工具

[转载]用协议分析工具学习TCP/IP(一) - tenfyguo的技术专栏 - 博客频道 - CSDN.NET

mikel阅读(877)

[转载]用协议分析工具学习TCP/IP(一) – tenfyguo的技术专栏 – 博客频道 – CSDN.NET.

一、前言
目 前,网络的速度发展非常快,学习网络的人也越来越多,稍有网络常识的人都知道 TCP/IP协议是网络的基础,是Internet的语言,可以说没有TCP/IP协议就没有互联网的今天。目前号称搞网的人非常多,许多人就是从一把夹 线钳,一个测线器联网开始接触网络的,如果只是联网玩玩,知道几个Ping之类的命令就行了,如果想在网络上有更多的发展不管是黑道还是红道,必须要把 TCP/IP协议搞的非常明白。
学习过TCP/IP协议的人多有一种感觉,这东西太抽象了,没有什么数据实例,看完不久就忘了。本文将介绍一种直观的学习方法,利用协议分析工具学习TCP/IP,在学习的过程中能直观的看到数据的具体传输过程。
为了初学者更容易理解,本文将搭建一个最简单的网络环境,不包含子网。
二、试验环境
1、网络环境
如图1所示

为了表述方便,下文中208号机即指地址为192.168.113.208的计算机,1号机指地址为192.168.113.1的计算机。
2、操作系统
两台机器都为Windows 2000 ,1号机机器作为服务器,安装FTP服务
3、协议分析工具
Windows环境下常用的工具有:Sniffer Pro、Natxray、Iris以及windows 2000自带的网络监视器等。本文选用Iris作为协议分析工具。
在客户机208号机安装IRIS软件。
三、测试过程
1、测试例子:将1号机计算机中的一个文件通过FTP下载到208号机中。
2、IRIS的设置。
由于IRIS具有网络监听的功能,如果网络环境中还有其它的机器将抓很多别的数据包,这样为学习带来诸多不便,为了清楚地看清楚上述例子的传输过程首先将IRIS设置为只抓208号机和1号机之间的数据包。设置过程如下:
1)用热键CTRL+B弹出如图所示的地址表,在表中填写机器的IP地址,为了对抓的包看得更清楚不要添主机的名字(name),设置好后关闭此窗口。
如图2所示

2)用热键CTRL+E弹出如图所示过滤设置,选择左栏“IP address”,右栏按下图将address book中的地址拽到下面,设置好后确定,这样就这抓这两台计算机之间的包。

如图3所示

3、抓包
按 下IRIS工具栏中 开始按钮。在浏览器中输入:FTP://192.168.113.1,找到要下载的文件,鼠标右键该文件,在弹出的菜单中选择“复制到文件夹”开始下载, 下载完后在IRIS工具栏中按 按钮停止抓包。图4显示的就是FTP的整个过程,下面我们将详细分析这个过程。
如图4所示

说明:为了能抓到ARP协议的包,在WINDOWS 2000 中运行arp –d 清除arp缓存。
四、过程分析
1、TCP/IP的基本原理
本文的重点虽然是根据实例来解析TCP/IP,但要讲明白下面的过程必须简要讲一下TCP/IP的基本原理。
A.网络是分层的,每一层分别负责不同的通信功能。
TCP/IP通常被认为是一个四层协议系统,TCP/IP协议族是一组不同的协议组合在一起构成的协议族。尽管通常称该协议族为TCP/IP,但TCP和IP只是其中的两种协议而已,如表1所示。每一层负责不同的功能:
如表1所示

分层的概念说起来非常简单,但在实际的应用中非常的重要,在进行网络设置和排除故障时对网络层次理解得很透,将对工作有很大的帮助。例如:设置路由是网络层IP协议的事,要查找MAC地址是链路层ARP的事,常用的Ping命令由ICMP协议来做的。
图5显示了各层协议的关系,理解它们之间的关系对下面的协议分析非常重要。
如图5所示

b.数据发送时是自上而下,层层加码;数据接收时是自下而上,层层解码。
当 应用程序用TCP传送数据时,数据被送入协议栈中,然后逐个通过每一层直到被当作一串比特流送入网络。其中每一层对收到的数据都要增加一些首部信息(有时 还要增加尾部信息),该过程如图6所示。TCP传给IP的数据单元称作TCP报文段或简称为TCP段。I P传给网络接口层的数据单元称作IP数据报。 通过以太网传输的比特流称作帧(Frame)。
数据发送时是按照图6自上而下,层层加码;数据接收时是自下而上,层层解码。

如图6所示

c. 逻辑上通讯是在同级完成的
垂 直方向的结构层次是当今普遍认可的数据处理的功能流程。每一层都有与其相邻层的接口。为了通信,两个系统必须在各层之间传递数据、指令、地址等信息,通信 的逻辑流程与真正的数据流的不同。虽然通信流程垂直通过各层次,但每一层都在逻辑 上能够直接与远程计算机系统的相应层直接通信。
从图7可以看出,通讯实际上是按垂直方向进行的,但在逻辑上通信是在同级进行的。

如图7所示

2、过程描述
为了更好的分析协议,我们先描述一下上述例子数据的传输步骤。如图8所示:
1)FTP客户端请求TCP用服务器的IP地址建立连接。
2)TCP发送一个连接请求分段到远端的主机,即用上述IP地址发送一份IP数据报。
3) 如果目的主机在本地网络上,那么IP数据报可以直接送到目的主机上。如果目的主机在一个远程网络上,那么就通过IP选路函数来确定位于本地网络上的下一站路由器地址,并让它转发IP数据报。在这两种情况下,IP数据报都是被送到位于本地网络上的一台主机或路由器。
4) 本例是一个以太网,那么发送端主机必须把32位的IP地址变换成48位的以太网地址,该地址也称为MAC地址,它是出厂时写到网卡上的世界唯一的硬件地址。把IP地址翻译到对应的MAC地址是由ARP协议完成的。
5) 如图的虚线所示,ARP发送一份称作ARP请求的以太网数据帧给以太网上的每个主机,这个过程称作广播。ARP请求数据帧中包含目的主机的IP地址,其意思是“如果你是这个IP地址的拥有者,请回答你的硬件地址。”
6) 目的主机的ARP层收到这份广播后,识别出这是发送端在寻问它的IP地址,于是发送一个ARP应答。这个ARP应答包含I P地址及对应的硬件地址。
7) 收到ARP应答后,使ARP进行请求—应答交换的IP数据包现在就可以传送了。
8) 发送IP数据报到目的主机。
如图8所示

3、实例分析
下面通过分析用iris捕获的包来分析一下TCP/IP的工作过程,为了更清晰的解释数据传送的过程,我们按传输的不同阶段抓了四组数据,分别是查找服务器、建立连接、数据传输和终止连接。每组数据,按下面三步进行解释。
·  显示数据包
·  解释该数据包
·  按层分析该包的头信息【未完待续】

[转载]一种基于代理CGI技术的跨域永久性解决方案 - tenfyguo的技术专栏 - 博客频道 - CSDN.NET

mikel阅读(1031)

[转载]一种基于代理CGI技术的跨域永久性解决方案 – tenfyguo的技术专栏 – 博客频道 – CSDN.NET.

      前两天,我的一位同事问我有没有一种办法实现imgcache.qq.com和face.qq.com的cookie互操作,我说有,方案很简单,只要把 cookie的domain设置成.qq.com的域名后缀就可以了,这样一来,在face.qq.com域名中设置的cookie的值,在 imgcache.qq.com域中也可以检索到,同样,在imgcache.qq.com设置中的cookie也可以在face.qq.com中检索 到,所以可以非常方便的实现cookie的跨域名互操作的目的。

那么,这种方案有没问题呢?或者说有哪些方面是我们觉得有点“不爽”的?答案是肯定的,首先一个重要的问题是跨域名的cookie污染,通过设 置cookie的域名为.qq.com,该方案会使得所有发送到*.qq.com的http Request的请求头(cookie的信息是通过Http 请求头进行携带的)均带上cookie信息,一方面这些cookie的信息只是face.qq.com中才需要使用的,而对其他后缀也是qq.com的业 务是不需要使用到这些cookie信息的,因此,这里发送多余的请求头信息对网络带宽来说是一种浪费,如果cookie的信息量比较少还可以,如果携带的 信息量很大显然该方案很容易受到挑战的。那么,该方案有什么好处呢?简单,是的,该方案的实现成本很低。

好,回到我们刚才的需求场景:实现用户最近设置的头像列表(共保存6个),由于face.qq.com一般是在qq客户端的mini portal打开的,用户一般不会在地址栏看到face.qq.com的域名,为了提高性能,只把首页放到face.qq.com的域名下,而对其他的静 态页面放到自建的CDN imgcache.qq.com,以实现用户访问业务的最近接入。显然,我们通过把用户最近设置6个头像item的id保存到cookie中,并且设置域 名.qq.com是可以使得在imgcache.qq.com域名下的静态页面访问和设置的最近设置列表并且该设置共享到face.qq.com域名下 的。

如果现在我们要求设置的cookie信息既能够在face.qq.com中访问,同时imgcache.qq.com的静态页面也可以访问,但 又不造成域名污染,有没办法?这里tenfy想到了一种可以实现上面头像的需求并且不造成cookie的跨域名污染,取名为基于代理cgi的技术方案,顾 名思义,该方案需要在face.qq.com的域名下部署一个代理cgi以实现cookie信息的跨域名访问,我们暂把该代理cgi叫做 cookie_proxy.cgi,该cgi的功能很简单:1,设置当前登录用户的最近设置的头像item;2,获取当前登录用户的最近设置头像的 item list,并且这些item 信息是设置到域名为face.qq.com的cookie中的,通过这样的处理使得cookie的信息只会在浏览器发送到face.qq.com的请求头 中才会带上,从而对其他域名的业务不会造成污染。有了该代理cgi就很方便了,在域名为face.qq.com的页面,获取和设置最近设置的头像 list,只需要操作自己域名下的cookie信息即可;而对imgcache.qq.com域名下页面,现在则无法通过从cookie中读取最近的设置 列表,但可以通过访问域名face.qq.com下的代理cgi获取最近的设置列表,同理,也不要把最近设置的item设置到 imgcache.qq.com的cookie中,因为这样,face.qq.com下的页面是无法访问的,而是通过代理cgi进行设置即可,当然这里的 操作都是通过调用异步的接口进行设置的,并且也不是关键的路径。

那么,对上面的需求来说,哪种方案更好呢?大家可能会觉得代理cgi的方案更好,但,如果是我,则会选择方案一,理由是实现简单,方便,几乎没 什么维护成本,并且在客户端使用的头像业务会使得跨域名的cookie污染问题变得更加不care,原因是头像的mimi portal是qq客户端单独打开一个IE控件进行显示的,并且用户无法在该mini portal中通过浏览器URL进行其他业务的访问,有的话,也是跟头像业务相关的其他几个小入口,这使得因为cookie请求头带来的多余带宽浪费显得 更加微不足道了(因为用户再单独打开的浏览器访问qq.com的页面是不可能带上这些cookie的)。

[转载]从cookie中读取uin需要注意的问题 - tenfyguo的技术专栏 - 博客频道 - CSDN.NET

mikel阅读(1031)

[转载]从cookie中读取uin需要注意的问题 – tenfyguo的技术专栏 – 博客频道 – CSDN.NET.

问题的产生:

用户登录站点后,系统会在cookie植入对应的uin,其值的形式是oxxxxxxxxxx,xxx是对应的qq号码,如果不满足10位,则前面补充0,那么前端是可以通过js从cookie读取对应的qq号码的。

 

之前的读取方法:

    /* 
    * 获取uin 
    */  
    function getUin() {  
       var uin  = QQVIP.cookie.get("uin");  
       if(uin == null || uin =='' || uin.length < 7) {  
         return '';  
       }  
      
       try {  
           uin = QQVIP.string.trim(uin.substr(2));  
           return uin;  
       }catch(e){  
           return '';  
       }  
    };  

很明显,该方法是从uin的第二位开始读取认为是qq号码,对9位号码,该API是ok的,如uin=o0123456789,则可以读取出对应的uin,

但对于22亿的10位号码,则明显将会被截断。

那么新的解决方法是什么?

    /* 
    * 获取uin 
    */  
    function getUin() {  
       var uin  = QQVIP.cookie.get("uin");  
       if(uin == null || uin =='' || uin.length < 7) {  
         return '';  
       }  
      
       try {  
      
    //注意,该方法是从cookie中读取qq号码,对于少于10位的qq号码,  
    //前面是补0,对于10位的号码,则前面没有补0,使得oxxxx,其中xxx保  
    //留10位,所以统一从第二位读取qq号码是有问题的,正确的  
    //是:parseInt(QQVIP.string.trim(uin.substr(1)),10);  
    //注意调用parseInt函数,第二个参数必须输入10,以表示是按照10进制  
    //进行转换,否则以0开始的数字串会被认为是按八进制进行转换。  
    uin = parseInt(QQVIP.string.trim(uin.substr(1)),10);  
    return uin;  
      
       }catch(e){  
           return '';  
       }  
    };  

[转载]Cookie应用 - liuliu_刘刘 - 博客园

mikel阅读(1040)

[转载]Cookie应用 – liuliu_刘刘 – 博客园.

/***设置Cookie记录QQ***/
function SetCookieVerifyingQQ(VerifyingQQ)
{

    var CookieQQ=GetCookieVerifyingQQ();
    
    if(CookieQQ==undefined)
    {
        CookieQQ=VerifyingQQ;
    }
    else
    {
        CookieQQ=CookieQQ+"|"+VerifyingQQ;
    }
    
    //获取当前时间
    var date=new Date();
    var expireDays=1;
    //将date设置为1天以后的时间
    date.setTime(date.getTime()+expireDays*24*3600*1000);
    //将VerifyingQQ设置为1天后过期
    document.cookie="VerifyingQQ="+CookieQQ+";expires="+date.toGMTString();
    
}

/***获取Cookie记录QQ***/
function GetCookieVerifyingQQ()
{
    var strcookie=document.cookie;
    var arrcookie=strcookie.split("; ");
    var VerifyingQQ;
    
    //遍历cookie数组,处理每个cookie对
    for(var i=0;i<arrcookie.length;i++)
    {
          var arr=arrcookie[i].split("=");
          //找到名称为userId的cookie,并返回它的值
          if("VerifyingQQ"==arr[0])
          {
                 VerifyingQQ=arr[1];
                 break;
          }
    }
    return VerifyingQQ;
}