OAuth 认证方式有多种,但常用的有两种
- Password
- Authorize Code
Password 方式
这种方式比较简单,就是把用户名和密码传递给认证平台,认证平台返回 token。
比如我们使用github
,把在github
上注册的用户名密码传给github
,github
认证后返回token
。
但这有个很明显的弊端:就是当前系统可以知道你在 github 上的账号密码,比较不安全。
Authorize Code
这种方式和Password
方式最大的区别就是,这个登录是在认证平台(github
)提供的登录页面,也就是在github
的网站进行登录和验证,验证通过后返回一个code
,然后系统拿着这个code
再去认证平台获取token
。
这种方式比较安全,当前网站无法接触到你在认证平台上的任何信息。
Auth 字段
- client_id
- scope
- redirect_uri
如果传这个参数,则必须和注册的时候输入的地址完全一样,不然获取不到 code;不传则跳转到注册填写的地址。 - login
- state
跳转的时候带上的这个 state 和返回 code 的时候返回的 state 保持一致。 - allow_signup
是否允许动态注册,就是如果用户没有注册,则自动注册,然后返回信息。
https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/
请求授权,获取 code
GET https://github.com/login/oauth/authorize
链接上可以传上面的 auth 字段参数
请求 token
请求 token 时,client_id、client_secret、code 是必须的,
redirect_uri、state 不是必须的
POST https://github.com/login/oauth/access_token
OAuth Code 如何保证安全
- 一次性的 code
code 获取了 token 后就失效了 - id+secret
没这两个值,是无法获取 token 的 - redirect_uri
如果这个和注册时不一致,就会报错
cookie 和 session
cookie 是存储在客户端,每次请求都会携带它,不管是文件请求还是 api 的请求,服务端可以读取到 cookie 的值。
登录后让在当前页
这个实现起来也很简单,就是登录之前先把当前页面的地址存起来,登录成功后再跳转到即可。
通过next/router
中的withRouter
可以获取到当前页面的url
地址
实现思路有两种:
- 在登录事件中,先请求服务器,把 url 地址存起来,然后再跳转到登录页面
1 | const onGoToAuth = useCallback(e => { |
- 另一种是超链接直接链接过去,在服务端跳转
1 | <a href={`prepare-auth?url=${router.asPath}`}> |
请求代理
上一个项目使用的是 express 的 http 代理插件,轻松搞定,这个是纯手写。
其实就是根据规则转发请求。
前端和服务端请求的解决
getInitalProps
在客户端和服务端都会执行,当在客户端发起请求时比价好办,上面的代理直接转给了真正的服务服务器。但在服务端执行时,由于写的是相对路径,服务器默认会在自己身上找,那这个请求肯定是找不到的,所以请求是服务端发出的还是客户端发出的,需要区别对待。
上一个项目是通过
createStore
的第三个参数thunk.withExtraArgument
来解决。可参考
这里做的比较直接,直接是在发出请求的时候判断下,是服务端还是客户端,然后做响应的处理,服务端的话,就要以全路径的方式请求。把路径拼全就行了。
1 | const isServer = typeof window === "undefined"; |
###获取 post 请求的数据
使用koa-body
插件即可,安装完后在 server 中注册即可
1 | const server = new Koa(); |
在 post 的请求里面,就可以通过ctx.request.body
的方式来获取 post 传递的值。
getInitialProps 的参数为什么和官网的不一样
今天在做项目时,教程里getInitialProps
的参数有ctx
参数,而页面的getInitialProps
中没有。结果看了下官网给的绑定 redux 的 demo,在绑定 redux 的地方也是有 ctx 的。因为我自定义了_app.js 文件,所以怀疑可能是这引起的。
因为所有的页面都会执行 app.js,所以这的定义可能会影响到组件中的功能。
看了下我的_app.js
中是没有定义getInitialProps
,所以各个组件的getInitialProps
是默认的,官网上显示getInitialProps
默认是没有 ctx 属性的,所以就在_app.js
中自定义了getInitialProps
,并验证这里是有ctx
属性的,所以透传下去,各个组件也就也有ctx
属性了。
getInitialProps
返回的对象在组件中获取不到
原因是在_app.js
中我自定义了getInitialProps
,但是没有把从getInitialProps
获取到的值返回方式和接收方式不一致
1 | static async getInitialProps(appContext) { |
解决路由切换回来 tab 选中丢失情况
使用next/router
提供的 router 功能,把 tab key 的值放到 url 中,然后从 url 中获取 tab key 的值即可。
这样切换路由再回来时,url 中是带参数的。
缓存本页的数据
由于页面上数据变化不大,因此可以在本页缓存数据,以避免每次点击 tab 都会重新请求数据
在页面中声明一个全局变量,存储远程获取的数据,然后在getInitialProps
中判断,如果缓存变量中有数据就直接返回不再请求。
// index.js
1 | function Index() {} |
但这有一个弊端,如果是首页,也就是当getInitialProps
是在服务端执行时,全局变量的值会一直存在,即使在新的客户端用新的用户登录,全局变量中的数据仍然存在;这是为什么呢?
这是因为全局变量是这个模块的全局变量,并不是Index组件(或方法)的变量,即使重新渲染,原来缓存的数据仍然存在;当nextjs启动服务加载了index.js模块后,这个全局变量就一直存在。
我们赋值过后它就一直有值。所以需要判断如果是服务端渲染就不从这个变量中取值,也不给他们赋值;
1 | const isServer = typeof window === "undefined"; |
模块中的其他全局变量也要注意这个问题的存在!!!
切换 tab 时,url 的 key 参数没有发生变化
这需要吧 withRouter 放到 connect 外面,放里面就有上面的说的问题
1 | export default withRouter(connect(state => ({ user: state.user }))(Index)); |
第一次服务端获取到的数据没缓存
第一次服务端获取到的数据没缓存,当第一次切换 tab 时仍重新请求了数据,这可以使用useEffect
来解决,第一次页面加载时,如果 props 中有值就缓存起来
1 | useEffect(() => { |
使用lru-cache
指定缓存策略
上面的缓存没问题,但是缓存是一直存在的,不会过期,这不是太好,可以通过lru-cache
来制定有时效的换成策略。
这个使用起来比较简单,查看官网写即可。
详情页
查询、翻页等操作都反应到 url 参数中,这样页面进行回退等操作时,能记录上次的状态。
github 限制了列表请求最多只返回 1000 条数据的请求,超过第 1000 条数据的请求就报错不返回了,比如你每页 20 条,最大只能访问 50 页;因为没人会翻在 000 条数据内还找不到要找的库。
客户端数据缓存
有很多数据是变化不大的,可以针对客户端请求做缓存策略。注意服务端渲染时不要做缓存。
1 | const isServer = typeof window === "undefined"; |
转换 base64 编码成可识别文字
window 全局提供了一个方法可以转换,方法就是atob(content)
,但是服务端没这个方法,所以可以安装atob
包,在服务端把这个包赋给全局变量
1 | const atob = require("atob"); |
转换显示 markdown 文件
markdown
文件默认读取出的内容是 base64 字节流,通过atob
方法可以转换成它本来的内容,但是特殊符号例如空格仍是
,所以仍需转换真正的 html 字符串,通过markdown-it
组件可以完成
react 做了 xss 攻击处理,所以需要通过下面的方式显示 html 字符串
1 | // 把base64转换成了原来的字符串,但是特殊符号还不是html的显示方式 |
markdown 内容中文乱码问题
需要把 base64 转换 utf8
1 | function base64_to_utf8(str) { |
图片无法显示问题
markdown 中的 html 是无法直接显示,通过制定显示 markdown 中的 html 可以解决
1 | const md = new MarkdownIt({ |
使用github-markdown-css
样式,使 markdown 内容显示更完美
1 | npm i github-markdown-css |
生成分析文件
首先 next 对应的插件@zeit/next-bundle-analyzer
,然后在next.config.js
中进行配置,并设置打包命令
1 | // next.config.js |
动态加载不变的文件
封装了一个 Markdown 文件显示的组件,这个组件的内容是不变的,所以可以动态加载,动态加载的文件的会被 webpack 单独打包,因为它的打包后的 hash 不变,可以方便浏览器缓存,提高体验。
优化 moment 包
moment
中包含很多我们用不上的文件,比如它的国际化文件中包含了很多国家的语言文件,我们只需要中文的,所以只需要加载中文的语言包就行。
- 配置
webpack
忽略moment
组件的所有语言包 - 在使用
moment
的地方手动引入要用的语言包
1 | // next.config.js |
去抖动
做自动匹配查询时,可以使用lodash/debounce
来去抖动
Gatsby