SublimeText3在有代理服务器的情况下安装Package Control的方法

官方网站提供的方法: https://sublime.wbond.net/installation

import urllib.request,os; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); open(os.path.join(ipp, pf), 'wb').write(urllib.request.urlopen( 'http://sublime.wbond.net/' + pf.replace(' ','%20')).read())

如果你在代理后面,就指定一下代理服务器

import urllib.request,os; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler({'http':'YOUR_HTTP_PROXY_SERVER:PORT'})) ); open(os.path.join(ipp, pf), 'wb').write(urllib.request.urlopen( 'http://sublime.wbond.net/' + pf.replace(' ','%20')).read())

could not find main class com.sun.deploy.panel.ControlPanel. Programm will exit

日志

昨天解决了一个比较诡异的问题,我手工添加了一个JRE之后,Java Control Panel起不来了,或许是因为我手工添加的JRE路径不对,之后Java Control Panel就报错“could not find main class com.sun.deploy.panel.ControlPanel. Programm will exit”

竟然google不到Solution, 尝试卸载重装Java重启系统都未能解决问题。

然后就想肯定是新加的这个配置导致的,于是思路调整到找这个配置的思路上来。于是找到这个文档:Deployment Configuration File and Properties

定位到这个文件 C:\Users\ehaojii\AppData\LocalLow\Sun\Java\Deployment\deployment.properties, 把里面新加的deployment.javaws.jre.2的相关条目删除之后,问题解决了。嗯。

使用云计算资源玩编程

一、那些云概念

一图胜千言,最近这两年大肆炒作的云计算本质上就是下面这张图。基础设施即服务(IaaS),平台即服务(PaaS)和软件即服务(SaaS)。

他们之间的区别和联系从下图的“食物链”上可以看出来,本质上都是对资源的管理和使其可供消费。

二、我对云计算的理解

先讲个段子,一位中国留学生去美国打工时当过报童,不带计算器,习惯动作抬头望天时心算找零。顾客大为惊讶,纷纷掏出计算器验证,皆无误,也抬头望天,惊恐问:云计算?该留学生茫然。有一天,一位面熟的老外向他打招呼,他望着天想了一会才叫出老外的名字,众人大惊:云存储?

上面这个段子,也从一定层面上反映出云这个概念的炒作性,以至于很多人忽略了本质,被各个厂商的口号忽悠过去,现在任何服务都想和云沾边。

再贴一个微博上的对联,@云计算风云:上联:云计算、云手机、云存储、云服务器、云杀毒、云办公、云优盘、云打印、云邮件、云输入法、云播放器、云游戏—云里雾里;下联:谷歌云、微软云、苹果云、脸谱云、亚马逊云、IBM云、百度云、阿里云、腾讯云、360云、搜狗云、华为云—不知所云;横批:神马都是浮云!

我对云计算的理解就是:平台开发商出售计算能力,且想方设法让这些计算资源可以方便的取得和管理;应用开发者按需消费所需要的资源/服务,架构于这样的平台上的应用也可以获得高可用高扩展的特性。

下面摘自wikipedia,列举了这三种类型的服务各自比较有标志性的厂商或产品

IaaS: Amazon CloudFormation (and underlying services such as Amazon EC2), Rackspace CloudTerremarkWindows Azure Virtual Machines,Google Compute Engine. and Joyent.

PaaS: Amazon Elastic Beanstalk, Cloud FoundryHerokuForce.comEngineYardMendixGoogle App EngineWindows Azure Compute and OrangeScape.

SaaS: Google Apps, innkeypos, Quickbooks Online, Successfactors Bizx, Limelight Video Platform, Salesforce.com, Microsoft Office 365 and Onlive.

三、有人应该要问了我们在玩的OpenStack是什么?

OpenStack是为搭建IaaS平台服务的开源软件,让任何人都可以自行建立和提供云端运算服务,在我们这的作用是建立企业内部的私有云。如果我不关机OpenStack用到的技术细节的话,看上去就是虚拟化和装机。这步走出去成功以后,对我们的好处就是以后可以很方便的拿到一套产品的开发或运行环境。

