linwalker

linwalker 查看完整档案

杭州编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

linwalker 评论了文章 · 2018-10-18

babel-plugin-react-css-modules的配置

前言

题外话。因为‘笔记’不支持上传图片,所以只好在‘文章’里面写了。。。

之前一直在react项目中使用css-modules。感觉使用起来最大的好处就是代码隔离(scoped),但是同时也产生了不少麻烦。举个栗子:
不使用css-modules

<div className="the-class">something</div>

使用css-modules

import styles from './styles.css'

<div className={ styles.theTitle }>something</div>

像这样,每次都要用style.xxx这种方式来写,而且类名要使用小驼峰命名法(习惯css的中杆命名法的我很忧伤),其实还是很麻烦,而它的最大的好处---代码隔离,在不使用css-modules也可以做到,只是麻烦一点点而已:

// HomePage.js

<div className="home-page-the-class">something</div>

// AccountPage.js

<div className="account-page-the-class">something</div>

// SelectPanel.js

<div className="component-select-panel-the-class">something</div>

所以找了很多新的解决方法,之前还找到了styled-component这个方案,但是用了之后感觉还是不太合适(谁用谁知道),到目前为止(2018-07-21)找到的比较好的方案是使用babel-plugin-react-css-modules

插件效果

如果使用这个插件,跟css-modules对比一下:

// css-modules
import styles from './styles.css'

<div className={ styles.theTitle }>something</div>

// babel-plugin-react-css-modules
import './styles.css'

<div styleName="the-title">something</div>

而这两种写法达到的效果是一样的。是不是感觉方便很多。

关于插件

官方github链接
在者之前,就有了react-css-modules这个插件,作者后来又开发了这个使用起来更为方便的新插件,算是很走心了。不过配置起来遇到了很多坑。官方文档中说了很多,但都没有到点子上,相关的配置也不是很详细。

接下来所进行的配置,将达到以下效果:

  1. 更方便的使用 css-modules
  2. 支持 scss
  3. 支持全局 scss 变量

开发环境

  1. node // v9.4.0
  2. npm // 6.0.1
  3. create-react-app // 1.5.2
  4. webpack // 3.8.1
  5. react // 16.4.1

以上的版本作为参考,不同的版本可以会导致后面的配置无效。

0.解压webpck配置

npm run eject

1.安装所有依赖的包

// 这里需要注意的是要将此插件安装到 生产模式(--save),而不是开发模式(--save-dev)
npm install babel-plugin-react-css-modules --save

// 该插件让‘babel-plugin-react-css-modules’支持‘scss’
npm install postcss-scss --save-dev

// sass和scss的依赖库
npm install node-sass --save-dev

// sass-loader就不需要说明了吧
npm install sass-loader --save-dev

// 这个插件可以实现全局scss变量,或者说全局的scss文件
npm install sass-resources-loader --save-dev

2.配置

配置部分实在很坑。因为官方提供的配置不是在create-react-app这个脚手架的基础上进行配置的,所以会有不同。

找到/config/webpack.config.dev.js。在里面慢慢找(真的是慢慢找),找到:

module.exports.module.rules[ {oneOf} ]

上图:
找到配置项

onOf是一个数组,在这个数组中的最后添加对scss文件的处理。添加以下对象:

{
  test: /\.scss$/,
  use: [
    'style-loader',
    // 简洁方式
    // 'css-loader?modules&localIdentName=[local]-[hash:base64:10]',
    {
      loader: 'css-loader',
      options: {
        module: true,
        localIdentName: '[local]-[hash:base64:10]'
      }
    },
    'sass-loader',
    {
      loader: 'sass-resources-loader',
      options: {
        resources: path.resolve(__dirname, '../src/assets/styles-variable.scss')
      }
    }
  ]
},

上图:
配置scss

这里解释一下css-loader的配置,options.module为是否要使用css-modules;options.localIdentName为编译之后使用的类名的格式。(其中的‘sass-resources-loader’后面再说)

以上,只是让项目支持了sass,并开启了css-modules。接下来,需要配置babel-plugin-react-css-modules这个插件。

