Build an Instagram clone with AngularJS, Satellizer, Node.js and MongoDB
去 https://github.com/sahat/satellizer 点击右边菜单的 Download ZIP 按钮,解压之后拷贝 satellizer.js 到 instagram/client/vendor 文件夹。
打开 index.html 然后 Satellizer 在 app.js 之前引入。
<!-- lang: js -->
<script src="vendor/satellizer.js"></script>
<script src="app.js"></script>
...
打开 app.js 导入 Satellizer 模块,然后添加 $authProvider 参数到 .config。
<!-- lang: js -->
angular.module('Instagram', ['ngRoute', 'ngMessages', 'satellizer'])
.config(function($routeProvider, $authProvider) {
在写这篇文章的时候, Satellizer 模块有些面这些默认的 OAuth provider : Google, Facebook, Twitter, LinkedIn, GitHub, Yahoo 和 Windows Live。对于这些 provider 你需要设置的参数只有 Client ID ,其他的都是默认可选的.
对于 Instagram, 或者其他基于 OAuth 2.0 的 provider,你需要填写的要多一点了。同样在 app.js 里面,在 $routeProvider 之后,添加下面的代码:
<!-- lang: js -->
$authProvider.loginUrl = 'http://localhost:3000/auth/login';
$authProvider.signupUrl = 'http://localhost:3000/auth/signup';
$authProvider.oauth2({
name: 'instagram',
url: 'http://localhost:3000/auth/instagram',
redirectUri: 'http://localhost:8000',
clientId: '799d1f8ea0e44ac8b70e7f18fcacedd1',
requiredUrlParams: ['scope'],
scope: ['likes'],
scopeDelimiter: '+',
authorizationEndpoint: 'https://api.instagram.com/oauth/authorize'
});
看一下目录结构,你就应该已经猜到了,我们会另起一个 Express 服务器,和这个 Angular 应用运行在不同的端口上。我们的后端 API 和前端完全分离。我之所以这样做是因为,这对于单页应用来说是一个很普遍的用例,而且我原来也从来没有用过这种架构来做页面应用,我觉得这是个很好的学习机会。
通常我都会把我的单页应用放到 public 文件夹下面,并且用 Express 或者其他的 web 服务来管理。正因如此,我不需要去解决 CORS(cross original resource sharing) 的问题。
首先第一点,你会注意到,我们用了绝对 URL,因为我们的服务器是运行在另一个端口的。我们的服务可能是运行在另外一个完全不同的主机上,比如说 Node.js 后端托管在 Digital Ocean 而 AngularJS 前端托管在 Heroku 或者 GitHub Pages 上。
loginUrl 和signupUrl 指向了我们用来处理用户登录和注册的服务端点,当我们在 LoginCtrl 和 SignupCtrl controller 调用 $auth.login() 和 $auth.signup() 的时候,Satellizer 会以 POST 方式发送请求到这些端点。
name 你可以随便用什么,只要在 $auth.authenticate() 调用的时候保持一致就可以。
url 服务端点是大多数操作的起点,它用来处理 Instagram 账户的创建,账户合并,以及在用户第二次登录的时候返回已有账户信息。
redirectUri 需要和 http://instagram.com/developer 中你的应用所定义的 Redirect URI 一致。如下图所示。这是我们的应用认证成功之后,Instagrame 将会重定向到的页面,我们将在那处理获取用户信息,反馈,照片,以及点赞。
scope 和 scopeDelimiter 相互关联,scope 用来请求追加的权限,比如点赞,吐槽一张照片,关注或拉黑某人,而 scopeDelimiter 用来表示多个 scope 之间的分隔符。内置的 provider 已经设置了默认的分隔符,所以你不用去管了。
刚开始写 Satellizer 的时候,我是用逗号做分隔符的,但是后来我发现 Google 和某些 provider 用的是空格,而 Instagram 用的又是(“+”),多说都是泪,看下面的例子吧:
注意: 如果你觉得有 scope 数组又有 scopeDelimiter 实在太烦的话,我可以考虑只用一个 scope 字符串,它会拿到 provider 返回来的 scope,至于之后怎么办你自己解决。当然你有更好的办法你可以在 GitHub 上开个讨论。
最后, authorizationEndpoint 是确认画面的 URL。不爽的是,有时候它需要你去研究一下 API 才能找到这个 URL,所以说好文档对开发人员来说真的很重要。
在继续之前,我们来在 instagram.com/developer/clients/register/ 上创建一个新的应用,因为你得用你自己的 Client ID 和 Client Secret,我的当然不会给你用咯。输入你的application name, description, 还有设置 RedirectURI 和 Website 指向 http://localhost:8000, 然后,用你从 Instagram 拿到的 clientId 和 clientSecret 替换 app.js 里面我的 clientId 字符串,以及在 server/config.js 里面的 clientSecret。
回到 controllers/home.js 的 HomeCtrl 然后更新你的代码,首先注入下面这个服务:
<!-- lang: js -->
.controller('HomeCtrl', function($scope, $window, $rootScope, $auth) {
注意: $scope, $window 和 $rootScope 是内置的 Angular services。
然后,我们用 Satellizer 的 $auth 服务来实现 isAuthenticated() 和 linkInstagram() 方法:
<!-- lang: js -->
$scope.isAuthenticated = function() {
return $auth.isAuthenticated();
};
$scope.linkInstagram = function() {
$auth.link('instagram')
.then(function(response) {
$window.localStorage.currentUser = JSON.stringify(response.data.user);
$rootScope.currentUser = JSON.parse($window.localStorage.currentUser);
});
};
注意: 希望你熟悉承诺(promises)语法,会用 .then / .catch 方法。但是,如果,你只会用一点点回调,而对承诺完全陌生的话,那么你应该会想看一下 Egghead.io screencast 上关于 AngularJS 承诺的使用。其实我也一直不懂承诺是怎么运作的,直到今年早些时候我开始学习 AngularJS 才开始用。
你刷新一下浏览器,应该看不到任何改变,因为 isAuthenticated() 会返回 false ,并且按钮 linkInstagram() 你也看不到。好了让我们回头来看看到目前为止我们做了什么。这是一个很好的机会,用来检查一下你是不是漏掉什么。