四、技术层面的那些云计算资源

无论如何,下面这些服务都值得你点开链接,了解一下: Amazon EC2,Amazon S3,Google App Engine,appfog,engineyard,Heroku

五、使用Parse云环境编程

5.1 为什么选Parse这个平台呢?

1. 因为它是Gigaom网站评出来的2012年最值得关注的云计算新兴公司之一,他的模式和appfog比较类似,但是专注移动应用领域,除了Rest API还提供了Android和iOS平台的API,相对来说上手更简单,更适合我们这个Demo

2. 一个web应用除了部署环境之外,还有不得不解决的就是数据库存储的问题,这里我们想使用Parse做云端数据存储。

5.2 构建数据模型

理论结束,正式开始,在这里我想演示怎么做一个基于Cloud在线地址簿(with scalable possibility)

需求有两点: 1. 支持在线存储我自己的通讯录 2. 支持多用户注册登录,可以让别人也在线存储他们的通讯录

5.2.1 首先,需要在parse.com注册一个帐号,或者直接用你的Github帐号登录。

5.2.2 然后,登录后会定向到这个页面,https://parse.com/apps,选择”+Create New App”,填一个名字然后确定你的应用就创建成功了。

有了这个Application ID和 Rest Key其实就可以在线创建和存储数据了,Rest API 可以参考 https://parse.com/docs/rest#objects

这里很值得夸赞的是,示例代码里已经直接填好了你的app的ApplicationID和Rest API Key,基本上你就是拷贝下来,粘帖到命令行就可以尝试了。

5.2.3 考虑第一个需求,创建单用户版本的地址簿。


Note:请使用自己的ID和Key来执行下面的命令,这样才能在你刚才创建的应用的Dashboard里看到你的执行效果。

curl -X POST
  -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
  -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" \
  -H "Content-Type: application/json"
  -d '{"name":"demo","cellphone":"13799998888","address":"No.1068"}' https://api.parse.com/1/classes/AddressBook

然后我们在网页上的管理界面的Data Browser里面可以看到刚刚通过Rest API创建的数据了

上面是创建数据的例子,下面列出增加,查找,删除,更新的全部代码

curl -X POST \
 -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
 -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" \
 -H "Content-Type: application/json" \
 -d '{"name":"tomcat","cellphone":"13788889999","address":"No.1068"}' https://api.parse.com/1/classes/AddressBook

{
 "createdAt": "2012-10-30T07:53:36.824Z",
 "objectId": "8weFDfQ343"
}

curl -X GET \
 -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
 -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" https://api.parse.com/1/classes/AddressBook/8weFDfQ343

{"name":"tomcat","cellphone":"13788889999","address":"No.1068", "createdAt":"2012-10-30T07:53:36.824Z","updatedAt":"2012-10-30T07:53:36.824Z","objectId":"8weFDfQ343"}

curl -X PUT \
 -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
 -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" \
 -H "Content-Type: application/json" \
 -d '{"address":"No.1068 Tianshan Road West"}' https://api.parse.com/1/classes/AddressBook/8weFDfQ343

{"updatedAt":"2012-10-30T09:00:33.314Z"}

curl -X GET \
 -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
 -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" https://api.parse.com/1/classes/AddressBook/8weFDfQ343

{"name":"tomcat","cellphone":"13788889999","address":"No.1068 Tianshan Road West","createdAt":"2012-10-30T07:53:36.824Z","updatedAt":"2012-10-30T09:00:33.314Z","objectId":"8weFDfQ343"}

curl -X DELETE \
 -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
 -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" https://api.parse.com/1/classes/AddressBook/8weFDfQ343

5.2.4 考虑第二个需求,创建多用户版本的地址簿,对于用户注册,登录,验证邮箱,重置密码等的基本需求Parse已经帮我们解决了,左边是User相关API文档的截图。

5.2.4.1 注册用户

curl -X POST -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
  -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" \
  -H "Content-Type: application/json" \
  -d '{"username":"test","password":"test","phone":"415-392-0202"}' https://api.parse.com/1/users