在官方文档中有描述到这个插件的实现原理。它是先将所有的jsx中的元素的styleName属性遍历一遍,然后和生成的css文件进行匹配,生成css-modules对象,最后将对应的类名添加的元素的className中。
所以这个过程是在编译.jsx/.js文件时进行的。
个人见解,不要打我。

在刚刚的onOf数组中找到对.jsx/.js/.mjs文件的处理,并添加plugins

plugins: [
  [
    'react-css-modules',
    {
      generateScopedName: '[local]-[hash:base64:10]',
      filetypes: {
        '.scss': {
          syntax: 'postcss-scss'
        }
      }
    }
  ]
]

上图:
配置plugin

*重点来了
在配置项中,有generateScopedName这个选项,它的作用和css-loader中的localIdentName是一样的,都是设置编译之后的类名的格式。
经过我多次实验,generateScopedNamelocalIdentName这两个配置项必须要有,并且它的值必须相同。否则即使编译成功,不报错,也还是无法达到预期效果。

filetypes则是让这个插件支持scss。这里其实还可以添加插件,不过还没有实践。具体可以在官方文件中查看。

3.全局变量(样式文件)的支持

其实这个并不是必要的,与babel-plugin-react-css-modules这个插件无关。只是在项目中,这个需求基本都会使用到,所以在这里提及一下。

sass-resources-loader可以帮助我们使用全局的scss文件。使用方法也是很简单的,安装好插件,然后在刚刚提到的“对 scss 文件的处理”(图2)配置需要引入的公共文件即可。

最后

以上,关于react的css方案就是这样了。以后一定会有更好的解决方案,前端的火车‘呜呜呜’,只能不停的开了。

查看原文

linwalker 赞了文章 · 2018-01-17

实现环形进度条的几种方法

环形进度条的问题,网上有很多的demo,也有各种不同的实现方式,很棒的实现也有很多,我这自己做一下一方面是想开阔一下自己的思路,一方面好久没看SVG和Canvas的东西了,基础的拿来熟悉下。

DIV + CSS3

这个是最基本的实现方式,我在想怎样用尽量少的DOM结构来实现,最终还是用了三个div,不过这个方法仅供开阔思路,样式表现在PC还好,手机上的问题就多了,仅供参考。

  • 1、利用div的border画一个背景的圆环

    <div class="demo1-bg1"></div>
    /* css */
    .demo1-bg1{
        width: 100px;
        height: 100px;
        display: -webkit-box;
        -webkit-box-pack: center;
        -webkit-box-align: center;
        margin: 50px auto;
        background: fff;
        border-radius: 50%;
        box-shadow: 0 0 0 10px red inset;
    }

图片描述

  • 2、添加两个子元素div,分别设置border来实现两个半圆环并遮盖背景圆环

    <div class="demo1-bg1">
        <div id="J_bg2_1" class="demo1-bg2-1"></div>
        <div id="J_bg2_2" class="demo1-bg2-2"></div>
    </div>
    /* css */
    .demo1-bg2-1,.demo1-bg2-2{
        position: relative;
        margin: 0;
        padding: 0;
        -webkit-box-flex: 1;
        height: 80px;
        background: #fff;
        border: 10px solid grey;
    }
    .demo1-bg2-1{
        border-radius: 50px 0 0 50px;
        border-color: grey transparent grey grey;
        transform-origin: 100% 50%;
        z-index: 1;
    }
    .demo1-bg2-2{
        border-radius: 0 50px 50px 0;
        border-color: grey grey grey transparent;
        transform-origin: 0 50%;
        z-index: 2;
    }

图片描述 --> 图片描述 --> 图片描述

  • 3、用JS旋转两个子元素,露出背景圆环

图片描述

DIV + CSS3 实现圆环进度条

SVG

这个原理也很简单,是利用SVG的stroke和dash-array属性来实现,也是我常用的实现方式。

  • 绘制一个圆环路径,填充灰色圆环

<svg xmlns="http://www.w3.org/200/svg" height="150" width="110">
    <circle cx="55" cy="55" r="50" fill="none" stroke="grey" stroke-width="5" stroke-linecap="round"/>
</svg>

图片描述

  • 绘制一个内圆环,半径/圆心和外圆环一样,刚好重叠

