php 验证身份证号码

身份证号码的结构
身份证号码是特征组合码,由17位数字本体码和一位校验码组成。
排列顺序从左至右依此为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。

地址码(前六位数)
表示编码对象常住户口所在县(市、旗、区)的行政区划代码,按GB/T2260的规定执行。

出生日期码(第七位至十四位)
表示编码对象出生的年、月、日,按GB/T7408的规定执行,年、月、日代码之间不用分隔符。

顺序码(第十五位至十七位)
表示在同一地址码所标识的区域范围,对同年、同月、同日出生的人编定的顺序号,顺序码奇数分配给男性,偶数分配给女性。

校验码(第十八位数)
1.十七位数字本体码加权求和公式
S= SUM(Ai * Wi), i=0, … , 16, 先对前17位数字的权求和。
Ai:表示第i位置上的身份证号码数字值
Wi:表示第i位置上的加权因子
Wi:7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2

  1. 计算模
    Y = mod(S, 11)

3.通过模得到对应的校验码
Y: 0 1 2 3 4 5 6 7 8 9 10
校验码: 1 0 X 9 8 7 6 5 4 3 2

验证身份证号码方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
function checkIdCard($idcard){

// 只能是18位
if(strlen($idcard)!=18){
return false;
}

// 取出本体码
$idcard_base = substr($idcard, 0, 17);

// 取出校验码
$verify_code = substr($idcard, 17, 1);

// 加权因子
$factor = array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2);

// 校验码对应值
$verify_code_list = array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2');

// 根据前17位计算校验码
$total = 0;
for($i=0; $i<17; $i++){
$total += substr($idcard_base, $i, 1)*$factor[$i];
}

// 取模
$mod = $total % 11;

// 比较校验码
if($verify_code == $verify_code_list[$mod]){
return true;
}else{
return false;
}

}


$idcard = '这里填写要验证的身份证号码';
var_dump(checkIdCard($idcard));
?>

转载自 http://blog.csdn.net/fdipzone/article/details/35859879

sessionStorage 、localStorage 和 cookie 之间的区别

sessionStorage 和 localStorage 是 HTML5 Web Storage API 提供的,可以方便的在web请求之间保存数据。有了本地数据,就可以避免数据在浏览器和服务器间不必要地来回传递。
sessionStorage、localStorage、cookie都是在浏览器端存储的数据,其中sessionStorage的概念很特别,引入了一个“浏览器窗口”的概念。sessionStorage是在同源的同窗口(或tab)中,始终存在的数据。也就是说只要这个浏览器窗口没有关闭,即使刷新页面或进入同源另一页面,数据仍然存在。关闭窗口后,sessionStorage即被销毁。同时“独立”打开的不同窗口,即使是同一页面,sessionStorage对象也是不同的。
Web Storage带来的好处:减少网络流量:一旦数据保存在本地后,就可以避免再向服务器请求数据,因此减少不必要的数据请求,减少数据在浏览器和服务器间不必要地来回传递。快速显示数据:性能好,从本地读数据比通过网络从服务器获得数据快得多,本地数据可以即时获得。再加上网页本身也可以有缓存,因此整个页面和数据都在本地的话,可以立即显示。临时存储:很多时候数据只需要在用户浏览一组页面期间使用,关闭窗口后数据就可以丢弃了,这种情况使用sessionStorage非常方便。

浏览器本地存储与服务器端存储之间的区别
其实数据既可以在浏览器本地存储,也可以在服务器端存储。
浏览器端可以保存一些数据,需要的时候直接从本地获取,sessionStorage、localStorage和cookie都由浏览器存储在本地的数据。
服务器端也可以保存所有用户的所有数据,但需要的时候浏览器要向服务器请求数据。1.服务器端可以保存用户的持久数据,如数据库和云存储将用户的大量数据保存在服务器端。2.服务器端也可以保存用户的临时会话数据。服务器端的session机制,如jsp的 session 对象,数据保存在服务器上。实现上,服务器和浏览器之间仅需传递session id即可,服务器根据session id找到对应用户的session对象。会话数据仅在一段时间内有效,这个时间就是server端设置的session有效期。
服务器端保存所有的用户的数据,所以服务器端的开销较大,而浏览器端保存则把不同用户需要的数据分布保存在用户各自的浏览器中。浏览器端一般只用来存储小数据,而服务器可以存储大数据或小数据。服务器存储数据安全一些,浏览器只适合存储一般数据。

sessionStorage 、localStorage 和 cookie 之间的区别
共同点:都是保存在浏览器端,且同源的。区别:cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。而sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下。存储大小限制也不同,cookie数据不能超过4k,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。Web Storage 支持事件通知机制,可以将数据更新的通知发送给监听者。Web Storage 的 api 接口使用更方便。

sessionStorage 和 localStorage 之间的区别见上面的区别3、4

sessionStorage与页面 js 数据对象的区别
页面中一般的 js 对象或数据的生存期是仅在当前页面有效,因此刷新页面或转到另一页面这样的重新加载页面的情况,数据就不存在了。而sessionStorage 只要同源的同窗口(或tab)中,刷新页面或进入同源的不同页面,数据始终存在。也就是说只要这个浏览器窗口没有关闭,加载新页面或重新加载,数据仍然存在。

cookie,容量4kb,默认各种浏览器都支持,缺陷就是每次请求,浏览器都会把本机存的cookies发送到服务器,无形中浪费带宽。
userdata,只有ie支持,单个容量64kb,每个域名最多可存10个共计640k数据。默认保存在C:\Documents and Settings\Administrator\UserData\目录下,保存格式为xml。关于userdata更多资料参考 http://msdn.microsoft.com/library/default.asp?url=/workshop/author/behaviors/reference/behaviors/userdata.asp

sessionStorage与localStorage
Web Storage实际上由两部分组成:sessionStorage与localStorage。
sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此sessionStorage不是一种持久化的本地存储,仅仅是会话级别的存储。
localStorage用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。

为什么选择Web Storage而不是Cookie?
与Cookie相比,Web Storage存在不少的优势,概括为以下几点:

  1. 存储空间更大:IE8下每个独立的存储空间为10M,其他浏览器实现略有不同,但都比Cookie要大很多。
  2. 存储内容不会发送到服务器:当设置了Cookie后,Cookie的内容会随着请求一并发送的服务器,这对于本地存储的数据是一种带宽浪费。而Web Storage中的数据则仅仅是存在本地,不会与服务器发生任何交互。
  3. 更多丰富易用的接口:Web Storage提供了一套更为丰富的接口,使得数据操作更为简便。
  4. 独立的存储空间:每个域(包括子域)有独立的存储空间,各个存储空间是完全独立的,因此不会造成数据混乱。

兼容性如何?
接下来的各种测试是在以下浏览器中进行的:IE8、Firefox3.6、Chrome5、Safari4、Opera10,事实证明各个浏览器在API方面的实现基本上一致,存在一定的兼容性问题,但不影响正常的使用。