{"createdAt":"2012-10-31T02:45:10.402Z","objectId":"EXmRl19aJq","sessionToken":"3cbkov9ol4o1iaungkmni0ssc"}

5.2.4.2 创建关联
下面这条是更新命令,有两处值得一提,1. 8weFDfQ343 是刚才第一条AddressBook记录的ID 2. “__op”:”AddRelation” 是Parse为我们提供的做关联的内置命令,相当于数据库的外键关联。

curl -X PUT \
  -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
  -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" \
  -H "Content-Type: application/json" \
  -d '{"owner":{"__op":"AddRelation","objects":[{"__type":"Pointer","className":"_User","objectId":"EXmRl19aJq"}]}}' https://api.parse.com/1/classes/AddressBook/8weFDfQ343

{"updatedAt":"2012-10-31T02:56:40.557Z"}

5.2.4.3 使用一条命令创建地址簿联系人记录并且关联到这个用户
如果创建地址簿记录和关联到用户需要两个命令,就难免显得麻烦了,一般情况下的用例是:用户登录,添加他的地址簿记录,这个关联是可以一步做到的。

curl -X POST \
  -H "X-Parse-Application-Id: 5o93Gh8W63z2npIUErJvwwYz2IkpJIba6eL1ROAa" \
  -H "X-Parse-REST-API-Key: E7Xg39e89nYjGhc1V0fXeukHMXOAo5SCkrQAgiKM" \
  -H "Content-Type: application/json" \
  -d '{"name":"tim","cellphone":"13799998888","address":"No.1068 Tianshan Road West","cheatMode":false, "owner":{"__op":"AddRelation","objects":[{"__type":"Pointer","className":"_User","objectId":"EXmRl19aJq"}]}}' \

https://api.parse.com/1/classes/AddressBook

{"createdAt":"2012-10-31T02:59:44.762Z","objectId":"Yb7TdS01Mn"}

这个在Dashboard中的效果也非常直观

5.3 创建用户界面

技术上使用Bootstrap+Sinatra搭建,程序源码可以在这里找到 https://github.com/jihao/ab-parse-demo,你可以clone下来使用ruby ab.rb就能在本机跑了,当然你需要有ruby的运行环境。

这里大概解释一下结构,不太讨论技术细节,毕竟了解ruby的应该可以很容易看懂源码,如果觉得这100来行代码很酷,很简洁,立刻可以online,很想知道实现细节的同学们欢迎来找我讨论。

  1. Gemfile是用bundle init创建出来的,这个文件定义了程序依赖的其他gem
  2. views目录定义了所有的视图模板
  3. ab.rb是程序的核心controller

Sinatra是基于Ruby语言的非常简单的web框架,号称以最小精力为代价快速创建web应用为目的的DSL。
我们看一段代码ab.rb里处理用户注册的代码来感受一下氛围,前面几行是简单的参数校验,第15行是使用RestClient访问parse api创建用户,创建成功后重定向用户到登录界面。

post '/signup' do
  name = email = params[:email]
  password = params[:password]
  confirm_password = params[:confirm_password]
  if name.empty? || password.empty? || confirm_password.empty?
    flash[:notice] = "Should not be empty"
    return erb :signup
  end
  if password != confirm_password
    flash[:notice] = "2 fields does not match"
    return erb :signup
  end

  headers = AUTH_HASH.merge({:content_type=>:json, :accept=>:json})
  result = RestClient.post "https://api.parse.com/1/users",
    {"username"=>name,"password"=>password,"email"=>email}.to_json,
    headers
  @result = JSON.parse result
  flash[:notice] = "Succeed (ObjectId=%s)" % @result["objectId"]
  redirect "/signin"
end

5.4 部署应用

我选择使用上文中提到的Heroku来部署这个Sinatra应用,从头开始到部署完成非常简单

5.4.1 打开这个页面 https://toolbelt.heroku.com,选择你的平台安装

hao@ab-parse-demo$ heroku login

5.4.2 在ab-parse-demo的git repository下面,执行如下命令

