背景

公司为客户开发微信公众号相关服务时,有时未能准备好公众号,所以需要使用公司的公众号,但是大家都知道微信网页授权域名最多只支持两个,这就造成了如果有多个项目需要同时开发时产生了如下问题:

  • 网页授权地址不够用
  • 公众号不够用
  • 某功能比如微信快捷登录突然失效(授权地址被改掉)

解决方案

关于域名占用的问题,其实在github上已经有现成的方法了,可以实现多域名的授权,而且实现内容也比较简单,就是一个粗暴的静态html文件去处理授权用的参数。博主之前曾经做过一个网页授权扫码登录的Demo就用到了这个静态文件。

正常情况下如果用到了网页授权获取用户信息,一般是需要一台服务器一个备案过的域名的,那么如果没有服务器改咋整呢?

云函数很巧妙地解决了这个问题,我们只需要一个自己的域名(不用其实也可以)就可以通过云函数来托管这个授权用的文件来实现通用的授权服务。

下面我们来看一下如何去做这么一个简易的基础服务。

需求分析

首先我们知道配置网页授权域名的时候需要在公众号添加这个域名,要求我们在服务器上上传一个验证文件,并且这个文件要挂在根目录下才可以访问到,这就要求我们增加一个文件上传的功能。

这种情况下云函数就需要具备如下能力:

  • 静态文件托管
  • txt验证文件上传

显然自己手动从零编写一个云函数就有些繁琐了,不过还有我们有内置应用模板帮助简化工作量。

实现步骤

应用创建

在云函数的后台直接创建应用,使用koa模板。

应用修改

应用创建好之后会在云函数列表里出现名为koa-starter的函数,我们需要修改这个函数的代码。

应用模板的源码在github上就可以获取->koa-starter

这里讲解一下几个核心修改的实现吧:

  1. app.js 内增加文件上传的支持,小文件是可以直接上传的。
app.use(    koaBody({        multipart: true,        formidable: {            multipart: true,            maxFileSize: 400 * 1024 * 1024 // 设置上传文件大小最大限制,默认4M        }    }))
  1. 然后增加一些全局的处理
// 全局异常处理app.use(async (ctx, next) => {    try {        await next()    } catch (err) {        ctx.body = {            code: -1,            data: ctx.data,            message: ctx.msg || err.message || 服务开小差了,请稍后再试,            etime: Date.now()        }    }})// pretty json resultapp.use(async (ctx, next) => {    await next()    if (ctx.data) {        ctx.set(Content-Type, application/json)        ctx.body = {            code: ctx.code || 0,            data: ctx.data,            message: ctx.msg || success,            etime: Date.now()        }    }})
  1. routes/index.js中增加上传文件的路由处理
router.post("/uptxt", async (ctx, next) => {    if (!ctx.request.files) {        ctx.data = 未选择文件        await next()        return    }    // 获取上传文件    let file = ctx.request.files.file     // 创建可读流    let reader = fs.createReadStream(file.path)    let filePath = `/tmp/${file.name}`    // 创建可写流    const upStream = fs.createWriteStream(filePath)    await reader.pipe(upStream);    ctx.data = file.name    ctx.code = 0    ctx.msg = 上传验证文件成功    await next()});
  1. 前端的模板目录views下面增加两份页面代码。
    • auth.html文件,用于授权处理,代码参考
    • up.html文件,用于文件上传。

文件上传功能我们需要注意一点:

  • 云函数在执行过程中,都拥有一块500MB的临时磁盘空间 /tmp,用户可以在执行代码时对该空间进行一些读写操作,也可以创建子目录,但这部分数据在函数执行完成后不会保留。

因为需要上传一个验证文件所以这个临时目录自然会有这个txt文件,但是微信需要验证这个文件的有效性,所以这就意味着tmp目录下的东西需要被我们访问到,那该怎么办?

解决办法当然是有的,那就是手动修改静态资源目录为tmp。

修改app.js文件:

app.use(require(koa-static)(/tmp))

这样在上传之后就可以直接访问到了。

  1. 首页及上传页的路由处理:
router.get("/", async (ctx, next) => {    await ctx.render("index");});router.get("/up", async (ctx, next) => {  await ctx.render("up");});
  1. 授权页的访问路由处理:
router.get("/auth.html", async (ctx, next) => {  await ctx.render("auth");});
  1. 保存代码并配置访问服务。

前端接入

vue项目为例

插件引入:

在项目中加入生成回调地址的wechatAuth.js 文件。

部分代码参考:

// 引入插件import wechatAuth from @/plugins/wechatAuth// 设置APPIDwechatAuth.setAppId(process.env.VUE_APP_WECHAT_APPID)// 使用插件生成授权回调地址wechatAuth.redirect_uri = processUrl()/** * 处理url链接 * @returns {string} */function processUrl() {  // 本地环境拿code  if (process.env.NODE_ENV === development) {    // 中间授权页地址    return `${process.env.VUE_APP_WECHAT_AUTH_URL}?backUrl=${window.location.href}`  }  const url = window.location.href  // 解决多次登录url添加重复的code与state问题  const urlParams = qs.parse(url.split(?)[1])  let redirectUrl = url  if (urlParams.code && urlParams.state) {    delete urlParams.code    delete urlParams.state    const query = qs.stringify(urlParams)    if (query.length) {      redirectUrl = `${url.split(?)[0]}?${query}`    } else {      redirectUrl = `${url.split(?)[0]}`    }  }  return redirectUrl}

环境变量配置:

#appid 可填入申请的测试公众号id或者其它准备好的IDVUE_APP_WECHAT_APPID=#authUrl 网页授权中间页VUE_APP_WECHAT_AUTH_URL=云函数http访问服务地址/auth.html

整个授权服务的流程可概括为下图:

因为我们只是把获取微信授权code的过程统一放到了云函数去处理,所以多个项目在调试时都可以使用同一个地址,减少了资源占用,不失为一个可用方案。

我们仅需要一个云函数就可以实现微信授权的本地调试以及几个项目几个公众号共用一个授权服务,免去独立域名、独立服务器的烦恼。

服务Demo演示

这里提供了一个云函数网页授权服务的Demo地址:
http://cloud.xuedingmiao.com/

参考资料

本文作者:薛定喵君
原文地址:http://xuedingmiao.com/blog/scf_wx_page_auth_service.html