0

72

Egg+Nuxt打造「趣技」初版上线总结

唐羲

2019-09-06 01:08:01

前言

“做点什么事”的想法一直在我心里无法磨灭,但作为一个技术人员来讲,总要花更高比重的时间和经历去学习技术、积累经验。长久以来,我花了太多时间在各种杂七杂八的探索之上,年初立下的 Flag 到现在来看,也并没有完成几个。意识到自己必须回到技术之路上之后,我觉得自己需要一个契机,来做这样一层转变。

几经思索之后,我在技术与技术之外(产品、运营)找到了平衡点。在一个月里,成功上线了趣技社区 的初始版本,并邀请了几位朋友体验反馈,虽然问题和功能方面都还有欠缺,但是我觉得自己可以在这个时候开始维护这个项目的同时进行技术研究了。

本篇文章记录了趣技这个项目从开发到部署过程的总结,包括功能规划、技术选型以及一些技术实现细节。

项目介绍

趣技的定位很简单,一个专业有趣的技术知识社区,至于怎么专业怎么有趣的问题还需要努力想想。主要面向的人群是互联网行业人群,受众群里和大多数社区高度重合,本社区也没有什么创新点,和掘金这些当然没得比,所以只能说是用心做的“垃圾玩具”。

初步规划的模块就是文章和小书模块,文章是比较分散的输出,小书代表知识系统化的总结。最开始想的工具和导航模块,感觉没必要加上,一是因为太过泛滥,二是价值有限。

技术架构

技术层面,对于我这个前端来讲,可供选择的不多。最终敲定后端接口服务使用 Egg.js,数据库存储选用 MongoDB,前端为了快速开发以及那一点点 SEO 需求,选用了 Nuxt.js。由于自己下定了决心 TypeScript 必须要上,所以用了上了 TypeScript 版的 Egg。前后分离部署,通过 JWT 进行前后通信。

Egg.js

初次接触 TypeScript 和 Egg.js,只能用“爽”字来形容。TypeScript 安全的类型系统、强大的智能提示都让我对自己的代码很有信心。Egg 作为国内企业级的 Node 开发框架,对 Koa2 进行上层封装,让开发者在协作开发时有统一的约定规范,官方文档也非常清晰,一键部署、一键停止服务非常方便,基本没有遇到什么坑。

MongoDB

文档型数据库灵活易用,和关系型数据库比起来,快速出活是极好的选择,而且量级小的产品也不用担心会有性能,只要文档内嵌文档的数量不是太多。项目中选用 egg-mongoose 来操作数据库,定义 Model,操作 Model 的方式非常容易理解。

其他类似HTTPS证书申请和配置,Nginx 反向代理前后端服务,七牛云存储图片资源,都是常规操作,具体可圈可点的没有多少,这里不详细说了。

值得记录的点

前后端分离,Github授权登录时如何处理

起初遇到这个问题,的确没有思路,Github Auth App 中需要配置一个 callback 回调地址,这个地址是拉取 Github 授权成功(用户点击授权按钮)后,进行跳转的回调地址,回调地址上会带上用户的信息,开发者需要对这些信息处理登录或者注册的业务。如果是前后一体的网站,直接跳转到定义好的页面没有问题,但是前后分离的情况下,回调地址不可能写前端页面的地址,因为不可能前端处理用户信息处理存储的逻辑,那就需要跳一个后端的地址,这个地址需要传递后端生成的 token 给前端页面,让前端页面得知登录成功。文字说明太过抽象,看代码:

// 后端
// github auth
const github = app.passport.authenticate('github', {
  // 3. 授权成功,passport中间件处理成功信息后,会跳转到下面这个地址,我们要做的就是注册这个地址进行生成token、和前端页面通信的动作
  successRedirect: '/passport/github/call_back'
})
// 1. "/passport/github"为前端页面访问的地址,拉取github授权
router.get('/passport/github', github)
// 2. "/passport/github/callback"为github auth app中配置的接口地址,需要带上后端服务的请求地址
router.get('/passport/github/callback', github)
// 4. 注册第3步需要的路由
router.get('/passport/github/call_back', controller.v1.user.githubCallback)

接着上面,看看生成 token、与前端页面通信的具体实现

// 后端
// Github 登录回调,传递token给客户端
public async githubCallback() {
  const { ctx, app } = this
  const user = ctx.user
  if (user._id) {
    // 获取到了用户信息,生成token(具体处理过程不展示)
    const token = await ctx.service.user.createToken({id: user._id})
    const data = {
      user,
      token
    }
  // 渲染一个页面,显示登录中,同时 postMessage 一个 token 信息,让前端页面接收到
  return ctx.body = `<!doctype html>
    <html lang="zh-CN">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Auth Github</title>
      </head>
      <body>
        <p>登陆中...</p>
        <script>
          window.onload = function () {
            window.opener.postMessage('${JSON.stringify(data)}', 'http://127.0.0.1:3030')
            window.close()
          }
        </script>
      </body>
    </html>`
  }
}

那么问题是前端页面应该如何接收到这个信息呢?

// 前端域名是http://127.0.0.1:3030,打开了一个 http://127.0.0.1:7001 的窗口进行访问,回调过来时,由于设置了前端页面域名为接受信息的域名,所以能够接收到。
handleGithubAuth() {
  // 弹出 500 * 500 的窗口
  window.open(`${config.dev ? 'http://127.0.0.1:7001'}/passport/github`, 'newwindow', 'height=500, width=500, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, resizable=no,location=n o, status=no')
  // 通过监听,父页面可以拿到子页面传递的token,父(前端页面),子(小窗)
  window.addEventListener('message', (e) => {
    try {
      if (typeof e.data === 'string') {
        const _data = JSON.parse(e.data)
        if (_data.token) {
          // 处理登录逻辑
          setTimeout(() => {
            this.$store.commit('user/setToken', _data.token)
            Cookie.set('token', _data.token)
            this.$store.commit('user/setUserInfo', _data.user)
            Cookie.set('userInfo', JSON.stringify(_data.user))
            this.$router.push('/')
          }, 300)
        }
      }
    } catch (err) {
    }
  }, false)
}

总结

目前还只能总结这么多,目前还有很多事情做。希望朋友们能够注册体验一些,给点反馈意见,目前已经收集到几十条,每天都在修复。最后,感谢大家!🙏

发表评论

登陆 后发表评论

评论列表

还没有评论,快来做第一个评论的人吧