<circle class="demo2" id="J_progress_bar" cx="55" cy="55" r="50" fill="none" stroke="red" stroke-width="5"/>

图片描述

  • 设置内圆环的stroke-dasharray属性,stroke-dasharray的值得意思是,第一个值为圆环第一段填充颜色的长度,第二个值为圆环接下来不填充颜色的长度,以此类推并重复。这里设置为:stroke-dasharray="0,10000" 第一个值就是填充红色的值,为0,第二段为不填充的长度,超过圆环的周长即可。此时进度条为0%:
    图片描述

这时候发现stroke-dasharray填充是从3点钟位置开始的,所以就让内圆环旋转-90度:

.demo2{
    transform-origin: center;
    transform: rotate(-90deg);
}

图片描述

  • JS控制内圆环的stroke-dasharray的值来控制内圆环第一段绘制红色的长度

var demo2 =  document.querySelector("#J_demo2");
var btn1 = document.querySelector("#J_btn_1");
var btn2 = document.querySelector("#J_btn_2");

var circleLength = Math.floor(2 * Math.PI * demo2.getAttribute("r"));

window.onload = rotateCircle;

btn2.onclick = rotateCircle;
function rotateCircle () {
    var val = parseFloat(btn1.value).toFixed(2);
    val = Math.max(0,val);
    val = Math.min(100,val);
    demo2.setAttribute("stroke-dasharray","" + circleLength * val / 100 + ",10000");
}

SVG 实现圆环进度条

查看原文

赞 36 收藏 38 评论 10

linwalker 赞了文章 · 2018-01-17

js初级应用之svg实现环形进度条

图片描述

整理一个svg绘制环形进度条的demo,需要的同学拿去用即可

定义svg绘图区域

在html页面的任何位置,添加svg绘图面板。定义svg绘图区域大小

<svg width="500px" height="500px" version="1.1" xmlns="http://www.w3.org/2000/svg"></svg>

绘制一个圆形

cx 和 cy 属性定义圆点的 x 和 y 坐标,单位省略为px,如果省略 cx 和 cy,圆的中心会被设置为 (0, 0),r 属性定义圆的半径,stroke定义描边的颜色,stroke-width定义描边宽度,fill定义填充颜色

<circle cx="250" cy="250" r="40" stroke="black" stroke-width="2" fill="red"/>

定义一个path路径区域

<path id="ring" fill="#76B13C" />

使用path指令绘制扇形

首先查看一下常用的path指令,获取svg中的path,指定半径为100,进度为50,我们绘制一个扇形

  • M = moveto(M X,Y) :将画笔移动到指定的坐标位置

  • L = lineto(L X,Y) :画直线到指定的坐标位置

  • H = horizontal lineto(H X):画水平线到指定的X坐标位置

  • V = vertical lineto(V Y):画垂直线到指定的Y坐标位置

  • A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线

  • Z = closepath():关闭路径

    var path = document.getElementById('ring');
    var r=100;
    var progress=50;
    
    //将path平移到我们需要的坐标位置
    ring.setAttribute('transform', 'translate('+r+','+r+')');
    
    // 计算当前的进度对应的角度值
    var degrees = progress * (360/100);  
    
    // 计算当前角度对应的弧度值
    var rad = degrees* (Math.PI / 180);
    
    //极坐标转换成直角坐标
    var x = (Math.sin(rad) * r).toFixed(2);
    var y = -(Math.cos(rad) * r).toFixed(2);

    //大于180度时候画大角度弧,小于180度的画小角度弧,(deg > 180) ? 1 : 0
    var lenghty = window.Number(degrees > 180);
    
    //path 属性
    var descriptions = ['M', 0, 0, 'v', -r, 'A', r, r, 0, lenghty, 1, x, y, 'z'];
    
    // 给path 设置属性
    path.setAttribute('d', descriptions.join(' '));

在扇形上覆盖一个圆形

<circle cx="100" cy="100" r="82" fill="#FFF" />

封装绘图函数

path参数为绘图面板id,progress为进度值0-100,r为半径