转载自 http://devilswrwr.blog.163.com/blog/static/6583867020124822919358/

关于debian系统下crontab执行时间不正常的设置

前两天用用crontab指定了几个脚本,有一个是要凌晨一点钟执行的,今天看了下,发现执行时间比我设置的要晚了8小时,到早上9点。于是马上看了下系统时间

1
2
3
4
# date
Mon Jun 25 14:34:18 CST 2012
# date -u
Mon Jun 25 06:34:29 UTC 2012

显示的本地时间和UTC的时间都是正常的,但是显示我那系统crontab执行的时间是按照UTC时间来了,于是我再次设置了下时区为国内的,记得我之前好像设置过了,不过还是尝试一下,删除原来的时区文件,又重新复制到本地

1
2
rm /etc/localtime
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

然后vi /etc/localtime 查看了下时区文件,确实是CST-8,再向crontab里添加一个测试脚本,重启cron
/etc/init.d/cron restart
发现它依然没有按照国内的时间来执行,之前也没遇到过问题,而且同样的设置,目前另外的机子上也正常执行,最后才想起我这个用的是debian系统。上网找了一圈,发现debian系统下面,仅是设置/etc/localtime是不够的,更加需要的是/etc/timezone这个文件。
找了下debian设置时区的方法,网上说用tzconfig,试了下,提示我要用dpkg-reconfigure tzdata,看来不是所有系统命令都一样的,而且我用的是精简版的系统,有些写入时间命令有些不能执行,估计也跟这个是VPS的关系吧。运行后按照提示设置完毕,重启cron试了下,依然时间不对。想着这个应该是设置/etc/localtime的时区而已。
最后用了tzselect程序来设置时区,运行tzselect命令后,按照自己要的时间选择选项,最后选1保存确认即可。再次重启cron,添加测试任务,这次终于按照本地时间运行了,如果不行就重新登录一下或者重启下。早知道debian下要这样设置,就不用搞了这么久了,继续郁闷。

转载自 http://www.ddhow.com/blog/crontab.html

AMH 4.2 虚拟主机面板 安装教程

一、面板介绍
Amysql Host 面板,简称AMH,是一个基于Nginx架构的类似 虚拟主机 独立面板。这个面板安装简单,非常适合Linux新手,以及一些恐惧命令行的Linux VPS用户。
AMH的主要功能有:在线划分虚拟主机(多个网站)、FTP帐号自定义、MYSQL在线创建和管理、数据备份(支持本地和异地备份)、任务计划(crontab自动任务)、以及模块扩展,可以通过AMH官方在面板的基础功能上扩展更多的功能。面板支持在线升级,减少工作量,更傻瓜化、简单化。
Amysql Host 官方:http://amysql.com/AMH.htm AMH
演示:http://108.61.194.60:8888/index.php?c=index&a;=login
账号: amh,密码:amh_password

二、面板模块
AMH已全面支持运行于 Centos Ubuntu ( Ubuntu 12系列或以下) Debian
AMH含有模块 Nginx 1.4.7 MySQL 5.5.34 PHP 5.3.27 PureFTPd 1.0.36

三、安装
请使用纯净的系统,不要存在类似的服务,比如WDCP 或者其他编译的程序
第一步:使用root账号连接SSH
第二步:下载并运行AMH安装脚本

1
wget http://amysql.com/file/AMH/4.2/amh.sh; chmod 775 amh.sh; ./amh.sh 2>&1 | tee amh.log;

或者

1
wget http://api.ifdream.net/linux/amh/amh.sh; chmod 775 amh.sh; ./amh.sh 2>&1 | tee amh.log;

根据提示输入选择1~3选项。1为安装amh,2为卸载amh,3为退出不做操作。如下图:
QQ截图20140722234319
输入1回车,接着输入MySQL Root密码与AMH面板管理员密码即进入安装流程,如下图。
QQ截图20140722234509
安装过程大约需10~20分钟(以服务器性能为准),最后如看到安装成功提示,说明系统已安装完成。

访问http://ip:8888 即可进入AMH web端管理,默认账号为admin。如下图
QQ截图20140723002730

四、AMH面板 常用命令
以下命令适合在SSH中操作。
虚拟主机 : amh host
PHP管理 : amh php
Nginx管理 : amh nginx
MySQL管理 : amh mysql
FTP管理 : amh ftp
数据备份 : amh backup
一键还原 : amh revert
参数设置 : amh SetParam
模块扩展 : amh module
任务计划 : amh crontab
在线升级 : amh upgrade
面板信息 : amh info

五、AMH相关目录
网站目录 : /home/wwwroot
Nginx目录 : /usr/local/nginx
PHP目录 : /usr/local/php
MySQL目录 : /usr/local/mysql
MySQL数据目录 : /usr/local/mysql/data

转载自 http://blog.ifdream.net/linux/amh42/

免费超大量邮件发送服务Mailgun提供SMTP和API支持

一、Mailgun申请与使用方法

1、Mailgun官方网站:http://www.mailgun.com

2、进入Mailgun,注册一个账号。
Mailgun注册账号

3、然后Mailgun会告诉可以使用PHP、JAVA、Curl、Ruby、Python、C#等语言来调用Mailgun的API发送邮件,想要发送大量的邮件使用API开发是必不可少的。
Mailgun开发代码

4、一般地我们使用Mailgun的SMTP就可以了。Mailgun注册后会自动为我们生成一个超长的二级域名作为发件人,但是我们可以自己添加域名,这样还可以获得更多的免费发送邮件配额。
Mailgun添加自己的域名

5、添加域名时一般使用自己的二级域名。
Mailgun使用自己的二级域名

6、然后Mailgun会生成域名的TXT记录。
Mailgun生成域名解析记录

7、根据页面的提示,到域名的DNS管理处修改。
Mailgun修改域名DNS解析

8、然后回到Mailgun点击验证域名,一般只有DNS全部生效后,Mailgun才会显示域名验证成功。

9、Mailgun的域名验证成功后,就可以开始使用这个域名来发送邮件了,SMTP服务器地址、账号、密码、端口等都可以看到。
Mailgun验证域名成功

10、Mailgun还有简单的域名反垃圾服务。
Mailgun反垃圾

11、如果没有用自己的域名,可以使用Mailgun默认生成的域名发送邮件。
Mailgun使用默认域名发送邮件

二、Mailgun邮件跟踪、发送日志和取消订阅实用功能

1、Mailgun提供了强大的邮件跟踪统计功能。
Mailgun邮件跟踪统计

2、在这里可以看到自己用Mailgun发出去的邮件的送达、阅读、点击等情况。
Mailgun查看邮件发送情况

3、Mailgun提供的日志中,可以用来查看Mailgun操作记录和一些错误信息。
Mailgun发送邮件日志