hao@ab-parse-demo$ heroku create ab-parse-demo

5.4.3 然后就可以通过git push来部署了,这里给不想亲自尝试的同学们show下这条命令的log。
可以看到,首先代码被提交到Heroku上,然后它根据Gemfile自动安装了gem依赖,然后应用被编译部署到 http://ab-parse-demo.herokuapp.com


hao@ab-parse-demo$ git push heroku master
Warning: Permanently added the RSA host key for IP address '50.19.85.154' to the list of known hosts.
Counting objects: 15, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (8/8), 1.30 KiB, done.
Total 8 (delta 7), reused 0 (delta 0)

-----> Heroku receiving push
-----> Ruby/Rack app detected
-----> Installing dependencies using Bundler version 1.2.1
       Running: bundle install --without development:test --path vendor/bundle --binstubs bin/
       Fetching gem metadata from http://ruby.taobao.org/.
       Fetching full source index from http://ruby.taobao.org/
       Using mime-types (1.19)
       Using rack (1.4.1)
       Using rack-flash3 (1.0.1)
       Using rack-protection (1.2.0)
       Using rest-client (1.6.7)
       Using tilt (1.3.3)
       Using sinatra (1.3.3)
       Using bundler (1.2.1)
       Your bundle is complete! It was installed into ./vendor/bundle
       Cleaning up the bundler cache.
-----> Discovering process types
       Procfile declares types     -> (none)
       Default types for Ruby/Rack -> console, rake, web
-----> Compiled slug size: 892K
-----> Launching... done, v7
       http://ab-parse-demo.herokuapp.com deployed to Heroku

To git@heroku.com:ab-parse-demo.git
   981fb68..30889ab  master -> master

hao@ab-parse-demo$ heroku ps
=== web: `bundle exec rackup config.ru -p $PORT`
web.1: up 2012/11/01 22:54:42 (~ 10m ago)

5.4.4 最终效果

5.4 水平扩展应用

这是heroku管理这个应用的后台,Dynos默认是1,1个节点是免费的,然后这个蓝色条是可以拖动的,如果我要这个应用水平扩展到10个节点以支持更多的用户的话,可以拖动以预览相对应的费用。

六、小结

综上所述,这就是我们触手可及的云计算,对于程序员来说是相当不错的一件事情。

参考

  1. http://en.wikipedia.org/wiki/Cloud_computing
  2. http://parse.com
  3. http://sinatrarb.com
  4. http://heroku.com

Ruby中神奇的下划线

【原文地址】:http://po-ru.com/diary/rubys-magic-underscore
【原文作者】:Paul Battley

【译注】:我从RubyWeeklyNews看到的这篇小文章,挺有趣,比较短就翻译了玩一下,其实英文原文也很简单的

【译文】:
我今天发现当下划线作为变量名的时候Ruby对其处理的方法有点不一样。【译注:我们一般只注意到下划线在IRB中可以作为上一个返回值的容器】

假设,为了参数,我们有个数据集看上去是这样的:

people = {
  "Alice" => ["green", "alice@example.com"],
  "Bob"   => ["brown", "bob@example.com"]
}

我们想忽略眼睛的颜色和年龄,然后将每个元素转成一个简单的包含名字和邮件地址的数组,我们也许会这样做

people.map { |name, fields| [name, fields.last] }

但是这样写的话就不太清楚fields.last是什么了。 Ruby有很强大的解构赋值的特性,所以我们可以用括号到达结构的内容,使得代码看起来更直接:

people.map { |name, (eye_color, email)| [name, email] }

这样好点了,但是eye_color这个未使用的变量在上下文里是个干扰。这并不利于我们理解这段代码的作用。