function draw(path,progress,r) {
    path.setAttribute('transform', 'translate('+r+','+r+')');
    var degrees = progress * (360/100);  
    var rad = degrees* (Math.PI / 180);
    var x = (Math.sin(rad) * r).toFixed(2);
    var y = -(Math.cos(rad) * r).toFixed(2);
    var lenghty = window.Number(degrees > 180);
    var descriptions = ['M', 0, 0, 'v', -r, 'A', r, r, 0, lenghty, 1, x, y, 'z'];
    path.setAttribute('d', descriptions.join(' '));
}    
查看原文

赞 8 收藏 38 评论 4

linwalker 赞了文章 · 2018-01-10

JavaScript系列——JavaScript同步、异步、回调执行顺序之经典闭包setTimeout面试题分析

同步、异步、回调?傻傻分不清楚。

大家注意了,教大家一道口诀:

同步优先、异步靠边、回调垫底(读起来不顺)

用公式表达就是:

同步 => 异步 => 回调

这口诀有什么用呢?用来对付面试的。

有一道经典的面试题:

for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log('i: ',i);
    }, 1000);
}

console.log(i);

//输出
5
i:  5
i:  5
i:  5
i:  5
i:  5

这道题目大家都遇到过了吧,那么为什么会输出这个呢?记住我们的口诀 同步 => 异步 => 回调

1、for循环和循环体外部的console是同步的,所以先执行for循环,再执行外部的console.log。(同步优先)

2、for循环里面有一个setTimeout回调,他是垫底的存在,只能最后执行。(回调垫底)

那么,为什么我们最先输出的是5呢?

非常好理解,for循环先执行,但是不会给setTimeout传参(回调垫底),等for循环执行完,就会给setTimeout传参,而外部的console打印出5是因为for循环执行完成了。

知乎有大神讲解过 80% 应聘者都不及格的 JS 面试题 ,就是以这个例子为开头的。但是没有说为什么setTimeout是输出5个5。

这里涉及到JavaScript执行栈和消息队列的概念,概念的详细解释可以看阮老师的 JavaScript 运行机制详解:再谈Event Loop - 阮一峰的网络日志,或者看 并发模型与Event Loop

clipboard.png

《图片来自于MDN官方》

我拿这个例子做一下讲解,JavaScript单线程如何处理回调呢?JavaScript同步的代码是在堆栈中顺序执行的,而setTimeout回调会先放到消息队列,for循环每执行一次,就会放一个setTimeout到消息队列排队等候,当同步的代码执行完了,再去调用消息队列的回调方法。

在这个经典例子中,也就是说,先执行for循环,按顺序放了5个setTimeout回调到消息队列,然后for循环结束,下面还有一个同步的console,执行完console之后,堆栈中已经没有同步的代码了,就去消息队列找,发现找到了5个setTimeout,注意setTimeout是有顺序的。

那么,setTimeout既然在最后才执行,那么他输出的i又是什么呢?答案就是5。。有人说不是废话吗?

现在告诉大家为什么setTimeout全都是5,JavaScript在把setTimeout放到消息队列的过程中,循环的i是不会及时保存进去的,相当于你写了一个异步的方法,但是ajax的结果还没返回,只能等到返回之后才能传参到异步函数中。
在这里也是一样,for循环结束之后,因为i是用var定义的,所以var是全局变量(这里没有函数,如果有就是函数内部的变量),这个时候的i是5,从外部的console输出结果就可以知道。那么当执行setTimeout的时候,由于全局变量的i已经是5了,所以传入setTimeout中的每个参数都是5。很多人都会以为setTimeout里面的i是for循环过程中的i,这种理解是不对的。

===========================================分割线=========================================

看了上面的解释,你是不是有点头晕,没事,继续深入讲解。

我们给第一个例子加一行代码。

for (var i = 0; i < 5; ++i) {
    setTimeout(function() {
        console.log('2: ',i);
    }, 1000);
    console.log('1: ', i); //新加一行代码
}

console.log(i);

//输出
1:  0
1:  1
1:  2
1:  3
1:  4
5
2:  5
2:  5
2:  5
2:  5
2:  5

来,大家再跟着我一起念一遍:同步 => 异步 => 回调 (强化记忆)

