18APRIL |
|---|
原文地址:
翻译的不好,见谅。另注:关于文中提到的,这里有一篇中文的介绍
译文:
我们常常使用一个提升站点性能的办法,就是把js文件和css文件打包压缩。但是压缩以后如果我们要调试或者修改呢?那简直是恶梦。不过,现在有一种可以部分解决这个问题的方式,叫做Source Map,我们翻译成源码图吧。
Source Map可以把压缩的文件转换成压缩前的样子。这就意味着,你可以尽管压缩你的代码,然后还可以方便的调试源码。现在chrome和firefox浏览器的开发工具已经支持SourceMap了。
本文介绍SourceMap的工作原理和生成方法,我们只介绍js的sourcemap。
备注:firefox默认支持SourceMap,Chrome浏览器需要手动开启支持。设置方法:在chrome浏览器的控制面板里(settings),里面的常规选项卡里有Enable js sourcemap 和Enable css source map 两个选项,勾选即可。
Source Map 工作方式
顾名思义,源图由一大堆信息组成,把压缩的代码转换成阅读的原始代码。您可以指定一个不同的源映射。做法就是把压缩的代码中加入一句注释:
//# sourceMappingURL=/path/to/script.js.map
支持Source Map的js压缩工具会自动加入这句注释,浏览器只在开发工具打开的时候才会识这句注释并加载map文件(如果是chrome浏览器,则需要设置打开)。
map文件其实是一个json格式的文件,包含一些相关性的信息,示例如下:
{
version: 3,
file: "script.js.map",
sources: [
"app.js",
"content.js",
"widget.js"
],
sourceRoot: "/",
names: ["slideUp", "slideDown", "save"],
mappings: "AAA0B,kBAAhBA,QAAOC,SACjBD,OAAOC,OAAO..."
}
每个节点的含义如下:
用UgilfyJS压缩工具生成map文件
UgilfyJS是一个优秀的压缩js的命令行工具,版本2以上的版本支持SourceMap,使用方式为:
uglifyjs [input files] -o script.min.js --source-map script.js.map --source-map-root http://example.com/js -c -m
--source-map – 输出的文件名.--source-map-root – (optional) sourceRoot属性.--source-map-url – (optional) url. //# sourceMappingURL=/path/to/script.js.map--in-source-map – (optional) 源路径--prefix or -p – (optional)从 sources 移除n 级路径 . 比如, -p 3 会移除三级路径, 比如 one/two/three/file.js 会变成 file.js. 使用-p relative会使用相对路径.grunt 有个ugilfyJS插件,可参考对应文档使用。
The Sources Tab in Chrome Dev Tools
(本人电脑可能是版本问题,并未出现对应的js)
The Debugger Tab in the Firefox Developer Tools
Firefox的开发工具的debugger可以看到,不过当你尝试的时候会发现只有当打开对应的js时才会出现内容,之前不会加载出来(或者说渲染出来).
Source Map允许程序员优化代码的同时又能够方便的调试源代码,着实是个好东西.
这篇文章压缩js用到了UglifyJS.如果你还没有用过UglifyJS优化你的网站,可以把这个工具加入你的工作流中。
18APRIL |
|---|
移动web开发,最让人心烦的估计就是各平台下运行结果不一致的问题了,明明在电脑上调试的好好的,在手机上却错误,而且错的莫名其妙。咋办? 1、http://jsconsole.com/ 最初,大家期望都不高,能把console.log输出出来就好了,当然,高版本iphone在safari中内置这个功能了,但其他手机呢? 这时候我们可以求助jsconsole.com,他其实提供了一个在线的服务,帮你来完成console.log的输出。你需要改什么?什么都不需要改。打开网页看看吧。 不多说,输入:listen,然后就可以得到一个脚本地址的标签,把该标签贴进你的网页里面即可。 原理其实就是把页面中控制台输出的内容显示打你当前的网页上了,不错吧。 2、weinre 有了输出结果,我们其实更不满足了,我还想想在pc上的浏览器那样,看dom的情况嘞,这个咋办? 我们知道chrome等webkit浏览器底下有个开发工具,可以查看dom情况,脚本执行情况,比单纯的console.log输出强多了,得此工具,可以得天下乎。 weinre就是这样一个强大的工具,原理是:在安装一个weinre的服务,其实就是一个提供api的网站,然后跟jsconsole中一样,把生成的一段代码加入到你的网页中去(也可以不加入,用收藏夹方式调试也可以)。然后这段脚本就把页面中所有的信息(webkit的)发到你的服务器,然后展示给你。这个脚本依赖webkit,所以现在支持webkit的浏览器。不过现在主流的移动端浏览器都是webkit的,ie和firefox嘛,还只占很小比例。 weinre据说早期是java开发的,现在有nodejs版,使用可以参考以下文章: http://ju.outofmemory.cn/entry/1355 (经测试,在chrome浏览器上正常,而ios7的safari中与某些脚本有冲突,会报错,不知道是不是nodejs版的weinre还没正式完成?) 3、firebug-lite 前面说到,weinre只支持webkit浏览器,其他浏览器我们咋处理哪? 做web开发的,一定想到firebug那近乎完美个的调试工具,想来做web开发这几年基本上没有离开过firebug,重装电脑第一个要装的浏览器是firefox,第一个要装的插件就是firebug啦。 不过之前一直没用过,把firebug-lite弄到手机浏览器上去。 其实很简单啦,跟前面的一个,把一段脚本嵌入到你的页面去就好了。 参考:https://getfirebug.com/firebuglite 不过firebug这么强大的东东,在小小的浏览器窗口中施展的着实不甚自在。要是集weinre和firebug的种种优点于一身,哈哈。
18APRIL |
|---|
做视频流的都知道苹果的hls视频流格式,其实微软也有一种流格式叫做Smooth Streaming。
hls在苹果的设备上可是默认支持的,不用安装什么解码器,直接浏览器打开就能播放,据说人家那是硬件解码,别的设备就得自己想办法了。
Smooth Streaming就没这么好运气了,即便在微软的windows8.1的操作系统中,微软也没能提供web版的Smooth Streaming播放器。只是win8 store App倒是提供了Smooth Streaming Client 的SDK。
今天我们讨论的重点就是这个播放器的sdk。
首先是播放器的主体:Playerframework:http://playerframework.codeplex.com/
这个播放器还算正常,但是怎么说呢。如果你是WinJS开发的App,总感觉多此一举,毕竟html5自带的播放器就可以播放视频,通过js扩展出的各种漂亮的播放器,都不比微软的差。
这个播放器呢,使用还算简单,不多说了。我们说他的Smooth Streaming Client SDK
好吧,我一直以为win8.1是win8的一个更新呢,原来可以认为是两个东西哦。
这里有一个关于html app中使用这东西的文档,不翻译了。
18APRIL |
|---|
我们有时候在div上加a标签,用以产生标准的类似按钮的鼠标效果,减少css代码。
但是加了click事件之后,发现由于事件冒泡,href的链接事件也会触发。
经测试,这个href有以下现象:
写“#”是我们之前常常使用的方法。不过使用jqm或backbone的无刷新跳转的方式,就遇到麻烦了。这类框架支持不跳转页面而是通过”#home”这样的路径来记录页面浏览历史。
这时候我们可以在click事件中阻止冒泡即可防止出发href事件。
最简单的方法自然是:return false;
16SEPTEMBER |
|---|
网页开发中经常遇到js排序的问题,这曾经是前端工程师最头疼的问题。不过使用Backbonejs的同学,还是有不少捷径可走的。 1、sortBy方法: var sortcollection=this.collection.sortBy(function(music){ return music.get("title").toLowerCase(); }); this.collection.reset(sortcollection); 直接sortBy并不能重新排序,必须手动触发reset方法。 2、comparator: this.collection.comparator = function(music) { return music.get("id");// }; 自动排序,号称每增加一个会自动排到正确的位置。不过我没有试验成功。 3、逆序reverse 话说reverse方法实际上是js数组标准的方法,在这里使用起来仍然很方便。 var sortcollection=this.collection.sortBy(function(music){ return music.get("title").toLowerCase(); }).reverse(); this.collection.reset(sortcollection); 之所以一开始并没有想到可以直接用,是因为collection在跟踪模式下显示并不是一个数组而是一个对象。 另外,在使用中发现,collection没有提供清空方法,而且reset时,页面已有的元素并没有被排序,而是重新渲染了一批新的经过排序的内容进来。我理解这是因为集合数据并不跟html的内容同步的原因,处理也蛮简单的,我们直接清空容器dom的html就可以了。
28AUGUST |
|---|
最近开始学习backbonejs。 学习中遇到的种种问题,以笔记形式记录在此。今天是第一篇笔记,记录比较零碎,勿怪。 view中的经常会发现this及其属性在不同的方法中不一致的问题,这是因为针对不同的方法,this指向的并不是本view。 解决方法也很简单: _.bindAll(this, ‘render’); 这里假设要绑定的是render方法,如果要绑定多个的话,直接用逗号隔开即可_.bindAll(this, ‘render’,‘anotherfunction’);。
27AUGUST |
|---|
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script> <script> $(document).ready(function(){ // hide #back-top first $("#back-top").hide(); // fade in #back-top $(function () { $(window).scroll(function () { if ($(this).scrollTop() > 100) { $('#back-top').fadeIn(); } else { $('#back-top').fadeOut(); } }); // scroll body to 0px on click $('#back-top a').click(function () { $('body,html').animate({ scrollTop: 0 }, 800); return false; }); }); }); </script>
27AUGUST |
|---|
Posted December 26th, 2012 by Sam Hocevar. The Do What The Fuck You Want To Public License (WTFPL) is a free software license. There is a long ongoing battle between GPL zealots and BSD fanatics, about which license type is the most free of the two. In fact, both license types have unacceptable obnoxious clauses (such as reproducing a huge disclaimer that is written in all caps) that severely restrain our freedoms. The WTFPL can solve this problem. When analysing whether a license is free or not, you usually check that it allows free usage, modification and redistribution. Then you check that the additional restrictions do not impair fundamental freedoms. The WTFPL renders this task trivial: it allows everything and has no additional restrictions. How could life be easier? You just DO WHAT THE FUCK YOU WANT TO.
` DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE Version 2, December 2004 Copyright (C) 2004 Sam Hocevar <[email protected]> Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. You just DO WHAT THE FUCK YOU WANT TO. `
28JUNE |
|---|
var networkInfo = Windows.Networking.Connectivity.NetworkInformation;
//Retrieve the ConnectionProfile
var internetConnectionProfile = networkInfo.getInternetConnectionProfile();
//Pass the returned object to a function that accesses the connection data
var connectionProfileInfo = getConnectionProfileInfo(internetConnectionProfile);
原创文章,转载请注明: 转载自Happiness space
本文链接地址: 判断网络是否连接(win8 app HTML&JS)
[判断网络是否连接(win8 app HTML&JS)](https://byszsz.com/archives/523.htm?utm_source=rss&utm_medium=rss&utm_campaign=%25e5%2588%25a4%25e6%2596%25ad%25e7%25bd%2591%25e7%25bb%259c%25e6%2598%25af%25e5%2590%25a6%25e8%25bf%259e%25e6%258e%25a5%25ef%25bc%2588win8-app-htmljs%25ef%25bc%2589)
28JUNE |
|---|
访问twitter的信息,返回的结果是个数组:
\[
{
...
},{
...
}
\]
结果在windowsRT的JsonObject.Parse时发现程序报错。经测试发现该方法Parse根是数组的json对象确实会出现问题,而且Json.Net的组件同样会发生问题。
既然直接Parse数组不成,把它拼成个对象呗:
``````
content = "{\\"result\\":"+ content+"}";
JsonObject jsonObject = JsonObject.Parse(content);
``````
OK了
原创文章,转载请注明: 转载自Happiness space
本文链接地址: JsonObject.Parse 接收json数组时的问题
[JsonObject.Parse 接收json数组时的问题](https://byszsz.com/archives/521.htm?utm_source=rss&utm_medium=rss&utm_campaign=jsonobject-parse-%25e6%258e%25a5%25e6%2594%25b6json%25e6%2595%25b0%25e7%25bb%2584%25e6%2597%25b6%25e7%259a%2584%25e9%2597%25ae%25e9%25a2%2598)
28JUNE |
|---|
安装步骤其实还算简单:
1、安装node.js。
2、更新npm,命令行下执行:
npm update
``````
3、使用npm安装Coffee Script:
``````
npm install \-g coffee-script
如果网络有问题,可以去这里下载一个zip包使用:
[https://github.com/jashkenas/coffee-script/](https://github.com/jashkenas/coffee-script/)
关于zip包的使用方式,其实就是把下载下来的zip包解压到:
C:\\Users\\username\\AppData\\Roaming\\npm\\node\_modules\\coffee-script\\
目录中就可以了,另外有说需要添加环境变量的:
NODE\_PATH\=C:\\Users\\username\\AppData\\Roaming\\npm\\node\_modules
到现在为止,其实Coffee Script已经安装完成,你可以通过查看版本号的命令来检查安装时候成功。
coffee –v
然后我们就要开始配置Sublime Text2的环境了。Sublime Text2其实是提供了一个插件的,可以在用install命令直接安装。不过安装后并不好使(编译报错),需要修改插件配置, 在插件Sublime Text的安装目录:
\\Data\\Packages\\CoffeeScript\\CoffeeScript.sublime-build
打开该文件,使用如下代码覆盖:
{
``````
"cmd": \["C:\\\\Program Files\\\\nodejs\\\\coffee.cmd", "$file","&&","C:\\\\Program Files\\\\nodejs\\\\coffee.cmd","\-c","$file"\],
``````
"file\_regex": "^(...\*?):(\[0-9\]\*):?(\[0-9\]\*)",
``````
"selector": "source.coffee"
``````
}
可以看到这段代码跟我们网上查到的代码有些不一样,其实coffee命令直接跟$file的话,是控制台输出,中间夹一个”-c”的参数,就是编译了,编译就是把我们创建的.coffee文件编译成指定的.js文件。我们这里的配置的参数是同步录下同名的js文件。(如果文件已经存在,会覆盖)
当然,这里有个coffee.cmd,其实我看到这段代码时也很迷惑的,这段coffee.cmd的作用是什么呢?因为nodejs里面并没有这个命令啊。
经过一番查找,命令也很简单:
@echo off
``````
"C:\\Program Files\\nodejs\\node.exe" "C:\\Program Files\\nodejs\\node\_modules\\npm\\node\_modules\\coffee-script\\bin\\coffee" %\*
好了,把这个命令保存成coffee.cmd,放在nodejs的根目录下就可以了。
好了,现在就使用sublime text来编写coffee script文件了。
如果你还不太熟悉,这里有个小提示:
参考链接:
http://www.ibm.com/developerworks/cn/web/wa-coffee1/
http://www.cnblogs.com/2gua/archive/2012/07/04/2576352.html
http://www.ituring.com.cn/article/4048
http://rritw.com/a/bianchengyuyan/C__/20121026/242739.html
原创文章,转载请注明: 转载自Happiness space
28JUNE |
|---|
本文转自残阳似血的博客
首先还是要先说一下Amazon S3,全称是Amazon Simple Storage Service。EC2和S3是Amazon最早推出的两项云服务。在传统的计算机领域,主要包括计算、存储、网络这几个方面, 在云计算时代,前两者分别对应虚拟化和cloud storage,由此可以显现出Amazon EC2和S3的重要性。如今随着云计算的大红大紫,也有很多使用Amazon S3的例子,典型的有Dropbox,还有之前被FB收购的Instagram,其照片存储就使用的S3。
关于REST,这也是比较火的一种Web服务架构。简单来说,资源是由URI指定,对资源的操作包括GET、PUT、POST、DELETE和HEAD,返回结果常常是XML或者其他形式。如果你想了解更多,可以查看REST的Wiki页面。
Amazon S3的操作包括三部分:Service,Buckets和Objects。Service只包括GET操作,就是返回所有的Buckets的列表。Object顾名思义,是指存储在云端的文件,值得注意的是,S3中并没有明确的文件夹的概念,而是通过指定object的路径来实现,比如说,object可以为“photos/1.jpg”。而Bucket拥有全局名,名称由用户定义,用来存放Object,由于是全局名,所以要确保名字是别人没用过的。
访问Web服务时,Http request headers需要一些参数。主要包括:
这个是Amazon文档中的说明:
Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature;
Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );
StringToSign = HTTP-Verb + "\\n" +
Content-MD5 + "\\n" +
Content-Type + "\\n" +
Date + "\\n" +
CanonicalizedAmzHeaders +
CanonicalizedResource;
CanonicalizedResource = \[ "/" + Bucket \] +
<HTTP-Request-URI, from the protocol name up to the query string\> +
\[ sub-resource, if present. For example "?acl", "?location", "?logging", or "?torrent"\];
CanonicalizedAmzHeaders = <described below>
``````
首先Authorization是由”AWS {0}:{1}“组成的,第0个参数为你的Access Key ID,在注册了AWS之后,在[这里](https://aws-portal.amazon.com/gp/aws/securityCredentials)可以得到。第1个参数是计算出来的签名值。
签名值的计算方法是对一个UTF-8的字符串,用你的Secret Access Key(同样在Access Key ID处获取)进行SHA1加密。
StringToSign字符串也需要满足一定的格式。如上所示,第一行是你的操作名,应该为PUT、GET、DELETE、HEAD和POST中的一种。第二行是内容的MD5值的base64编码,和headers中的Content-MD5值应保持一致。第三行是Content-Type,同样需要和headers中的一致。第四行Date,和headers中的Date一致。需要说明的是CanonicalizedAmzHeaders和CanonicalizedResource。
CanonicalizedAmzHeaders就是把headers中的x-amz-开头的作为key转化为小写并按顺序排列,key和value之间用冒号相连,用换行符“\\n”把它们给连接起来。比如说headers中有:
> X-Amz-Meta-ReviewedBy: [email protected] X-Amz-Meta-ReviewedBy: [email protected] X-Amz-Meta-FileChecksum: 0x02661779 X-Amz-Meta-ChecksumAlgorithm: crc32 > ```
那么CanonicalizedAmzHeaders就是:
> ```
> x-amz-meta-checksumalgorithm:crc32\\n x-amz-meta-filechecksum:0x02661779\\n x-amz-meta-reviewedby:[email protected],[email protected]
> ```
CanonicalizedResource是指规范化的资源。如果访问资源没有指定bucket,那么就是“/”;如果包括bucket,而不包括object,那就是“/bucket_name/”,注意前后的“/”不要落了;如果既包括bucket,也包括object,那么就是“/bucket_name/object_name”;另外,有时候比如是访问bucket的acl(访问控制列表acess control list)时,object_name就是?acl,因此这时CanonicalizedResource就是“/bucket_name/?acl”,访问object的acl时,CanonicalizedResource 就是“/bucket_name/object_name?acl”。
需要说明的是,如果计算出的CanonicalizedAmzHeaders不为空时,要确保CanonicalizedAmzHeaders和CanonicalizedResource之间有换行符“\n”连接。
关于Authorization的详细内容,可以参考官方文档。
##### Service
对Service的操作只包括Get,即获取用户所有的Buckets列表。Request headers除了通用的,没有其他的内容。比如
> ```
> GET / HTTP/1.1 Host: s3.amazonaws.com Date: date Authorization: signatureValue
> ```
返回的XML中包括Owner和各个Buckets,比如:
> ```
> <?xml version="1.0" encoding="UTF-8"?>
> ```
>
> ```
> <ListAllMyBucketsResult xmlns\="http://doc.s3.amazonaws.com/2006-03-01"\>
> ```
>
> ```
> <Owner\>
> ```
>
> ```
> <ID\>bcaf1ffd86f461ca5fb16fd081034f</ID\>
> ```
>
> ```
> <DisplayName\>webfile</DisplayName\>
> ```
>
> ```
> </Owner\>
> ```
>
> ```
> <Buckets\>
> ```
>
> ```
> <Bucket\>
> ```
>
> ```
> <Name\>quotes</Name\>
> ```
>
> ```
> <CreationDate\>2006-02-03T16:45:09.000Z</CreationDate\>
> ```
>
> ```
> </Bucket\>
> ```
>
> ```
> <Bucket\>
> ```
>
> ```
> <Name\>samples</Name\>
> ```
>
> ```
> <CreationDate\>2006-02-03T16:41:58.000Z</CreationDate\>
> ```
>
> ```
> </Bucket\>
> ```
>
> ```
> </Buckets\>
> ```
>
> ```
> </ListAllMyBucketsResult\>
> ```
##### Buckets
由于项目中只用到了Buckets的PUT、GET、DELETE,关于acl、lifecycle、policy等就不作过多说明,如果这方面有疑问,可以参考官方文档。
接下来,如果Http request headers中内容没有什么特别说明的,将会略去不写。
###### [PUT Bucket](http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUT.html)
需要说明的是,在request headers可以加入bucket的权限控制,即指定x-amz-acl,合法的值包括:private,public-read、public-read-write、authenticated-read、bucket-owner-read、bucket-owner-full-control,从名字就可以看出具体的含义。
在request body中可以包括位置信息,即用户期望Bucket放置在Amazon的哪个数据中心。默认为US Standard,其他数据中心包括US West (Oregon) Region、US West (Northern California) Region、EU (Ireland) Region、Asia Pacific (Singapore) Region、Asia Pacific (Tokyo) Region、South America (Sao Paulo) Region。对于我们中国用户来说,离得最近的是东京的数据中心。不过在body中内容中,这七个数据中心写成:’EU’、 ‘eu-west-1′、’us-west-1′、 ‘us-west-2′、’ap-southeast-1′、’ap-northeast-1′和’sa-east-1′。
比如请求如下:
> ```
> PUT / HTTP/1.1 Host: BucketName.s3.amazonaws.com Content-Length: length Date: date Authorization: signatureValue
> ``````
> <CreateBucketConfiguration xmlns\="http://s3.amazonaws.com/doc/2006-03-01/"\>
> ```
>
> ```
> <LocationConstraint\>BucketRegion</LocationConstraint\>
> ```
>
> ```
> </CreateBucketConfiguration\>
> ```
###### [GET Bucket](http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGET.html)
Get Bucket主要是列出这个Bucket下所有的objects。值得提的是四个参数Prefix、Marker、MaxKeys和Delimiter,利用这四个参数,可以达到多种效果。
首先是Prefix,它表示这个Bucket中返回的Object以这个值为开头。Marker表示,返回这个值以后的Objects,比如说,第一次调用没有返回全部结果,则把第一次调用返回的Objects的最后一个作为Maker调用,以返回其以后的Objects。MaxKeys返回单次请求返回的最大Objects数,默认为1000。Delimiter表示分隔符,是在设置了Prefix之后,能够返回共同的Prefix(在结果中为CommonPrefix)。
因此,通过设置MaxKeys和Marker可以达到翻页效果,每次返回的最后一个Object作为下一次请求的Marker,在返回值中,如果IsTruncated为true,那么表示还有下一页。此外,通过设置Prefix和将Delimiter设为”/“,可以达到返回某个文件夹下所有内容的效果,其中CommonPrefix下的Prefix表示文件夹路径,而每个Contents中是Object的信息。
下面是一个请求的例子:
> ```
> GET ?prefix=N&marker=Ned&max-keys=40 HTTP/1.1 Host: quotes.s3.amazonaws.com Date: Wed, 01 Mar 2009 12:00:00 GMT Authorization: AWS AKIAIOSFODNN7EXAMPLE:xQE0diMbLRepdf3YB+FIEXAMPLE=
> ```
返回结果为:
> ```
> HTTP/1.1 200 OK x-amz-id-2: gyB+3jRPnrkN98ZajxHXr3u7EFM67bNgSAxexeEHndCX/7GRnfTXxReKUQF28IfP x-amz-request-id: 3B3C7C725673C630 Date: Wed, 01 Mar 2009 12:00:00 GMT Content-Type: application/xml Content-Length: 302 Connection: close Server: AmazonS3
> ``````
> <?xml version="1.0" encoding="UTF-8"?>
> ```
>
> ```
> <ListBucketResult xmlns\="http://s3.amazonaws.com/doc/2006-03-01/"\>
> ```
>
> ```
> <Name\>bucket</Name\>
> ```
>
> ```
> <Prefix/>
> ```
>
> ```
> <Marker/>
> ```
>
> ```
> <MaxKeys\>1000</MaxKeys\>
> ```
>
> ```
> <IsTruncated\>false</IsTruncated\>
> ```
>
> ```
> <Contents\>
> ```
>
> ```
> <Key\>my-image.jpg</Key\>
> ```
>
> ```
> <LastModified\>2009-10-12T17:50:30.000Z</LastModified\>
> ```
>
> ```
> <ETag\>"fba9dede5f27731c9771645a39863328"</ETag\>
> ```
>
> ```
> <Size\>434234</Size\>
> ```
>
> ```
> <StorageClass\>STANDARD</StorageClass\>
> ```
>
> ```
> <Owner\>
> ```
>
> ```
> <ID\>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID\>
> ```
>
> ```
> <DisplayName\>[email protected]</DisplayName\>
> ```
>
> ```
> </Owner\>
> ```
>
> ```
> </Contents\>
> ```
>
> ```
> <Contents\>
> ```
>
> ```
> <Key\>my-third-image.jpg</Key\>
> ```
>
> ```
> <LastModified\>2009-10-12T17:50:30.000Z</LastModified\>
> ```
>
> ```
> <ETag\>"1b2cf535f27731c974343645a3985328"</ETag\>
> ```
>
> ```
> <Size\>64994</Size\>
> ```
>
> ```
> <StorageClass\>STANDARD</StorageClass\>
> ```
>
> ```
> <Owner\>
> ```
>
> ```
> <ID\>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID\>
> ```
>
> ```
> <DisplayName\>[email protected]</DisplayName\>
> ```
>
> ```
> ...
> ```
###### [DELETE Bucket](http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketDELETE.html)
DELETE Bucket没有什么需要特殊说明的。
##### Objects
###### [PUT Object](http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectPUT.html)
PUT Object基本操作类似,在Http body中添加Object的内容,这里就需要计算Content-Type等值。与PUT Bucket类似,可以在Http headers中加入x-amz-acl,以控制Object的权限。
###### [GET Object](http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectGET.html)
在GET Object时,Response headers中会包括这个Object的相关信息,除了Content-Length和Content-Type等,Etag其实就是内容的MD5后的16进制的字符串。而Response body中就是文件的内容。
###### [DELETE Object](http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectDELETE.html)
DELETE操作仍然没有什么特别说明的。
综上,本文主要说明了Amazon S3中对Service的GET,以及对Bucket和Object的PUT、GET和DELETE操作。但是REST API中还有一部分没有提到,这个可以查看具体的文档,其中解释得还是比较详细的。这篇文章还是为了想写Amazon S3客户端调用的同学们提供导向作用。
最后,提及一下当时的项目,项目主要是要实现一个本地的多云存储的备份。我们的项目使用了Python,而Amazon S3有一些第三方的API调用的实现,比较知名的有Boto,但是由于其支持很多云服务,显得过于庞大,最后我决定自己实现,而Amazon S3这块的中文资料也不是很多,这里分享出来,让入门的同学能够更快的上手,仅此而已。如果想看我们项目中Amazon S3 REST API的客户端实现,可以移步这里。
**原创文章,转载请注明:** 转载自[Happiness space](https://byszsz.com/)
**本文链接地址:** [Amazon S3 REST API详解(转载)](https://byszsz.com/archives/554.htm)
02MAY |
|---|
直接上例子``` function marktext(text) {
var re = new RegExp(key, "gi");
return text.replace(re, "<mark>$&</mark>");
},
function marktext(text) {
var re = new RegExp(key, "gi");
return text.replace(re, function (word) { return "<mark>" + word + "</mark>" });
```
}, 这两个方法都是给指定不区分大小写的内容做mark标记的方法,功能一样,相比直接替换的优势就是不会改变被替换文字的大小写。第二个方法,使用起来更灵活。
18FEBRUARY |
|---|
今天手机接上电脑时,系统报了一个错误,db_adb.exe的错误,感觉很奇怪,不知道什么原因,就上网搜索了一下。
结果发现这个东西是金山毒霸的一个进程,连接手机的时候要强制安装豌豆荚,估计是卸载豌豆荚引发的问题。
但是,我的电脑上没有安装金山毒霸啊!!!
于是在网上看了一些投诉,都是投诉这个病毒的。
而且不止在金山毒霸的软件中出现,在金山的多款软件中都有出现。
我的电脑上有安装金山的快盘软件,每次连接手机的时候,都会提示要安装豌豆荚,还要安装驱动,而且还自动下载驱动去了,最可笑的是竟然中兴的手机下载了个三星的驱动非要装,我说这金山的工程师们,你们是吃shi的么?
吃shi也就罢了,偏偏不该在哪儿有本事的地方挺本事,db_adb.exe这个进程死活杀不掉!到底人家是杀毒的,知道杀毒都咋使啥招儿,人家造的病毒,查不出杀不掉啊。结束进程啊,可以,你结束后,就变俩db_adb.exe了,玩用户没商量。
怎么办?卸载了事吧?快盘不用了!金山的啥都不想用了!金山这是想搞啥?看自己招牌够不够硬,砸砸试试?你又不姓马,还学人家化腾?
这么一个垃圾进程,360愣是没查出来,傻了吧,这个进程一旦启动,360的手机助手就连不上手机了,哈哈,你不想跟金山打架,他在背后搞你呢,为此我还差点放弃360的手机助手。在此,希望360也要升级下,这类流氓软件坚决要处理啊。
原创文章,转载请注明: 转载自Happiness space
本文链接地址: db_adb.exe,国内杀毒厂商制造的新病毒
17FEBRUARY |
|---|
首先,== equality 等同或称值相等,=== identity 恒等或称严格相等。
\==, 两边值类型不同的时候,要先进行类型转换,再比较。
\===,不做类型转换,类型不同的一定不等。
例:
var a = 3; var b = "3"; a==b 返回 true a===b 返回 false
因为a,b的类型不一样
\===用来进行严格的比较判断。
先说 ===,这个比较简单。下面的规则用来判断两个值是否===相等:
1、如果类型不同,就[不相等]
2、如果两个都是数值,并且是同一个值,那么[相等];(!例外)的是,如果其中至少一个是NaN,那么[不相等]。(判断一个值是否是NaN,只能用isNaN()来判断)
3、如果两个都是字符串,每个位置的字符都一样,那么[相等];否则[不相等]。
4、如果两个值都是true,或者都是false,那么[相等]。
5、如果两个值都引用同一个对象或函数,那么[相等];否则[不相等]。
6、如果两个值都是null,或者都是undefined,那么[相等]。
再说 ==,根据以下规则:
1、如果两个值类型相同,进行 === 比较。
2、如果两个值类型不同,他们可能相等。根据下面规则进行类型转换再比较:
a、如果一个是null、一个是undefined,那么[相等]。
b、如果一个是字符串,一个是数值,把字符串转换成数值再进行比较。
c、如果任一值是 true
1、对于string,number等基础类型,==和===是有区别的
1)不同类型间比较,==之比较“转化成同一类型后的值”看“值”是否相等,===如果类型不同,其结果就是不等
2)同类型比较,直接进行“值”比较,两者结果一样
2、对于Array,Object等高级类型,==和===是没有区别的
进行“指针地址”比较
3、基础类型与高级类型,==和===是有区别的
1)对于==,将高级转化为基础类型,进行“值”比较
2)因为类型不同,===结果为false
原创文章,转载请注明: 转载自Happiness space
本文链接地址: js中2个等号与3个等号的区别
15FEBRUARY |
|---|
这是未来会支持的chrome浏览器的离线存储api,叫SyncFileSystem API,现在正式版的chrome浏览器尚不支持,该api支持云端同步功能,同步到google网盘,不过,该api现在只是处于草稿状态,这里记录下地址。
原创文章,转载请注明: 转载自Happiness space
本文链接地址: 记录一个chrome的HTML5离线存储api地址
11AUGUST |
|---|
今天安装了一个QQ电脑管家,号称内置好几个木马查杀软件,于是下载下来试试。
安装后直觉感觉电脑速度变慢,不过我的电脑本来启动的东西就多,于是跑到系统服务中,把所有不常用的服务改为手动,并且关闭这些服务。
然后QQ电脑管家提示有几个软件需要升级,好啊,升级吧。
升级时还告诉我所有要升级的软件都有插件,我记得在公司用360升级的时候,没几个有插件的啊,不过没关系,有插件不是都有提示么?咱相信腾讯是个像样的大公司,不是360那样的小人公司。
这一相信不要紧,QQ提供的这些升级软件虽然提示有插件了,但安装时并没有提示(只有一个有提示,其他的都没看到),等我装完了,发现我常用的chrome浏览器打开新标签是一个什么AVG的页面,也有搜索,而且剧慢无比,我该主页该新标签页,全都没有效果。直到最后才发现,每次启动chrome浏览器时,会自动新安装一个chrome的插件。卸载并结束进程中的几个AVG名字的进程,这才好了。
点评一下,虽然腾讯总是说人家周鸿t是流氓,不过他自己也不是什么好东西,试用电脑管家后发现还不如人家360。搞这些流氓软件应该是8年前的把戏了,现在还搞,不怕砸了自己的名声么?
另外简单查了一下AVG,似乎还是个国外正经杀毒软件厂商,这里也提醒这个厂商,做让人讨厌的事,会毁了自己的。
原创文章,转载请注明: 转载自Happiness space|Jason and chris
本文链接地址: 令人讨厌的QQ电脑管家
22JUNE |
|---|
老外的电影都很奇怪,上次的《惊天战神》中,直接交代雅典娜和阿波罗都被打死了。也不知道他们到底信不信神,呵呵。而且那样的电影竟然还想拍续集。诸神之战也是交代的一个宙斯在地球上的私生子,这一次死的竟然是宙斯。
荷马史诗中有交代,宙斯根本就是一个花心大罗卜,从神到人,老婆很多很多,最近的这些电影都是从宙斯人类的后代入手介绍的。不过这个诸神之战2(又名诸神之怒)中,宙斯已经不是最强的那个了,起初是战神,他是宙斯神界的一个儿子(竟然不是女儿雅典娜),似乎很牛。后来来了个更可怕的:宙斯他爹。如果你以为是宙斯一家大团圆,你就错了。
我们知道,宙斯家谱很混乱,而且都乱了很多代,我们还是不提盖亚了吧。反正三代都是儿子推翻老爹的政权(真该学学人家金家),而且都是把老爹打死,不过这个故事里面却说是封进地狱里,跟死差不多吧。
宙斯这一代,是哥仨联合起来灭的他们老爸,灭了之后三兄弟没人分了一个地盘,宙斯就管神和人,基本上是最大的了,波塞冬管海洋,哈迪斯管地狱,诸神之战1中,哈迪斯看起来挺坏的。
剧透就不透了,感觉还行,不过结局很神奇,波塞冬和宙斯都死了,哈迪斯也失去了神力,差不多就是神族灭亡了,看来要拍续集的话,不能再乘坐诸神之战了,应该跟诸神没啥关系了。
个人感觉比《惊天战神》要强,但是比第一部稍差些,还是值得一看的。
原创文章,转载请注明: 转载自Happiness space|Jason and chris
本文链接地址: 观《诸神之战2》前需要了解的神话历史
15JUNE |
|---|
searchify是一个Search-as-a-Service平台,解决企业站点的站内全文搜索速度慢,建设成本高的问题,不过对于小站来说,价格还是比较贵的,今天只讨论他的一些基本概念以及运行使用方式。
其实最初了解searchify时,思路一直在google上,所以就想当然的以为searchify会抓取网站中的页面,形成索引,我们在使用的时候就跟google的站点搜索那样,直接搜索然后出结果就行了。但是事实上完全不是这么回事。
这里有两个概念要先了解下:
index就是索引,我们知道,这就是我们要搜索的内容的集合。在searchify中,我们可以创建多个index(免费期间会有限制),这就是说我们可以创建多个集合,比如你建了两个网站,上面都有站点搜索,那你显然需要两个集合,免得a站点的搜索结果中包含了B站点的内容。也就是说,当我们需要把搜索的内容分开时,就需要创建多个索引。
doc就是文档,当然这个文档和word文档有区别的。它是一个抽象出来的专门用于搜索的一个数据对象(其实最终表现为一个页面),比如相亲网站要搜人(找到某个人的资料页),那么这个人的数据对象大致要包含的内容就有:
这些属性(元数据metadata),这些东西合起来,就是一个用于搜索的数据对象,如果输入关键词,要搜人,那关键词就从这些字段的属性中去匹配,全文搜索。当然相亲可能还会关心照片,不过照片是没办法用文字搜索的,所以不宜列入,只可以在搜索结果中显示。
刚才说了,searchify并不主动抓取你的页面数据,因为抓取内容的方式并不区分这个属性是姓名还是性格,比如有人要找演员高明,搜出来的全是自以为自己很高明的人,这时候就需要详细搜索,所以抓取页面的方式的缺陷就表现出来,而要做Search-as-a-Service,显然要比google要搜的更准确。那么干脆,你的每个页面的metadata都由你自己设置好了。你把页面的元数据都提交到你的index中,然后就可以搜索了。怎么提交给他呢?
searchify注册成功之后,会自动给你生成两个api地址(其实两个地址是一个地址):
searchify的文档地址:
https://www.searchify.com/documentation/
原创文章,转载请注明: 转载自Happiness space|Jason and chris
本文链接地址: 搜索即服务平台searchify的基本概念
29APRIL |
|---|
大约去年的冬季,某网站组织了一个统计,说今年书呆子最担心的事情是:复仇者联盟是部烂片!
其实,这部电影我也很期待,并且为此把钢铁侠1、2,美国队长,雷神托尔这几部片子都收集起来仔细看了看,不过绿巨人浩克的没看,因为听评价说不咋地,不看应该也没啥。而且这几部电影本身相关性就比较弱,不看前几部也可以看下去。
刚才看了几个宣传片,感觉还可以,但总体感觉宣传片做的比《X战警:第一课》要差,而且6个超级英雄中的教授和鹰眼,之前没有听说过,宣传片中只是展示了下鹰眼是个弓箭手,还会飞,其他也没交代,不知道干啥的,有人说就是在托尔那个拿个复合弓的人,不过很不明显。
下面有个优酷上的宣传片,分享下,5月4号全球公映。
原创文章,转载请注明: 转载自Happiness space|Jason and chris
本文链接地址: 《复仇者联盟》与书呆子最担心的事
28APRIL |
|---|
之前做的项目,json都是格式化成字符串传到后台,然后使用字符串转换成json的方法把json字符串转换为对象,此次使用Zencoder的API,处理完成后对方会提交一个notification到我们的地址,此时在request中无法获得所需的内容,所以获得的提醒内容总是空的。
其实,最初在写这些方法时,曾经自己做过实验,使用jquery中ajax的json方式post的数据,但结果非常简单,jquery直接把post json的请求转为类似url的get请求方式,以至于在参数不太多的情况下,都可以直接按照get url方式来模拟该请求,也就是说json并没有以对象方式post回服务器,而是仍然以字符串post的。
最初我是参照我自己的实验对notification进行了处理,正式测试中却发现Zencoder的提醒没有被获取的,那么他是以什么方式post这些数据的呢?
经尝试,$_post和$_REQUEST都获取不到。查找资料后发现,PHP默认只识别application/x-www.form-urlencoded标准的数据类型,因此,对型如text/xml 或者 soap 或者 application/octet-stream 之类的内容无法解析,如果用$_POST数组来接收就会失败!那怎么办呢?
网上说可以用它来获取:$GLOBALS[‘HTTP_RAW_POST_DATA’] ,不过似乎比较麻烦,当然,还有一个简单的办法: php://input 。
php://input 允许读取 POST 的原始数据。和 $HTTP_RAW_POST_DATA 比起来,它给内存带来的压力较小,并且不需要任何特殊的 php.ini 设置。php://input 不能用于 enctype=”multipart/form-data”。
使用起来很简单:
file_get_contents(“php://input”);
原创文章,转载请注明: 转载自Happiness space|Jason and chris
14APRIL |
|---|
Zencoder是一个优秀的第三方视频编码云服务平台,提供专门的视频编码服务,比如一般的网站要将flv格式视频转换成mp4视频这样的工作,非常占用服务器的CPU,普通网站自己处理很不值得。Zencoder就是专门提供这类服务的一个云平台,使用非常的方便。
Zencoder提供视频编码的服务,当然,你并不需要把你的视频文件拷贝给他,而是通过API告诉他视频的地址并且授予访问权限(支持http/https/ftp/ftps/sftp/s3),告诉他编码方式,然后告诉他编码完了以后放置在什么位置(支持S3, FTP, FTPS, or SFTP),就可以了。他会自动把视频处理好放置在已设定的位置去。如果你的视频比较大或者像我一样批量提交,希望每个视频处理的结果通知我,他一样支持,你只需要在提交任务的时候,告诉他一个可访问的路径,比如http://yourdomain/notify.php,当然程序你要写写的,他就会在完成每个任务时给予提醒。
Zencoder的服务本身在amazon上,所以针对以S3为存储方式的应用支持良好。本人昨天向zencoder发送了一千五百左右的小视频处理请求(包含许多5秒内的测试视频),在所有任务提交2分钟之后,竟然全部完成了,全部处理耗时不到半小时,神速啊。至于价格,如果你不确定每月处理多少视频,那就是按需处理,每分钟视频5美分的价格;如果每月都处理很大量的视频,可以每月交纳一定的费用,然后按照最低每分钟2美分的价格来使用。
Zencoder并没有提供通常的视频处理任务的图形界面,因为比较是一个云服务,并不是针对个人的一两个视频的处理工作。而且创建视频处理的Job时(每一个视频的处理任务称为Job),要提交路径等信息,及时提供完善的图形界面,也无非是填入各种信息然后生成一个api参数Post回去,所以创建Job的过程干脆就做成了生成API参数的样子,与Facebook API的调用测试一样方便。
Zencoder的API非常的简单方便,文档写的也很详细,文档路径为https://app.zencoder.com/docs,里面甚至有很多关于视频格式的资料,不过关于视频格式的问题,还是找视频方面的专家更好些。
.net的SDK非常简单,官方的sdk是于2年前完成的,在github中的zencoder-dotnet https://github.com/zencoder/zencoder-dotnet,虽然是2年前完成的,但所有功能都已经完成。不过,怎么说呢,其本身的实现也特别的简单,说白了就是把你生成的API的参数给POST了一下,没做别的更深入的功能(做了也比较多余)。
最简单的用法是:
首先添加引用:
using ZencoderDotNet; using ZencoderDotNet.Api;
然后拼出参数(我们使用JSON格式):
string json = @"{ ""api\_key"": ""93h630j1dsyshjef620qlkavnmxxxx"", ""input"": ""s3://bucket-name/file-name.avi"" }"; Job job = Job.create(json, "json");
当然这里的api_key需要你注册一个帐号使用。如果只是学习下,zencoder提供免费的5秒内视频的编码测试,只是编码后会添加zencoder的测试水印(要在参数中添加”test”:true)。
如果无法访问github,可以从这里下载
原创文章,转载请注明: 转载自Happiness space
08APRIL |
|---|
前两天为了使用一个美国的免费服务,注册了一个美国的手机号码,可以接电话,收发短信,这个东西在apple的app store中还有免费的应用(美国的store),所以就一直挂在touch上。
本来,这个号码刚注册两天,也没有在什么网站上留过,应该没什么垃圾短信,结果从注册第二天开始,每天都有垃圾短信收到,更可恶的是,每天都会有两个美国打来的电话,我接过一次,对方只说了句hello就挂了,真是厌恶至极。而且,是晚上啊,大半夜的,估计美国那边刚上班吧。
实在没辙,只要把通知中心的声音提醒取消掉了。
看来美国也有垃圾短信和骚扰电话啊,不过他们似乎前几秒不收钱,所以骚扰电话更猖獗,接了以后也可以跟你说几句,这算是中国移动和电信做的好事么?
于是想到了之前我想建设的骚扰电话数据库,一直也没有朋友提交垃圾号码,而我收到的骚扰电话在停了几周后,又开始了兢兢业业每天一次的骚扰工作,不堪其扰啊。
现在的骚扰电话,号段越来越宽了,甚至有了139号段的骚扰电话,但基本上都是来自与广东东莞。
08APRIL |
|---|
最近上vpn比较多,我自己的网站竟然常常封闭我自己的IP,提示我必须输入验证码才能访问,过分的是,输入只能看一页,第二页还得再输入一遍,这个cloudflare做的也太过分了。于是登录cloudflare后台一探究竟。
首先,最近vpn的流量比较大,所有其他免费的vpn都不够用的,只有mxvpn的一个特定线路可以使用,可能大家都用这个线路,有的人拿他做点坏事也不一定,所以就不停的被cloudflare判做恶意站点或是机器人,所以验证码也就出现了。
之前没使用vpn的时候,偶尔也发生这个问题,着实让我头疼了一番,不过这次一并解决,写出来与大家分享下。
cloudflare中站点管理的最后一项,是个threat-control,这个是威胁控制,就是处理谁攻击你的计算机的一项,点击进去会发现有很多个ip(不同站点不同),这些ip都是它认为发生过有危险的访问,这些ip会被拒绝访问,找到自己的ip,点击Trust,信任该IP就可以了。
而且,即使你被要求输入验证码,当点击了信任以后,并不需求输入验证码,直接刷新就可以进入站点。
08APRIL |
|---|
华夏银行的U盾在广告中宣称:与其他U盾不同,使用其U盾不用到网站上下载驱动,言外之意,是使用方便呗。今天真正使用时,发现其实不然。
说明书中称:安装U盾需要三个步骤,当然都是很简单很必须的。打开他的U盾,发现其中设置了个自动启动的程序,就是要安装驱动。哦,原来这就是与其他U盾不同的地方啊。搞一个U盘,把驱动放进去,就算先进了?还要拿到广告里面宣传一番!
这下好,驱动省得下载了,(其实其他银行的也没见一定要下载的,大多都是自动安装的)。不同的是,别人的U盾自动完成后就可以用了,但华夏的不可以!
插上U盾后,还有8个步骤要走,要下载一大堆的驱动和证书,而且全部都是手动下载啊,这就是你们所谓的方便么?!
最可气的是,这8个步骤所要下载的内容在网上没有。
比如,安装cfca数字证书链,这个完全应该不用手动控制的,你的U盘干啥去了?好嘛,我去下载,不过网页上已经改名字了,咱会猜,能猜到。
下一个呢,个人网银安全控件,网页上根本没有,倒是有俩名字完全一样的下载程序,无法区分哪个是,下载后发现大小不一致,俩都安装了吧!要重启啊!
不甘心真的重启了,发现使用证书打开时,反而打不开网页了。
这帮人就是脑残,既然使用证书,干嘛还非得跟U盾结合起来。用了U盾,要什么证书登录。看其说明的最后一步,要用U盾在证书用户中登录,还得使用用户名、密码、验证码,我的个天哪,啥都得输入,你搞个U盾还有证书糊弄用户呢吧?
现在真的有些公司,明明自己没啥技术,却非搞得客户很麻烦,麻烦以后,客户就安全了?一点都不知道踏踏实实做事,难道就没见过人家别的银行网站咋登录的么?
03APRIL |
|---|
其实最近一直在考虑关于个人密码安全的问题,而且我本来也是对各种密码还算比较注意的,但今天突然发现密码被盗了。所以我今天所说的安全措施绝对不是成功的保护措施,所讲的内容更不是什么传教。我把我的方法分享下,如果你觉得有可取之处,我会觉得很好;如果你觉得不足并能指出给我,那太感谢了。
首先,如果你使用多个网站,脑子不一定记得住所有的密码。
一个人一生要记住多少密码?使用一个网上银行,要记住取款密码、电话银行密码、网银登录密码、网银交易密码。第一次设置的时候,银行提醒密码设置要不相同,同时记录4个密码,还不算困难,但是重点是这东西你有不是天天用,哪里能记得住?有一次要修改交易密码(交行),对方要我输入电话银行密码,这个真要难度,我就注册的时候用过一次,5年了啊,你以为我是脑子是磁带机啊。后来我到柜台去修改密码,交行的小同志直接说,那么多密码,设置成一个就行!
开始只有一个银行卡,密码从来都是记在脑子里,后来多办了三四张,又搞了五六个信用卡,这就应付不过来了。其实密码安全和方便的思考,从那时候就开始了。
怎么呢?搞一个记事本,把所有的东西都记录在里面,当然,密码不要直接写进去,给自己个提示就可以了,就是说脑子里面还得记录好几套算法,看到哪个这个提示,你就得能想到这密码是什么。当然这套算法不能太困难,所以呢,记事本还要加密一下,怎么加密呢?很简单,用winrar压缩一下,压缩时再设置一个复杂的密码就可以了,这个密码可要记好了啊,这才记一个密码啊!
好了,加密好了,这个记事本放在那里啊?建议放在U盘里,做两份,分别锁进两个抽屉,用的时候再拿出来!所以家里常备几个小容量的U盘是很必要的。现在银行使用的所谓U盾,其实就是这么个思路。
这样很麻烦啊。谁让网络环境这样呢。当然,我还想过另外一个办法,就是电脑做双系统,搞一个xp,只在使用网银的时候开,磁盘使用NTFS格式化,设置其他人不可访问个人信息,平常使用win7,网银用xp(当年许多网银只支持IE6,不过现在好了)。不过这个我也嫌麻烦,没实施过。
网银嘛,可以麻烦点,日常使用的网站可不能那么麻烦,那又得怎么办呢?我们得把普通网站分个类:
可有可无的,没有个人数据。
常用的邮箱、qq、msn等包含大量联系人或好友信息。
微博、博客等有大量个人信息的网站。
可有可无的,那就搞一个统一的密码和用户名,比如用户名密码都叫做张三,有人爱盗,就盗去吧;
常用的邮箱就不一样,之前注册都要验证邮箱,不太重要的网站密码忘了就忘了,可以通过邮箱找回,你的邮箱密码被盗了,那就完了,所以常用邮箱要绑定手机、绑定密保卡、绑定所有能绑定的东西!哈哈,说大了,因为邮箱是常用的东西,密码泄露是很有可能的,但要是安全设置到位,还是能找回来的。当然,邮箱密码的复杂度一定要够,要足够长,要定期换,因为这个也是唯一一个常用的密码了。
其他所有的一般的网站,微博啥的,盗了也无所谓,只要能通过邮箱找回,密码可以设置复杂一些,也不用全记住,不过毕竟也没啥好盗取的。(所以我特别纳闷我的微博帐号为啥会被盗,因为我很少登录,一般也只会在我的touch上开一下)。
对于这些不是太重要,又还要常常登录的网站,每次找回密码来登陆,确实也很麻烦,如果你有大量的用户名和密码,可以使用以下的密码记录和管理工具(这里声明下:所有的密码记录管理工具都不是足够安全的,)。
chrome浏览器可以记住你的密码,(废话,哪个浏览器都能)。chrome浏览器有一个登录同步的功能,表面看可以同步收藏夹的功能,实际上也可以同步所记录的密码。他的缺点是不能查看和管理密码。也就是说缺少个密码管理器,只记录了密码,而且保存的这个密码似乎是明文。别的浏览器也有这个功能,而且应该大家都在各自的浏览器上用着呢,chrome的同步功能很不错,但你也可以说google偷走了你的密码(他应该犯不上)。
1Password,以浏览器插件的形式,每次你修改密码或登录的时候,捕获到你的信息输入,可以选择性的保存。你需要使用密码的时候,可以打开来看,这个默认是关闭的,输入密码才会打开(这个密码要记住)。另外,他会自动匹配你的dropbox,dropbox会自动把加密的密码文件同步到你的dropbox中,所以实现了在多台计算机中共享密码的。这也说明了1Password不像google那样把你的密码同步到他的服务器上去。什么,dropbox是什么?现在没用过dropbox的人可真不好找,赶快用我的邀请链接注册一个吧,也算帮我的忙,阿弥陀佛。这里你还得记住dropbox的密码才行,还好你的客户端可以记住。缺点是每次用都得输入密码,又麻烦又安全
LastPass,他跟1Password有些类似,但有个更强大(或者说恐怖)的功能,读取你浏览器中记录的用户名密码,存入密码表中,你打开网页时他会根据域名自动选择要自动填写、自动登录、或者全手动不记录。他号称有了LastPass,从此只需要一个密码(LastPass)就可以安全又方便的生活在这个互联网中了。使用的时候,只要一次打开LastPass,输入密码,只要浏览器不关,就可以一直使用。而且他会自动把你的密码表加密后同步到他的云端,这只是他说,是不是加密了,我也不知道哦,不过因为一丁点的安全问题,谷歌都成被告了,这些国外的公司不敢随便乱来的,又不是天朝的公司,没人管。缺点是::太方便了,如果有人使用你的计算机,也很容易看到你的密码。
注意:后两种插件建议只安装最常用的浏览器的插件,不要安全ie的插件,ie还要用来上网银呢。事实上,我的做法是,IE只用于上网银,别的时候不开;火狐只用于工作,调试个脚本啥的,也是火狐的强项;chrome用来平时上网最方便不过,插件只安装在这上面安全又方便。
还有,这个插件实际上捕获不到安全做到极致的网站的密码(比如使用https登录的网站),所以也不同太担心它会保留你的常用邮箱的密码或网银的密码。
再次强调一下,方便的东西一定要只用于不重要的内容。
03APRIL |
|---|
昨天晚上收到美团的短信,说我的帐号更换了绑定手机号,新绑定的号码为130*****228,刚开始还以为是诈骗,不过想想觉得到美团看看也无所谓,就登录进去看了下,发现绑定的手机号真的变成后缀成228的一个号码了。
然后到账户信息中心看了下,发现我的帐号里面竟然还有钱,虽然不多,呵呵。于是抓紧修改绑定的手机号,然后修改密码。都改完了,发现忘记把那个绑定的号码记录下来了,很郁闷,我本来还想仔细了些下这个号码呢。
到现在还比较纳闷的一件事就是,为啥这人盗了我的号,不把钱花了呢。如果花了不方便,干嘛绑定他的手机号呢?应该不是黑客所为,但我的美团那个密码虽然简单,也不是常用密码啊,不是随便就能猜出来的啊。
我只能猜想:是美团自己编造了一个号码,跟大家修改绑定的手机号,然后通知你来登录,把忘了美团的客户再邀请回来。为啥啊?因为现在的团购网站都快不行了,都挺不过几个月了!
还有一种可能,就是美团的员工把客户的资料给卖了。哈哈都是瞎猜的啊,美团莫怪。
这里,希望遇到同类问题的朋友或已经了解真相的朋友能告知。
注:
刚才接到美团的电话,他们并没有查到之前的号码,并保证他们绝对不会透漏任何客户信息。这挺好的,不过一个更可怕的消息是,我的新浪微博的帐号也被盗了,看来我的个人信息风险很大啊,下午就得重新开始修改所有网站的密码了。
不过,美团的帐号与之前csdn的帐号一致,丢了情有可原,新浪的帐号丢了就严重了,这个密码是我所有所使用的密码中最复杂的一个,而且跟任何一个其他人的密码都不一样,看来下午得重新修改一下午的密码了。黑客兄弟,俺是穷人,没啥好盗的,你去别处寻觅下吧!
24MARCH |
|---|
由于000webhost的空间数据库时常抽风,一直考虑换个数据库的,所以发现了免费的云端数据库Xeround
这个是个好东西,免费的可以提供10M的mysql存储空间,注册申请啥的网上网上都写得蛮详细的了,这里就不做详细介绍了(其实只要英语不是太差,都看得懂的)。
我这里使用遇到了问题,链接数据库时,发现他提供的连接路径有两个,而且路径中有”.:”这样的字符,开始还想,难道这是使用了IPv6么?但是后来发现把那个点去掉,就可以在本地计算机上连接。
我创建的数据库使用的是Amazon Ec2上创建的数据库,它提供了两种连接字符串:
总体来说,思路是对的,看样子这个数据库的性能还不错。但是,在000webhost上,怎么写连接字符串都无法访问。百度上也曾有人问到这个问题,但没见到回答。如果哪位读者成功了,记得分享啊。
17MARCH |
|---|
做个视频网站,但并没有使用专门的流媒体服务器,所以要在apache上开个流式媒体输出。
这里的流式媒体输出跟专业的流媒体服务是两回事。这是在apache中添加一个模块,使得以视频流的方式访问,并不需要全部加载完视频后就可以播放。
这里只支持h264编码的mp4后缀。所需要启用的模块为:
mod_h264_streaming
sudo apt-get install apache2-threaded-dev
cd ~
wget http://h264.code-shop.com/download/apache_mod_h264_streaming-2.2.7.tar.gz
tar -zxvf apache_mod_h264_streaming-2.2.7.tar.gz
cd ~/mod_h264_streaming-2.2.7
./configure –with-apxs=`which apxs2`
make
sudo make install
注意:ubuntu 的apxs2 位于 /usr/bin/apxs2
LoadModule h264_streaming_module /usr/lib/apache2/modules/mod_h264_streaming.so
AddHandler h264-streaming.extensions .mp4
sudo /etc/init.d/apache2 restart
由于有些缩略图的文件名中包含”.mp4.”这样的部分,路径中出现这样的部分同样会被该模块捕获,但又处理不了,所以会报错。为此我们专门修改了文件的路径。
17MARCH |
|---|
前几天开了个Amazon aws的帐号,由于是第一次碰,一点点摸索着运行了一台Ubuntu 10.04的实例,遇到的问题记录下。
linux一般使用SSH客户端进行连接,所以必须要打开22端口,否则实例化后连不上,等于白白消耗你的信用卡。
一般启动一个LAMP的话,这几个端口是需要打开的:
21:ftp
22:ssh
80:http
mysql:3306
当然这些端口都可以修改的。但第一次一定要开的就是ssh的端口。
Amazon的帮助文档还是比较全面的,我这个不懂linux的人,也可以参照帮助文档连接,不过,文档中可是有错误的。比如点击连接计算机的时候,这里就只是说输入这样的命令就可以进去:
ssh -i xxx.pem root@your Public DNS
可是总也连不上去,提示:
Permission denied (publickey)
后来查了下,说linux第一次进去时,root是没启用的,要用ec2-user这样一个用户名。无奈,尝试了,还是不行,进不去啊。(后来查的说是在ami上自己安装的ubuntu使用ec2-user,我们是直接实例化的系统,不一样)
直到最后才搜索到ubuntu的登录又得用另外一个用户名:ubuntu。
其他关于ubuntu的设置,就不记录了,网上的资料比较多。
17MARCH |
|---|
min.us是一个非常不错的在线存储空间,有需要的可以去申请下。
min.us是美国一家在线文件存储提供商,直接注册提供10G的在线存储空间,比dropbox等要大很多,最吸引我的一点是其支持直接外链,很是方便。主要指标为:
注册提供10G空间。
可以通过邀请好友提升空间到50G,1G/好友。比dropbox的每好友250M强多了。
提供直接外链。
外链流量无限制。
最大单文件大小限制:空间总容量的20%,10G的空间,大概有200M
邀请链接:min.us
17MARCH |
|---|
上一篇写了网易邮箱的签名设置方法,普遍反馈太难了,为此,今天特制作一个邮箱设置的页面,用jquery实现的。
下面是一个邮箱模板:
点击该链接进去,可以看到一个签名模板,你可以点击其中的姓名,在弹出的输入框里写上你自己的姓名。如图:
填写你的名字,同理,你还可以在部门和电话的位置分别填写部门和电话,填写成功后检查是否正确,下面是我填写的一个示例:

这是后签名就已经设置好了。
复制这段文字,要从名字一直复制到最后的公司两个字,如图

右键选择复制,然后进入你的邮箱,在前面设置的位置,将复制的内容粘贴保存即可。如图:

这次还算简单吧。
11MARCH |
|---|
刚上网搜索了一下,跟这个同名的还有另一个,似乎也挺不错的,这个类库是github.com上的一个开源项目:PHPThumb
它是一个开源的图片缩略图类库,可以完成多种复杂的图片缩略图生成和现实,使用起来非常的方便。
这里有一篇关于其简单实用的说明,英文比较简单,就不翻译了:
这里有一个API列表:
不过非常可惜,API中并没有任何说明,只能对照其英文名称猜测其效果,当然即使猜对了,也要测试下看看是否是正确的。
这里我也写了个简单调用它的方法,不过我是把该图片保存成为文件了,basic-usage中的例子全是直接在页面中输出的:
require\_once 'phpthumb/ThumbLib.inc.php';
try {
$thumb = PhpThumbFactory::create($realpath);
} catch (Exception $e) {
// handle error here however you'd like
}
$thumb->adaptiveResize($width, $height);
$thumb->save($realpath . '.' . $width . 'x' . $height . '.png');
使用过程中,对几个API的理解逐渐加深,这里记录下:
resize ($maxWidth, $maxHeight)
resize方法是最常用的缩略图方法,它直接等比例将最大的边缩小到符合要求的高度/宽度,当图片宽高比与要求不一致时,将会有边显示空白。
adaptiveResize ($width, $height)
adaptiveResize方法是在一些特定情况下,不要求图片的完整,而要求显示特定宽高的最多内容。具体计算方式是:
当图片宽度大于所要求宽度,而高度一致时,取该高度下,从图片左右的中间去所要求的宽度;
当图片的高度大了并且宽度一致时,去该宽度下,图片上下居中位置取得所要求的高度;
当宽高都不正确时,先将图片缩放到所要求大小,再按前两条执行。
save ($fileName, $format = ‘GIF|JPG|PNG’)
当对图片做好处理后,可以用save方法,将图片保存为format中所指示的编码之一,如果未指定编码,将以原编码方式保存。$fileName是要保存的路径。
11MARCH |
|---|
本人电话号码使用已久,在各种网站上还注册时留过电话号码,今年明显感觉骚扰电话大量增加,为此一直想要更换个电话号码,但更换电话号码成本也很高,虽说大不了挨个通知,但实际上我也总收到朋友们更换号码的短信,实际上会存成两个号码,到最后很可能还是联系不上。所以一直想办法来解决这个问题。
对待骚扰电话,我向来是宁可错杀一千,也不漏过一个。不过我对电话倒不是很敏感,所有电话,往往反映很迟钝,以至于我媳妇总说给我配个电话多余,都接不通。
所以这种情况下,我实际上很少听到骚扰电话,只是每次拿起手机,就发现有几个未接电话。我的手机是早期的windows mobile的手机,之所以用这个手机,实际上比较熟悉这里的软件,值得一提的是一个叫做QCellCore的软件。它可以拦截骚扰电话(黑名单)、拦截垃圾短信,在拦截黑名单的时候,有三种模式:
无人接听;
空号;
已关机。
我一直使用空号模式,之前用这种方式成功阻击了一个保险公司的推销电话,后来再也不来骚扰了(估计已经从他们的数据库中删除了),现在的安卓智能手机似乎也有个黑名单的功能,不过好像没有这个功能强大。
现在我对于每个收到的未接的非通讯录号码,直接加入黑名单,无奈,现在的骚扰电话方式越来越多样,每次拨给你的号码都不一样,逼得我曾经想屏蔽整个150、151号段,最近是屏蔽所有来自东莞的电话(不知道为啥,东莞的骗子电话特别多)。当然,一个人的黑名单,对于骗子来说,他每购买使用一个电话号码,就足够骚扰几亿人了,成本太低了。
于是,就有这么一个想法:把电话黑名单列出来,供大家参考,同时,将来维护一个电话黑名单数据库,让这些电话只能打少量的骚扰,只要有人汇报黑名单,就让他上黑名单,或许将来我们可以做的跟对付垃圾评论一样吧。
下面是我的手机中的黑名单记录:
[items]
04008195095=&3&00:00:00&23:59:59
4008125860=英孚推销&3&00:00:00&23:59:59
010950950=中意人寿推销&3&00:00:00&23:59:59
15920739461=&3&00:00:00&23:59:59
02129986075=&3&00:00:00&23:59:59
15011661034=&3&00:00:00&23:59:59
14790689147=&3&00:00:00&23:59:59
15010216974=&3&00:00:00&23:59:59
13425734244=&3&00:00:00&23:59:59
15813627825=&3&00:00:00&23:59:59
13726327445=&3&00:00:00&23:59:59
13516514201=&3&00:00:00&23:59:59
13726312720=&3&00:00:00&23:59:59
13630005435=&3&00:00:00&23:59:59
15820671693=&3&00:00:00&23:59:59
15015517040=&3&00:00:00&23:59:59
15044953394=&3&00:00:00&23:59:59
15813658456=&3&00:00:00&23:59:59
15919054480=&3&00:00:00&23:59:59
15007577742=&3&00:00:00&23:59:59
15813653546=&3&00:00:00&23:59:59
15015624093=&3&00:00:00&23:59:59
15011653831=&3&00:00:00&23:59:59
后面的时间,是全天拦截,=号后面的,是记录的备注名称。
我正在找合适的空间,google code似乎被 墙 了,再找个合适的空间维护这个黑名单记录吧。
11MARCH |
|---|
本站点由于没有收入,只好使用免费的web空间000webhost,但免费的东西一定有各种各样的问题,前些天某个下午我自己访问站点的时候,竟然打不开网站,没有办法,向000webhost发邮件求助,不过我估计人家也爱答不理的,反正最后邮件也没回,但晚上看的时候,已经好了。不过由此开始,我就一直在想别的办法。
开始的时候,000webhost的ping值在300-400之间,而我的站点的ping值在500-600之间,着实无奈,直到发现有一个免费的cdn产品:CloudFlare。
CDN,全称Content Delivery Networks,即内容分发网络。其原理很简单,即用户访问时内容不是从原始服务器上获得的,而是从CDN智能解析的服务器上获取的,内容是缓存到CDN服务器上的。通常CDN服务商会有多个机房,而智能解析总是到访问最快的机房,所以通过CDN能显著地加快访问速度。另外还有一个好处是CDN加速可以减轻服务器的流量压力。
但是CDN也不是没有缺点,对于动态网页,由于有缓存,所以会造成延迟,很可能获取的的页面不是最新的,或者一些带有query的页面无法执行。但是对于静态内容效果还是十分显著的。
人家别人拿CDN是用来缓存静态内容的,我的网站要解决的问题却是整个网站的访问问题,也就是说,当000webhost临时出现故障的时候,我的站点还可以正常访问,抗几分钟算几分钟吧。当然,为此带来的短时间上的不同步,还是可以承受的。
本着试一试的态度,在CloudFlare上注册了一个帐号(注册使用的教程网上很多,这里就不写了),在dns上设置了nameserver的地址,就一直没转过去,后来发现从godaddy的dns管理中的的nameserver中添加CloudFlare的dnsserver是无效的,要在nameserver setting中做专门的设置,如图:
不过这个设置要24小时生效。
第二天,收到了CloudFlare的邮件,说设置已经成功了,马上登陆上去看了下,似乎快了点(感觉不明显)。然后ping下,效果如图:
这个ping值还是挺让人欣慰的啊。
不过使用它了之后,你修改了页面的样式或者添加了新的插件,是不能直接看到效果的,差不多要等个5分钟到10分钟吧。
最后呢,我还有个问题,某天我自己打开网站的时候,网站直接被转入一个什么安全的网站(man-hosting.com),说我的ip被两次发现有垃圾评论,不过输入一个什么验证码之后就没事了。我比较担心这个功能把许多来自中国的ip都给挡住了,而且本人也没有实地测试,不知道哪位大神能给予指点。
05MARCH |
|---|
今天又一次遇到PHP 的一个提醒:Notice: Trying to get property of non-object problem,这种错误很常见。上次出错,没查到原因,就简单的以这是一个Notice为由,没有做处理,今天有点时间,就查了查。
我这里实际是调用了一个zend的数据库访问的方法,使用了fetchAll方法,但由于数据库中没有该记录,所以返回的对象是null,所以我就判断对象是否为null:
if($obj==null){
...
}
这么写的结果,就是产生了上面那个notice,也真是奇怪,对象为null,竟然不能访问了?
翻查资料后,发现,判断是否为null,需要这么判断:
if (isset($obj)) {
echo "This var is set set so I will print.";
}
这个isset是做什么的呢?
isset函数是检测变量是否设置。
格式:bool isset ( mixed var [, mixed var [, …]] )
返回值:
若变量不存在则返回 FALSE
若变量存在且其值为NULL,也返回 FALSE
若变量存在且值不为NULL,则返回 TURE
同时检查多个变量时,每个单项都符合上一条要求时才返回 TRUE,否则结果为 FALSE
如果已经使用 unset() 释放了一个变量之后,它将不再是 isset()。若使用 isset() 测试一个被设置成 NULL 的变量,将返回 FALSE。同时要注意的是一个 NULL 字节(”\0”)并不等同于 PHP 的 NULL 常数。
警告: isset() 只能用于变量,因为传递任何其它参数都将造成解析错误。若想检测常量是否已设置,可使用 defined() 函数。
看来刚才我那边的判断所出的问题,就是因为这个“是一个 NULL 字节(”\0”)并不等同于 PHP 的 NULL 常数”。
04MARCH |
|---|
使用chrome浏览器或者safari浏览器,经常会发现自己的textarea很奇怪,可以拖动放大缩小,而且还有个奇怪的边。最初我们遇到这类问题的时候,直接给设计交代说那是浏览器的特性,俺们管不着,结果人家拿来google做例子,我们做开发的无话可说。但美工也不知道怎么做,只好开发人员硬着头皮去比较google的源码去研究。也没啥好方法,一个一个尝试呗。
其实找到以后也很简单,这里记录下,免得忘记:
其一、页面输入框(input 标签)聚焦高亮。

屏蔽input聚焦高亮效果的样式:
input {outline: none;}
textarea {outline: none;}
**其二、文本框(textarea 标签)缩放功能。

**
/\*css2.0\*/
textarea {width: 400px;max-width: 400px;height: 400px;max-height: 400px;}
/\*css3.0\*/
texearea {resize: none;}
可能将来的网页会偏向简洁设计,所以webkit开发团队已经不考虑当设计的输入框是圆角的时候输入框和文本框出现的问题,所以当你隐藏了点击前的样式而你如果忘记去掉了聚焦后的webkit赋予的默认样式,webkit引擎浏览器的就会出现问题。
如图:
如果考虑兼容webkit核心的浏览器,建议设计输入框或者文本框的时候尽量保持原始的样式,如果设计做了很好看的圆角背影的效果,建议前端最好将下边的代码加到base.css里边去,避免用webkit浏览器看出现不同的效果。
04MARCH |
|---|
最近的项目中要自定义滚动条,之前这类的工作也做过,但是这次项目紧啊,看到这个设计一直在发愁。好在这次项目只需要兼容最新的chrome浏览器就可以,所以直接查找了下Webkit下滚动条的css写法,感觉不错。记录下来。
:window-inactive – 用于所有的滚动条轨道,指示应用滚动条的某个页面容器(元素)是否当前被激活。(在webkit最近的版本中,该伪类也可以用于::selection伪元素。webkit团队有计划扩展它并推动成为一个标准的伪类)。
28FEBRUARY |
|---|
昨天登录wordpress后台,发现提示有个插件有更新,本人向来喜欢最新的东西,有更新自然就忍不住要更新,就随手点了更新,结果不知咋回事更新了半天也没更新完,无奈刷新页面,网站却提示“正在执行例行维护,请一分钟后回来”。
有问题要解决啊,搜索下吧,发现月光也遇到过类似的问题,不过他那个是更新主题的时候出的问题,解决办法就是删除站点根目录下的“.maintenance”的文件,我就照做了,但是问题并没有解决,而是直接报错了,报错的路径是所更新的插件的路径。
之前我也更新过其他插件,而且站点的所有插件都是在线安装的,并没有出过什么问题,所以我认为程序并没有出错,很有可能是更新过程中,某些文件写了一半,却因为某些原因(比如虚拟主机临时故障)中断了,造成插件加载失败。于是将该插件的目录删除,并手工上传了一份最新的插件进去,结果就好了。
同时,这里贴一下网上其他朋友遇到问题时的解决办法:
首先,打开你的web文件,找到 /wp_admin/includes/class-wp-filesystem-direct.php 文件,修改它,没有权限请修改文件夹属性为777,然后去修改function mkdir这个函数,用下面的代码替换原有的代码:
function mkdir($path,$chmod=false,$chown=false,$chgrp=false){
if( ! $chmod)
$chmod = $this->permission;
// Fix “Cound not create directory” problem
if(ini\_get(‘safe\_mode’) && substr($path, -1) == ‘/’)
{
$path = substr($path, 0, -1);
}
// Fix “Cound not create directory” problem
if ( ! @mkdir($path) )
return false;
$this->chmod($path, $chmod);
if ( $chown )
$this->chown($path, $chown);
if ( $chgrp )
$this->chgrp($path, $chgrp);
return true;
}
22FEBRUARY |
|---|
html5的标准还没有最终确定,但关于视频播放器的标准基本上被卡住了。mazilla和Opera由于没有H264的版权,坚持不支持H264标准的mp4格式视频,只支持ogg和webm两种格式,其中webm是谷歌去年提供的一个新的标准,并将该格式开源;ogg似乎是早期的一种手机视频的格式,这个了解不多。但也许mp4视频效果比较好,现在应用比较广。而IE9、safari、chrome三大厂商不担心版权问题,都提供了对H264的支持。所以当前兼容所有支持html5的视频播放方案差不错都是这样:
对同一视频提供三种格式,即mp4、webm、ogg,但其实仔细想想,只需要提供两种:mp4和webm,就可以了。
videojs就提供了这样一套解决方案,他是一个兼容html5的视频播放工具,早期版本兼容所有浏览器,方法是:提供三个后缀名的视频,并在不支持html5的浏览器下生成一个flash的版本。
最新的3.1.0版本优化了之前的做法,只需要提供两个格式的视频,页面制作起来更加方便,只有两步走:
1、引用脚本,videojs很为你着想,直接cdn了,你都不需要下载这些代码放入自己的网站
2、页面中加入一个html5的video标签,要这么加:
其中post就是视频的缩略图,那俩source一个指向mp4视频,一个指向webm视频,在页面加载过程中,video.js会判断浏览器支持哪个格式视频,会自动加载可播放的视频。
简单吧!
进阶:使用api
获取对象:
var myPlayer = _V_(“my_video_1”);
后面那个就是就是video标签的id值,这是myPlayer就是播放器对象了。
播放:
myPlayer.play();
暂停:
myPlayer.pause();
获取播放进度:
var whereYouAt = myPlayer.currentTime();
设置播放进度:
myPlayer.currentTime(120);
视频持续时间,加载完成视频才可以知道视频时长,且在flash情况下无效
var howLongIsThis = myPlayer.duration();
缓冲,就是返回下载了多少
var whatHasBeenBuffered = myPlayer.buffered();
百分比的缓冲
var howMuchIsDownloaded = myPlayer.bufferedPercent();
声音大小(0-1之间)
var howLoudIsIt = myPlayer.volume();
设置声音大小
myPlayer.volume(0.5);
取得视频的宽度
var howWideIsIt = myPlayer.width();
设置宽度:
myPlayer.width(640);
获取高度
var howTallIsIt = myPlayer.height();
设置高度:
myPlayer.height(480);
一步到位的设置大小:
myPlayer.size(640,480);
全屏
myPlayer.enterFullScreen();
离开全屏
myPlayer.enterFullScreen();
添加事件
var myFunc = function(){
// Do something when the event is fired
};
myPlayer.addEvent(“eventName”, myFunc);
删除事件
myPlayer.removeEvent(“eventName”, myFunc);
所有事件列表:
Name
Description
loadstart
开始加载
play
播放.
pause
暂停.
timeupdate
Fired when the current playback position has changed. During playback this is fired every 15-250 milliseconds, depnding on the playback technology in use.就是时间变化吧,与具体的播放技术有关,不同浏览器及格式不同。
ended
播放结束
durationchange
Fired when the duration of the media resource is changed, or known for the first time.下载进度变化吧。
progress
进度变化.
resize
大小修改.
volumechange
音量变化.
error
出错.
虽然文章说明在不支持html5的情况下,会以flash播放,但在支持html5的firefox下播放mp4时,却遇到很大的困难,虽然调用了flash,但一直无法播放(不过我也一直怀疑我的firefox下的flash有问题,不知道是不是真的)。不过如果你听从videojs的建议,放两个格式的视频,就不会有这个问题了。
最后,这么好使的脚本,是免费的么?
经查,该脚本遵循LGPLv3协议,听着协议又头大了?这里讲个常识:
如果你的项目中要使用开源的代码,而你的项目又不开源,可选的开源协议有:BSD、MIT、LGPL、Apache Licence 2.0。其中前两种甚至可以修改源代码,但一定要标注版权;后两种可以随便用,但是不要随便改,呵呵。所以你要使用这个脚本的话,是完全可以的,api也这么全,至于css,并不受版权保护,你可以根据需要改变为你要的样式即可。
关于开源协议,如果有朋友有兴趣,可以留言讨论,我将来会单独写一篇关于开源协议使用分析的文章。
另:附件中提供了3.1.0版本和2.0.2版本,我们并没有将2.0.2版本的用法,但压缩包里面有demo,使用的时候要调用下:VideoJS.setupAllWhenReady();
另外video的写法中还有专门针对flash的写法,当然你也可以用这个插件实现纯粹的flash播放(只写flash那部分就好,可以保证统一的浏览效果,不过ios的浏览器不兼容flash,这就要你自己进行判断来处理了)。
21FEBRUARY |
|---|
用了没多久的sae,sina竟然废弃之前的赠送云豆的约定,不再赠送云豆,摆明了要收费啊。当然国内干什么都比较困难,大家生存压力都很大,sina也要赚钱的嘛,咱们理解,也只能无奈,我想国内免费的云,可能早晚都是这样一个结局吧。所以把空间转到了000webhost的这个“免费的胜过收费的”空间来了。这里访问起来没sinaapp的快,但似乎还没有其他的问题,而且更好的消息是,这是咱自己的域名了。不像在sina,绑个域名还要走很多手续,不就是个个人博客么,有必要那么麻烦么?
现在也挺好,Jason’s blog也已经改头换面,换成了Jason and chris 的空间了,所以以后的定位也不仅限于技术文章了,是两个人的空间了,而且这个域名也有些纪念意义,在此,向chris表示感谢。当然,网站的主题思想仍然是基于开放的一种心态,跟大家分享各方面的经验,以达到共同提高的目的。
同时,对原网站的老客户表达下歉意,没有提前转告大家,直接给原来sae的站点配置了个跳转,转这里了,大家访问的时候不要奇怪。但是各位的帐号并没有随着转过来,之前有朋友注册了多个帐号忘了登录名的,这次要重新注册了,不过还好,我们仍然启用了社交网站登录链接,使用微博啊、qq啊注册登录都很方便,而且也更加安全。
另外,发现使用000webhost,在上传代码的时候速度很慢,而且时常短线,网站却是相对稳定。本人使用较少,不得其解,若有知道咋回事的朋友,望留言告知,谢谢。
20JANUARY |
|---|
这里简单记录一下过程,其实大多数的说明在网上都有详细的说明,这里只详细记录遇到的过程。
1、在IIS中创建证书申请,注意一般加密的位长要设置为2048位。写入一个txt文件,可以生成一段密文,类似下面这段:
-—-BEGIN NEW CERTIFICATE REQUEST—–
…….
-—-END NEW CERTIFICATE REQUEST—–
2、把生成的证书文件提交给认证厂商。
3、对方会返回一个加密认证后的文本,将该文本保存为.cer文件,导入到iis中(使用完成证书申请)。
4、windows azure中的源代码中,找到Roles文件夹,选中.WebPortal文件,右键属性中,找到Certificates,点击Add Certificate
这是后会增加一行,前面的都不要管,只是在Thumbprint项中,点击后面的选择,会自动找到本机安装的所有证书,选择该证书即可。
5、在iis中将该证书导入到windows azure中的对应的证书目录中。然后上传编译好的代码。
注意事项,申请证书的计算机与完成认证过程的计算机需要是同一台,而且,申请和完成期间最好计算机不要做更新。我的电脑恰巧在第二天早上做了关于证书方面的更新,所以发生了证书完成后无效的问题,也就是说密钥被更新给弄乱了(具体情况不了解,反正是不能用了)。
解决办法就是找到证书的指纹,然后运行命令:
certutil -repairstore my “THUMBPRINT_OF_CERTIFICATE”
其中THUMBPRINT_OF_CERTIFICATE要替换成对应的指纹。
然后就可以了。
要回家了,先发这些吧,有时间再整理。
28DECEMBER |
|---|
转自zendchina,可查看原文,之所以转载,是因为网上的各种资料都不太全,本文很全面。本人在该问题上也遇到很多困难,终于从本文获得启发。希望遇到同样问题的朋友们能尽快解决该问题。同时声明:网传的windows下不能使用zend调试是错的,很难用也是错的,只是不得法而已,不要以讹传讹。我自己尝试的辛苦,希望朋友们能少走些弯路。
下载xdebug,看清版本,很重要,这里使用php_xdebug-2.0.5-5.2.dll,符件中有;
必须以Zend方式加载,见php.ini中配置。配置D:EasyPHPPHPphp.ini,先把optimization注释掉使用”;”
如下:
;[Zend]
;zend_optimizer.optimization_level=1023
;zend_extension_ts=“../Zend/ZendExtensionManager.dll”
;zend_extension_manager.optimizer_ts=“../Zend/Optimizer-3.3.0”
;zend_extension_ts=“D:/EasyPHP/PHP/ext/ZendDebugger.dll”
;zend_debugger.allow_hosts=127.0.0.1⁄32
;zend_debugger.expose_remotely=always
;extension=php_xdebug-2.0.5-5.2.dll
[Xdebug]
zend_extension_ts=D:/EasyPHP/PHP/ext/php_xdebug-2.0.5-5.2.dll
xdebug.profiler_enable=on
xdebug.trace_output_dir=“D:/EasyPHP/xdebug”
xdebug.profiler_output_dir=“D:/EasyPHP/xdebug”
xdebug.remote_enable=On
xdebug.remote_host=“localhost”
xdebug.remote_port=19000
xdebug.remote_handler=“dbgp”
请修改端口为19000防止端口被占用,修改web browse,如下图
不要使用FF会报下面的错误:
waiting for XDebug seession…就不动了,选择IE正常
![]()
然后再按下面配置:
**另外:
**
Zend Studio for Eclipse开启XDebug的方法:
- 6.0.0pluginscom.zend.php_6.0.0.v20080107plugin_customization.ini
将这行org.eclipse.ui.workbench/UIActivities.com.zend.php.debug.ui.XDebugActivity=false
改成true,保存后,重新启动Zend Studio 7,php debug里面就可以选择Xdebug进行调试了。
选择要debugger的web页面
另外WinCacheGrind的使用参考:http://hi.baidu.com/lostdays/blog/item/c2ef51a920c62ff81f17a2f4.html
我的phpinfo信息如下,方便出现问题对比版本:
System
Windows NT WV 5.1 build 2600
Build Date
Nov 8 2007 23:18:08
Configure Command
cscript /nologo configure.js “–enable-snapshot-build” “–with-gd=shared”
Server API
Apache 2.0 Handler
Virtual Directory Support
enabled
Configuration File (php.ini) Path
C:WINDOWS
Loaded Configuration File
D:EasyPHPPHPphp.ini
PHP API
20041225
PHP Extension
20060613
Zend Extension
220060519
Debug Build
no
Thread Safety
enabled
Zend Memory Manager
enabled
IPv6 Support
enabled
Registered PHP Streams
php, file, data, http, ftp, compress.zlib, compress.bzip2, zip
Registered Stream Socket Transports
tcp, udp
Registered Stream Filters
convert.iconv.*, string.rot13, string.toupper, string.tolower, string.strip_tags, convert.*, consumed, zlib.*, bzip2.*
27DECEMBER |
|---|
@access
使用范围:class,function,var,define,module
该标记用于指明关键字的存取权限:private、public或proteced
@author
指明作者
@copyright
使用范围:class,function,var,define,module,use
指明版权信息
@deprecated
使用范围:class,function,var,define,module,constent,global,include
指明不用或者废弃的关键字
@example
该标记用于解析一段文件内容,并将他们高亮显示。Phpdoc会试图从该标记给的文件路径中读取文件内容
@const
使用范围:define
用来指明php中define的常量
@final
使用范围:class,function,var
指明关键字是一个最终的类、方法、属性,禁止派生、修改。
@filesource
和example类似,只不过该标记将直接读取当前解析的php文件的内容并显示。
@global
指明在此函数中引用的全局变量
@ingore
用于在文档中忽略指定的关键字
@license
相当于html标签中的,首先是URL,接着是要显示的内容
例如百度
可以写作 @license http://www.baidu.com 百度
@link
类似于license
但还可以通过link指到文档中的任何一个关键字
@name
为关键字指定一个别名。
@package
使用范围:页面级别的-> define,function,include
类级别的->class,var,methods
用于逻辑上将一个或几个关键字分到一组。
@abstrcut
说明当前类是一个抽象类
@param
指明一个函数的参数
@return
指明一个方法或函数的返回指
@static
指明关建字是静态的。
@var
指明变量类型
@version
指明版本信息
@todo
指明应该改进或没有实现的地方
@throws
指明此函数可能抛出的错误异常,极其发生的情况
普通的文档标记标记必须在每行的开头以@标记,除此之外,还有一种标记叫做inline tag,用{@}表示,具体包括以下几种:
{@link}
用法同@link
{@source}
显示一段函数或方法的内容
a.注释必须是
/**
* 注释内容
*/
的形式
b.对于引用了全局变量的函数,必须使用glboal标记。
c.对于变量,必须用var标记其类型(int,string,bool…)
d.函数必须通过param和return标记指明其参数和返回值
e.对于出现两次或两次以上的关键字,要通过ingore忽略掉多余的,只保留一个即可
f.调用了其他函数或类的地方,要使用link或其他标记链接到相应的部分,便于文档的阅读。
g.必要的地方使用非文档性注释,提高代码易读性。
h.描述性内容尽量简明扼要,尽可能使用短语而非句子。
i.全局变量,静态变量和常量必须用相应标记说明
<?php
/\*\*
\* Sample File 2, phpDocumentor Quickstart
\*
\* This file demonstrates the rich information that can be included in
\* in-code documentation through DocBlocks and tags.
\* @author Greg Beaver <[email protected]>
\* @version 1.0
\* @package sample
\*/
//PHP code
/\*\*
\* A sample function docblock
\* @global string document the fact that this function uses $\_myvar
\* @staticvar integer $staticvar this is actually what is returned
\* @param string $param1 name to declare
\* @param string $param2 value of the name
\* @return integer
\*/
function firstFunc($param1, $param2 = 'optional') {
static $staticvar = 7;
global $\_myvar;
return $staticvar;
}
20DECEMBER |
|---|
ffmpeg简介
FFmpeg 是一款跨平台的,对视频、音频进行录制、转换、播放的命令行形式软件,它使用的是 libavcodec 编解码器。FFmpeg 官方网站是 http://ffmpeg.org/ 。
官方网站提供的是未编译的 FFmpeg 源代码。有兴趣的朋友可以自己下载源代码编译。一般我们都会偷懒,去下载现成的。
php_ffmpeg资源
源码及编译方式见:
各种win32版本的下载:
配置方法
1,解压
2,将 php_ffmpeg.dll 文件 复制到 php 的 extension 目录
3,将其他所有文件复制到 Windows 的SysWOW64目录(注意:32位系统复制到windows的 system32目录里)
4,在 php.ini 文件中,增加一句: extension=php_ffmpeg.dll
5,重启 apache
可以用 phpinfo(); 函数看安装结果。如果出现 ffmpeg support enable 字样,说明安装成功。
刚开始的时候,把相关文件拷贝到了system32中,总出错,提示“unable to load dynamic library‘ xx/xx/php_ffmpeg.dll’”,后来感觉可能是版本不对,想找到相应的64位的php_ffmpeg.dll文件,走了很多弯路,最后看到转载的这篇文章,才成功。
原创版本没有找到,网上传播很广的都是改过的转载,但链接都没有了。 我这里加入了两个链接,在此向原作者致谢,同时未经许可,对文字做了修改,请谅解。另外把我自己下载的版本提供给大家下载。我的是php5.3版本的:
20DECEMBER |
|---|
很久之前,曾经用过一个代码编辑器,具体名字叫什么不记得了,但对我印象最深的是他的本地修改历史记录。我一直在意代码的历史记录,以至于在本地搞个vss来保存不同的版本。现在用的是svn。但总觉得许多时候本地修改记录本地来记录更合适一些。今天使用Eclipse时突然发现有个本地历史的功能,原来我想要的功能在Eclipse中已经包含了。
首先我们可以看看关于本地历史的设置位置:
窗口->首选项->常用->工作空间->本地历史,默认设置如下图所示:

这三个项分别是:
我们可以根据自己的情况进行设置。然后就可以从文件列表中直接打开本地历史记录了,还集成了比较功能呢。
12DECEMBER |
|---|
本文记录youtube 的几个常用api,并使用其中一个介绍其使用方式。
首先,我们来看看这几个api:
http://gdata.youtube.com/feeds/users/username/uploads
\- videos uploaded by username
http://gdata.youtube.com/feeds/users/username/favorites
\- videos bookmarked by username
http://gdata.youtube.com/feeds/users/username/playlists
\- playlists created by username
http://gdata.youtube.com/feeds/users/username/subscriptions
\- username's subscriptions
就是说,如果你要显示username这个用户(不知道有没有这个用户)的视频,可以访问
http://gdata.youtube.com/feeds/users/username/uploads
这个路径来获取数据。其他的就不用讲了。
有趣的是,google给这四个api设置了差不多的参数,常用的有:
?max-results=50:
最多要返回的记录数(默认情况下是25),不过要记住,youtube每次访问可返回的最大值恰好也是50,如果想要获得更多内容,可以多请求几次,哈哈。
?alt=rss or ?alt=json:
alt参数用于设置返回内容的格式。
?vq=query:
用于筛选结果,会从 metadata (title, tags, description)中进行检索,比如“google+ maps”,就是搜索google map。
?orderby={updated, viewCount, rating, relevance}:
排序,示例中以updated为第一排序,relevanced作为最后一个排序。
使用jquery的AJAX进行访问也很简单:
$.ajax({
type: "GET",
url: "http://gdata.youtube.com/feeds/users/username/uploads?alt=json”,
cache: false,
dataType: 'jsonp',
success: function (data) {
showMyVideos(data);
},
error: function (XMLHttpRequest, textStatus, errorThrown, data) {
alert("Not able to fetch the data due to feed unavailability!!!");
}
});
这里的showMyVideos(data)的方法就没必要写了,实际上我们只要随便找之前用到的播放器把内容显示出来就行了。而data结构还是比较复杂的,这里简单说一下。
data实际是个数组,针对其中的每一个entry(=entries[i]),我们常用的内容有:
entry.title.$t:视频的标题
entries[i].media$group.media$thumbnail[0].url:视频的缩略图
entries[i].media$group.media$content[0].url:视频的实际地址,这里content[0]实际是第一视频的地址,这个是flv格式的视频,如果不支持flash,可以取第二个。
entry.published.$t:这是发布时间。
entry.yt$statistics.viewCount:这个是被浏览次数。
似乎没有直接提供视频的id,但是我们可以通过字符串算出来:
entries[i].media$group.media$content[0].url.substr(playerUrl.indexOf(‘v/’) + 2, 11)
好了,基本的东西就这些了,其实也可以自己在firebug中输出一下,然后对比一下就知道怎么回事了,或者输出rss自己对比xml。还是很简单的。
10DECEMBER |
|---|
关于facebook的项目停了一段时间,最近突然又来了一个这类的项目,是要制作一个专页上的链接。话说创建专页不难啊,但是由于不懂,而且时间比较紧,遇到不少问题,至今还有些搞不明白。幸得本站用户KIVU的鼎力帮助,项目算顺利完成了。这里把制作过程记录下来,分享给大家,以减少大家走的弯路。
首先,专页是facebook专门推出用来展示个人或者企业的一个工具,创建专页在facebook上面有专门的帮助文档,很详细(不过也很多,说实话,我没有详细去看,创建专页自己试试就可以创建出来),url如下:
[http://www.facebook.com/help/?faq=104002523024878&ref\_query=crea](http://www.facebook.com/help/?faq=104002523024878&ref_query=crea)
我们可以看到很多公司做了挺漂亮的专页,但我们创建的专页却仍然只是由涂鸦墙、视频等信息组成的页面,我们自己的页面怎么实现呢?或者说,在左侧上部的链接中,加入我们自己制作的页面。
这里要感谢本站用户KIVU,一句话说的很明白,左侧的链接都是应用,也就是facebook app。其中涂鸦墙等几个是facebook自己的app,也有几个是必须显示不可删除的,添加怎么办呢?Facebook似乎没有提供一个地方要指定加入一个应用的地方。感觉把专页中的设置尝试遍了,也没找到对应的功能。
KIVU在百忙之中给予支持:
1、创建新应用(前面我们有文章提到过),在编辑应用基本信息的界面左下角,找到“View App Profile Page”这个链接,现在即使是简体中文的界面,这个链接的文字也没有翻译,应该是查看我的应用的专页吧。
2、进入该页面以后(url如http://www.facebook.com/apps/application.php?id=XXX),可以在其左下角找到“加入到我的专业链接”,其实这是一个按钮,点击后会弹出你所有的可以被加入的专页(包括其他应用)。如图:
找到你想加入的页面,点 add to page就可以了。至于页面中的内容,就是你的app中指定的页面的内容了。还算简单吧。
3、往往你制作好页面以后,页面高度没有按照自己的页面来,就是说即使你设置了高度自适应(那似乎只对应用页面有效,对专业中的页面无效),页面中还是出现了滚动条,其实也很简单,我们只需要写这样一段代码就可以了
FB.Canvas.setSize({ width: 520, height: 2400 });
这里要记住啊,width:520基本上是必须这么写的,因为专页中的页正好是520的宽度。很有爱啊。对了别忘了,要加入facebook js api的引用啊:
<script src="http://connect.facebook.net/en\_US/all.js#xfbml=1"></script>
还有一点要说明,刚创建的应用好像点查看我的应用专页时会直接进入应用详细页,要等几个。我昨天创建的应用就这样,害得我好苦,不过今天都可以点进去了。所以有些东西是不可仓促行事的,必要的时间是需要留出来的。
如果你的页面是纯静态的话,还有一种实现方式,就是使用别人做的应用来帮忙实现。当然了,这是其他人做好了的应用,facebook有好多此类的应用。比如我们在facebook中搜索FBML:
可以看到有许多做静态FBML的应用,随便找一个把它加入到你的专页中去,然后在编辑专业的应用程序栏找到该应用,如图:
然后点击访问应用程序,可以到编辑FBML页面的地方,如图:
如果你的页面足够简单,使用该方法是可以非常轻易制作一个应用的页面,而且不需要自己准备服务器,够爽吧?
当然,如果你的页面中脚本比较多,可能并不适合你,因为需要学习许多FBML中js的用法,我还没用过,也还不清楚是否麻烦。
好了,本文通过两种方式实现了专页中的个性页面,实际上实现过程中还遇到了rss、youtube和twitter中的问题,我会在后面的文章中继续与大家分享。
22NOVEMBER |
|---|
今天用到了一个新的小功能,是关于headLink和headScript的,对于新手来说需要学习的东西很多啊。经查资料找到用法,这里记录下:
headLink用于对css文件的引用,而headScript用于js脚本文件的引用。可以直接在模板中使用,代码如下:
/application/views/layouts/layout.phtml
headLink()->appendStylesheet($this->baseUrl() . '/css/global.css')
->headLink()->prependStylesheet($this->baseUrl() . '/css/resets.css')
?>
headLink() ?>
也可以在代码中添加,比如这里在Bootstrap.php中进行了添加:
/application/Bootstrap.php (function not complete)
function \_initView()
{
$view->headLink()->prependStylesheet('css/resets.css')
->headLink()->appendStylesheet('css/global.css')
->headLink()->appendStylesheet('css/forms.css')
->headLink()->appendStylesheet('css/pages.css');
}
headScript使用方法一致,不过方法名改了,代码如下:
/application/Bootstrap.php (function not complete)
function \_initView()
{
$view->headScript()->prependFile('js/jquery-1.3.2.min.js')
->headScript()->appendFile('js/jquery-ui-1.7.min.js')
->headScript()->appendFile('js/jquery.easing.1.3.js')
->headScript()->appendFile('js/docready.js');
}
我们举的例子都是在Bootstrap.php中使用的例子,再举个控制器中使用的例子:
public function init ()
{
$this->\_helper->layout()->setLayout("default");
$this->view->headLink()->appendStylesheet('/public/css/commshot.css');
}
这个init是控制器的初始化方法。
10NOVEMBER |
|---|
今天早上看到的消息,说微软近日表示在Sliverlight的版本5正式发布后停止开发,虽然我觉得这是早晚的事,但现在听到这个消息还是比较惊讶,比原来预计的似乎早了很久。
记得09年初的时候,我对Sliverlight做过几天的研究。因为那时候Sliverlight炒的很火,话说美国总统奥巴马的竞选演说,使用的就是Sliverlight发布的视频。可谓盛极一时。但我当时的感觉总觉得微软是在于ADOBE抢一块蛋糕,或者是搅Adobe的局。因为大家都很清楚,HTML5标准一旦确定,可能Sliverlight和Flash都不存在了(当时我谈到这个想法时,记得还被朋友们嘲笑过)。
当时业界对HTML5标准确定的时间估计在2022年(记忆中),按照那个推算,至少要到2015年以后,HTML5才会逐渐被各家浏览器支持,也就是说,Flash和Silverlight可能要支撑到2020年以后才会逐步消失。这些是当年的估计了。但Iphone的出现与移动智能设备的迅速发展,大大加速了这一进程,去年已经有很多有远见的公司大量招聘HTML5的开发人员了。
此后Sliverlight版本升级到4.0后,又在很多方面接触Silverlight,比如WP7和Xbox,我感觉他的类库比较乱,WP7一套、7.1还有一套、PC上又有一套,而且互不兼容,各自为政——虽然都叫Silverlight。而就在不久前,微软还宣布要用Silverlight来统一这几大平台(Windows、Windows Phone和Xbox),要让他们兼容么?但微软还在各个方面独立发展,版本分裂严重。
今天早上看到的第一个消息是Adobe宣布移动版的Flash不再研发,后面就看到了微软的这则消息。难道这两家商量好的?Adobe的这个决定不难理解,他买下了PhoneGap(这个恰巧昨天的一篇文章就提到,呵呵),势必要推进在HTML5方面的开发标准,放弃移动版Flash甚至最终整个放弃Flash,完全推进HTML5。而微软呢?Silverlight一出生,就是冲着Flash来的,竞争对手要放弃了,他也没啥继续存在的必要了?
我觉得没这么简单。微软下一步一定要在手机和平板上有所作为,就要找到新的竞争目标,而现在市场占有率最高的手机系统是安卓。安卓是谷歌“无心插柳”的结果,这一结果使得谷歌不太好轻易放弃既得利益。我们知道,谷歌还有个系统叫做ChomeOS,那个才是给未来的HTML5市场准备的;安卓虽也支持HTML5,毕竟不是主要的优势。大力推广HTML5对ChomeOS有利但对安卓不利(影响原生App应该的开发者),安卓束缚了谷歌的手脚,使其在既得利益面前左右为难。微软的这项决策,即决定自己未来的方向,又笼络大批支持HTML5的开发者,还对谷歌来这么这么一手,可谓可谓一箭多雕。
闲话苹果,之前有看到分析称,推进HTML5对苹果有利,不推进也对苹果有利,苹果真是牛啊。
09NOVEMBER |
|---|
前两个月还在感叹WebOS的垮掉,现在突然发现HTML5比预期的时间提前来到了。难道真的是惠普的WebOS就是死在早了那么一两年么?那么真如现在所说的Oracle要低价收购的话,那真是在最合适的时间捡到一个大便宜啊。好了,废话不多说,今天推荐10款不错的HTML5的开发框架。
Sencha Touch是专门为移动设备开发应用的Javascrt框架。通过Sencha Touch你可以创建非常像native app的web app,用户界面组件和数据管理全部基于HTML5和CSS3的web标准,全面兼容Android和Apple iOS。这个是大名鼎鼎的EXT框架的公司整合其另外两个框架发展而来的,自然首先推荐。
今天 Sencha Touch 发布 2.0 的首个开发者预览版。这在 Sencha Touch 1.0 发布的一年之后。新版本主要工作侧重于性能提升、易用以及内置打包方面。
相关连接:
下载 Touch
Touch 文档
下载 SDK 工具 (Mac-only)
下图是 ST 2.0 和 1.0 在启动时间的比较:
另外 2.0 版本在文档方面也做了很多改进,查看 2.0 文档。
PhoneGap是一款HTML5平台,通过它,开发商可以使用HTML、CSS及JavaScript来开发本地移动应用程序。因此,目前开发商可以只 编写一次应用程序,然后在6个主要的移动平台和应用程序商店(app store)里进行发布,这些移动平台和应用程序商店包括:iOS、Android、BlackBerry、webOS、bada以及Symbian。上个月该公司已经被Adobe公司协议收购,有了强大的后台支撑。(话说Adobe也通过收购在HTML5开发标准上占了一席之地啊)
下面是一些使用该框架的代码示例:
获取地理位置:
//GAP will invoke this function once it has the location function gotLocation(lat,lon){ $('lat').innerHTML = "latitude: " + lat; $('lon').innerHTML = "longitude: " + lon; }
获取摄像头捕捉的照片
function takePhoto(){ var photo = gap:takePhoto(); return photo; }
honeGap Mobile App XDK,可以让开发人员创建、模拟和测试PhoneGap项目。该工具是一个集成开发环境(IDE),提供了用于创建HTML5和PhoneGap应用的全套开发工具。AppMobi称,新的XDK为PhoneGap项目开发提供了一个直观的开发环境。
备注: phoneGap已出现就有很大反响,现在国内有个http://phonegap.cn爱好者网站,里面有中文的使用介绍。
ChocolateChip-UI 是一个手机移动 Web 开发框架,使用 HTML5, WAML, CSS 和 JavaScript,基于 ChocolateChip JavaScript 库,包含新的 ChUI.JS 和 ChUI.css.可以在没有什么设计的情况下,开发出类似原生应用的界面来。
Joshfire是一个支持多种设备的开发框架,仅采用HTML5和JavaScript等标准,可以帮助开发者迅速开发本地专用的网络应用,用于浏览器、Node.JS、桌面电脑、智能手机、智能电视以及联网设备。
不管你针对哪种设备开发,Joshfire可以让你「一次开发,多处兼容」,自动兼容手机、平板电脑、电视等设备。
如果你想发布新闻、图片、音乐、视频,Joshfire框架可以帮你轻松开发全功能内容应用。
忘掉那些为触控板、电视遥控器、键盘、鼠标、Kinect准备的专门设计吧,你只需要描述你的应用,其他的交给Joshfire。
下载地址:http://framework.joshfire.com/download
它是针对移动和触摸设备的JavaScript 框架。DHTMLX Touch基于HTML5,创建移动web应用。它不只是一组UI 小工具,而是一个完整的框架,可以针对移动和触摸设备创建跨平台的web应用。它兼容主流的web浏览器,用DHTMLX Touch创建的应用,可以在iPad、iPhone、Android智能手机等上面运行流畅。
Jo这个框架可用于开发那支持HTML5的移动设备,如iOS,webOS, Android和Chrome OS等平台。
它拥有标准,类原生的UI元素比如用于屏幕登录的Web表单控件,还有弹出小部件可用于在用户点击界面时提供一些额外的信息。
可以查看其网站提供的示例页面,它例子展示了在多种移动设备平上的Jo应用情况。
http://joapp.com/downloads.html
mobl 其实是一个新的开源的编程语言,主要用于加速手机应用的开发,mobl 可方便构建手机 web 应用程序,包括 iOS、Android 和其他支持 HTML5 技术的手机。
主要特点:
Statically typed language, enabling great IDE support and as-you-type error detection, while reducing the amount of typing other statically typed languages require, through type inference.
Scripting language syntax similar to Javascript.
Declarative domain-specific language (DSL) for defining user interfaces.
Declarative concise DSL for defining data models. Data is stored on the device. No server back-end required.
Easy access to existing “native” Javascript libraries and widgets.
Compiler generates static Javascript and HTML files, ready to be deployed to any web host and to be cached on the device, to enable offline web applications.
SproutCore是一款HTML5的应用框架
Perkins 是一个 HTML5/CSS3 框架,主要为简化开发者和设计师的工具。使用一个基础的 HTML5 模板,包含多数所支持的标签以及一些 CSS 样式,便于创建诸如导航、圆角、渐进等效果。遵循MIT协议。
http://code.google.com/p/perkins-less/
一家名叫AppMobi的小公司,正致力一项工作,使开发者能创建可深入移动设备硬件或操作系统功能的HTML5应用, 如重力感应、加速计、GPS、相机、声音与震动、文件系统等,InfoWorld报道。‘其iOS MobiUs浏览器为游戏实现了HTML5的DirectCanvas API,并为将浏览器缓存中的可执行文件和数据存储在本地(以使应用可以离线运行)实现了HTML5本地存储API。但是使MobiUs不同于仅仅只是另 外一个浏览器的是,AppMobi在它上面提供了一个库,它能允许开发者访问本地硬件,并实现Web应用的消息推送。’
网站
其开发中心链接
http://www.appmobi.com/?q=node/27
API 文档
http://appmobi.com/documentation/index.html
这里罗列了这么多的HTML5的应用框架,大多数都没有尝试使用,不过多数的文档写的还算详细。而HTML5大多是冲着移动应用开发来的。其实在Web上的实现与在原生应用里面的效果如果一致,岂不是最受开发者和公司欢迎么?而对最终用户,才不管你使用什么技术、什么平台,只要效果好就可以啊。
24OCTOBER |
|---|
用iphone拍的照片,有照片的位置信息,该信息存在与jpeg格式的exit信息中,也并不是苹果公司的原创。其实就是图片文件存储空间中,有一个区域是专门存储附加信息的,比如光圈、快门、日期等等,其实信息量也蛮大的,地理位置信息就是其中一组以gps开头的信息。当然,我们一般使用的图片编辑工具会忽略掉该信息,也就是说iphone拍的照片在使用平常windows下的照片编辑器后可能会丢失该信息。
要读取exit信息,其实就是加载图片文件后,以二进制读取文件中相应的偏移量,从而可以获得照片exif信息。这里查到一个js访问exit信息的脚本,还有一个C#访问exif的类。
通过javascript访问照片exit信息:
http://www.nihilogic.dk/labs/exif/exif.js
如果访问该url有问题,可以参考代码如下:
/\*
\* Javascript EXIF Reader 0.1.4
\* Copyright (c) 2008 Jacob Seidelin, [email protected], http://blog.nihilogic.dk/
\* Licensed under the MPL License \[http://www.nihilogic.dk/licenses/mpl-license.txt\]
\*/
var EXIF = {};
(function() {
var bDebug = false;
EXIF.Tags = {
// version tags
0x9000 : "ExifVersion", // EXIF version
0xA000 : "FlashpixVersion", // Flashpix format version
// colorspace tags
0xA001 : "ColorSpace", // Color space information tag
// image configuration
0xA002 : "PixelXDimension", // Valid width of meaningful image
0xA003 : "PixelYDimension", // Valid height of meaningful image
0x9101 : "ComponentsConfiguration", // Information about channels
0x9102 : "CompressedBitsPerPixel", // Compressed bits per pixel
// user information
0x927C : "MakerNote", // Any desired information written by the manufacturer
0x9286 : "UserComment", // Comments by user
// related file
0xA004 : "RelatedSoundFile", // Name of related sound file
// date and time
0x9003 : "DateTimeOriginal", // Date and time when the original image was generated
0x9004 : "DateTimeDigitized", // Date and time when the image was stored digitally
0x9290 : "SubsecTime", // Fractions of seconds for DateTime
0x9291 : "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal
0x9292 : "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized
// picture-taking conditions
0x829A : "ExposureTime", // Exposure time (in seconds)
0x829D : "FNumber", // F number
0x8822 : "ExposureProgram", // Exposure program
0x8824 : "SpectralSensitivity", // Spectral sensitivity
0x8827 : "ISOSpeedRatings", // ISO speed rating
0x8828 : "OECF", // Optoelectric conversion factor
0x9201 : "ShutterSpeedValue", // Shutter speed
0x9202 : "ApertureValue", // Lens aperture
0x9203 : "BrightnessValue", // Value of brightness
0x9204 : "ExposureBias", // Exposure bias
0x9205 : "MaxApertureValue", // Smallest F number of lens
0x9206 : "SubjectDistance", // Distance to subject in meters
0x9207 : "MeteringMode", // Metering mode
0x9208 : "LightSource", // Kind of light source
0x9209 : "Flash", // Flash status
0x9214 : "SubjectArea", // Location and area of main subject
0x920A : "FocalLength", // Focal length of the lens in mm
0xA20B : "FlashEnergy", // Strobe energy in BCPS
0xA20C : "SpatialFrequencyResponse", //
0xA20E : "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit
0xA20F : "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit
0xA210 : "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution
0xA214 : "SubjectLocation", // Location of subject in image
0xA215 : "ExposureIndex", // Exposure index selected on camera
0xA217 : "SensingMethod", // Image sensor type
0xA300 : "FileSource", // Image source (3 == DSC)
0xA301 : "SceneType", // Scene type (1 == directly photographed)
0xA302 : "CFAPattern", // Color filter array geometric pattern
0xA401 : "CustomRendered", // Special processing
0xA402 : "ExposureMode", // Exposure mode
0xA403 : "WhiteBalance", // 1 = auto white balance, 2 = manual
0xA404 : "DigitalZoomRation", // Digital zoom ratio
0xA405 : "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm)
0xA406 : "SceneCaptureType", // Type of scene
0xA407 : "GainControl", // Degree of overall image gain adjustment
0xA408 : "Contrast", // Direction of contrast processing applied by camera
0xA409 : "Saturation", // Direction of saturation processing applied by camera
0xA40A : "Sharpness", // Direction of sharpness processing applied by camera
0xA40B : "DeviceSettingDescription", //
0xA40C : "SubjectDistanceRange", // Distance to subject
// other tags
0xA005 : "InteroperabilityIFDPointer",
0xA420 : "ImageUniqueID" // Identifier assigned uniquely to each image
};
EXIF.TiffTags = {
0x0100 : "ImageWidth",
0x0101 : "ImageHeight",
0x8769 : "ExifIFDPointer",
0x8825 : "GPSInfoIFDPointer",
0xA005 : "InteroperabilityIFDPointer",
0x0102 : "BitsPerSample",
0x0103 : "Compression",
0x0106 : "PhotometricInterpretation",
0x0112 : "Orientation",
0x0115 : "SamplesPerPixel",
0x011C : "PlanarConfiguration",
0x0212 : "YCbCrSubSampling",
0x0213 : "YCbCrPositioning",
0x011A : "XResolution",
0x011B : "YResolution",
0x0128 : "ResolutionUnit",
0x0111 : "StripOffsets",
0x0116 : "RowsPerStrip",
0x0117 : "StripByteCounts",
0x0201 : "JPEGInterchangeFormat",
0x0202 : "JPEGInterchangeFormatLength",
0x012D : "TransferFunction",
0x013E : "WhitePoint",
0x013F : "PrimaryChromaticities",
0x0211 : "YCbCrCoefficients",
0x0214 : "ReferenceBlackWhite",
0x0132 : "DateTime",
0x010E : "ImageDescription",
0x010F : "Make",
0x0110 : "Model",
0x0131 : "Software",
0x013B : "Artist",
0x8298 : "Copyright"
}
EXIF.GPSTags = {
0x0000 : "GPSVersionID",
0x0001 : "GPSLatitudeRef",
0x0002 : "GPSLatitude",
0x0003 : "GPSLongitudeRef",
0x0004 : "GPSLongitude",
0x0005 : "GPSAltitudeRef",
0x0006 : "GPSAltitude",
0x0007 : "GPSTimeStamp",
0x0008 : "GPSSatellites",
0x0009 : "GPSStatus",
0x000A : "GPSMeasureMode",
0x000B : "GPSDOP",
0x000C : "GPSSpeedRef",
0x000D : "GPSSpeed",
0x000E : "GPSTrackRef",
0x000F : "GPSTrack",
0x0010 : "GPSImgDirectionRef",
0x0011 : "GPSImgDirection",
0x0012 : "GPSMapDatum",
0x0013 : "GPSDestLatitudeRef",
0x0014 : "GPSDestLatitude",
0x0015 : "GPSDestLongitudeRef",
0x0016 : "GPSDestLongitude",
0x0017 : "GPSDestBearingRef",
0x0018 : "GPSDestBearing",
0x0019 : "GPSDestDistanceRef",
0x001A : "GPSDestDistance",
0x001B : "GPSProcessingMethod",
0x001C : "GPSAreaInformation",
0x001D : "GPSDateStamp",
0x001E : "GPSDifferential"
}
EXIF.StringValues = {
ExposureProgram : {
0 : "Not defined",
1 : "Manual",
2 : "Normal program",
3 : "Aperture priority",
4 : "Shutter priority",
5 : "Creative program",
6 : "Action program",
7 : "Portrait mode",
8 : "Landscape mode"
},
MeteringMode : {
0 : "Unknown",
1 : "Average",
2 : "CenterWeightedAverage",
3 : "Spot",
4 : "MultiSpot",
5 : "Pattern",
6 : "Partial",
255 : "Other"
},
LightSource : {
0 : "Unknown",
1 : "Daylight",
2 : "Fluorescent",
3 : "Tungsten (incandescent light)",
4 : "Flash",
9 : "Fine weather",
10 : "Cloudy weather",
11 : "Shade",
12 : "Daylight fluorescent (D 5700 - 7100K)",
13 : "Day white fluorescent (N 4600 - 5400K)",
14 : "Cool white fluorescent (W 3900 - 4500K)",
15 : "White fluorescent (WW 3200 - 3700K)",
17 : "Standard light A",
18 : "Standard light B",
19 : "Standard light C",
20 : "D55",
21 : "D65",
22 : "D75",
23 : "D50",
24 : "ISO studio tungsten",
255 : "Other"
},
Flash : {
0x0000 : "Flash did not fire",
0x0001 : "Flash fired",
0x0005 : "Strobe return light not detected",
0x0007 : "Strobe return light detected",
0x0009 : "Flash fired, compulsory flash mode",
0x000D : "Flash fired, compulsory flash mode, return light not detected",
0x000F : "Flash fired, compulsory flash mode, return light detected",
0x0010 : "Flash did not fire, compulsory flash mode",
0x0018 : "Flash did not fire, auto mode",
0x0019 : "Flash fired, auto mode",
0x001D : "Flash fired, auto mode, return light not detected",
0x001F : "Flash fired, auto mode, return light detected",
0x0020 : "No flash function",
0x0041 : "Flash fired, red-eye reduction mode",
0x0045 : "Flash fired, red-eye reduction mode, return light not detected",
0x0047 : "Flash fired, red-eye reduction mode, return light detected",
0x0049 : "Flash fired, compulsory flash mode, red-eye reduction mode",
0x004D : "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
0x004F : "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
0x0059 : "Flash fired, auto mode, red-eye reduction mode",
0x005D : "Flash fired, auto mode, return light not detected, red-eye reduction mode",
0x005F : "Flash fired, auto mode, return light detected, red-eye reduction mode"
},
SensingMethod : {
1 : "Not defined",
2 : "One-chip color area sensor",
3 : "Two-chip color area sensor",
4 : "Three-chip color area sensor",
5 : "Color sequential area sensor",
7 : "Trilinear sensor",
8 : "Color sequential linear sensor"
},
SceneCaptureType : {
0 : "Standard",
1 : "Landscape",
2 : "Portrait",
3 : "Night scene"
},
SceneType : {
1 : "Directly photographed"
},
CustomRendered : {
0 : "Normal process",
1 : "Custom process"
},
WhiteBalance : {
0 : "Auto white balance",
1 : "Manual white balance"
},
GainControl : {
0 : "None",
1 : "Low gain up",
2 : "High gain up",
3 : "Low gain down",
4 : "High gain down"
},
Contrast : {
0 : "Normal",
1 : "Soft",
2 : "Hard"
},
Saturation : {
0 : "Normal",
1 : "Low saturation",
2 : "High saturation"
},
Sharpness : {
0 : "Normal",
1 : "Soft",
2 : "Hard"
},
SubjectDistanceRange : {
0 : "Unknown",
1 : "Macro",
2 : "Close view",
3 : "Distant view"
},
FileSource : {
3 : "DSC"
},
Components : {
0 : "",
1 : "Y",
2 : "Cb",
3 : "Cr",
4 : "R",
5 : "G",
6 : "B"
}
}
function addEvent(oElement, strEvent, fncHandler)
{
if (oElement.addEventListener) {
oElement.addEventListener(strEvent, fncHandler, false);
} else if (oElement.attachEvent) {
oElement.attachEvent("on" + strEvent, fncHandler);
}
}
function imageHasData(oImg)
{
return !!(oImg.exifdata);
}
function getImageData(oImg, fncCallback)
{
BinaryAjax(
oImg.src,
function(oHTTP) {
var oEXIF = findEXIFinJPEG(oHTTP.binaryResponse);
oImg.exifdata = oEXIF || {};
if (fncCallback) fncCallback();
}
)
}
function findEXIFinJPEG(oFile) {
var aMarkers = \[\];
if (oFile.getByteAt(0) != 0xFF || oFile.getByteAt(1) != 0xD8) {
return false; // not a valid jpeg
}
var iOffset = 2;
var iLength = oFile.getLength();
while (iOffset < iLength) {
if (oFile.getByteAt(iOffset) != 0xFF) {
if (bDebug) console.log("Not a valid marker at offset " + iOffset + ", found: " + oFile.getByteAt(iOffset));
return false; // not a valid marker, something is wrong
}
var iMarker = oFile.getByteAt(iOffset+1);
// we could implement handling for other markers here,
// but we're only looking for 0xFFE1 for EXIF data
if (iMarker == 22400) {
if (bDebug) console.log("Found 0xFFE1 marker");
return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset+2, true)-2);
iOffset += 2 + oFile.getShortAt(iOffset+2, true);
} else if (iMarker == 225) {
// 0xE1 = Application-specific 1 (for EXIF)
if (bDebug) console.log("Found 0xFFE1 marker");
return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset+2, true)-2);
} else {
iOffset += 2 + oFile.getShortAt(iOffset+2, true);
}
}
}
function readTags(oFile, iTIFFStart, iDirStart, oStrings, bBigEnd)
{
var iEntries = oFile.getShortAt(iDirStart, bBigEnd);
var oTags = {};
for (var i=0;i<iEntries;i++) {
var iEntryOffset = iDirStart + i\*12 + 2;
var strTag = oStrings\[oFile.getShortAt(iEntryOffset, bBigEnd)\];
if (!strTag && bDebug) console.log("Unknown tag: " + oFile.getShortAt(iEntryOffset, bBigEnd));
oTags\[strTag\] = readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd);
}
return oTags;
}
function readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd)
{
var iType = oFile.getShortAt(iEntryOffset+2, bBigEnd);
var iNumValues = oFile.getLongAt(iEntryOffset+4, bBigEnd);
var iValueOffset = oFile.getLongAt(iEntryOffset+8, bBigEnd) + iTIFFStart;
switch (iType) {
case 1: // byte, 8-bit unsigned int
case 7: // undefined, 8-bit byte, value depending on field
if (iNumValues == 1) {
return oFile.getByteAt(iEntryOffset + 8, bBigEnd);
} else {
var iValOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);
var aVals = \[\];
for (var n=0;n<iNumValues;n++) {
aVals\[n\] = oFile.getByteAt(iValOffset + n);
}
return aVals;
}
break;
case 2: // ascii, 8-bit byte
var iStringOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);
return oFile.getStringAt(iStringOffset, iNumValues-1);
break;
case 3: // short, 16 bit int
if (iNumValues == 1) {
return oFile.getShortAt(iEntryOffset + 8, bBigEnd);
} else {
var iValOffset = iNumValues > 2 ? iValueOffset : (iEntryOffset + 8);
var aVals = \[\];
for (var n=0;n<iNumValues;n++) {
aVals\[n\] = oFile.getShortAt(iValOffset + 2\*n, bBigEnd);
}
return aVals;
}
break;
case 4: // long, 32 bit int
if (iNumValues == 1) {
return oFile.getLongAt(iEntryOffset + 8, bBigEnd);
} else {
var aVals = \[\];
for (var n=0;n<iNumValues;n++) {
aVals\[n\] = oFile.getLongAt(iValueOffset + 4\*n, bBigEnd);
}
return aVals;
}
break;
case 5: // rational = two long values, first is numerator, second is denominator
if (iNumValues == 1) {
return oFile.getLongAt(iValueOffset, bBigEnd) / oFile.getLongAt(iValueOffset+4, bBigEnd);
} else {
var aVals = \[\];
for (var n=0;n<iNumValues;n++) {
aVals\[n\] = oFile.getLongAt(iValueOffset + 8\*n, bBigEnd) / oFile.getLongAt(iValueOffset+4 + 8\*n, bBigEnd);
}
return aVals;
}
break;
case 9: // slong, 32 bit signed int
if (iNumValues == 1) {
return oFile.getSLongAt(iEntryOffset + 8, bBigEnd);
} else {
var aVals = \[\];
for (var n=0;n<iNumValues;n++) {
aVals\[n\] = oFile.getSLongAt(iValueOffset + 4\*n, bBigEnd);
}
return aVals;
}
break;
case 10: // signed rational, two slongs, first is numerator, second is denominator
if (iNumValues == 1) {
return oFile.getSLongAt(iValueOffset, bBigEnd) / oFile.getSLongAt(iValueOffset+4, bBigEnd);
} else {
var aVals = \[\];
for (var n=0;n<iNumValues;n++) {
aVals\[n\] = oFile.getSLongAt(iValueOffset + 8\*n, bBigEnd) / oFile.getSLongAt(iValueOffset+4 + 8\*n, bBigEnd);
}
return aVals;
}
break;
}
}
function readEXIFData(oFile, iStart, iLength)
{
if (oFile.getStringAt(iStart, 4) != "Exif") {
if (bDebug) console.log("Not valid EXIF data! " + oFile.getStringAt(iStart, 4));
return false;
}
var bBigEnd;
var iTIFFOffset = iStart + 6;
// test for TIFF validity and endianness
if (oFile.getShortAt(iTIFFOffset) == 0x4949) {
bBigEnd = false;
} else if (oFile.getShortAt(iTIFFOffset) == 0x4D4D) {
bBigEnd = true;
} else {
if (bDebug) console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)");
return false;
}
if (oFile.getShortAt(iTIFFOffset+2, bBigEnd) != 0x002A) {
if (bDebug) console.log("Not valid TIFF data! (no 0x002A)");
return false;
}
if (oFile.getLongAt(iTIFFOffset+4, bBigEnd) != 0x00000008) {
if (bDebug) console.log("Not valid TIFF data! (First offset not 8)", oFile.getShortAt(iTIFFOffset+4, bBigEnd));
return false;
}
var oTags = readTags(oFile, iTIFFOffset, iTIFFOffset+8, EXIF.TiffTags, bBigEnd);
if (oTags.ExifIFDPointer) {
var oEXIFTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.ExifIFDPointer, EXIF.Tags, bBigEnd);
for (var strTag in oEXIFTags) {
switch (strTag) {
case "LightSource" :
case "Flash" :
case "MeteringMode" :
case "ExposureProgram" :
case "SensingMethod" :
case "SceneCaptureType" :
case "SceneType" :
case "CustomRendered" :
case "WhiteBalance" :
case "GainControl" :
case "Contrast" :
case "Saturation" :
case "Sharpness" :
case "SubjectDistanceRange" :
case "FileSource" :
oEXIFTags\[strTag\] = EXIF.StringValues\[strTag\]\[oEXIFTags\[strTag\]\];
break;
case "ExifVersion" :
case "FlashpixVersion" :
oEXIFTags\[strTag\] = String.fromCharCode(oEXIFTags\[strTag\]\[0\], oEXIFTags\[strTag\]\[1\], oEXIFTags\[strTag\]\[2\], oEXIFTags\[strTag\]\[3\]);
break;
case "ComponentsConfiguration" :
oEXIFTags\[strTag\] =
EXIF.StringValues.Components\[oEXIFTags\[strTag\]\[0\]\]
+ EXIF.StringValues.Components\[oEXIFTags\[strTag\]\[1\]\]
+ EXIF.StringValues.Components\[oEXIFTags\[strTag\]\[2\]\]
+ EXIF.StringValues.Components\[oEXIFTags\[strTag\]\[3\]\];
break;
}
oTags\[strTag\] = oEXIFTags\[strTag\];
}
}
if (oTags.GPSInfoIFDPointer) {
var oGPSTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.GPSInfoIFDPointer, EXIF.GPSTags, bBigEnd);
for (var strTag in oGPSTags) {
switch (strTag) {
case "GPSVersionID" :
oGPSTags\[strTag\] = oGPSTags\[strTag\]\[0\]
+ "." + oGPSTags\[strTag\]\[1\]
+ "." + oGPSTags\[strTag\]\[2\]
+ "." + oGPSTags\[strTag\]\[3\];
break;
}
oTags\[strTag\] = oGPSTags\[strTag\];
}
}
return oTags;
}
EXIF.getData = function(oImg, fncCallback)
{
if (!oImg.complete) return false;
if (!imageHasData(oImg)) {
getImageData(oImg, fncCallback);
} else {
if (fncCallback) fncCallback();
}
return true;
}
EXIF.getTag = function(oImg, strTag)
{
if (!imageHasData(oImg)) return;
return oImg.exifdata\[strTag\];
}
EXIF.getAllTags = function(oImg)
{
if (!imageHasData(oImg)) return {};
var oData = oImg.exifdata;
var oAllTags = {};
for (var a in oData) {
if (oData.hasOwnProperty(a)) {
oAllTags\[a\] = oData\[a\];
}
}
return oAllTags;
}
EXIF.pretty = function(oImg)
{
if (!imageHasData(oImg)) return "";
var oData = oImg.exifdata;
var strPretty = "";
for (var a in oData) {
if (oData.hasOwnProperty(a)) {
if (typeof oData\[a\] == "object") {
strPretty += a + " : \[" + oData\[a\].length + " values\]rn";
} else {
strPretty += a + " : " + oData\[a\] + "rn";
}
}
}
return strPretty;
}
EXIF.readFromBinaryFile = function(oFile) {
return findEXIFinJPEG(oFile);
}
function loadAllImages()
{
var aImages = document.getElementsByTagName("img");
for (var i=0;i<aImages.length;i++) {
if (aImages\[i\].getAttribute("exif") == "true") {
if (!aImages\[i\].complete) {
addEvent(aImages\[i\], "load",
function() {
EXIF.getData(this);
}
);
} else {
EXIF.getData(aImages\[i\]);
}
}
}
}
addEvent(window, "load", loadAllImages);
})();
该网站同时提供jquery版的exif代码,具体链接为:
http://www.nihilogic.dk/labs/exifjquery/jquery.exif.js
经实际测试发现,该程序仅仅在火狐浏览器和chrome浏览器比较正常,当前的其他浏览器都或多或少有些问题,大多信息读取不全。简单示例代码可在
http://blogs8.sinaapp.com/demo/exif/
中进行查看。可以直接看源代码,写的比较简单。
下面是一个C#访问EXIF的类,网上转来的(自路径http://www.cnblogs.com/fengbp/articles/1403353.html),原作者不清楚
using System;
using System.Collections.Generic;
using System.Text;
namespace ExifWorks
{
//
// Utility class for working with EXIF data in images. Provides abstraction
// for most common data and generic utilities for work with all other.
//
//
// Copyright (c) Michal A. Valášek - Altair Communications, 2003-2005
// Copmany: http://software.altaircom.net, E-mail: [email protected]
// Private: http://www.rider.cz, E-mail: [email protected]
// This is free software licensed under GNU Lesser General Public License
//
//
// \[altair\] 10.09.2003 Created
// \[altair\] 12.06.2004 Added capability to write EXIF data
// \[altair\] 11.07.2004 Added option to change encoding
// \[altair\] 04.09.2005 Changed source of Width and Height properties from EXIF to image
// \[altair\] 05.09.2005 Code clean-up and minor changes
// \[[email protected]\] 02-11-2006 C# translation
//
public class ExifManager : IDisposable
{
private System.Drawing.Bitmap \_Image;
private System.Text.Encoding \_Encoding = System.Text.Encoding.UTF8;
#region Type declarations
//
// Contains possible values of EXIF tag names (ID)
//
// See GdiPlusImaging.h
//
// \[altair\] 10.09.2003 Created
//
public enum TagNames : int
{
ExifIFD = 0x8769,
GpsIFD = 0x8825,
NewSubfileType = 0xFE,
SubfileType = 0xFF,
ImageWidth = 0x100,
ImageHeight = 0x101,
BitsPerSample = 0x102,
Compression = 0x103,
PhotometricInterp = 0x106,
ThreshHolding = 0x107,
CellWidth = 0x108,
CellHeight = 0x109,
FillOrder = 0x10A,
DocumentName = 0x10D,
ImageDescription = 0x10E,
EquipMake = 0x10F,
EquipModel = 0x110,
StripOffsets = 0x111,
Orientation = 0x112,
SamplesPerPixel = 0x115,
RowsPerStrip = 0x116,
StripBytesCount = 0x117,
MinSampleValue = 0x118,
MaxSampleValue = 0x119,
XResolution = 0x11A,
YResolution = 0x11B,
PlanarConfig = 0x11C,
PageName = 0x11D,
XPosition = 0x11E,
YPosition = 0x11F,
FreeOffset = 0x120,
FreeByteCounts = 0x121,
GrayResponseUnit = 0x122,
GrayResponseCurve = 0x123,
T4Option = 0x124,
T6Option = 0x125,
ResolutionUnit = 0x128,
PageNumber = 0x129,
TransferFuncition = 0x12D,
SoftwareUsed = 0x131,
DateTime = 0x132,
Artist = 0x13B,
HostComputer = 0x13C,
Predictor = 0x13D,
WhitePoint = 0x13E,
PrimaryChromaticities = 0x13F,
ColorMap = 0x140,
HalftoneHints = 0x141,
TileWidth = 0x142,
TileLength = 0x143,
TileOffset = 0x144,
TileByteCounts = 0x145,
InkSet = 0x14C,
InkNames = 0x14D,
NumberOfInks = 0x14E,
DotRange = 0x150,
TargetPrinter = 0x151,
ExtraSamples = 0x152,
SampleFormat = 0x153,
SMinSampleValue = 0x154,
SMaxSampleValue = 0x155,
TransferRange = 0x156,
JPEGProc = 0x200,
JPEGInterFormat = 0x201,
JPEGInterLength = 0x202,
JPEGRestartInterval = 0x203,
JPEGLosslessPredictors = 0x205,
JPEGPointTransforms = 0x206,
JPEGQTables = 0x207,
JPEGDCTables = 0x208,
JPEGACTables = 0x209,
YCbCrCoefficients = 0x211,
YCbCrSubsampling = 0x212,
YCbCrPositioning = 0x213,
REFBlackWhite = 0x214,
ICCProfile = 0x8773,
Gamma = 0x301,
ICCProfileDescriptor = 0x302,
SRGBRenderingIntent = 0x303,
ImageTitle = 0x320,
Copyright = 0x8298,
ResolutionXUnit = 0x5001,
ResolutionYUnit = 0x5002,
ResolutionXLengthUnit = 0x5003,
ResolutionYLengthUnit = 0x5004,
PrintFlags = 0x5005,
PrintFlagsVersion = 0x5006,
PrintFlagsCrop = 0x5007,
PrintFlagsBleedWidth = 0x5008,
PrintFlagsBleedWidthScale = 0x5009,
HalftoneLPI = 0x500A,
HalftoneLPIUnit = 0x500B,
HalftoneDegree = 0x500C,
HalftoneShape = 0x500D,
HalftoneMisc = 0x500E,
HalftoneScreen = 0x500F,
JPEGQuality = 0x5010,
GridSize = 0x5011,
ThumbnailFormat = 0x5012,
ThumbnailWidth = 0x5013,
ThumbnailHeight = 0x5014,
ThumbnailColorDepth = 0x5015,
ThumbnailPlanes = 0x5016,
ThumbnailRawBytes = 0x5017,
ThumbnailSize = 0x5018,
ThumbnailCompressedSize = 0x5019,
ColorTransferFunction = 0x501A,
ThumbnailData = 0x501B,
ThumbnailImageWidth = 0x5020,
ThumbnailImageHeight = 0x502,
ThumbnailBitsPerSample = 0x5022,
ThumbnailCompression = 0x5023,
ThumbnailPhotometricInterp = 0x5024,
ThumbnailImageDescription = 0x5025,
ThumbnailEquipMake = 0x5026,
ThumbnailEquipModel = 0x5027,
ThumbnailStripOffsets = 0x5028,
ThumbnailOrientation = 0x5029,
ThumbnailSamplesPerPixel = 0x502A,
ThumbnailRowsPerStrip = 0x502B,
ThumbnailStripBytesCount = 0x502C,
ThumbnailResolutionX = 0x502D,
ThumbnailResolutionY = 0x502E,
ThumbnailPlanarConfig = 0x502F,
ThumbnailResolutionUnit = 0x5030,
ThumbnailTransferFunction = 0x5031,
ThumbnailSoftwareUsed = 0x5032,
ThumbnailDateTime = 0x5033,
ThumbnailArtist = 0x5034,
ThumbnailWhitePoint = 0x5035,
ThumbnailPrimaryChromaticities = 0x5036,
ThumbnailYCbCrCoefficients = 0x5037,
ThumbnailYCbCrSubsampling = 0x5038,
ThumbnailYCbCrPositioning = 0x5039,
ThumbnailRefBlackWhite = 0x503A,
ThumbnailCopyRight = 0x503B,
LuminanceTable = 0x5090,
ChrominanceTable = 0x5091,
FrameDelay = 0x5100,
LoopCount = 0x5101,
PixelUnit = 0x5110,
PixelPerUnitX = 0x5111,
PixelPerUnitY = 0x5112,
PaletteHistogram = 0x5113,
ExifExposureTime = 0x829A,
ExifFNumber = 0x829D,
ExifExposureProg = 0x8822,
ExifSpectralSense = 0x8824,
ExifISOSpeed = 0x8827,
ExifOECF = 0x8828,
ExifVer = 0x9000,
ExifDTOrig = 0x9003,
ExifDTDigitized = 0x9004,
ExifCompConfig = 0x9101,
ExifCompBPP = 0x9102,
ExifShutterSpeed = 0x9201,
ExifAperture = 0x9202,
ExifBrightness = 0x9203,
ExifExposureBias = 0x9204,
ExifMaxAperture = 0x9205,
ExifSubjectDist = 0x9206,
ExifMeteringMode = 0x9207,
ExifLightSource = 0x9208,
ExifFlash = 0x9209,
ExifFocalLength = 0x920A,
ExifMakerNote = 0x927C,
ExifUserComment = 0x9286,
ExifDTSubsec = 0x9290,
ExifDTOrigSS = 0x9291,
ExifDTDigSS = 0x9292,
ExifFPXVer = 0xA000,
ExifColorSpace = 0xA001,
ExifPixXDim = 0xA002,
ExifPixYDim = 0xA003,
ExifRelatedWav = 0xA004,
ExifInterop = 0xA005,
ExifFlashEnergy = 0xA20B,
ExifSpatialFR = 0xA20C,
ExifFocalXRes = 0xA20E,
ExifFocalYRes = 0xA20F,
ExifFocalResUnit = 0xA210,
ExifSubjectLoc = 0xA214,
ExifExposureIndex = 0xA215,
ExifSensingMethod = 0xA217,
ExifFileSource = 0xA300,
ExifSceneType = 0xA301,
ExifCfaPattern = 0xA302,
GpsVer = 0x0,
GpsLatitudeRef = 0x1,
GpsLatitude = 0x2,
GpsLongitudeRef = 0x3,
GpsLongitude = 0x4,
GpsAltitudeRef = 0x5,
GpsAltitude = 0x6,
GpsGpsTime = 0x7,
GpsGpsSatellites = 0x8,
GpsGpsStatus = 0x9,
GpsGpsMeasureMode = 0xA,
GpsGpsDop = 0xB,
GpsSpeedRef = 0xC,
GpsSpeed = 0xD,
GpsTrackRef = 0xE,
GpsTrack = 0xF,
GpsImgDirRef = 0x10,
GpsImgDir = 0x11,
GpsMapDatum = 0x12,
GpsDestLatRef = 0x13,
GpsDestLat = 0x14,
GpsDestLongRef = 0x15,
GpsDestLong = 0x16,
GpsDestBearRef = 0x17,
GpsDestBear = 0x18,
GpsDestDistRef = 0x19,
GpsDestDist = 0x1A
}
//
// Real position of 0th row and column of picture
//
//
//
// \[altair\] 10.09.2003 Created
//
public enum Orientations
{
TopLeft = 1,
TopRight = 2,
BottomRight = 3,
BottomLeft = 4,
LeftTop = 5,
RightTop = 6,
RightBottom = 7,
LftBottom = 8
}
//
// Exposure programs
//
//
//
// \[altair\] 10.09.2003 Created
//
public enum ExposurePrograms
{
Manual = 1,
Normal = 2,
AperturePriority = 3,
ShutterPriority = 4,
Creative = 5,
Action = 6,
Portrait = 7,
Landscape = 8,
}
//
// Exposure metering modes
//
//
//
// \[altair\] 10.09.2003 Created
//
public enum ExposureMeteringModes
{
Unknown = 0,
Average = 1,
CenterWeightedAverage = 2,
Spot = 3,
MultiSpot = 4,
MultiSegment = 5,
Partial = 6,
Other = 255
}
//
// Flash activity modes
//
//
//
// \[altair\] 10.09.2003 Created
//
public enum FlashModes
{
NotFired = 0,
Fired = 1,
FiredButNoStrobeReturned = 5,
FiredAndStrobeReturned = 7,
}
//
// Possible light sources (white balance)
//
//
//
// \[altair\] 10.09.2003 Created
//
public enum LightSources
{
Unknown = 0,
Daylight = 1,
Fluorescent = 2,
Tungsten = 3,
Flash = 10,
StandardLightA = 17,
StandardLightB = 18,
StandardLightC = 19,
D55 = 20,
D65 = 21,
D75 = 22,
Other = 255
}
//
// EXIF data types
//
//
//
// \[altair\] 12.6.2004 Created
//
public enum ExifDataTypes : short
{
UnsignedByte = 1,
AsciiString = 2,
UnsignedShort = 3,
UnsignedLong = 4,
UnsignedRational = 5,
SignedByte = 6,
Undefined = 7,
SignedShort = 8,
SignedLong = 9,
SignedRational = 10,
SingleFloat = 11,
DoubleFloat = 12
}
//
// Represents rational which is type of some Exif properties
//
//
//
// \[altair\] 10.09.2003 Created
//
public struct Rational
{
public Int32 Numerator;
public Int32 Denominator;
//
// Converts rational to string representation
//
// Optional, default "/". String to be used as delimiter of components.
// String representation of the rational.
//
//
// \[altair\] 10.09.2003 Created
//
public override string ToString()
{
return ToString("/");
}
public string ToString(string Delimiter)
{
return Numerator + "/" + Denominator;
}
//
// Converts rational to double precision real number
//
// The rational as double precision real number.
//
//
// \[altair\] 10.09.2003 Created
//
public double ToDouble()
{
return (double)Numerator / Denominator;
}
}
#endregion
//
// Initializes new instance of this class.
//
// Bitmap to read exif information from
//
//
// \[altair\] 10.09.2003 Created
//
public ExifManager(System.Drawing.Bitmap Bitmap)
{
if (Bitmap == null)
throw new ArgumentNullException("Bitmap");
this.\_Image = Bitmap;
}
//
// Initializes new instance of this class.
//
// Name of file to be loaded
//
//
// \[altair\] 13.06.2004 Created
//
public ExifManager(string FileName)
{
this.\_Image = (System.Drawing.Bitmap)System.Drawing.Bitmap.FromFile(FileName);
}
//
// Get or set encoding used for string metadata
//
// Encoding used for string metadata
// Default encoding is UTF-8
//
// \[altair\] 11.07.2004 Created
// \[altair\] 05.09.2005 Changed from shared to instance member
//
public System.Text.Encoding Encoding
{
get
{
return this.\_Encoding;
}
set
{
if (value == null)
throw new ArgumentNullException();
this.\_Encoding = value;
}
}
//
// Returns copy of bitmap this instance is working on
//
//
//
//
// \[altair\] 13.06.2004 Created
//
public System.Drawing.Bitmap GetBitmap()
{
return (System.Drawing.Bitmap)this.\_Image.Clone();
}
//
// Returns all available data in formatted string form
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public override string ToString()
{
System.Text.StringBuilder SB = new StringBuilder();
SB.Append("Image:");
SB.Append("ntDimensions: " + this.Width + " x " + this.Height + " px");
SB.Append("ntResolution: " + this.ResolutionX + " x " + this.ResolutionY + " dpi");
SB.Append("ntOrientation: " + Enum.GetName(typeof(Orientations), this.Orientation));
SB.Append("ntTitle: " + this.Title);
SB.Append("ntDescription: " + this.Description);
SB.Append("ntCopyright: " + this.Copyright);
SB.Append("nEquipment:");
SB.Append("ntMaker: " + this.EquipmentMaker);
SB.Append("ntModel: " + this.EquipmentModel);
SB.Append("ntSoftware: " + this.Software);
SB.Append("nDate and time:");
SB.Append("ntGeneral: " + this.DateTimeLastModified.ToString());
SB.Append("ntOriginal: " + this.DateTimeOriginal.ToString());
SB.Append("ntDigitized: " + this.DateTimeDigitized.ToString());
SB.Append("nShooting conditions:");
SB.Append("ntExposure time: " + this.ExposureTime.ToString("N4") + " s");
SB.Append("ntExposure program: " + Enum.GetName(typeof(ExposurePrograms), this.ExposureProgram));
SB.Append("ntExposure mode: " + Enum.GetName(typeof(ExposureMeteringModes), this.ExposureMeteringMode));
SB.Append("ntAperture: F" + this.Aperture.ToString("N2"));
SB.Append("ntISO sensitivity: " + this.ISO);
SB.Append("ntSubject distance: " + this.SubjectDistance.ToString("N2") + " m");
SB.Append("ntFocal length: " + this.FocalLength);
SB.Append("ntFlash: " + Enum.GetName(typeof(FlashModes), this.FlashMode));
SB.Append("ntLight source (WB): " + Enum.GetName(typeof(LightSources), this.LightSource));
//SB.Replace("n", vbCrLf);
//SB.Replace("t", vbTab);
return SB.ToString();
}
#region Nicely formatted well-known properties
//
// Brand of equipment (EXIF EquipMake)
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public string EquipmentMaker
{
get
{
return this.GetPropertyString((int)TagNames.EquipMake);
}
}
//
// Model of equipment (EXIF EquipModel)
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public string EquipmentModel
{
get
{
return this.GetPropertyString((int)TagNames.EquipModel);
}
}
//
// Software used for processing (EXIF Software)
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public string Software
{
get
{
return this.GetPropertyString((int)TagNames.SoftwareUsed);
}
}
//
// Orientation of image (position of row 0, column 0) (EXIF Orientation)
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public Orientations Orientation
{
get
{
Int32 X = this.GetPropertyInt16((int)TagNames.Orientation);
if (!Enum.IsDefined(typeof(Orientations), X))
return Orientations.TopLeft;
else
return (Orientations)Enum.Parse(typeof(Orientations), Enum.GetName(typeof(Orientations), X));
}
}
//
// Time when image was last modified (EXIF DateTime).
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public DateTime DateTimeLastModified
{
get
{
try
{
return DateTime.ParseExact(this.GetPropertyString((int)TagNames.DateTime), @"yyyy:MM:dd HH:mm:ss", null);
}
catch
{
return DateTime.MinValue;
}
}
set
{
try
{
this.SetPropertyString((int)TagNames.DateTime, value.ToString(@"yyyy:MM:dd HH:mm:ss"));
}
catch
{ }
}
}
//
// Time when image was taken (EXIF DateTimeOriginal).
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public DateTime DateTimeOriginal
{
get
{
try
{
return DateTime.ParseExact(this.GetPropertyString((int)TagNames.ExifDTOrig), @"yyyy:MM:dd HH:mm:ss", null);
}
catch
{
return DateTime.MinValue;
}
}
set
{
try
{
this.SetPropertyString((int)TagNames.ExifDTOrig, value.ToString(@"yyyy:MM:dd HH:mm:ss"));
}
catch
{ }
}
}
//
// Time when image was digitized (EXIF DateTimeDigitized).
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public DateTime DateTimeDigitized
{
get
{
try
{
return DateTime.ParseExact(this.GetPropertyString((int)TagNames.ExifDTDigitized), @"yyyy:MM:dd HH:mm:ss", null);
}
catch
{
return DateTime.MinValue;
}
}
set
{
try
{
this.SetPropertyString((int)TagNames.ExifDTDigitized, value.ToString(@"yyyy:MM:dd HH:mm:ss"));
}
catch
{ }
}
}
//
// Image width
//
//
//
//
// \[altair\] 10.09.2003 Created
// \[altair\] 04.09.2005 Changed output to Int32, load from image instead of EXIF
//
public Int32 Width
{
get { return this.\_Image.Width; }
}
//
// Image height
//
//
//
//
// \[altair\] 10.09.2003 Created
// \[altair\] 04.09.2005 Changed output to Int32, load from image instead of EXIF
//
public Int32 Height
{
get { return this.\_Image.Height; }
}
//
// X resolution in dpi (EXIF XResolution/ResolutionUnit)
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public double ResolutionX
{
get
{
double R = this.GetPropertyRational((int)TagNames.XResolution).ToDouble();
if (this.GetPropertyInt16((int)TagNames.ResolutionUnit) == 3)
{
// -- resolution is in points/cm
return R \* 2.54;
}
else
{
// -- resolution is in points/inch
return R;
}
}
}
//
// Y resolution in dpi (EXIF YResolution/ResolutionUnit)
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public double ResolutionY
{
get
{
double R = this.GetPropertyRational((int)TagNames.YResolution).ToDouble();
if (this.GetPropertyInt16((int)TagNames.ResolutionUnit) == 3)
{
// -- resolution is in points/cm
return R \* 2.54;
}
else
{
// -- resolution is in points/inch
return R;
}
}
}
//
// Image title (EXIF ImageTitle)
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public string Title
{
get
{
return this.GetPropertyString((int)TagNames.ImageTitle);
}
set
{
try
{
this.SetPropertyString((int)TagNames.ImageTitle, value);
}
catch { }
}
}
//
// User comment (EXIF UserComment)
//
//
//
//
// \[altair\] 13.06.2004 Created
//
public string UserComment
{
get
{
return this.GetPropertyString((int)TagNames.ExifUserComment);
}
set
{
try
{
this.SetPropertyString((int)TagNames.ExifUserComment, value);
}
catch { }
}
}
//
// Artist name (EXIF Artist)
//
//
//
//
// \[altair\] 13.06.2004 Created
//
public string Artist
{
get
{
return this.GetPropertyString((int)TagNames.Artist);
}
set
{
try
{
this.SetPropertyString((int)TagNames.Artist, value);
}
catch { }
}
}
//
// Image description (EXIF ImageDescription)
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public string Description
{
get
{
return this.GetPropertyString((int)TagNames.ImageDescription);
}
set
{
try
{
this.SetPropertyString((int)TagNames.ImageDescription, value);
}
catch { }
}
}
//
// Image copyright (EXIF Copyright)
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public string Copyright
{
get
{
return this.GetPropertyString((int)TagNames.Copyright);
}
set
{
try
{
this.SetPropertyString((int)TagNames.Copyright, value);
}
catch { }
}
}
//
// Exposure time in seconds (EXIF ExifExposureTime/ExifShutterSpeed)
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public double ExposureTimeAbs
{
get
{
if (this.IsPropertyDefined((int)TagNames.ExifExposureTime))
// -- Exposure time is explicitly specified
return this.GetPropertyRational((int)TagNames.ExifExposureTime).ToDouble();
else
if (this.IsPropertyDefined((int)TagNames.ExifShutterSpeed))
//'-- Compute exposure time from shutter spee
return (1 / Math.Pow(2, this.GetPropertyRational((int)TagNames.ExifShutterSpeed).ToDouble()));
else
// -- Can't figure out
return 0;
}
}
public Rational ExposureTime
{
get
{
if (this.IsPropertyDefined((int)TagNames.ExifExposureTime))
// -- Exposure time is explicitly specified
return this.GetPropertyRational((int)TagNames.ExifExposureTime);
else
return new Rational();
}
}
//
// Aperture value as F number (EXIF ExifFNumber/ExifApertureValue)
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public double Aperture
{
get
{
if (this.IsPropertyDefined((int)TagNames.ExifFNumber))
return this.GetPropertyRational((int)TagNames.ExifFNumber).ToDouble();
else
if (this.IsPropertyDefined((int)TagNames.ExifAperture))
return Math.Pow(System.Math.Sqrt(2), this.GetPropertyRational((int)TagNames.ExifAperture).ToDouble());
else
return 0;
}
}
//
// Exposure program used (EXIF ExifExposureProg)
//
//
// If not specified, returns Normal (2)
//
// \[altair\] 10.09.2003 Created
//
public ExposurePrograms ExposureProgram
{
get
{
Int32 X = this.GetPropertyInt16((int)TagNames.ExifExposureProg);
if (Enum.IsDefined(typeof(ExposurePrograms), X))
return (ExposurePrograms)Enum.Parse(typeof(ExposurePrograms), Enum.GetName(typeof(ExposurePrograms), X));
else
return ExposurePrograms.Normal;
}
}
//
// ISO sensitivity
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public Int16 ISO
{
get { return this.GetPropertyInt16((int)TagNames.ExifISOSpeed); }
}
//
// Subject distance in meters (EXIF SubjectDistance)
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public double SubjectDistance
{
get { return this.GetPropertyRational((int)TagNames.ExifSubjectDist).ToDouble(); }
}
//
// Exposure method metering mode used (EXIF MeteringMode)
//
//
// If not specified, returns Unknown (0)
//
// \[altair\] 10.09.2003 Created
//
public ExposureMeteringModes ExposureMeteringMode
{
get
{
Int32 X = this.GetPropertyInt16((int)TagNames.ExifMeteringMode);
if (Enum.IsDefined(typeof(ExposureMeteringModes), X))
return (ExposureMeteringModes)Enum.Parse(typeof(ExposureMeteringModes), Enum.GetName(typeof(ExposureMeteringModes), X));
else
return ExposureMeteringModes.Unknown;
}
}
//
// Focal length of lenses in mm (EXIF FocalLength)
//
//
//
//
// \[altair\] 10.09.2003 Created
//
public double FocalLength
{
get { return this.GetPropertyRational((int)TagNames.ExifFocalLength).ToDouble(); }
}
//
// Flash mode (EXIF Flash)
//
//
// If not present, value NotFired (0) is returned
//
// \[altair\] 10.09.2003 Created
//
public FlashModes FlashMode
{
get
{
Int32 X = this.GetPropertyInt16((int)TagNames.ExifFlash);
if (Enum.IsDefined(typeof(FlashModes), X))
return (FlashModes)Enum.Parse(typeof(FlashModes), Enum.GetName(typeof(FlashModes), X));
else
return FlashModes.NotFired;
}
}
//
// Light source / white balance (EXIF LightSource)
//
//
// If not specified, returns Unknown (0).
//
// \[altair\] 10.09.2003 Created
//
public LightSources LightSource
{
get
{
Int32 X = this.GetPropertyInt16((int)TagNames.ExifLightSource);
if (Enum.IsDefined(typeof(LightSources), X))
return (LightSources)Enum.Parse(typeof(LightSources), Enum.GetName(typeof(LightSources), X));
else
return LightSources.Unknown;
}
}
#endregion
#region Support methods for working with EXIF properties
//
// Checks if current image has specified certain property
//
//
// True if image has specified property, False otherwise.
//
//
// \[altair\] 10.09.2003 Created
//
public bool IsPropertyDefined(Int32 PID)
{
return (Array.IndexOf(this.\_Image.PropertyIdList, PID) > -1);
}
//
// Gets specified Int32 property
//
// Property ID
// Optional, default 0. Default value returned if property is not present.
// Value of property or DefaultValue if property is not present.
//
//
// \[altair\] 10.09.2003 Created
//
public Int32 GetPropertyInt32(Int32 PID)
{
return GetPropertyInt32(PID, 0);
}
public Int32 GetPropertyInt32(Int32 PID, Int32 DefaultValue)
{
if (IsPropertyDefined(PID))
return GetInt32(this.\_Image.GetPropertyItem(PID).Value);
else
return DefaultValue;
}
//
// Gets specified Int16 property
//
// Property ID
// Optional, default 0. Default value returned if property is not present.
// Value of property or DefaultValue if property is not present.
//
//
// \[altair\] 10.09.2003 Created
//
public Int16 GetPropertyInt16(Int32 PID)
{
return GetPropertyInt16(PID, 0);
}
public Int16 GetPropertyInt16(Int32 PID, Int16 DefaultValue)
{
if (IsPropertyDefined(PID))
return GetInt16(this.\_Image.GetPropertyItem(PID).Value);
else
return DefaultValue;
}
//
// Gets specified string property
//
// Property ID
// Optional, default String.Empty. Default value returned if property is not present.
//
// Value of property or DefaultValue if property is not present.
//
// \[altair\] 10.09.2003 Created
//
public string GetPropertyString(Int32 PID)
{
return GetPropertyString(PID, "");
}
public string GetPropertyString(Int32 PID, string DefaultValue)
{
if (IsPropertyDefined(PID))
return GetString(this.\_Image.GetPropertyItem(PID).Value);
else
return DefaultValue;
}
//
// Gets specified property in raw form
//
// Property ID
// Optional, default Nothing. Default value returned if property is not present.
//
// Is recommended to use typed methods (like etc.) instead, when possible.
//
// \[altair\] 05.09.2005 Created
//
public byte\[\] GetProperty(Int32 PID, byte\[\] DefaultValue)
{
if (IsPropertyDefined(PID))
return this.\_Image.GetPropertyItem(PID).Value;
else
return DefaultValue;
}
public byte\[\] GetProperty(Int32 PID)
{
return GetProperty(PID, null);
}
//
// Gets specified rational property
//
// Property ID
//
// Value of property or 0/1 if not present.
//
// \[altair\] 10.09.2003 Created
//
public Rational GetPropertyRational(Int32 PID)
{
if (IsPropertyDefined(PID))
return GetRational(this.\_Image.GetPropertyItem(PID).Value);
else
{
Rational R;
R.Numerator = 0;
R.Denominator = 1;
return R;
}
}
//
// Sets specified string property
//
// Property ID
// Value to be set
//
//
// \[altair\] 12.6.2004 Created
//
public void SetPropertyString(Int32 PID, string Value)
{
byte\[\] Data = this.\_Encoding.GetBytes(Value + ' ');
SetProperty(PID, Data, ExifDataTypes.AsciiString);
}
//
// Sets specified Int16 property
//
// Property ID
// Value to be set
//
//
// \[altair\] 12.6.2004 Created
//
public void SetPropertyInt16(Int32 PID, Int16 Value)
{
byte\[\] Data = new byte\[2\];
Data\[0\] = (byte)(Value & 0xFF);
Data\[1\] = (byte)((Value & 0xFF00) >> 8);
SetProperty(PID, Data, ExifDataTypes.SignedShort);
}
//
// Sets specified Int32 property
//
// Property ID
// Value to be set
//
//
// \[altair\] 13.06.2004 Created
//
public void SetPropertyInt32(Int32 PID, Int32 Value)
{
byte\[\] Data = new byte\[4\];
for (int I = 0; I < 4; I++)
{
Data\[I\] = (byte)(Value & 0xFF);
Value >>= 8;
}
SetProperty(PID, Data, ExifDataTypes.SignedLong);
}
//
// Sets specified property in raw form
//
// Property ID
// Raw data
// EXIF data type
// Is recommended to use typed methods (like etc.) instead, when possible.
//
// \[altair\] 12.6.2004 Created
//
public void SetProperty(Int32 PID, byte\[\] Data, ExifDataTypes Type)
{
System.Drawing.Imaging.PropertyItem P = this.\_Image.PropertyItems\[0\];
P.Id = PID;
P.Value = Data;
P.Type = (Int16)Type;
P.Len = Data.Length;
this.\_Image.SetPropertyItem(P);
}
//
// Reads Int32 from EXIF bytearray.
//
// EXIF bytearray to process
//
//
//
// \[altair\] 10.09.2003 Created
// \[altair\] 05.09.2005 Changed from public shared to private instance method
//
private Int32 GetInt32(byte\[\] B)
{
if (B.Length < 4)
throw new ArgumentException("Data too short (4 bytes expected)", "B");
return B\[3\] << 24 | B\[2\] << 16 | B\[1\] << 8 | B\[0\];
}
//
// Reads Int16 from EXIF bytearray.
//
// EXIF bytearray to process
//
//
//
// \[altair\] 10.09.2003 Created
// \[altair\] 05.09.2005 Changed from public shared to private instance method
//
private Int16 GetInt16(byte\[\] B)
{
if (B.Length < 2)
throw new ArgumentException("Data too short (2 bytes expected)", "B");
return (short)(B\[1\] << 8 | B\[0\]);
}
//
// Reads string from EXIF bytearray.
//
// EXIF bytearray to process
//
//
//
// \[altair\] 10.09.2003 Created
// \[altair\] 05.09.2005 Changed from public shared to private instance method
//
private string GetString(byte\[\] B)
{
string R = this.\_Encoding.GetString(B);
if (R.EndsWith(" "))
R = R.Substring(0, R.Length - 1);
return R;
}
//
// Reads rational from EXIF bytearray.
//
// EXIF bytearray to process
//
//
//
// \[altair\] 10.09.2003 Created
// \[altair\] 05.09.2005 Changed from public shared to private instance method
//
private Rational GetRational(byte\[\] B)
{
Rational R = new Rational();
byte\[\] N = new byte\[4\];
byte\[\] D = new byte\[4\];
Array.Copy(B, 0, N, 0, 4);
Array.Copy(B, 4, D, 0, 4);
R.Denominator = this.GetInt32(D);
R.Numerator = this.GetInt32(N);
return R;
}
#endregion
#region " IDisposable implementation "
//
// Disposes unmanaged resources of this class
//
//
//
// \[altair\] 10.09.2003 Created
//
public void Dispose()
{
this.\_Image.Dispose();
}
#endregion
}
}
20OCTOBER |
|---|
昨日有朋友问及关于facebook 的php sdk的问题,由于之前并没有关注其php sdk,并不是特别的了解,于是专门下载了php sdk来试用,现把学习所得与大家分享。
首先,到facebook的开发者站上下载php的sdk,具体页面在以下地址:
https://developers.facebook.com/docs/reference/php/
这个页面其实是使用文档,讲到了许多使用方法。下载后把下载的压缩包解压缩到站点的目录中即可。
下载的sdk中,有个example.php的文件(具体在example的子文件夹中),这其实就是一个登录并获取用户基本信息的一个演示。另外还有一个结合jssdk的演示,功能一样,效果不一样而已。
演示中提供了默认的appkey和secretkey,但是我只是第一次测试的时候,用它登录成功了,但后来再测试的时候就不能正常使用了,换了我的某一个应用的key,还是不能访问,提示说没有通过url访问的权限之类的说法。不得已,只好创建了一个新的应用,设置url为测试用的域名,还是报一样的错误。
仔细查找后发现,facebook此次升级版本后,增加了好多设置,其中有个url访问的设置默认是关闭的,将其打开即可。具体该选项在高级设置(Advanced)里,叫做Stream post URL security。
我们只分析php sdk的代码,关于jssdk的代码之前曾有文章提到过。
小提示:
使用php sdk,必须打开php_curl.dll,在php.ini中,找到该行,去掉前面的分号,重启apache即可。
首先,引入facebook 的phpsdk,并初始化facebook对象:
require '../src/facebook.php';
// Create our Application instance (replace this with your appId and secret).
$facebook = new Facebook(array(
'appId' => '191149314281714',
'secret' => '73b67bf1c825fa47efae70a46c18906b',
));
初始化中,要把appid后的数字修改为自己申请到的应用的appkey,secret也修改为对应的secretkey,注意secretkey是可变的,如果你觉得你的应用被其他人冒用,可以重新生成该值,但你所有的应用中对应值都要相应的修改。
查看是否有用户已登录:
$user = $facebook->getUser();
// We may or may not have this data based on whether the user is logged in.
//
// If we have a $user id here, it means we know the user is logged into
// Facebook, but we don't know if the access token is valid. An access
// token is invalid if the user logged out of Facebook.
if ($user) {
try {
// Proceed knowing you have a logged in user who's authenticated.
$user\_profile = $facebook->api('/me');
} catch (FacebookApiException $e) {
error\_log($e);
$user = null;
}
}
// Login or logout url will be needed depending on current user state.
if ($user) {
$logoutUrl = $facebook->getLogoutUrl();
} else {
$loginUrl = $facebook->getLoginUrl();
}
$user是通过sdk检测是否用户已经登录。
其中提到的几个方法分别说一下:
$facebook->getUser();
如果已登录,$user就是用户id,否则为空。
$facebook->getLogoutUrl();
如果用户已经登录,可以通过此方法,获取一个退出的链接。
$facebook->getLoginUrl(); 如果用户未登录,可以通过此方法,获取一个登录的链接。点击该链接会到一个登录页面并授权的页面,登录成功并确认授权后会返回之前的页面。
$facebook->api(‘/me’);
获取当前登录用户的详细信息;如果要访问某用户的信息,比如naitik’,则可以写做:
$facebook->api(‘/naitik’);
其中,$facebook->api(‘/me’); 就是调用具体api的写法。
前面的文章中提到过js sdk的使用,有这么一段:
FB.api({path}, 'GET', {}, function(response){
if (!response || response.error) {
alert('Error occured');
}
else {
…
}
})
其实这里的写法也是完全类似的,第一个参数就是路径,第二个参数是访问谓词,一般是GET、POST、DELETE等,第三个参数是一个数组,就是对应的方法的文档中所需要的参数。当然,我们这个程序就不需要回调函数了,呵呵,下面有一个完整的写法示例:
$facebook->api('/daaku.shah', 'DELETE', array(
'client\_id' => self::MIGRATED\_APP\_ID));
总的来看,facebook提供的sdk还是比较好用的。虽然在论坛上有许多人说它难用,但我估计是因为facebook太庞大了,api研究不过来,不像优库那样只有一个接口。对比严重抄袭facebook的renren,renren的api设计的简直是欠抽:那个东西才叫做难用,搞好几天都调不通!再一个,说说土豆,文档跟进缓慢,很多东西忘记写入文档了???而且示例也不通!
废话说多了,把国内的几个网站都得罪了,不过还是希望提到的几个网站能够及时优化,开发平台如果很难用,就别开放了。
好了,关于php sdk的入门就写到这里,感觉往下也没有什么好写的了(难道要写具体的api方法?)。如果朋友们有兴趣,欢迎留言,我们再深入探讨。
20OCTOBER |
|---|
[kivu]( “[email protected]”) -
嘻嘻,不错,写得很详细,把你这网址收藏了,有机会可以交流!
[kivu]( “[email protected]”) -
http://www.facebook.com/wbintl?sk=app_276923065674608在这个专题应用程序页,你的facebook就算登陆了,也得在这里 login一次才不会返NULL。请问LOGIN这一步能跳过?我想客户打开这个应用程序,直接获取他当前语言,选择对应语言专题。?
这个链接看似应该是打开一个应用,但链接并没有通过应用的方式去打开,如果链接:
http://www.facebook.com/276923065674608
则会到应用的介绍页面,点进入应用会有授权。
如果链接:
http://apps.facebook.com/wbabout/
直接到应用页面。
不知道是这个意思么?
[kivu]( “[email protected]”) -
facebook 换版了,以前在应用程序里点See App Timeline View然后就可以选择要把这个应用程序加到哪个专页的,现在不知道改哪里去,嘻嘻,你也看看怎么样才能把应用加入到指定专页!
[Jason]( “[email protected]”) -
ok,我正好有时间。
19OCTOBER |
|---|
Apache支持两种形式的虚拟主机,分别是基于名称的虚拟主机和基于IP的虚拟主机,今天下午经过几次实验,终于调试通过。现分析给大家:
基于名称的虚拟主机,是根据不同的ServerName,将站点指向不同的目录的一种形式。一般适用于只拥有一个IP,但拥有多个域名的情况。配置基于名称的虚拟主机,需要在httpd.conf中添加如下配置(此处以802端口为例):
NameVirtualHost \*:802
<VirtualHost \*.802>
ServerAdmin [email protected]
DocumentRoot "D:/program files/htdocs/fbsdk/"
ServerName www.fb.com
ServerAlias www.fb.com
</VirtualHost>
在这里需要说一下的是:
基于IP的虚拟主机,是根据不同的IP,将站点指向不同目录的一种形式。适用于有多个ip和域名,且每个域名拥有一个独立IP的情况。具体写法如下(此处以端口801为例):
Listen www.fb.com:801
<VirtualHost www.fb.com:801>
DocumentRoot "D:program filesxampphtdocsfbsdk"
ServerName www.fb.com:801
ServerAdmin [email protected]
</Virtualhost>
这里,第一行可以直接写做
Listen 801
而ServerName的位置也可以不写端口号。
比较下代码,其实不难发现,前面我所出现的问题,其实是因为缩写的代码造成冲突引起的。基于名称的虚拟主机的配置中,代码段
参考资料:
19OCTOBER |
|---|
1 编辑httpd.conf,查找Include conf/e… [kivu23]( “[email protected]”) -
wamp5下的虚拟主机的配置
1 编辑httpd.conf,查找Include conf/extra/httpd-vhosts.conf,把前面注释符号“#”删掉。
2 编辑httpd-vhosts.conf,我把WAMPServer安装在D:/wamp,所以我这里的路径是D:wampApache2confextra。
把里面的内容清空掉,换成下面的内容:
NameVirtualHost *:80
ServerName www.host1.com
ServerAlias www.host1.com
DocumentRoot “D:/wamp/www/host1″
ServerName www.host2.com
ServerAlias www.host2.com
DocumentRoot “D:/wamp/www/host2″
3 编辑httpd.conf,找到DocumentRoot “d:/wamp/www/”这项,这是默认根目录路径,但是要更改的不是这个,一直往下找,找到,然后在该后加上如下内容:
Options Indexes FollowSymLinks
AllowOverride all
Order Allow,Deny
Allow from all
Options Indexes FollowSymLinks
AllowOverride all
Order Allow,Deny
Allow from all
4 修改C:/WINDOWS/system3/drivers/etc/host这个文件,用记事本打开,加上如下内容:
127.0.0.1 www.host1.com
127.0.0.1 www.host2.com
好了,然后重启apache,在浏览器里面输入www.host1.com,看看访问到的内容是不是host1这个目录呢。
[Jason.Z]( “[email protected]”) -
@kivu23
多谢@kivu23 提供wamp5下的配置方法!
[kivu23]( “[email protected]”) -
@Jason.Z
嘻嘻,我又回来了,关注你出新东西,学学习
[Jason.Z]( “[email protected]”) -
多谢多谢,最近比较忙,写的东西少,我会尽量努力,争取多写一些好的东西出来。
18OCTOBER |
|---|
话说,最早使用Aptana的时候,还是06年。此后在各种Web的开发场合,一直大力宣传推广该软件的使用。Aptana经历一段时间的收费风波后,现在又开始免费使用了,于是搞了个最近的版本装上,却发现英文的还是不习惯啊,整了个中文包,记录下。
Aptana是一个基于Eclipse的集成开发环境,其最广为人知的是它非常强悍的JavaScript编辑器和调试器。近年来又扩展了Ruby和iphone的集成开发环境,最新版的还支持jquery和其他一些js框架,可以依据jquery的文档,直接在编辑器中对方法给予智能提示,这对我们这些习惯了vs开发的人来说,太有利了。
支持jquery看来很简单,这里有一段拷贝来的说明,大家看看吧,今天没时间测试了,明天再测试:
安装完毕Aptana后,还不能使用智能提示的,应为没有库,我们还要在我们的项目中加入库文件。
首先下载:[https://raw.github.com/aptana/javascript-jquery.ruble/master/support/jquery.1.4.2.sdocml](https://raw.github.com/aptana/javascript-jquery.ruble/master/support/jquery.1.4.2.sdocml) 一定 要保证是.sdocml文件不是文本文档
再下载:http://code.jquery.com/jquery-1.4.1-vsdoc.js或是[http://encosia.com/source/jquery/jquery-1.5.0-vsdoc.js](http://encosia.com/source/jquery/jquery-1.5.0-vsdoc.js)(需要3.0.1版本以上)
至于中文包,其实跟所有基于Eclipse的集成开发环境一样,都要找这个地址去安装,zend啊什么的,都要照这个路径安装就行,不过汉化的不全。
路径是:
http://download.eclipse.org/technology/babel/update-site/R0.9.0/helios/
至于怎么安装,不用说了吧。
18OCTOBER |
|---|
之前,在一个站点的配置中增加了对mp4等文件类型的支持,重装系统后,发现站点的代码报错,错误内容即标题。详细情况如下
之前的IIS配置中,.mp4类型的文件默认无法访问,所以个人在IIS7.5的mime类型中增加了.mp4的类型的支持,但问题是需要每台机器中增加这些设置,不利于团队协作,于是,只好将这些修改体现在代码上,具体实现方式为:
在站点的web.config中增加了
<staticContent>
<mimeMap fileExtension=".mp4" mimeType="video/mp4" />
<mimeMap fileExtension=".m4a" mimeType="video/mp4"/>
<mimeMap fileExtension=".m4v" mimeType="video/m4v" />
<mimeMap fileExtension=".ogv" mimeType="video/ogg"/>
<mimeMap fileExtension=".webm" mimeType="video/webm"/>
</staticContent>
修改后一直正常运行。
前不久本人的电脑C盘空间不足,折腾多次后,无奈格盘重装,分给C盘很大的空间,系统也改为64位。今日重新运行该网站,即报错:
配置错误: 在唯一密钥属性“fileExtension”设置为“.mp4”时,无法添加类型为“mimeMap”的重复集合项。
怪的是代码现在仍在他人机器上正常运行,所以百度寻求答案。有云IIS应用程序池如果是集成模式,改为经典模式即可,但尝试后没有效果。
当然google也查了,这次甚为奇怪,google查不到(某些关键字又被屏蔽了?)。
这时候才想起来自己解决,看说明,似乎是扩展名的设置重复了嘛。于是删除该段配置,发现程序正常运行,且对于.mp4文件仍旧支持。
原来是IIS7.5默认的文件扩展支持增多了,而且,更可恶的是百度到的各种解释,竟然没有一个说这种解决方法的。
不过,为了与大家的一致,这里加了一段多余的配置:
<staticContent>
<remove fileExtension=".mp4" />
<remove fileExtension=".m4a" />
<remove fileExtension=".m4v" />
<mimeMap fileExtension=".mp4" mimeType="video/mp4" />
<mimeMap fileExtension=".m4a" mimeType="video/mp4"/>
……
</staticContent>
14OCTOBER |
|---|
最近搞了几个免费的php空间,分别在上面安装了Joomla, Wordpress, Drupal这几个开源的php内容管理系统,本来还想试试xoops,但免费空间上传确实太慢了,没能完成其安装和测试,先拿这几个比比吧。
wordpress使用时间相对长一些,最初在新浪sae中搭建自己的博客使用的就是这个系统,插件很丰富,国内也有很多专门针对wordpress进行开发的团队,至于提供theme和plugin介绍和下载的站点也不计其数。代码看起来也比较简单,稍微有些php的知识,就可以完成代码及功能的修改。汉化也很完善,不懂英语的人也可以使用该平台迅速搭建自己的博客平台。
当然,wordpress定位于博客系统,做博客非常合适。但是做其他就有点困难了。前些日子仅仅了解wordpress,对一个要做自己公司网站的朋友推荐了wordpress系统,当然也可以实现,毕竟稍微稍有点别扭。只能说,在简单易用上,wordpress是胜了的。
wordpress安装皮肤和插件还算比较简单,下载了之后,解压,然后上传到web站点的对应目录即可。
Joomla!汉化还是比较完整的,国内有许多专门的Joomla!爱好者站,后台的管理界面很漂亮,而且安装过程中还可以安装示例数据,这样就比较容易理解哪里的数据对应哪部分内容。
Joomla!有很多特别漂亮的皮肤,当然其中大多是收费的(谁闲的没事去做特别漂亮的皮肤,然后免费共享出去啊!),也有很多收费的组件和扩展。
说白了,Joomla!是一种形式的生态系统,许多人在免费使用它,也有许多人或公司依靠制作组件、扩展和皮肤来赚钱。
不过互联网上也常常传来不同的声音,说Joomla!不好的,怪它收费的,说要离开Joomla!去做drupal的。其实是其定位不同,不同的人适合不同的情况而已。
Drupal国内也有很多爱好者,据说文档很全。但我看到的结果是在国内爱好者还是比较少的,至少要比Joomla要少。而且几乎没有中文的文档。
Drupal的优势是其架构很优秀,使用该架构为基础,在其上进行二次开发会比较顺手。当然,这也有一定的学习成本,像我这样刚刚试着装了Drupal的新手,完全不知道在后台应该干啥。
Drupal代码只有2M多,上传到免费空间也就不成问题,网上说的优势也挺多。但是我分别在本地和免费空间上安装后,发现它运行速度明显比Joomla!慢,而且慢的不是一点半点。
插件和皮肤的安装特别简单,它和Joomla都提供了直接根据下载路径进行安装的功能,甚是方便。但其对插件的管理方式又有所不同,Drupal全都罗列出来,看着很头疼。而且插件启用的多了,内存竟然占满了(免费空间大概仅仅提供几十兆的内存),新手遇到大问题了,哈哈。
先看看各公开的参数比较:
系统要求
Drupal
Joomla
Wordpress
费用
免费
免费
免费
操作系统
任何
任何
与操作系统无关
许可
GNU GPL
GNU/GPL v2
GNU GPL
编程语言
PHP
PHP
PHP 4.2版本或更高
根权限(Root Access)
没有
没有
没有
命令行权限(Shell Access)
没有
没有
没有
网络服务器
Apache, IIS
Apache
Apache, mod_rewrite
安全
Drupal
Joomla
Wordpress
审核跟踪
是
否
有限的
验证码
自由添加*
自由添加
否
内容核准
是
是
是
Email核实
是
是
自由添加
登录历史
是
是
自由添加
插件认证
是
是
是
问题通知
否
否
自由添加
Sandbox
否
否
有限的
Session 管理
是
是
自由添加
SSL 兼容
是
是
是
版本控制
是
自由添加
自由添加
支持
Drupal
Joomla
Wordpress
商业手册
有
有
无
商业支持
有
有
无
商业培训
有
有
无
开发者社团
有
有
有
在线帮助
有
有
有
插件 API
有
有
有
专业服务
有
有
无
论坛
有
有
有
第三方开发者
有
有
有
用户讨论会
有
有
有
易用性
Drupal
Joomla
Wordpress
拖放内容
自由添加
否
是
邮件讨论
自由添加
自由添加
有限的
友好的 URLs
是
是
是
批量上传
自由添加
是
自由添加
拼写检查
自由添加
否
自由添加
样式向导
有限的
否
否
订阅
自由添加
自由添加
是
模板语言
有限的
是
否
用户界面层
否
是
是
撤消编辑
有限的
否
自由添加
所见即所得编辑
自由添加
是
是
Zip归档
否
否
自由添加
性能
Drupal
Joomla
Wordpress
高级缓存
有
有
自由添加
数据库复制
有限的
无
无
负载平衡
有
有
无
页面缓存
有
有
自由添加
静态内容输出
无
无
有限的
管理
Drupal
Joomla
Wordpress
广告管理
自由添加
有
需要插件
剪贴板
无
无
无
内容时序安排
自由添加
有
自由添加
在线管理
是
是
是
主题 / 皮肤
有
有
有
回收管理
无
有
无
网站统计
有
有
自由添加
在线主题设计管理
是
是
是
在线翻译管理
有
自由添加
有限的
流程自动化引擎
有限的
无
无
灵活性
Drupal
Joomla
Wordpress
内容重用
有限
是
否
可扩充的用户资料
是
是
否
多语言内容
是
自由添加
自由添加
多网站部署
是
自由添加
否
RSS
有
有
有
FTP 支持
有限的
是
自由添加
UTF-8 支持
是
是
是
CGI 模式支持
是
是
否
元数据(Metadata)
有
有
有
URL重写
是
是
是
商业
Drupal
Joomla
Wordpress
代理跟踪
自由添加
自由添加
无
库存管理
自由添加
自由添加
无
可插式支付
自由添加
自由添加
无
可插式货运
自由添加
自由添加
无
可插式传真
自由添加
自由添加
无
销货点
无
自由添加
无
购物车
自由添加
自由添加
无
wordpress适合做博客;Joomla!适合在无技术能力上的企业网站搭建,功能要比wordpress强大,美中不足就是想要的东西都得收费;Drupal,做啥都行很好很强大,不过你要是不会玩,还真玩不动,就是有门槛。
28SEPTEMBER |
|---|
土豆的api很有问题,其登录使用了OAuth的认证,相关说明却直接链接到OAuth的官网去了,那总得把自己的认证服务的路径写上去吧!
找了半天,终于从其论坛中找到以下几个地址,
Tudou OAuth服务URL
Request Token URL: 获取未授权的Request Token服务地址;
http://api.tudou.com/auth/request_token.oauth
User Authorization URL: 获取用户授权的Request Token服务地址;
http://api.tudou.com/auth/authorize.oauth
Access Token URL: 用授权的Request Token换取Access Token的服务地址;
http://api.tudou.com/auth/access_token.oauth
论坛说文档中增加了,其实文档中还是没有。先记下,免得忘记。
23SEPTEMBER |
|---|
发现一些与分享url有关的api接口,记录下
Facebook - http://graph.facebook.com/?ids=http://www.seomoz.org
Twitter - http://urls.api.twitter.com/1/urls/count.json?url=http://www.seomoz.org
Linkedin - http://www.linkedin.com/cws/share-count?url=http://www.seomoz.org
Stumbleupon - http://www.stumbleupon.com/services/1.01/badge.getinfo?url=http://www.seomoz.org
Delicious - http://feeds.delicious.com/v2/json/urlinfo/data?url=http://www.seomoz.org
Google Buzz - https://www.googleapis.com/buzz/v1/activities/count?alt=json&url=http://www.seomoz.org
Reddit - http://www.reddit.com/api/info.json?url=http://www.seomoz.org
参考资料:
1、Seo blog:http://www.seomoz.org/blog/how-to-track-your-social-media-strategy
23SEPTEMBER |
|---|
最近工作比较乱,不想多说。研究一周多的php工作要暂停,不知道到时候恢复的时候是不是能想的起来。先记录下来一部分,做个备忘,免得将来又从头开始。
环境配置不记录了,前面有提到。zend framework就是一组类库,下载下来放在站点根目录的library目录中即可,index.php做程序入口,所有访问经该入口路由到具体的处理。
根据当前的开发方式,实际开发时,每创建一个页面,有以下几个步骤:
在application目录的models目录下,创建表的表对象,其实很简单,通过zend studio可以直接创建,代码如下:
<?php
require_once ‘Zend/Db/Table/Abstract.php’;
class feed extends Zend_Db_Table_Abstract
{
/**
* The default table name
*/
protected $_name = ‘feed’;
}
这是一个简单的feed对象,对应数据库中的feed表,有了它以后,可以直接用面向对象的方式访问该表,而不需要编写sql语句。当然,也可以在里面封装些数据库的常用操作方法。数据操作的方法记录在后面。
比如
routes.register.type = “Zend_Controller_Router_Route_Static”
routes.register.route = “user/register”
routes.register.defaults.controller = “register”
routes.register.defaults.action = “index”
这是一段注册页面的配置,注册类型是静态地址,地址为user/register,对应的控制器是register,控制器中的action是index。
再看另一段配置:
routes.shot.route = “project/shot/list/:pid”
routes.shot.defaults.controller = “shot”
routes.shot.defaults.action = “index”
routes.shot.map.1=“pid”
这里pid是参数,意为project/shot/list/xx 这样一个路径xx就是pid参数,可以用request方式获取到值。
在我们的方法中,所有程序都放在了modules文件夹中,在modules/default/controller中创建控制器,比如我们路由中有个register的控制器,其名称就是:registerContrlller.php。
看下面这个代码:
<?php
require_once ‘Zend/Controller/Action.php’;
class shotController extends Zend_Controller_Action{
public function init ()
{
$this->_helper->layout()->setLayout(“default”);
}public function indexAction ()
{…
}
}
这个控制器是针对前面第二段路由配置的,类名是shotController,继承自
Custom_Controllers_AuthController(这个zendstudio会自动生成,不用担心记不住),indexAction就是该路由的触发的动作,一个控制器中可以有多个action。init方法是初始化方法,比如某控制器中有多个action,则每个action执行前都会执行init方法。
所谓模板,就是以phtml为后缀的一个html块,不过其中可以写php代码,示例:
New Shot
<?php
echo $this->form;?>
Shot List
shot budget
<?php
if($this->shots!=null){
foreach ($this->shots as $u){
?>
<?php echo $u->name ?> <?php echo $u->budget ?> id?>‘>edit Shapez Theme
· Home Playground About
09
SEPTEMBER确认的php和apache环境配置:
1.请PHPer确认你的PHP版本是否在5.2.0以上..如果不是的话..请更新到5.2.0,否则.Zend Framework 好像用不了。
PHP源码最新版下载地址为:http://www.php.net/downloads.php.
2.你的PHP环境配置好了之后,请打开php.ini文件,确认PDO扩展是否打开:
;extension=php_mysql.dll
;extension=php_pdo.dll
;extension=php_pdo_mysql.dll
修改
extension=php_mysql.dll
extension=php_pdo.dll
extension=php_pdo_mysql.dll 去掉分号
还有:
; Windows: “path1;path2”
include_path = “.;c:phpincludes;e:wwwrootzendflibrary” (zend framework的libary支持)3.打开APACHE文件夹里面的httpd.conf文件.配置mod_rewrite.so的支持
#LoadModule rewrite_module modules/mod_rewrite.so
修改:LoadModule rewrite_module modules/mod_rewrite.so (去掉#号)才支持Zend framework4. 查找到httpd.conf文件,如果AllowOverride为None的话..请一定把None都改成all.这样你写.htaccess这样的文件才会起到作用..
将所有的:AllowOverride None替换AllowOverride all 支持.htaccess文件
5.重新启动你的APACHE服务器.这样我们的PHP环境就可以运用Zend Framewrok了.
安装zend tool
下载zend framework的zip文件,解压,配置环境变量的path路径:
在path变量的结尾处增加:
;D:Program FilesZendZendFrameworkbin
该路径是解压目录中的bin目录的路径,注意前面的;不用丢掉
测试安装是否成功:
在命令行中输入:
zf show version
这句话是显示版本,如果顺利,会输出版本,否则会报错。我这里显示:
zend framework version:1.11.10
创建项目
可以使用zendtool创建项目,命令行:
通过命令行进入站点根目录(一般是htdocs目录),键入命令:
zf create project zf_proj
然后回车,会创建一个zf_proj的目录,里面自动创建一些必要的文件和文件夹。目录结构如下:
如果没有安装phpuint这个单元测试工具的话,会有提示,不过没关系,不用理它。
当然,你也可以把下载的目录拷贝到自建的目录,刚才那个命令并未做任何配置的工作。
一般来说,图片、js和css文件要防止public目录下,而library文件夹里面要放下在到的zendframework的文件,就是把压缩包里的library文件夹里的内容(其实只有一个zend文件夹)拷贝过来就可以了。
好了,现在我们测试下,打开以下地址:
就可以看到欢迎页面,这里就不上图了。
06
SEPTEMBER这段时间,工作比较乱,各种开发环境的搭建、尝试多个开发工具,Win7的C盘hold不住了,本来我觉得50G的C盘不算太小,但某天却惊奇的发现C盘剩余空间是0!当然不久就变成100多兆了,用360清理助手也只能清理出300多兆(我平常经常做清理,垃圾本来就不多的)。但是想想我的软件都装到D盘去了啊,C盘只安装了那些必须安装到C盘的东西,什么visual studio、sqlserver还有windows phone开发包什么的,这些装别的盘符麻烦,而且本来50G的空间我觉得并不小。所以我决定手动清理下C盘。
清理之前,我们先看看下面这个搞笑图片:
盖茨的磁盘空间理论
卸载不必要的程序
首先,把该卸载的卸载掉。虽然大多数软件没有安装在C盘,但VS的插件似乎必须的装到C盘,那个intel C ++的编译器,再加上他的ipp库,足足占了2个G的空间,还有一些vs内置的插件,比如水晶报表啥的,不需要用的都卸载掉,当然这个是特例。
清理用户文件夹
第二个需要清除的文件夹是用户文件夹:
C:UsersuserAppData
users是电脑上当前用户的文件夹,电脑如果自己用,就不要建那么多用户了,每个用户都需要占一部分的空间,不常用的用户文件夹,直接删除即可,当然一般也没有多大空间,我们这里要清除的是最常用的用户的appdata文件夹。
其实这个文件夹是比较有用的,如果整个删除的话,可能会引起部分程序无法使用。
这个文件夹中又分为三个文件夹,其中Roamin文件夹和local文件夹占据了大量的空间(各占1G左右)。仔细分析其中的文件夹后发现,占据空间最大的分别是微软、苹果和谷歌。要命的是,google的谷歌浏览器程序文件竟然在这里,更新文件也不会自动删除,浏览器缓存高达800M。
谷歌浏览器缓存路径默认为:
C:UsersuserAppDataLocalGoogleChromeUser DataDefaultCache
建议直接删除。
还有微软的文件夹中,带了很多不常用的程序,如果不需要,就删掉吧,路径:
C:UsersuserAppDataLocalMicrosoft
另外,如果你使用Windows Live Mail,最好把默认的邮件保存路径改到其他路径,否则该文件夹也将是容量杀手。
清理windows文件夹
前面讲的清理路径,都不是特别大量的文件夹,但windows目录占用了20G的空间,能不能清理下该目录呢?
百度上找到一个清理目录,但所述的文件都不大,而且实际去删除的时候报没有权限(我没用的administrator),就放弃了。
系统更新目录
记得早在win2003时,windows更新目录会占很大的空间,可以直接删除:
C:WindowsDownloaded Installations
现在win7中这个目录仍然存在,但我这里是空的,不知道是不是360帮忙清除掉了,该文件夹可以安全删除。
C:WindowsDownloaded Program Files
这个是ie上安装的插件什么的目录,不需要的插件可以直接删,不过都不大。
安装目录
查看C盘中有个隐藏目录
C:WindowsInstaller
该目录下竟然有10多G的占用,这个目录应该是安装程序是保留的临时文件。记得之前的windows安装程序的卸载直接放在程序目录里面,而且也不大,但这个目录里面放的,全是很大而且改名的msi或msp文件,能不能直接删呢?
经查,大多数的说法是该目录的文件如果删除,程序就无法卸载,也有人说删除后一部分功能不能使用了,比如office会彻底不能用。
其中有个$PatchCache$文件夹,看到有人评论说该目录可以完全删除(也有人说不可以删的),相信这句话是有风险的,但我还是信了。我删掉该目录(回收站),然后重启了电脑,打开office等常用的程序的,发现还能用,现在可以确信了。
这就是说:
C:WindowsInstaller$PatchCache$
目录是可以完全删除的,当时我的这个目录清出来6G的空间。
对于Installer下面的msi文件,据说有个工具可以帮忙清理,下面有段来自网上的摘录:
在命令行,通过命令msizap.exe G可以删除一些孤儿文件:
C:>msizap.exe G
MsiZapInfo: Performing operations for user S-1-5-21-1935655697-1409082233-839522115-1003
Removing orphaned cached files.
Removed file: C:WINDOWSInstaller102db95.mst
Removed file: C:WINDOWSInstaller102dba2.mst
Removed file: C:WINDOWSInstaller102dbac.mst
Removed file: C:WINDOWSInstaller337c229.mst
Removed file: C:WINDOWSInstaller3b1ae.mst
Removed file: C:WINDOWSInstaller41485c5.mst
Removed file: C:WINDOWSInstaller65a2f.mst
Removed file: C:WINDOWSInstaller76bf7.mst
Removed file: C:WINDOWSInstaller9419741.mst
Removed file: C:WINDOWSInstaller9419770.mst
Removed file: C:WINDOWSInstaller9419778.mst
Removed file: C:WINDOWSInstallera43b7be.mst
Removed file: C:WINDOWSInstallerf8fe2f1.mst网上还可以找到这样一个工具:
Windows Installer UnUsed Files Cleanup Tool (简称WICleanup)是一个用于清理 Windows Installer 冗余文件的工具。
软件主页为:http://www.kztechs.com/wicleanup/
下载地址:http://www.kztechs.com/wicleanup/wicleanup.zip我试了,确实能删除,删掉几个msi和mst文件,但问题是删掉的都是小文件。仔细看这段中的孤儿文件,估计只是检查某些程序已经卸载但没有删除掉的安装文件,所以不解决大问题,还得靠自己手工。
把详情打开,看看哪些文件永远都不需要删除的,自己删掉吧,比如vs的几个更新,有300兆的,有500兆的,挑大个的删。
总结
经此次清理,C盘空间达到12G,又可以折腾一阵子了。其实之前也做过多次清理,但从未触及Installer文件夹,此次也算有所发现吧。
27
AUGUST上个月被邀请了加入google+,也没怎么玩,不久前有朋友咨询有没有邀请,发现还是有部分朋友无法注册,本人有一些邀请,如果谁还需要邀请,就留言留下你的邮箱就可以了。
附访问方法:
(C:windowssystem32driversetc)添加以下内容既可
#—— Google+ ——
203.208.46.29 plus.google.com
203.208.46.29 talkgadget.google.com
203.208.46.29 picasaweb.google.com
203.208.46.29 lh1.ggpht.com
203.208.46.29 lh2.ggpht.com
203.208.46.29 lh3.ggpht.com
203.208.46.29 lh4.ggpht.com
203.208.46.29 lh5.ggpht.com
203.208.46.29 lh6.ggpht.com
203.208.46.29 lh6.googleusercontent.com
203.208.46.29 lh5.googleusercontent.com
203.208.46.29 lh4.googleusercontent.com
203.208.46.29 lh3.googleusercontent.com
203.208.46.29 lh2.googleusercontent.com
203.208.46.29 lh1.googleusercontent.com
25
AUGUST最近在研究各种api,本来要先搞人人的api,但人人的各种申请太繁琐了,.NET的skd很好用,代码写的很简单,但是php的那份死活调不通,作者也已经好几个月没有更新过了,同时有几个特别想测试的api偏偏需要申请,你说这学习研究阶段,懒的去申请,所以就暂缓搞人人的api了。这两天在新浪云里面整了个应用,顺便了解下新浪的api。
首先,需要注册新浪开发者账户,http://open.weibo.com/,当然可以使用新浪微博登录,不过对账户信息有限制,很简单,通过邮箱认证下即可。(今天很奇怪,新浪的认证用不了网易的163邮箱,估计这俩公司又闹起来了,呵呵,qq的倒能用)。
然后要创建一个新应用,一共有三种形式的应用:
- 站内应用
- 就是创建站点,说是站内应用,其实从站外也可以访问的;
- 可以从应用设置中设置站内访问地址,和实际地址,这跟非死不可一样,就是在app页面中用iframe中嵌套的那个页面;
- 安全设置中可以选择是否限定域名,如果不限定域名,那么你可以用这个appkey和secret key做很多网站,学习测试最好不要限制,如果运营最好要做限制。
- 客户端
- 可以开发客户端。
- 其他
- 浏览器插件什么的。
我们创建的自然应该是站内应用了,设置还算比较简单。新浪的开发文档看起来很简单,至少比非死不可的简单多了:
http://open.weibo.com/wiki/%E9%A6%96%E9%A1%B5
它的php sdk在这里:
http://code.google.com/p/libweibo/
直接就有介绍,看着不错,下载了一份放到apache的htdocs目录,然后按照介绍中写的进行操作。
有问题了,说明里面有:
CANVAS_PAGE为“应用页面”中设置的”站内应用地址“
这个说法,但config.php中并不包含这部分,算了,不管了,估计用不着。但运行的时候却出错了。
错误1: Notice: Undefined index: SCRIPT_URI in D:Apache2.2htdocssinaappindex.php on line 13
这个错误仅仅是个提示,但他会影响到认证成功后返回的路径。网上也有很多人问该问题,有人说要启用apache的mod_rewrite模块,但我试过了,启用似乎不解决问题,也有人说需要启用php的php_curl扩展,我查了,我也启用了。问题的出处是访问$_SERVER[‘SCRIPT_URI’]这个值的时候,访问不到,而许多人也说SCRIPT_URI并不是内置的。最终也没有查到,不知道作者是不是启用了其他不常用的扩展或者模块,但是我们可以自己来处理:在访问前定义下:$_SERVER[‘SCRIPT_URI’]=‘http://yoururl’,就可以了。
错误2:乱码
认证成功后,页面上全是乱码,用记事本把所有的php文件另存为utf8,windows的(似乎作者上传的是linux下编写的php,但是问题仍旧很奇怪)
错误3:浏览器兼容性
这个还不知道是怎么回事,反正用chrome浏览器打开后,运行是不正常的,换firefox下可正常使用,这个实在奇怪,服务器端的代码,难道还挑客户端的浏览器?
好了,今天先写到这里吧。对这些问题有研究的朋友,不妨告知。
18
AUGUST近期参与了一些开发facebook相关应用的项目,由于国内访问困难,相关资料查找及测试都比较困难,起初只做一些简单的调用,比如要从手机上打开facebook的发送消息的页面,并把对应的参数传递过去。这个本应该是个很简单的工作,但由于我们对其并不熟悉,相关资料都查过好多。本想咨询做过facebook相关项目开发的iphone团队,可对方使用的是ios上的一个sdk,至于要打开哪个页面,就不知道了。当然,后来这些问题都解决了,我上一篇文章中有对具体方法的描述,但感觉就像一个外行在做开发,我觉得个人站长都应该对此很明白(这些内容甚至不需要使用facebook的api)。
现在另一个项目是开发facebook上面的web app,开发web app需要注册成为facebook开发者,这个很简单,进入
https://developers.facebook.com/
网站自己注册就可以了,如果不是开发者,会提示要注册激活,美国人可以直接绑定手机号码激活,我们只好使用双币信用卡激活了,反正标着visa和万事达卡的信用卡都可以激活(我用了一个即将到期的万事达卡,现在还不知道到期后会不会让重新激活),激活并不会收费,放心激活就行了。
入门级javascript sdk:
注册过程不详细写了,很简单,需要注意的是:
1、website项目中的站点url和domain两个项目是不需要http://开头的;而app on facebook的canvas URL,是需要写http的。而且这一段是实际上从facebook打开这个app时,嵌入的iframe的地址。
2、如果本机测试,可以直接使用localhost作为地址,真正上线的时候记得改回来就行。也可以用改host文件的方法随便指定一个域名,加端口访问也没问题,就是不能用ip地址,提交通不过。
注册好了,就开始开发吧,我们首先不使用C#的sdk,直接使用facebook提供的javascript api:
1、创建一个静态页面,引入脚本
http://connect.facebook.net/en_US/all.js
除此之外,还要引用jquery,写脚本方便嘛;
2、另外需要在页面上方这么一个div:
如果不放,就会脚本报错。放哪儿都行,也不显示。但是要放在fb初始话的脚本前面。
3、写段脚本吧,这里我们有个登录按钮:
FB.init({ appId: ‘{这里是appid}’, status: true, cookie: true, xfbml: true });
$(“#FBLogin”).click(function () {
FB.login(function (response) {
if (response.session) {
// 登录成功
window.location = ‘about.aspx’
} else {
//
}
}, { perms: “publish_stream” });
});这里首先初始化了fb对象,需要appid,它在创建时自动生成。第二个呢注册了一个登录按钮的点击事件,点击后直接调用FB封装好的login方法,第一个参数是回调函数,第二个参数是一个json对象,这里注明了该app的访问权限是publish_stream,差不多是最小权限了吧。
所有权限的资料和其他api的资料可以直接访问以下地址查找:
https://developers.facebook.com/docs/
刚入个门,其实后面的对照文档中的,就都可以了,比如fql:
FB.api({
method: ‘fql.query’,
query: ‘SELECT name, pic FROM profile WHERE id=’ + uid
}, function(response){
fb_User.name = response[0].name;
fb_User.pic = response[0].pic;
…});
这是访问用户配置表,返回用户名称和头像。graph api相比更加简单,先看例子
FB.api({path}, ‘GET’, {}, function(response){
if (!response || response.error) {
alert(‘Error occured’);
}
else {
…
}
})path就是对应访问内容的路径,比如访问个人资料,就填me,访问某视频的信息,可以直接填视频的id。很简单吧,回调函数中都是返回的json对象,书写也很便利。
项目用到一半,除了登入退出外,也就用了这么两类的api,很简单吧。
Facebook C# SDK
项目开始时查到过两个SDK,一个叫做Facebook C# SDK,更新的很快;另一个叫做Facebook toolkit什么的,从名字上看似乎是微软自己的帮助系列,代码也很庞大,但有半年多没有更新了。而据我所知,今年6月份facebook的api还有改动(突然某天代码失效,后来发现api改了,竟然不兼容!)。所以我们选择更新的比较快的这个。
代码地址:
http://facebooksdk.codeplex.com/
仅支持vs2010,你可以下载了dll然后加载到项目的引用中去。今天我们介绍另外一种引用方式:
1、在vs2010中,新建web站点(以次举例,也支持mvc、windowsphone),右键项目,选择 Add Library Package Reference,
把最后那两个安装仅上就可以了。如果没有看到的话,可以从右侧搜索facebook就可以找到了。安装上之后,不仅对应的dll会被拷贝进了,web.config中也会生成对应的两段配置
configSections段中增加了
configuration段中增加了
我们把我们申请到的appid和appsecret填入即可。
前台的页面我们还是需要按照前面讲的javascript的方式进行编写,只是我们appid的位置可以这么写:
<%: Facebook.FacebookApplication.Current.AppId %>
到这里,第一步也算迈出去了。详细使用方法,我们举几个例子:
获取用户信息:
// Using dynamic (.Net 4.0 only) var client = new FacebookClient(); dynamic me = client.Get("me"); string firstName = me.first\_name; string lastName = me.last\_name; string email = me.email; // Using IDictionary<string, object> (.Net 3.5, .Net 4.0, WP7) var client = new FacebookClient(); var me = (IDictionary<string,object>)client.Get("me"); string firstName = (string)me\["first\_name"\]; string lastName = (string)me\["last\_name"\]; string email = (string)me\["email"\];获取某一条post的详细信息
// Using dynamic (.Net 4.0 only) var client = new FacebookClient("my\_access\_token"); dynamic result = client.Get("19292868552\_118464504835613"); string id = result.id; string fromName = result.from.name; string fromCategory = result.from.category; string message = result.message; int likes = result.likes; foreach (dynamic comment in result.comments.data) { string commentId = comment.id; string commentMessage = comment.message; }发布一条信息
var client = new FacebookClient("my\_access\_token"); dynamic parameters = new ExpandoObject(); parameters.message = "Check out this funny article"; parameters.link = "http://www.example.com/article.html"; parameters.picture = "http://www.example.com/article-thumbnail.jpg"; parameters.name = "Article Title"; parameters.caption = "Caption for the link"; parameters.description = "Longer description of the link"; parameters.actions = new { name = "View on Zombo", link = "http://www.zombo.com", }; parameters.privacy = new { value = "ALL\_FRIENDS", }; parameters.targeting = new { countries = "US", regions = "6,53", locales = "6", }; dynamic result = client.Post("me/feed", parameters);访问ADS API
var client = new FacebookClient("my\_access\_token"); dynamic reportSpec = new ExpandoObject(); reportSpec.report\_type = "perf"; reportSpec.summarize\_by = "ad"; reportSpec.agg\_time = "daily"; reportSpec.filter\_obj\_ids = new string\[\] { }; reportSpec.internal\_columns = false; reportSpec.time\_start\_offset = 172800; reportSpec.time\_stop\_offset = 86400; dynamic scheduleSpec = new ExpandoObject(); scheduleSpec.name = "test\_schedule"; scheduleSpec.time\_next\_ref = DateTime.UtcNow.AddDays(1).ToString(); scheduleSpec.report\_spec = reportSpec; scheduleSpec.frequency = 1; scheduleSpec.status = 1; scheduleSpec.email = 1; dynamic parameters = new ExpandoObject(); parameters.method = "ads.createAdreportSchedules"; parameters.account\_id = 1234567891235; parameters.schedule\_specs = scheduleSpecs; parameters.flags = 1; // Debug Mode dynamic result = client.Post(parameters);创建相册
// create album dynamic albumDetails = new ExpandoObject(); albumDetail.name = "test album"; dynamic fbResult = fbApp.Post('/me/albums', albumDetails); var albumID = fbResult.id; uploadPhotos(albumId);上传图片
private void uploadPhoto(string albumID) { var fbUpl = new Facebook.FacebookMediaObject { FileName = @"Some.png", ContentType = "image/png" }; var bytes = System.IO.File.ReadAllBytes(@"C:somePath" + fbUpl.FileName); fbUpl.SetValue(bytes); var photoDetails = new Dictionary<string, object>(); photoDetails.Add("message", "test photo"); // this will appear on the wall photoDetails.Add("image", fbUpl); // the name of this parameter does not matter var fbResult = fbApp.Post(@"/" + albumID + @"/photos", photoDetails); var result = (IDictionary<string, object>)fbResult; // Or we could use dynamic.. only the photo "id"(s) come back }上传视频
var fb =newFacebookClient("access_token");
dynamic parameters =newExpandoObject();
parameters.source =newFacebookMediaObject { ContentType ="video/3gpp", FileName ="video.3gp"}.SetValue(File.ReadAllBytes(@"c:video.3gp"));
parameters.title ="video title";
parameters.description ="video description";
dynamic result = fb.Post("/me/videos", parameters);
Console.WriteLine(result);可获的进度条和可取消的使用方式
var mediaObject =newFacebookMediaObject
{
ContentType ="image/jpeg",
FileName = Path.GetFileName(_filename)
}
.SetValue(File.ReadAllBytes(_filename));
var fb =newFacebookClient(_accessToken);
fb.PostCompleted += fb_PostCompleted;
fb.PostAsync("/me/photos",newDictionary<string,object> { {"source", mediaObject } });
fb.UploadProgressChanged += fb_UploadProgressChanged;
publicvoidfb_UploadProgressChanged(objectsender, FacebookUploadProgressChangedEventArgs e)
{
progressBar1.BeginInvoke(
newMethodInvoker(() =>
{
var totalBytesToSend = e.TotalBytesToSend;
var bytesSent = e.BytesSent;
var state = e.UserState;
progressBar1.Value = e.ProgressPercentage;
}));
}
publicvoidfb_PostCompleted(objectsender, FacebookApiEventArgs e)
{
if(e.Cancelled)
{
var cancellationError = e.Error;
MessageBox.Show("Upload cancelled");
}
elseif(e.Error ==null)
{
// upload successful.
MessageBox.Show(e.GetResultData().ToString());
}
else
{
// upload failed
MessageBox.Show(e.Error.Message);
}
}可以使用fb.CancelAsync()进行结束上传。好了就举这些例子吧,例子代码来源于作者博客。总体感觉使用还是比较方便的。
18
AUGUST就是样式有些难看,能不能再美一点?
[白兔]( “[email protected]”) -
就是样式有些难看,能不能再美一点?
自己创建的blog?很好很喜欢,加油喽!
[白兔]( “[email protected]”) -
自己创建的blog?很好很喜欢,加油喽!
还不太会调整,在学习,多谢关注
Jason.Z -
还不太会调整,在学习,多谢关注
请问FACEBOOK的集成require ‘src/facebook.php’;
[kivu]( “[email protected]”) -
请问FACEBOOK的集成require ‘src/facebook.php’;
// Create our Application instance (replace this with your appId and secret).
$facebook = new Facebook(array(
‘appId’ => ‘175974682484604’,
‘secret’ => ‘2cc482fd8cb824b1b95a3afe51b50aac’,
));设置已经正确了,为什么不可以用呢,是不是还要其它条件设置?谢谢!
你说的是php的sdk么?
Jason.Z -
你说的是php的sdk么?
@kivu
Jason.Z -
@kivu
facebook 官网的文档中,初始化确实这一段就可以了:require_once(“facebook.php”);
$config = array();
$config[‘appId’] = ‘YOUR_APP_ID’;
$config[‘secret’] = ‘YOUR_APP_SECRET’;
$config[‘fileUpload’] = false; // optional
$facebook = new Facebook($config);你说不可以的是什么情况?
我刚才下载了最近版的facebook,PHP sdk,测试过程中也遇到问题了,提示curl执行失败。…
Jason.Z -
我刚才下载了最近版的facebook,PHP sdk,测试过程中也遇到问题了,提示curl执行失败。
就是说,首先需要启用php.ini中的extension=php_curl.dll扩展;
其次,在访问facebook某些api的时候,一些访问路径被GFW给墙了,如果要做测试,最好使用VPN。
另外,似乎sdk中的key和安全码已经失效了,需要自己再申请一个帐号,创建应用,并设置应用可以通过url访问,然后把key和安全码换成自己的,否则会在链接登录到facebook时报一个应用错误的提示。
不知道你遇到的是否如此。欢迎来信交流。
@Jason.Z …
[kivu23]( “[email protected]”) -
php sdk后面我知道为什么失效了,要先给当前用户LOGIN,允使操作这个应用程序。
现在我就有点想不明白,这个应用程序本想让浏览客户看到PHP SDK调出来数据显示在专题的,如果不”LOGIN,允使操作这个应用程序”,$facebook->getUser()反回是null。
这是因为demo中的登录过程,包含了部分app授权的过程。我记得在js版的facebook登录的方法…
Jason.Z -
这是因为demo中的登录过程,包含了部分app授权的过程。我记得在js版的facebook登录的方法中,可以直接写进入访问权限的参数,代码大概是这样的
FB.login(handleSessionResponse, {
perms: ‘user_videos,publish_stream’
});
我记得这样写之后,从facebook直接点开该app时,会自动授权,不需要重新登录。
你的链接我看到了,我也研究下。
31
JULY近期有个mobile的项目,需要在项目中加两个功能,是在twitter和facebook上发消息的按钮,客户不想自己做界面,希望直接调用各自网站上的页面发消息。这俩网站国外很流行,国内却无法访问,也没怎么用过,不甚了了。于是开始查资料。
1、Twitter
twitter上发消息其实很简单,很多地方提供生产twitter按钮的功能,访问也完全支持get方式的访问。就是说完全可以直接输入一个url地址,就可以打开对应的twitter页面,且可以传入想要的信息内容。对比国外网站对twitter的引用和官网上提供的button按钮的说明,我们很快找到了这样一个地址,示例如下:
http://twitter.com/intent/tweet?status=i%20think%20is%20 @zsz417 http://baidu.com
这个地址用来修改用户的状态(status),不过twitter似乎也就有个状态而已,所有的信息无非就是通过status来表现的。你看,有内容,有url,还可以@某人,直接写入,就很完整了啊。当然,由于twitter中对状态有140个字符数的限制(国外的140个字符能表达充足的含义么),如果url比较长,最好通过短网址服务把网址做短喽。经过简单的几个测试后,就把该链接交给mobile开发的同事去用。
不过,很快mobile组那边就找过来,说每次发twitter都得登录,那是相当的麻烦。经跟踪发现,用mobile访问twitter,它会自动重定向到mobile.twitter.com进行登录,而且cookie仅仅对mobile.twitter.com有效(当然对当前连接也是有效的),而这个网址是web版本的,它需要www.twitter.com 这个网址上的cookie。(这个twitter也太不地道了,咋能这样呢?)
由于英语欠佳,资料查起来也很费力,而且baidu上面的资料几乎没有有效的,而google搜索相关敏感词语时,时不时就被墙了。喵了个咪的,人在天朝,全凭想象啊,猜吧。改下访问路径:
http://mobile.twitter.com/intent/tweet?status=i%20think%20is%20 @zsz417 http://baidu.com
键入地址后,竟然跳到了@intent这个用户的页面,我了个去,还是加密连接。看来这次猜的不给力啊。
边查边猜,发现另外一种写法:
那么,是不是它有对应的mobile版的写法呢:
https://mobile.twitter.com/?status=
这个加密连接,是自动重定向上去的(我修改agent为iphone了)。那在mobile 上直接访问这个,岂不更好?急忙交割mobile的同事去测试,OK,通过了。
2、Facebook
facebook的资料还是有一些的,比较适合我们的,资料上有这样一个链接(注意这个用法已过期):
http://www.facebook.com/sharer/sharer.php?u=http://www.sina.com.cn&t=this is sina
其中参数u是链接,t是标题,意味着可以分享一个网页,并且可以定义title。09年很多网页中也都是这么写的。但经过测试,facebook对这个sharer进行了优化,它自动抓取了网页的title以及首页的title,甚至抓取了其中的某些图片和描述信息,另外还有个用于用户输入的框。也就是说,现在这个只有u参数的写法还是对的,t这个参数已经无效了。几经测试,并不能完成客户的需求,只好再查找其他资源。(我并没有从facebook api文档中找到这个sharer的用法,惭愧)。
在facebook api文档的对话框项目中,发现有这么个用法:
其中:
- app_id是必填,且必须是1230204577581这么个值,这个值是facebook的一个应用(app)的id,叫做Cool Social App,我想这算是facebook提供的公用app吧;
- redirect_uri也是必填参数,在用户发布涂鸦墙消息后,返回到哪个页面。我们这个应用并没有自己的独立页面,所以返回到facebook上吧;
- message表明你要给涂鸦墙的输入框中带过去的内容文字,没有数量限制,当然,这个不是必填项目。
其他的非必填项有:
- link,链接地址;
- picture,可以显示一个图片;
- name,链接的title,显示在涂鸦墙输入框的下面第一行,字体是加粗的;
- caption,说明/摘要,显示在标题下面一行;
- description,详细介绍,显示在摘要的下面。
先留下来做记录吧,不知道facebook哪天又要修改它的api,/晕。
备注,注意翻 墙。