4、Mailgun还提供一个贴心的“Unsubscribes”功能,即取消订阅功能。
Mailgun取消订阅

5、这个功能主要是为了提高用户体验,比如有些人可能对不断收到信息推送服务的邮件很反感,在邮件最下方加一个“取消订阅”的功能,用户只要一点击以后就不会收到类似的邮件了。而我们也可以在Mailgun中看到取消订阅的情况统计。
Mailgun统计取消订阅情况

6、这是我用Mailgun测试发送的邮件。
Mailgun测试邮件

转载自 http://www.freehao123.com/amazon-ses-mailgun/ 有删减

P.S. 自带的 php sdk 不怎么会用。。。于是根据 curl 版写了个,附源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function mailgun_poster($api,$domain,$from,$to,$subject,$message,$type="html") {
$post_fields=array('from'=>$from,'to'=>$to,'subject'=>$subject,$type=>$message);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.mailgun.net/v2/".$domain."/messages");
curl_setopt($ch, CURLOPT_USERPWD, $api);
curl_setopt($ch, CURLOPT_HTTPGET, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,$post_fields);
$result=curl_exec($ch);
curl_close($ch);
$result=json_decode($result,1);
return $result["message"]=="Queued. Thank you.";
}

昨天无聊小小的重写了一下 bpcs_uploader

首先,我得声明,源项目在此 https://github.com/oott123/bpcs_uploader
其次,我得说明下我修改了那些部分,以及,为什么要对其进行修改。
在我第一次接触这份源码时,我只是将它用来备份网站数据。但是,随着时间的推移,我的博客也从 vps 转移到了别人的虚拟空间(好吧是因为手头有点紧和该死的 azure)。于是,我重写了我的备份脚本,将原先的 从服务器上传备份文件 改成了 由百度网盘下载我博客的服务提供商的自动备份文件。开始的一段时间还算正常,但渐渐的,突然发现少了好几天的备份。通过手动执行备份脚本,发现其在运行时报错,其返回”{“error_code”:36013,”error_msg”:”too many tasks”,”request_id”:**}”。想进入百度网盘查看离线下载任务,居然发现其离线下载列表与 api 的离线下载是分开来的。之后,我就改了它。╮(╯▽╰)╭
其实修改很简单,照着 百度提供的 api 新加一个获取离线下载列表功能就行了。期间想过再加一个取消失效任务的功能,但我试着访问了几次 api 以后发现,不需要,pcs 会自行删除失效任务。
上代码

1
2
//./bpcs_uploader.php Line 24 之后加入的,额这句加不加其实无所谓 ╮(╯_╰)╭
Usage: $argv[0] fetch_list

1
2
3
4
5
//./bpcs_uploader.php Line 97 之后加入的
case 'fetch_list':
//离线下载列表
fetch_file_list($access_token);
break;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//./_bpcs_files_/core.php Line 177 之后加入的
function fetch_file_list($access_token){
$fetch=do_api('https://pcs.baidu.com/rest/2.0/pcs/services/cloud_dl',"method=list_task&access_token=".$access_token,'POST');
$fetch=json_decode($fetch,1);
apierr($fetch);
$tmp=array();
$task_ids=array();
if (empty($fetch["task_info"])) {
echon("暂无离线下载任务");
die();
}
foreach ($fetch["task_info"] as $v) {
$task_ids[]=$v["task_id"];
}
$fetch2=do_api('https://pcs.baidu.com/rest/2.0/pcs/services/cloud_dl',"method=query_task&op_type=1&access_token=".$access_token."&task_ids=".implode(",",$task_ids),'POST');
$fetch2=json_decode($fetch2,1);
apierr($fetch2);
$i=0;
$status_codes=array(0=>"下载成功",1=>"下载进行中",2=>"系统错误",3=>"资源不存在",4=>"下载超时",5=>"资源存在但下载失败",6=>"存储空间不足",7=>"目标地址数据已存在",8=>"任务取消");
foreach ($fetch2["task_info"] as $k=>$v) {
$tmp[$i]["task_id"]=$k;
$tmp[$i]["task_name"]=$v["task_name"];
$tmp[$i]["status"]=$status_codes[$v["status"]];
$tmp[$i]["source_url"]=$v["source_url"];
$tmp[$i]["create_time"]=date("Y-m-d H:i:s",$v["create_time"]);
if (isset($v["finished_size"])) {
$tmp[$i]["finished"]=(round($v["finished_size"]/$v["file_size"]*10000)/100)."%";
}
$i++;
}
echon("离线下载任务列表:");
foreach ($tmp as $v) {
echon("----------------------------------");
echon(" 任务 ID: ".$v["task_id"]);
echon(" 文件名: ".$v["task_name"]);
echon(" 任务状态: ".$v["status"]);
if (isset($v["finished"])) {
echon(" 下载进度: ".$v["finished"]);
}
echon(" 下载源: ".$v["source_url"]);
echon(" 创建时间: ".$v["create_time"]);
}
return $tmp;
}

另外,比较看不顺眼执行时的刷屏,我把 curl 显示进度关掉了

1
2
3
4
5
6
//./_bpcs_files_/common.inc.php Line 79-83 被修改成了
if($method == 'POST'){
$cmd = "curl -X POST -k -L --data \"$param\" \"$url\" 2>/dev/null";
}else{
$cmd = "curl -X $method -k -L \"$url?$param\" 2>/dev/null";
}

OK,就这样了。

用PHP实现MD5算法

MD5即Message-Digest Algorithm 5(信息-摘要算法 5)。 MD5算法是一种消息摘要算法,以任意长度的信息作为输入进行计算,产生一个128-bit(16-byte)的指纹或报文摘要(fingerprint or message digest)。两个不同的信息产生相同信息摘要的概率相当小,从一个给定的信息摘要逆向生成原始信息的可能性更小。但是由于还是存在碰撞的情况,随着计算机运算能力的提高,MD5已经不太适用于有较高安全要求的场合,但是应对一般的文件完整性检查还是可以的。

MD5算法的标准实现过程可以分为5步。

1、MD5算法是对输入的数据进行补位,使得如果数据位长度LEN对512求余的结果是448。
即数据扩展至K512+448位。即K64+56个字节,K为整数。 具体补位操作:补一个1,然后补0至满足上述要求

2、补数据长度
用一个64位的数字表示数据的原始长度B,把B用两个32位数表示。这时,数据就被填补成长度为512位的倍数。

3. 初始化MD5参数
四个32位整数 (A,B,C,D) 用来计算信息摘要,初始化使用的是十六进制表示的数字

1
2
3
4
A=0X01234567
B=0X89abcdef
C=0Xfedcba98
D=0X76543210

4、处理位操作函数
X,Y,Z为32位整数。

1
2
3
4
F(X,Y,Z) = X&Y|NOT(X)&Z
G(X,Y,Z) = X&Z|Y?(Z)
H(X,Y,Z) = X xor Y xor Z
I(X,Y,Z) = Y xor (X|not(Z))