这个例子可以很清楚的看到先执行for循环,for循环里面的console是同步的,所以先输出,for循环结束后,执行外部的console输出5,最后再执行setTimeout回调 55555。。。

=====================================分割线============================================

这么简单,不够带劲是不是,那么面试官会问,怎么解决这个问题?

最简单的当然是let语法啦。。

for (let i = 0; i < 5; ++i) {
    setTimeout(function() {
        console.log('2: ',i);
    }, 1000);
}

console.log(i);

//输出
i is not defined
2:  0
2:  1
2:  2
2:  3
2:  4

咦,有同学问,为什么外部的i报错了呢?
又有同学问,你这个口诀在这里好像不适应啊?

let是ES6语法,ES5中的变量作用域是函数,而let语法的作用域是当前块,在这里就是for循环体。在这里,let本质上就是形成了一个闭包。也就是下面这种写法一样的意思。如果面试官对你说用下面的这种方式,还有let的方式,你可以严肃的告诉他:这就是一个意思!这也就是为什么有人说let是语法糖。

var loop = function (_i) {
    setTimeout(function() {
        console.log('2:', _i);
    }, 1000);
};

for (var _i = 0; _i < 5; _i++) {
    loop(_i);
}

console.log(i);

面试官总说闭包、闭包、闭包,什么是闭包?后面再讲。

写成ES5的形式,你是不是发现就适合我说的口诀了?而用let的时候,你发现看不懂?那是因为你没有真正了解ES6的语法原理。

我们来分析一下,用了let作为变量i的定义之后,for循环每执行一次,都会先给setTimeout传参,准确的说是给loop传参,loop形成了一个闭包,这样就执行了5个loop,每个loop传的参数分别是0,1,2,3,4,然后loop里面的setTimeout会进入消息队列排队等候。当外部的console执行完毕,因为for循环里的i变成了一个新的变量 _i ,所以在外部的console.log(i)是不存在的。

现在可以解释闭包的概念了:当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了。

我知道你又要我解释这句话了,loop(_i)是外部函数,setTimeout是内部函数,当setTimeout被loop的变量访问的时候,就形成了一个闭包。(别说你又晕了?)

随便举个新的例子。

function t() {
    var a = 10;
    var b = function() {
        console.log(a);    
    }
    b();
}
t(); //输出 10

跟我一起念口诀:同步 => 异步 => 回调 (强化记忆)
先执行函数t,然后js就进入了t内部,定义了一个变量,然后执行函数b,进入b内部,然后打印a,这里都是同步的代码,没什么异议,那么这里怎么解释闭包:函数t是外部函数,函数b是内部函数,当函数b被函数t的变量访问的时候,就形成了闭包。

========================================分割线==============================================

上面主要讲了同步和回调执行顺序的问题,接着我就举一个包含同步、异步、回调的例子。

let a = new Promise(
  function(resolve, reject) {
    console.log(1)
    setTimeout(() => console.log(2), 0)
    console.log(3)
    console.log(4)
    resolve(true)
  }
)
a.then(v => {
  console.log(8)
})

let b = new Promise(
  function() {
    console.log(5)
    setTimeout(() => console.log(6), 0)
  }
)

console.log(7)

看到这个例子,千万不要害怕?,先读一遍口诀:同步 => 异步 => 回调 (强化记忆)

1、看同步代码:a变量是一个Promise,我们知道Promise是异步的,是指他的then()和catch()方法,Promise本身还是同步的,所以这里先执行a变量内部的Promise同步代码。(同步优先)

    console.log(1)
    setTimeout(() => console.log(2), 0) //回调
    console.log(3)
    console.log(4)

2、Promise内部有4个console,第二个是一个setTimeout回调(回调垫底)。所以这里先输出1,3,4回调的方法丢到消息队列中排队等着。

3、接着执行resolve(true),进入then(),then是异步,下面还有同步没执行完呢,所以then也滚去消息队列排队等候。(真可怜)(异步靠边)
4、b变量也是一个Promise,和a一样,执行内部的同步代码,输出5,setTimeout滚去消息队列排队等候。

5、最下面同步输出7。

6、同步的代码执行完了,JavaScript就跑去消息队列呼叫异步的代码:异步,出来执行了。这里只有一个异步then,所以输出8。