很多语言(Haskell,Go,Clojure,还有很多其他的)有个惯例是用下划线代表未被使用的变量。Ruby代码风格指南中也这么推荐了(链接1:Christian Neukirchen’s Ruby Style Guide链接2:Github Ruby 代码风格指南

知道这个的话,我们可以这样写:

people.map { |name, (_, email)| [name, email] }

然后我们差不多相当满意了。但是如果我们需要忽略更多的元素呢?假设数据集是这样:

people = {
  "Alice" => ["green", 34, "alice@example.com"],
  "Bob"   => ["brown", 27, "bob@example.com"]
}

没问题。还是重用下划线:

people.map { |name, (_, _, email)| [name, email] }

你不能使用除_之前的其他变量来做这件事情,至少在Ruby1.9中是这样的,只能在变量被命名成_的时候这招才能工作,也许你们都会亲自尝试一下:

people.map { |name, (x, x, email)| [name, email] }
# SyntaxError: (eval):2: duplicated argument name

这个原因在Ruby的解析器里可以找到,shadowing_lvar_gen. 如果变量名称是一个下划线的时候,所有关于变量的通常的检查都会被掠过。

在Ruby1.8中多个下划线也具有上文同样的行为,但是出于不同的原因【译注:我没太理解这句】,Ruby1.8不关心块参数中重复的参数名称的情况。
同时,1.8和1.9都不关心多重赋值的时候参数名重复的情况:

a, a, b = 1, 2, 3
a, a, b = [1, 2, 3]

这两行代码都能正常工作(尽管我建议你不要信赖a的值)。

(感谢Ben发现parse.y中相应的处理代码.)

【译注】值得看的链接
http://stackoverflow.com/questions/9559561/where-and-how-is-the-underscore-variable-specified-in-ruby
http://stackoverflow.com/questions/123494/whats-your-favourite-irb-trick/864710#864710

git stash

My git stash commands usage order:

git stash [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]
git stash list [<options>]
git stash clear
git stash show [<stash>]
git stash drop [-q|--quiet] [<stash>]
git stash branch <branchname> [<stash>]

何种情况下使用这个git stash?我的使用场景
1. 比如我有个本地修改的数据库连接配置文件,不想提交到master,但是pull代码的时候又冲突了,我就先git stash save一下,pull下来之后再pop出来
2. 在某个branch有代码改动,但是突然有个别的任务需要切换branch的时候,我可以git stash save下来

OSGi, Felix, Maven, Pax Plugin, Eclipse

7.0 采用了osgi作为基础架构,平台已经差不多搭建完毕,项目正在开发上面的服务,因为之前没有参加基础架构搭建的工作,所以从头开始研究一下OSGi,然后发现开发环境不太理想,具体来说,maven-pax-plugin包装好的osgi环境还算方便,就是和eclipse的集成还不行。

准备说一说我是如何把开发环境理顺的。

首先是Maven Pax Plugin,如他项目主页介绍的,这款插件是OSGi的瑞士军刀,提供了创建,构建,管理,部署多种类型的OSGi bundle。它提供了一些脚本可供调用,也可以在maven命令行直接使用,这个比较赞。默认使用的是Felix的OSGi实现。

然后学习OSGi最好是找篇教程,Felix提供的这个教程貌似不错,我就准备照这个走一遍看看。

第一个例子是ServiceEventListener,例子不错但是教程本身没说如何创建此工程,我参考了maven-pax-plugin的usage页,用到两个命令mvn org.ops4j:maven-pax-plugin:create-project和mvn pax:create-bundle,然后用mvn eclipse:eclipse创建eclipse工程文件。

命令如下:

>mvn org.ops4j:maven-pax-plugin:create-project "-DgroupId=my.example" "-DartifactId=project"
>cd project
>mvn pax:create-bundle "-DbundleName=simple-api" "-Dpackage=org.example.simple" "-Dinternals=false"
>mvn pax:create-bundle "-DbundleName=simple-impl" "-Dpackage=org.example.simple" "-Dinterface=false"
>cd simple-api
>mvn eclipse:eclipse
>cd ../simple-impl
>mvn eclipse:eclipse

然后我就把这两个工程simple-api和simple-impl导入到了eclipse,首先发现两个问题

1. 这两个工程都不是Maven工程,而且项目依赖不正确
2. 这两个项目的pom文件都是红的,提示compile和testCompile相关的错

那我是怎么手工修这两个问题的呢?

1. right click project -> config -> Convert to maven project
首先将这两个工程转换成Mavn工程
然后,打开.classpath文件,将两个osgiR4的jar包依赖替换成这个

<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>;

2. 打开pom文件,用提示的quickFix修改掉这两个编译的问题,这时候pom文件里会多出这么一段

<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
  			<plugin>
  				<groupId>org.eclipse.m2e</groupId>
  				<artifactId>lifecycle-mapping</artifactId>
  				<version>1.0.0</version>
  				<configuration>
  					<lifecycleMappingMetadata>
  						<pluginExecutions>
  							<pluginExecution>
  								<pluginExecutionFilter>
  									<groupId>org.ops4j</groupId>
  									<artifactId>
  										maven-pax-plugin
  									</artifactId>
  									<versionRange>
  										[1.5,)
  									</versionRange>
  									<goals>
  										<goal>testCompile</goal>
  										<goal>compile</goal>
  									</goals>
  								</pluginExecutionFilter>
  								<action>
  									<ignore></ignore>
  								</action>
  							</pluginExecution>
  						</pluginExecutions>
  					</lifecycleMappingMetadata>
  				</configuration>
  			</plugin>

3. 这时候还需要right click project -> Maven -> update project configuration

这时候,环境问题基本就OK了,照着例子敲好代码,然后mvn clean install pax:provision就OK了

g! lb
START LEVEL 6
ID|State |Level|Name
0|Active | 0|System Bundle (4.0.2)
1|Active | 5|my.example.project.simple-api [org.example.simple] (1.0.1.SNAPSHOT)
2|Active | 5|my.example.project.simple-impl [org.example.simple] (1.0.1.SNAPSHOT)
3|Active | 1|Apache Felix Gogo Command (0.12.0)
4|Active | 1|Apache Felix Gogo Runtime (0.10.0)
5|Active | 1|Apache Felix Gogo Shell (0.10.0)

Note: 另外编译的时候发现版本问题,在顶级pom里加上这么一段解决了,还不知道本质原因

<plugin><!-- Compile -->
		        <groupId>org.apache.maven.plugins</groupId>
		        <artifactId>maven-compiler-plugin</artifactId>
		        <configuration>
		          <source>1.6</source>
		          <target>1.6</target>
		        </configuration>
		        <executions>
		          <execution>
		            <id>compile-for-this-application</id>
		            <goals>
		              <goal>compile</goal>
		            </goals>
		          </execution>
		        </executions>
	      </plugin>

新浪微博oauth2

前两天基于gem omniauth 1.0,实现了新浪微博oauth2的strategy,项目主页 https://github.com/jihao/omniauth-weibo

测试过程中发现新浪微博开放平台oauth2的两个问题
1. access_token response 不够标准,它返回的 “content-type”=>”text/plain;charset=UTF-8″, 其实返回值是json格式,如果http头返回”application/json” 就更好了,省得开发人员手工处理。

2. OAuth授权后获得access_token,后续验证用户信息调用account/get_uid,微博API要求请求参数里必须填写access_token,这里比较难说是谁的问题,但是比较来说github instagram和oauth2 gem的默认行为都兼容,唯独新浪微博需要手工设置query参数,对程序员的易用性兼顾不够。

rails-oauth-连接新浪微薄记录

这是一个简单的rails程序,技术上主要是devise,omniauth这个两个gem的使用

功能一个注册登陆系统,登陆完成之后,点击新浪图标,oauth连接/授权/关联新浪微薄账号,取得用户的相关微薄数据,上一张完成之后的页面截图就清楚些了。

步骤如下

  1. 使用devise实现注册登陆功能
  2. 使用omniauth实现新浪微薄的oauth认证
    1. 配置omniauth
    2. 完成新浪微薄oauth,保存access_token, access_token_secret到数据库
    3. 使用之前存下来的access_token, access_token_secret调用新浪API取所需要的数据

OK,下面是从创建这个rails app开始的完整步骤

首先是1.使用devise实现注册登陆功能

>rails new authentication
>cd authentication

修改Gemfile
     gem 'devise'
     gem 'omniauth'
     gem 'json'

>bundle install
>rails g controller home index
>rails g devise:install
>rails generate devise User
>rake db:migrate

修改config/routes.rb
    root :to => "home#index"
修改app/views/home/index.html.erb,显示注册、登陆、退出等URL
    <% if user_signed_in? -%> <!-- Provided by devise -->
	<div style="float:right">
	  <%= current_user.email %> |
	  <%= link_to '用户信息', edit_user_registration_path %> |
	  <%= link_to '退出登录', destroy_user_session_path, :method => :delete %> |
	</div>
    <% end -%>
    <% unless user_signed_in? -%>
	<div style="float:right">
	  <%= link_to '注册', new_user_registration_path %> |
	  <%= link_to '登录', new_user_session_path %>
	</div>
    <% end -%>

>rails s

devise真的很好用,这步骤做完之后,基本的注册登陆功能已经完备,可以访问http://localhost:3000 验证一下

接下来2.使用omniauth实现新浪微薄的oauth认证
A. 配置omniauth

修改config/application.rb
    require 'oa-oauth'
    config.middleware.use OmniAuth::Strategies::Tsina, 'APP_KEY', 'APP_SECRET'
    #可以加多个Provider,如果还需要连接其他认证

B. 完成新浪微薄oauth,保存access_token, access_token_secret到数据库
使用omniauth是如此简单,你只需要加上这个link(/auth/:provider)就行了,这里的provider是tsina

修改app/views/home/index.html.erb,增加一行
<div style="float:right">
 <%= current_user.email %> |
 <%= link_to '用户信息', edit_user_registration_path %> |
 <%= link_to '退出登录', destroy_user_session_path, :method => :delete %> |
 <%= link_to image_tag("tsina_24_24.png"), '/auth/tsina', :title=> "连接新浪微薄" %>
</div>

OK,用户登陆完就能看到这个链接了,点一下试试看,首先是一个授权app的页面,这时候输入新浪微博的用户密码,确认授权,然后不出意外你看到的是这个效果

我们再来回顾一下oAuth的流程,这个是新浪提供的一个流程图,我们正处于重定向到Callback_url这个阶段,而我们应用还没有定义回调的url

OK,下一步就是定义我们的callback_url

>rails g controller authorization oauth_create oauth_destroy

修改config/routes.rb
  get "authorization/oauth_create" #上个命令自动生成
  get "authorization/oauth_destroy" #同上
  match "/auth/:provider/callback" => "authorization#oauth_create" #定义callback_url

先看看oAuth成功没有,code可以简单点

app/controllers/authorization_controller.rb
class AuthorizationController < ApplicationController
  def oauth_create
    auth = request.env["omniauth.auth"]
    render :text=>auth.to_yaml
  end
end

再次点击新浪图标,我们能看到一堆文本信息,包括token,secret等等,说明oAuth已经成功了。

C. 使用之前存下来的access_token, access_token_secret调用新浪API取所需要的数据
现在我们需要的数据都能取到了,然后思考一个问题,貌似我们应该把这个授权信息存下来,然后就不需要每次用户都授权一下才能取API数据。
建立下面这个authorization model存授权信息

>rails g model authorization provider:string uid:string user_id:integer access_token:string access_token_secret:string
app/models/user.rb
class User < ActiveRecord::Base
  has_many :authorizations do
     def find_or_create_by_params(params) #辅助方法,别着急,一会儿就看到怎么用的了
          provider,uid = params[:provider],params[:uid]
          access_token,access_token_secret = params[:access_token],params[:access_token_secret]

          authorization = find_or_create_by_provider_and_uid(provider,uid)
          authorization.update_attributes(params.except(:provider,:uid))
     end
  end

  #... devise stuff
end

app/models/authorization.rb
class Authorization < ActiveRecord::Base
     belongs_to :user

     validates :user_id, :uid, :provider, :presence => true
     validates :uid, :uniqueness => { :scope => :provider }
end

OK,回到回调方法里,我们要把有用的数据存到model里进而持久化

app/controllers/authorization_controller.rb
class AuthorizationController < ApplicationController
  def oauth_create
    auth = request.env["omniauth.auth"]
    user = current_user
    user.authorizations.find_or_create_by_params({ #这里调用辅助方法
	:provider => auth["provider"],
	:uid => auth["uid"],
	:access_token => auth['credentials']['token'],
	:access_token_secret => auth['credentials']['secret']
    })
    flash[:notice] = "#{auth['provider']} user #{auth['uid']} successfully authenticated"
    redirect_to root_url
end

然后我们希望每次用户刷新首页的时候,我们去取一下他授权微薄的最新信息(home#index是我们的root_url还记得不)

class HomeController < ApplicationController
  def index
	oauth_retrive
  end

private
	require 'json'
	def oauth_retrive
		user = current_user
		if not user.blank?
			authorization = user.authorizations.first
			if not authorization.blank?
				access_token = authorization.access_token
				access_token_secret = authorization.access_token_secret

				consumer = OAuth::Consumer.new('APP_KEY','APP_SECRET',{:site =>'http://api.t.sina.com.cn'})
				accesstoken = OAuth::AccessToken.new(consumer, access_token, access_token_secret)
				response = accesstoken.get("http://api.t.sina.com.cn/account/verify_credentials.json")
				@weibo_user = JSON.parse(response.body)
			end
		end
	end
end

 

修改app/views/home/index.html.erb,增加读取weibo_user的信息
<% if user_signed_in? -%> <!-- Provided by devise -->
	<div style="float:right">
	  <%= current_user.email %> |
	  <%= link_to '用户信息', edit_user_registration_path %> |
	  <%= link_to '退出登录', destroy_user_session_path, :method => :delete %> |
	  <%= link_to image_tag("tsina_24_24.png"), '/auth/tsina', :title=> "连接新浪微薄" %>
	</div>
	<% if @weibo_user %>
	<div style="clear:both"></div>
	<div style="float:right">
		<p>已连接新浪微薄:<p/>
		<%= link_to image_tag(@weibo_user['profile_image_url']), "http://weibo.com/#{@weibo_user['id']}" %>
		<%= link_to @weibo_user['screen_name'], "http://weibo.com/#{@weibo_user['id']}" %>
		关注:<%= @weibo_user['followers_count'] %>
		粉丝:<%= @weibo_user['friends_count'] %>
		微薄:<%= @weibo_user['statuses_count'] %>
		收藏:<%= @weibo_user['favourites_count'] %>
	</div>
	<% end -%>
<% end -%>

按照这个步骤,就能成功看到最终成果了。

Notes:上文所用的环境如下,完整代码可以在这找到https://github.com/jihao/rails_apps_for_fun/tree/master/authentication

Rails 3.0.9
ruby 1.8.7 (2011-02-18 patchlevel 334) [i386-mingw32]
builder (2.1.2)
devise (1.4.2)
omniauth (0.2.6)

这个过程中找到的较好的参考文档/资源

  1. https://github.com/plataformatec/devise
  2. http://oauth.rubyforge.org/
  3. https://github.com/intridea/omniauth

技巧-用迅雷打开某些打不开的种子文件

有些时候我们从网上找到一个极具诱惑力的资源的种子,但是不幸的发现最猛的吸血迅雷却打不开这个torrent文件,非常非常的痛苦,这个时候不得不退而求其次,装个相对差一点的BitComet,发现种子文件竟然可以打开了,心情豁然开朗,但是坑爹的是等了半天竟然没有速度,这时候大家的心情我懂的!

这个问题的root cause应该是种子文件被修改,只能特定BT下载客户端才能识别了,八成是为了抵制吸血迅雷,这个时候大家一定都是想,要是能用吸血迅雷打开这个种子该多好啊!

好吧!Hold住!免费传授大家这招,在BitComet的你正在下载的这个任务上,右键->复制磁链,然后到迅雷里新建任务->粘贴,是不是成功了?速度也有了!

也许可以写个脚本生成Magnet URI?今天就不研究了。