5、主要变换过程
使用常数组T[1 … 64], T为32位整数用16进制表示,数据用16个32位的整数数组M表示。

具体过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/* 处理数据原文 */ 

For i = 0 to N/16-1 do

/*每一次,把数据原文存放在16个元素的数组X中. */
For j = 0 to 15 do
Set X[j] to M[i*16+j].
end /结束对J的循环

/* Save A as AA, B as BB, C as CC, and D as DD. */
AA = A
BB = B
CC = C
DD = D

/* 第1轮*/
* 以 [abcd k s i]表示如下操作
* a = b + ((a + F(b,c,d) + X[k] + T) s).
*/

/* Do the following 16 operations. */

[ABCD 0 7 1] [DABC 1 12 2] [CDAB 2 17 3] [BCDA 3 22 4]
[ABCD 4 7 5] [DABC 5 12 6] [CDAB 6 17 7] [BCDA 7 22 8]
[ABCD 8 7 9] [DABC 9 12 10] [CDAB 10 17 11] [BCDA 11 22 12]
[ABCD 12 7 13] [DABC 13 12 14] [CDAB 14 17 15] [BCDA 15 22 16]

/* 第2轮* */

/** 以 [abcd k s i]表示如下操作
* a = b + ((a + G(b,c,d) + X[k] + T) s).
*/

/* Do the following 16 operations. */

[ABCD 1 5 17] [DABC 6 9 18] [CDAB 11 14 19] [BCDA 0 20 20]
[ABCD 5 5 21] [DABC 10 9 22] [CDAB 15 14 23] [BCDA 4 20 24]
[ABCD 9 5 25] [DABC 14 9 26] [CDAB 3 14 27] [BCDA 8 20 28]
[ABCD 13 5 29] [DABC 2 9 30] [CDAB 7 14 31] [BCDA 12 20 32]

/* 第3轮*/

/** 以 [abcd k s i]表示如下操作
* a = b + ((a + H(b,c,d) + X[k] + T) s).
*/

/* Do the following 16 operations. */

[ABCD 5 4 33] [DABC 8 11 34] [CDAB 11 16 35] [BCDA 14 23 36]
[ABCD 1 4 37] [DABC 4 11 38] [CDAB 7 16 39] [BCDA 10 23 40]
[ABCD 13 4 41] [DABC 0 11 42] [CDAB 3 16 43] [BCDA 6 23 44]
[ABCD 9 4 45] [DABC 12 11 46] [CDAB 15 16 47] [BCDA 2 23 48]

/* 第4轮*/

/** 以 [abcd k s i]表示如下操作
* a = b + ((a + I(b,c,d) + X[k] + T) s).
*/

/* Do the following 16 operations. */

[ABCD 0 6 49] [DABC 7 10 50] [CDAB 14 15 51] [BCDA 5 21 52]
[ABCD 12 6 53] [DABC 3 10 54] [CDAB 10 15 55] [BCDA 1 21 56]
[ABCD 8 6 57] [DABC 15 10 58] [CDAB 6 15 59] [BCDA 13 21 60]
[ABCD 4 6 61] [DABC 11 10 62] [CDAB 2 15 63] [BCDA 9 21 64]

/* 然后进行如下操作 */
A = A + AA
B = B + BB
C = C + CC
D = D + DD
end /* 结束对I的循环*/

用PHP实现MD5算法
PHP的实现基本按照上面的算法实现,
对于PHP来说,特殊的位置有三点:

在于需要规避其在数组中,当数组元素超过整形长度时的自动转换;
无符号右移操作的实现;
将字符串转换成8位存储为一个元素的数据结构。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
<?php

$str = "1";
$md5 = new MD5($str);
echo $md5->getDigist();
echo "<br />", md5($str);