7、异步也over,轮到回调的孩子们:回调,出来执行了。这里有2个回调在排队,他们的时间都设置为0,所以不受时间影响,只跟排队先后顺序有关。则先输出a里面的回调2,最后输出b里面的回调6。

8、最终输出结果就是:1、3、4、5、7、8、2、6。

我们还可以稍微做一点修改,把a里面Promise的 setTimeout(() => console.log(2), 0)改成 setTimeout(() => console.log(2), 2),对,时间改成了2ms,为什么不改成1试试呢?1ms的话,浏览器都还没有反应过来呢。你改成大于或等于2的数字就能看到2个setTimeout的输出顺序发生了变化。所以回调函数正常情况下是在消息队列顺序执行的,但是使用setTimeout的时候,还需要注意时间的大小也会改变它的顺序。

====================================分割线==================================================

口诀不一定是万能的,只能作为一个辅助,更重要的还是要理解JavaScript的运行机制,才能对代码执行顺序有清晰的路线。

还有async/await等其他异步的方案,不管是哪种异步,基本都适用这个口诀,对于新手来说,可以快速读懂面试官出的js笔试题目。以后再也不用害怕做笔试题啦。

特殊情况下不适应口诀的也很正常,JavaScript博大精深,不是一句话就能概括出来的。

最后,在跟着我念一遍口诀:同步 => 异步 => 回调

如果文章对你有帮助,请点击一下推荐。

查看原文

赞 64 收藏 117 评论 23

linwalker 关注了用户 · 2018-01-02

二月 @hyy1115

我不入地狱谁入地狱

关注 496

linwalker 分享了头条 · 2017-07-03

node端使用js-xlsx生成xlsx文件,前端点击下载

赞 2 收藏 1 评论 0

linwalker 发布了文章 · 2017-06-09

第三方登入例子-GitHub授权登入(node-koa)

前言

第三方登入太常见了,微信,微博,QQ...总有一个你用过。当然看这篇文章的你,应该还用过github登入。这篇分享是在上一篇基于node的登入例子(node-koa-mongoose)的基础增加了github账号第三方授权登入功能,如果有些代码,这篇中美介绍,你可以先去看下上一篇的分享。

本项目源码地址https://github.com/linwalker/...

第三方登入

第三方登入主要基于OAuth 2.0。OAuth协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的 ---- 百度百科

更详细的介绍可以看这篇文章理解OAuth 2.0

github 授权登入

原理过程

先来大致了解下第三方通过GitHub账号授权登入的过程,具体实现结合后面代码讲解

  • 1.获取code

    第三方客户端向`https://github.com/login/oauth/authorize`发送get请求,带上`?client_id=XXXXXX`参数,这时会跳转到GitHub登入页面,授权后GitHub会向客户端返回`https://redirect_url?code=XXXXXX`。其中`client_id`和`redirect_url`是第三方事先在GitHub平台上配置好的。
    
  • 2.通过code获取access_token

    客户端处理`https://redirect_url?code=XXXXXX`请求,获取code值,向`https://github.com/login/oauth/access_token`发起post请求,请求参数为`client_di`,`client_secret`和`code`。
    
  • 3.通过access_token获取用户GitHub账号信息

第二步的请求会返回这样access_token=d0686dc49a22d64e77402db072b719f510f22421&scope=user&token_type=bearer的内容,拿到access_token只需要向https://api.github.com/user?access_token=xxx发送GET请求,即可获取到登录用户的基本信息,

具体实现

GitHub注册应用

首先你要有一个GitHub账号,然后进入settings -> OAuth application -> Register a new application。进来后你会看到下面这个页面:

应用注册(github-login).png

依次填好应用名称,应用地址和授权回掉地址后点击Register application按钮,会生成一个client Idclient Secret,用于后面向GitHub发送请求传参。

生成ID和Secret(github-login).png

Github授权请求(获取code)

在页面中添加GitHub登入跳转按钮,并在路由中对跳转请求进行转发处理:

//在node-login/components/LoginTab.js

<a href="/github/login">
     <Icon type="github" style={{fontSize: 20, color: '#000'}}/>
</a>

点击登入(github-login).png

