背景

公司為客戶開發微信公眾號相關服務時,有時未能準備好公眾號,所以需要使用公司的公眾號,但是大家都知道微信網頁授權域名最多只支持兩個,這就造成了如果有多個項目需要同時開發時產生了如下問題:

  • 網頁授權地址不夠用
  • 公眾號不夠用
  • 某功能比如微信快捷登錄突然失效(授權地址被改掉)

解決方案

關于域名占用的問題,其實在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