class MD5 {
const CHAR_ALIGNMENT = 8;

private $_digist;
private $_state;

public function __construct($str) {
$bin = $this->_str2bin($str);
$len = strlen($str) * self::CHAR_ALIGNMENT;
$bin[$len >> 5] |= 128 << ($len % 32);
$bin[((($len + 64) >> 9) << 4) + 14] = $len;

$this->_md5Init();
$this->_update($bin);
$this->_digist = $this->_bin2hex($this->_state);
}

/**
* 公有方法
* 获取信息摘要
* @return string
*/
public function getDigist() {
return $this->_digist;
}

private function _bin2hex($bin) {
$hex_tab = "0123456789abcdef";
$str = "";
for ($i = 0; $i < count($bin) * 4; $i++) {
$str .= $hex_tab[($bin[$i >> 2] >> (($i % 4) * 8 + 4)) & 0xF] .
$hex_tab[($bin[$i >> 2] >> (($i % 4) * 8 )) & 0xF];
}
return $str;
}

private function _update($bin) {
$bin_len = count($bin);
for ($i = 0; $i < $bin_len; $i += 16) {
$block = array();
for ($j = 0; $j < 16; $j++) {
$block[$j] += isset($bin[$i + $j]) ? $bin[$i + $j] : 0;
}
$this->_md5Transform($block);
unset($block);
}
}

/**
* 初始化
*/
private function _md5Init() {

$this->_state[0] = intval(0x67452301);
$this->_state[1] = intval(0xefcdab89);
$this->_state[2] = intval(0x98badcfe);
$this->_state[3] = intval(0x10325476);

return TRUE;
}

private function _md5Transform($block) {
$a = $this->_state[0];
$b = $this->_state[1];
$c = $this->_state[2];
$d = $this->_state[3];

$x = $block;

/** Round 1 */
MD5Tool::FF($a, $b, $c, $d, $x[0], MD5Tool::S11, 0xd76aa478); /* 1 */
MD5Tool::FF($d, $a, $b, $c, $x[1], MD5Tool::S12, 0xe8c7b756); /* 2 */
MD5Tool::FF($c, $d, $a, $b, $x[2], MD5Tool::S13, 0x242070db); /* 3 */
MD5Tool::FF($b, $c, $d, $a, $x[3], MD5Tool::S14, 0xc1bdceee); /* 4 */
MD5Tool::FF($a, $b, $c, $d, $x[4], MD5Tool::S11, 0xf57c0faf); /* 5 */
MD5Tool::FF($d, $a, $b, $c, $x[5], MD5Tool::S12, 0x4787c62a); /* 6 */
MD5Tool::FF($c, $d, $a, $b, $x[6], MD5Tool::S13, 0xa8304613); /* 7 */
MD5Tool::FF($b, $c, $d, $a, $x[7], MD5Tool::S14, 0xfd469501); /* 8 */
MD5Tool::FF($a, $b, $c, $d, $x[8], MD5Tool::S11, 0x698098d8); /* 9 */
MD5Tool::FF($d, $a, $b, $c, $x[9], MD5Tool::S12, 0x8b44f7af); /* 10 */
MD5Tool::FF($c, $d, $a, $b, $x[10], MD5Tool::S13, 0xffff5bb1); /* 11 */
MD5Tool::FF($b, $c, $d, $a, $x[11], MD5Tool::S14, 0x895cd7be); /* 12 */
MD5Tool::FF($a, $b, $c, $d, $x[12], MD5Tool::S11, 0x6b901122); /* 13 */
MD5Tool::FF($d, $a, $b, $c, $x[13], MD5Tool::S12, 0xfd987193); /* 14 */
MD5Tool::FF($c, $d, $a, $b, $x[14], MD5Tool::S13, 0xa679438e); /* 15 */
MD5Tool::FF($b, $c, $d, $a, $x[15], MD5Tool::S14, 0x49b40821); /* 16 */

/** Round 2 */
MD5Tool::GG($a, $b, $c, $d, $x[1], MD5Tool::S21, 0xf61e2562); /* 17 */
MD5Tool::GG($d, $a, $b, $c, $x[6], MD5Tool::S22, 0xc040b340); /* 18 */
MD5Tool::GG($c, $d, $a, $b, $x[11], MD5Tool::S23, 0x265e5a51); /* 19 */
MD5Tool::GG($b, $c, $d, $a, $x[0], MD5Tool::S24, 0xe9b6c7aa); /* 20 */
MD5Tool::GG($a, $b, $c, $d, $x[5], MD5Tool::S21, 0xd62f105d); /* 21 */
MD5Tool::GG($d, $a, $b, $c, $x[10], MD5Tool::S22, 0x2441453); /* 22 */
MD5Tool::GG($c, $d, $a, $b, $x[15], MD5Tool::S23, 0xd8a1e681); /* 23 */
MD5Tool::GG($b, $c, $d, $a, $x[4], MD5Tool::S24, 0xe7d3fbc8); /* 24 */
MD5Tool::GG($a, $b, $c, $d, $x[9], MD5Tool::S21, 0x21e1cde6); /* 25 */
MD5Tool::GG($d, $a, $b, $c, $x[14], MD5Tool::S22, 0xc33707d6); /* 26 */
MD5Tool::GG($c, $d, $a, $b, $x[3], MD5Tool::S23, 0xf4d50d87); /* 27 */
MD5Tool::GG($b, $c, $d, $a, $x[8], MD5Tool::S24, 0x455a14ed); /* 28 */
MD5Tool::GG($a, $b, $c, $d, $x[13], MD5Tool::S21, 0xa9e3e905); /* 29 */
MD5Tool::GG($d, $a, $b, $c, $x[2], MD5Tool::S22, 0xfcefa3f8); /* 30 */
MD5Tool::GG($c, $d, $a, $b, $x[7], MD5Tool::S23, 0x676f02d9); /* 31 */
MD5Tool::GG($b, $c, $d, $a, $x[12], MD5Tool::S24, 0x8d2a4c8a); /* 32 */

/** Round 3 */
MD5Tool::HH($a, $b, $c, $d, $x[5], MD5Tool::S31, 0xfffa3942); /* 33 */
MD5Tool::HH($d, $a, $b, $c, $x[8], MD5Tool::S32, 0x8771f681); /* 34 */
MD5Tool::HH($c, $d, $a, $b, $x[11], MD5Tool::S33, 0x6d9d6122); /* 35 */
MD5Tool::HH($b, $c, $d, $a, $x[14], MD5Tool::S34, 0xfde5380c); /* 36 */
MD5Tool::HH($a, $b, $c, $d, $x[1], MD5Tool::S31, 0xa4beea44); /* 37 */
MD5Tool::HH($d, $a, $b, $c, $x[4], MD5Tool::S32, 0x4bdecfa9); /* 38 */
MD5Tool::HH($c, $d, $a, $b, $x[7], MD5Tool::S33, 0xf6bb4b60); /* 39 */
MD5Tool::HH($b, $c, $d, $a, $x[10], MD5Tool::S34, 0xbebfbc70); /* 40 */
MD5Tool::HH($a, $b, $c, $d, $x[13], MD5Tool::S31, 0x289b7ec6); /* 41 */
MD5Tool::HH($d, $a, $b, $c, $x[0], MD5Tool::S32, 0xeaa127fa); /* 42 */
MD5Tool::HH($c, $d, $a, $b, $x[3], MD5Tool::S33, 0xd4ef3085); /* 43 */
MD5Tool::HH($b, $c, $d, $a, $x[6], MD5Tool::S34, 0x4881d05); /* 44 */
MD5Tool::HH($a, $b, $c, $d, $x[9], MD5Tool::S31, 0xd9d4d039); /* 45 */
MD5Tool::HH($d, $a, $b, $c, $x[12], MD5Tool::S32, 0xe6db99e5); /* 46 */
MD5Tool::HH($c, $d, $a, $b, $x[15], MD5Tool::S33, 0x1fa27cf8); /* 47 */
MD5Tool::HH($b, $c, $d, $a, $x[2], MD5Tool::S34, 0xc4ac5665); /* 48 */

/** Round 4 */
MD5Tool::II($a, $b, $c, $d, $x[0], MD5Tool::S41, 0xf4292244); /* 49 */
MD5Tool::II($d, $a, $b, $c, $x[7], MD5Tool::S42, 0x432aff97); /* 50 */
MD5Tool::II($c, $d, $a, $b, $x[14], MD5Tool::S43, 0xab9423a7); /* 51 */
MD5Tool::II($b, $c, $d, $a, $x[5], MD5Tool::S44, 0xfc93a039); /* 52 */
MD5Tool::II($a, $b, $c, $d, $x[12], MD5Tool::S41, 0x655b59c3); /* 53 */
MD5Tool::II($d, $a, $b, $c, $x[3], MD5Tool::S42, 0x8f0ccc92); /* 54 */
MD5Tool::II($c, $d, $a, $b, $x[10], MD5Tool::S43, 0xffeff47d); /* 55 */
MD5Tool::II($b, $c, $d, $a, $x[1], MD5Tool::S44, 0x85845dd1); /* 56 */
MD5Tool::II($a, $b, $c, $d, $x[8], MD5Tool::S41, 0x6fa87e4f); /* 57 */
MD5Tool::II($d, $a, $b, $c, $x[15], MD5Tool::S42, 0xfe2ce6e0); /* 58 */
MD5Tool::II($c, $d, $a, $b, $x[6], MD5Tool::S43, 0xa3014314); /* 59 */
MD5Tool::II($b, $c, $d, $a, $x[13], MD5Tool::S44, 0x4e0811a1); /* 60 */
MD5Tool::II($a, $b, $c, $d, $x[4], MD5Tool::S41, 0xf7537e82); /* 61 */
MD5Tool::II($d, $a, $b, $c, $x[11], MD5Tool::S42, 0xbd3af235); /* 62 */
MD5Tool::II($c, $d, $a, $b, $x[2], MD5Tool::S43, 0x2ad7d2bb); /* 63 */
MD5Tool::II($b, $c, $d, $a, $x[9], MD5Tool::S44, 0xeb86d391); /* 64 */

/**
* 注意,这里必须执行intval函数
*/
$this->_state[0] = intval($this->_state[0] + $a);
$this->_state[1] = intval($this->_state[1] + $b);
$this->_state[2] = intval($this->_state[2] + $c);
$this->_state[3] = intval($this->_state[3] + $d);
}

private function _str2bin($str) {
$bin = array();
$alignment = (1 << self::CHAR_ALIGNMENT) - 1;
$len = strlen($str);

for ($i = 0; $i < $len * self::CHAR_ALIGNMENT; $i += self::CHAR_ALIGNMENT) {
$key = $i >> 5;
$bin[$key] |= ( ord($str[$i / self::CHAR_ALIGNMENT]) & $alignment) << ($i % 32);
}

return $bin;
}

}