添加跳转按钮后,增加相应路由处理,路由入口中添加/github路径处理

//在node-login/routes/index.js

const github = require('./github');
router.use('/github', github.routes(), github.allowedMethods());

最后是具体的路由处理

//在node-login/routes/github.js
const config = require('../config');
const router = require('koa-router')();
const fetch = require('node-fetch');
const routers = router
    .get('/login', async (ctx) => {
        var dataStr = (new Date()).valueOf();
        //重定向到认证接口,并配置参数
        var path = "https://github.com/login/oauth/authorize";
        path += '?client_id=' + config.client_id;
        path += '&scope=' + config.scope;
        path += '&state=' + dataStr;
        //转发到授权服务器
        ctx.redirect(path);
    })
module.exports = routers;

在config中事先添加配置请求所需参数client_idclient_secretscope

module.exports = {
    'database': 'mongodb://localhost:27017/node-login',
    'client_id': '83b21756e93d6ce27075',
    'client_secret': 'd87c4163ece5695a9ded1e8bf2701c5ee2651f28',
    'scope': ['user'],
};

其中scope参数可选。就是你期待你的应用需要调用Github哪些信息,可以填写多个,以逗号分割,比如:scope=user,public_repo。state参数非必需,用于防治跨域伪造请求攻击。

现在可以运行一下项目,点击小黑猫,跳转到授权登入页面(没登入过,要输入账号密码),授权成功返回回掉地址。

授权(github-login).png

回掉地址中code就是返回的授权码,通过授权码再去获取令牌access_token

授权回掉(github-login).png

授权回掉处理(获取access_token)

在第一步授权请求https://github.com/login/oauth/authorize成功后GitHub会给应用返回一个回掉http://localhost:3003/github/oauth/callback?code=14de2c737aa02037132d&state=1496989988474。这个回掉地址就是之前在GitHub注册应用时填入的回掉地址,另外还带了需要的code参数,state就是上一步请求中带的state参数,原样返回。

现在我们要对这个回掉请求进行处理:

//node-login/routes/github.js
const config = require('../config');
const router = require('koa-router')();
const fetch = require('node-fetch');
const routers = router
    .get('/login', async (ctx) => {
        ...
    })
    .get('/oauth/callback', async (ctx) => {
        const code = ctx.query.code;
        let path = 'https://github.com/login/oauth/access_token';
        const params = {
            client_id: config.client_id,
            client_secret: config.client_secret,
            code: code
        }
        console.log(code);
        await fetch(path, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(params)
        })
        .then(res => {
            return res.text();
        })
        .then(body => {
            ctx.body = body;
        })
       .catch(e => {
            console.log(e);
        })
    })
module.exports = routers;

GitHub返回回掉地址时,先拿到请求中的code参数,然后向https://github.com/login/oauth/access_token发送post请求并带上client_id,client_secret,code参数,请求成功后会返回带有access_token的信息。

access_token(github-login).png

获取GitHub账号信息

最后带上获取的access_token请求https://api.github.com/user?access_token=xxx,返回的就是之前scope中对应的账号信息。

.get('/oauth/callback', async (ctx) => {
        const code = ctx.query.code;
        let path = 'https://github.com/login/oauth/access_token';
        const params = {
            client_id: config.client_id,
            client_secret: config.client_secret,
            code: code
        }
        console.log(code);
        await fetch(path, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(params)
        })
        .then(res => {
            return res.text();
        })
        .then(body => {
            const args = body.split('&');
            let arg = args[0].split('=');
            const access_token = arg[1];
            console.log(body);
            console.log(access_token);
            return access_token;
        })
        .then(async(token) => {
            const url = ' https://api.github.com/user?access_token=' + token;
            console.log(url);
            await fetch(url)
                .then(res => {
                    return res.json();
                })
                .then(res => {
                    console.log(res);
                    ctx.body = res;
                })
        })
        .catch(e => {
            console.log(e);
        })
    })

返回的用户信息如下:

user_info(github-login).png

总结

用一张图来总结

原理过程(github-login).png

查看原文

赞 8 收藏 18 评论 19

认证与成就

  • 获得 394 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-11-03
个人主页被 635 人浏览