class MD5Tool {
/** S11-S44原本是一个 4 * 4 的矩阵,在C实现中是用#define 实现的,
* 这里作为类的常量表示,在各种对象间共享
*/
const S11 = 7;
const S12 = 12;
const S13 = 17;
const S14 = 22;

const S21 = 5;
const S22 = 9;
const S23 = 14;
const S24 = 20;

const S31 = 4;
const S32 = 11;
const S33 = 16;
const S34 = 23;

const S41 = 6;
const S42 = 10;
const S43 = 15;
const S44 = 21;

/** F, G, H ,I 是4个基本的MD5函数,
* 在C实现中,一般是用宏实现,这里我们以类方法的形式给出
*/
public static function F($x, $y, $z) {
return ($x & $y) | ((~$x) & $z);
}

public static function G($x, $y, $z) {
return ($x & $z) | ($y & (~$z));
}

public static function H($x, $y, $z) {
return $x ^ $y ^ $z;
}

public static function I($x, $y, $z) {
return $y ^ ($x | (~$z));
}

/**
* 左移N位
* @param type $x
* @param type $n
* @return type
*/
public static function ROTATE_LEFT($x, $n) {
return ($x << $n) | self::URShift($x, (32 - $n));
}

/**
* PHP无符号右移
* @param type $x
* @param type $bits
* @return type
*/
public static function URShift($x, $bits) {
/** 转换成代表二进制数字的字符串 */
$bin = decbin($x);
$len = strlen($bin);

/** 字符串长度超出则截取底32位,长度不够,则填充高位为0到32位 */
if ($len > 32) {
$bin = substr($bin, $len - 32, 32);
} elseif ($len < 32) {
$bin = str_pad($bin, 32, '0', STR_PAD_LEFT);
}

/** 取出要移动的位数,并在左边填充0 */
return bindec(str_pad(substr($bin, 0, 32 - $bits), 32, '0', STR_PAD_LEFT));
}

/**
* FF,GG,HH和II将调用F,G,H,I进行近一步变换
* 其中FF,GG,HH和II分别为四轮转移调用
*
* 注意: 在PHP中,这里使用了引用返回,第一个元素
* 并且所有的返回值必须执行intval强制转换为整形,否则最终可能会被PHP自动转换
*/
public static function FF(&$a, $b, $c, $d, $x, $s, $ac) {
$a += self::F($b, $c, $d) + ($x) + $ac;
$a = self::ROTATE_LEFT($a, $s);
$a = intval($a + $b);
}

public static function GG(&$a, $b, $c, $d, $x, $s, $ac) {
$a += self::G($b, $c, $d) + ($x) + $ac;
$a = self::ROTATE_LEFT($a, $s);
$a = intval($a + $b);
}

public static function HH(&$a, $b, $c, $d, $x, $s, $ac) {
$a += self::H($b, $c, $d) + ($x) + $ac;
$a = self::ROTATE_LEFT($a, $s);
$a = intval($a + $b);
}

public static function II(&$a, $b, $c, $d, $x, $s, $ac) {
$a += self::I($b, $c, $d) + ($x) + $ac;
$a = self::ROTATE_LEFT($a, $s);
$a = intval($a + $b);
}

}

?>

在构造函数中,MD5算法的几个步骤基本一一对应。

转载自 http://www.codesky.net/article/201207/179541.html

P.S. 半夜无聊突然想到一个很久以前就想到过的问题,md5 算法产生的 hash 会不会重复,结果搜了一下,的确会。http://www.myhack58.com/Article/60/76/2008/19525.htm 这篇文章里有提到个工具,可以伪造 md5,下载了一份源代码以后有空的时候慢慢研究其算法,转载本文的目的同样如此。附下载链接,程序: fastcoll_v1.0.0.5.exe,源码: fastcoll_v1.0.0.5_source

Mysql复制表结构、表数据

1、复制表结构及数据到新表
CREATE TABLE 新表SELECT * FROM 旧表
这种方法会将oldtable中所有的内容都拷贝过来,当然我们可以用delete from newtable;来删除。
不过这种方法的一个最不好的地方就是新表中没有了旧表的primary key、Extra(auto_increment)等属性。需要自己用”alter”添加,而且容易搞错。

2、只复制表结构到新表
CREATE TABLE 新表SELECT * FROM 旧表WHERE 1=2
CREATE TABLE 新表LIKE 旧表

3、复制旧表的数据到新表(假设两个表结构一样)
INSERT INTO 新表SELECT * FROM 旧表

4、复制旧表的数据到新表(假设两个表结构不一样)
INSERT INTO 新表(字段1,字段2,.......) SELECT 字段1,字段2,...... FROM 旧表

5、可以将表1结构复制到表2
SELECT * INTO 表2 FROM 表1 WHERE 1=2

6、可以将表1内容全部复制到表2
SELECT * INTO 表2 FROM 表1

7、 show create table 旧表;
这样会将旧表的创建命令列出。我们只需要将该命令拷贝出来,更改table的名字,就可以建立一个完全一样的表

8、mysqldump
用mysqldump将表dump出来,改名字后再导回去或者直接在命令行中运行

转载自 http://www.2cto.com/database/201202/120259.html

PHP file_get_contents设置超时处理方法

话说,从PHP5开始,file_get_content已经支持context了(手册上写着:5.0.0 Added the context support. ),也就是说,从5.0开始,file_get_contents其实也可以POST数据。
今天说的这篇是讲超时的,确实在跨服务器提交的时候,不可避免的会遇到超时的情况,这个时候怎么办?set_time_limit是没有用的,只有用context中的timeout时间来控制。相反,我们不是要抑止,而是要管理。比如在超时返回错误后,进行一次尝试,就象js中的 settimeout那样,对函数重新处理。错误超过3次或者5次后,我们就确实的认为无法连接服务器而彻底放弃。这,是一个好办法,应该值得推荐使用。其实。不全是file_get_contents,只要支持context的都应该加上,避免超时浪费时间。这样可以被支持的函数大致有:fsocketopen(该函数的最后一个参数。好象比较推荐在读stream的时候,使用stream_time_out函数进行控制),fopen(也是从PHP5开始加入context支持),file(PHP5加入支持),curl(curl有自已的变量 CURLOPT_TIMEOUT)等 。
在使用file_get_contents函数的时候,经常会出现超时的情况,在这里要通过查看一下错误提示,看看是哪种错误,比较常见的是读取超时,这种情况大家可以通过一些方法来尽量的避免或者解决。这里就简单介绍两种:
一、增加超时的时间限制
这里需要注意:set_time_limit只是设置你的PHP程序的超时时间,而不是file_get_contents函数读取URL的超时时间。
我一开始以为set_time_limit也能影响到file_get_contents,后来经测试,是无效的。真正的修改 file_get_contents延时可以用resource $context的timeout参数:

1
2
3
4
5
6
7
8
9
10
$opts = array(
'http'=>array(
'method'=>"GET",
'timeout'=>1,//单位秒
)
);
$cnt=0;
while($cnt<3 && ($bb=file_get_contents("http://www.jb51.net", false, stream_context_create($opts)))===FALSE) $cnt++;
echo $cnt;
echo $bb;

二、一次有延时的话那就多试几次
有时候失败是因为网络等因素造成,没有解决办法,但是可以修改程序,失败时重试几次,仍然失败就放弃,因为file_get_contents()如果失 败将返回 FALSE,所以可以下面这样编写代码:

1
2
$cnt=0;
while($cnt<3 && ($bb=file_get_contents("http://www.jb51.net", false, stream_context_create($opts)))===FALSE) $cnt++;

以上方法对付超时已经OK了。那么Post呢?细心点有人发现了’method’=>”GET”, 对!是不是能设置成post呢?百度找了下相关资料,还真可以!而且有人写出了山寨版的post传值函数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Post($url, $post = null){
$context = array ();
if (is_array ( $post )) {
ksort ( $post );
$context ['http'] = array (
'timeout' => 60,
'method' => 'POST',
'content' => http_build_query( $post, '', '&' )
);
}
return file_get_contents ( $url, false, stream_context_create ( $context ) );
}
$data = array (
'name' => 'test',
'email' => 'admin@admin.com',
'submit' => 'submit',
);
echo Post ( 'http://www.jb51.net', $data );

OK , 上面函数完美了,既解决了超时控制又解决了Post传值。

转载自 http://www.jb51.net/article/41833.htm

HTML5读取本地文件

常见的语言比如php、shell等,是如何读取文件的呢?
实际上,大多数语言都需要先获取文件句柄,然后调用文件访问接口,打开文件句柄,读取文件!
那么,HTML5是否也是这样的呢?
答案是肯定的!
HTML5为我们提供了一种与本地文件系统交互的标准方式:File Api
该规范主要定义了以下数据结构:

  • File
  • FileList
  • Blob

HTML5访问本地文件系统时,需要先获取File对象句柄,怎么获取文件引用句柄呢?

选择文件

首先检测一下当前浏览器是否支持File Api:

1
2
3
4
5
6
function isSupportFileApi() {
if(window.File && window.FileList && window.FileReader && window.Blob) {
return true;
}
return false;
}

运行此示例:



HTML5虽然可以让我们访问本地文件系统,但是js只能被动地读取,也就是说只有用户主动触发了文件读取行为,js才能访问到FileApi,这通常发生在表单选择文件或者拖拽文件

表单输入

表单提交文件是最常见的场景,用户选择文件后,触发了文件选择框的change事件,通过访问文件选择框元素的files属性可以拿到选定的文件列表。
如果文件选择框指定了multiple,则一个文件选择框可以同时选择多个文件,files包含了所有选择的文件对象;如果没有指定,则只能选择一个文件,files[0]就是所选择的文件对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
function fileSelect1(e) {
var files = this.files;
for(var i = 0, len = files.length; i < len; i++) {
var f = files[i];
html.push(
'<p>',
f.name + '(' + (f.type || "n/a") + ')' + ' - ' + f.size + 'bytes',
'</p>'
);
}
document.getElementById('list1').innerHTML = html.join('');
}
document.getElementById('file1').onchange = fileSelect1;

运行此示例:




拖拽

拖拽是另一种常见的文件访问场景,这种方式通过一个叫dataTransfer的接口来获得拖拽的文件列表,更多关于dataTransfer
拖拽同样支持多选,用户可以拖拽多个文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function dropHandler(e) {
e.stopPropagation();
e.preventDefault();
var files = e.dataTransfer.files;
for(var i = 0, len = files.length; i < len; i++) {
var f = files[i];
html.push(
'<p>',
f.name + '(' + (f.type || "n/a") + ')' + ' - ' + f.size + 'bytes',
'</p>'
);
}
document.getElementById('list2').innerHTML = html.join('');
}
function dragOverHandler(e) {
e.stopPropagation();
e.preventDefault();
e.dataTransfer.dragEffect = 'copy';
}
var drag = document.getElementById('drag');
drag.addEventListener('drop', dropHandler, false);
drag.addEventListener('dragover', dragOverHandler, false);

运行此示例:


将文件拖到此处


PS:
拖拽有个特别需要注意的事情就是,阻止事件冒泡和事件默认行为,防止浏览器自动打开文件等行为,比如拖拽一个pdf,浏览器可能会打开pdf。

至此,我们知道,我们可以通过两种方式来获得文件句柄,那么如何读取文件内容呢?

读取文件

HTML5提供了一个叫FileReader的接口,用于异步读取文件内容,它主要定义了以下几个方法:

  • readAsBinaryString(File|Blob)
  • readAsText(File|Blob [, encoding])
  • readAsDataURL(File|Blob)
  • readAsArrayBuffer(File|Blob)

FileReader还提供以下事件:

  • onloadstart
  • onprogress
  • onload
  • onabort
  • onerror
  • onloadend

一旦调用了以上某个方法读取文件后,我们可以监听以上任何一个事件来获得进度、结果等。

预览本地图片

这里主要用到FileReader的readAsDataURL方法,通过将图片数据读取成Data URI的方法,将图片展示出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function fileSelect2(e) {
e = e || window.event;
var files = this.files;
var p = document.getElementById('preview2');
for(var i = 0, f; f = files[i]; i++) {
var reader = new FileReader();
reader.onload = (function(file) {
return function(e) {
var span = document.createElement('span');
span.innerHTML = '<img style="padding: 0 10px;" width="100" src="'+ this.result +'" alt="'+ file.name +'" />';
p.insertBefore(span, null);
};
})(f);
//读取文件内容
reader.readAsDataURL(f);
}
}
document.getElementById('files2').addEventListener('change', fileSelect2, false);

运行此示例:




调用FileReader的readAsDataURL接口时,浏览器将异步读取文件内容,通过给FileReader实例监听一个onload事件,数据加载完毕后,在onload事件处理中,通过reader的result属性即可获得文件内容。

预览文本文件

这里主要用到FileReader的readAsText,对于诸如mimeType为text/plain、text/html等文件均认为是文本文件,即mineType为text开头都可以用这个方法来预览。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function fileSelect3(e) {
e = e || window.event;
var files = this.files;
var p = document.getElementById('preview3');
for(var i = 0, f; f = files[i]; i++) {
var reader = new FileReader();
reader.onload = (function(file) {
return function(e) {
var div = document.createElement('div');
div.className = "text"
div.innerHTML = encodeHTML(this.result);
p.insertBefore(div, null);
};
})(f);
//读取文件内容
reader.readAsText(f);
}
}
document.getElementById('files3').addEventListener('change', fileSelect3, false);

运行此示例:




PS:由于需要在页面上预览文本,所以则需要对文件中的html特殊字符进行实体编码,避免浏览器解析文件中的html代码。

监控读取进度

既然FileReader是异步读取文件内容,那么就应该可以监听它的读取进度。
事实上,FileReader的onloadstart以及onprogress等事件,可以用来监听FileReader的读取进度。
在onprogress的事件处理器中,有一个ProgressEvent对象,这个事件对象实际上继承了Event对象,提供了三个只读属性:

  • lengthComputable
  • loaded
  • total

通过以上几个属性,即可实时显示读取进度,不过需要注意的是,此处的进度条是针对单次读取的进度,即一次readAsBinaryString等方法的读取进度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var input4 = document.getElementById('file4');
var bar = document.getElementById('progress-bar');
var progress = document.getElementById('progress');
function startHandler(e) {
bar.style.display = 'block';
}
function progressHandler(e) {
var percentLoaded = Math.round((e.loaded / e.total) * 100);
if (percentLoaded < 100) {
progress.style.width = percentLoaded + '%';
progress.textContent = percentLoaded + '%';
}
}
function loadHandler(e) {
progress.textContent = '100%';
progress.style.width = '100%';
}
function fileSelect4(e) {
var file = this.files[0];
if(!file) {
alert('请选择文件!');
return false;
}
if(file.size > 500 * 1024 * 1024) {
alert('文件太大,请选择500M以下文件,防止浏览器崩溃!');
return false;
}
progress.style.width = '0%';
progress.textContent = '0%';
var reader = new FileReader();
reader.onloadstart = startHandler;
reader.onprogress = progressHandler;
reader.onload = loadHandler;
reader.readAsBinaryString(this.files[0]);
}
input4.onchange = fileSelect4;

运行此示例:




分割文件

有的时候,一次性将一个大文件读入内存,并不是一个很好的选择(如果文件太大,可能直接导致浏览器崩溃),上述的监听进度示例就有可能在文件太大的情况下崩溃。
更稳健的方法是分段读取!

分段读取文件

HTML5 File Api提供了一个slice方法,允许分片读取文件内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function readBlob(start, end) {
var files = document.getElementById('file5').files;
if(!files.length) {
alert('请选择文件');
return false;
}
var file = files[0],
start = parseInt(start, 10) || 0,
end = parseInt(end, 10) || (file.size - 1);
var r = document.getElementById('range'),
c = document.getElementById('content');
var reader = new FileReader();
reader.onloadend = function(e) {
if(this.readyState == FileReader.DONE) {
c.textContent = this.result;
r.textContent = "Read bytes: " + (start + 1) + " - " + (end + 1) + " of " + file.size + " bytes";
}
};
var blob;
if(file.webkitSlice) {
blob = file.webkitSlice(start, end + 1);
} else if(file.mozSlice) {
blob = file.mozSlice(start, end + 1);
} else if(file.slice) {
blob = file.slice(start, end + 1);
}
reader.readAsBinaryString(blob);
};
document.getElementById('file5').onchange = function() {
readBlob(10, 100);
}

运行此示例:(读取10 ~ 100字节)





本例使用了FileReader的onloadend事件来检测读取成功与否,如果用onloadend则必须检测一下FileReader的readyState,因为read abort时也会触发onloadend事件,如果我们采用onload,则可以不用检测readyState。

分段读取进度

那分段读取一个大文件时,如何监控整个文件的读取进度呢?
这种情况下,因为我们调用了多次FileReader的文件读取方法,跟上文一次性把一个文件读到内存中的情况不大相同,不能用onprogress来监控。
我们可以监听onload事件,每次onload代表每个片段读取完毕,我们只需要在onload中计算已读取的百分比就可以了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
var bar2 = document.getElementById('progress-bar2');
var progress2 = document.getElementById('progress2');
var input6 = document.getElementById('file6');
var block = 1 * 1024 * 1024; // 每次读取1M
// 当前文件对象
var file;
// 当前已读取大小
var fileLoaded;
// 文件总大小
var fileSize;
// 每次读取一个block
function readBlob2() {
var blob;
if(file.webkitSlice) {
blob = file.webkitSlice(fileLoaded, fileLoaded + block + 1);
} else if(file.mozSlice) {
blob = file.mozSlice(fileLoaded, fileLoaded + block + 1);
} else if(file.slice) {
blob = file.slice(fileLoaded, fileLoaded + block + 1);
} else {
alert('不支持分段读取!');
return false;
}
reader.readAsBinaryString(blob);
}
// 每个blob读取完毕时调用
function loadHandler2(e) {
fileLoaded += e.total;
var percent = fileLoaded / fileSize;
if(percent < 1) {
// 继续读取下一块
readBlob2();
} else {
// 结束
percent = 1;
}
percent = Math.ceil(percent * 100) + '%';
progress2.innerHTML = percent;
progress2.style.width = percent;
}
function fileSelect6(e) {
file = this.files[0];
if(!file) {
alert('文件不能为空!');
return false;
}
fileLoaded = 0;
fileSize = file.size;
bar2.style.display = 'block';
// 开始读取
readBlob2();
}
var reader = new FileReader();
// 只需监听onload事件
reader.onload = loadHandler2;
input6.onchange = fileSelect6

运行此示例:(提示:请选择一个1G以上文件)




注意事项

在chrome浏览器上测试时,如果直接以file://xxx这种形式访问demo,会出现FileReader读取不到内容的情况,表现为FileReader的result为空或者FileReader根本就没有去读取文件内容,FileReader各个事件没有触发;
这种情况我想应该是类似于chrome不允许添加本地cookie那样,chrome也不允许以file://xxx这种页面上的js代码访问文件内容;
解决办法很简单,只需要将测试文件放到一个web服务器上,以http://xxx形式访问即可。

参考文章

转载自 http://hushicai.com/2014/03/29/html5-du-qu-ben-di-wen-jian.html