背景

公司為客戶開發(fā)微信公眾號(hào)相關(guān)服務(wù)時(shí),有時(shí)未能準(zhǔn)備好公眾號(hào),所以需要使用公司的公眾號(hào),但是大家都知道微信網(wǎng)頁(yè)授權(quán)域名最多只支持兩個(gè),這就造成了如果有多個(gè)項(xiàng)目需要同時(shí)開發(fā)時(shí)產(chǎn)生了如下問(wèn)題:

  • 網(wǎng)頁(yè)授權(quán)地址不夠用
  • 公眾號(hào)不夠用
  • 某功能比如微信快捷登錄突然失效(授權(quán)地址被改掉)

解決方案

關(guān)于域名占用的問(wèn)題,其實(shí)在github上已經(jīng)有現(xiàn)成的方法了,可以實(shí)現(xiàn)多域名的授權(quán),而且實(shí)現(xiàn)內(nèi)容也比較簡(jiǎn)單,就是一個(gè)粗暴的靜態(tài)html文件去處理授權(quán)用的參數(shù)。博主之前曾經(jīng)做過(guò)一個(gè)網(wǎng)頁(yè)授權(quán)掃碼登錄的Demo就用到了這個(gè)靜態(tài)文件。

正常情況下如果用到了網(wǎng)頁(yè)授權(quán)獲取用戶信息,一般是需要一臺(tái)服務(wù)器一個(gè)備案過(guò)的域名的,那么如果沒(méi)有服務(wù)器改咋整呢?

云函數(shù)很巧妙地解決了這個(gè)問(wèn)題,我們只需要一個(gè)自己的域名(不用其實(shí)也可以)就可以通過(guò)云函數(shù)來(lái)托管這個(gè)授權(quán)用的文件來(lái)實(shí)現(xiàn)通用的授權(quán)服務(wù)。

下面我們來(lái)看一下如何去做這么一個(gè)簡(jiǎn)易的基礎(chǔ)服務(wù)。

需求分析

首先我們知道配置網(wǎng)頁(yè)授權(quán)域名的時(shí)候需要在公眾號(hào)添加這個(gè)域名,要求我們?cè)诜?wù)器上上傳一個(gè)驗(yàn)證文件,并且這個(gè)文件要掛在根目錄下才可以訪問(wèn)到,這就要求我們?cè)黾右粋€(gè)文件上傳的功能。

這種情況下云函數(shù)就需要具備如下能力:

  • 靜態(tài)文件托管
  • txt驗(yàn)證文件上傳

顯然自己手動(dòng)從零編寫一個(gè)云函數(shù)就有些繁瑣了,不過(guò)還有我們有內(nèi)置應(yīng)用模板幫助簡(jiǎn)化工作量。

實(shí)現(xiàn)步驟

應(yīng)用創(chuàng)建

在云函數(shù)的后臺(tái)直接創(chuàng)建應(yīng)用,使用koa模板。

應(yīng)用修改

應(yīng)用創(chuàng)建好之后會(huì)在云函數(shù)列表里出現(xiàn)名為koa-starter的函數(shù),我們需要修改這個(gè)函數(shù)的代碼。

應(yīng)用模板的源碼在github上就可以獲取->koa-starter

這里講解一下幾個(gè)核心修改的實(shí)現(xiàn)吧:

  1. app.js 內(nèi)增加文件上傳的支持,小文件是可以直接上傳的。
app.use(    koaBody({        multipart: true,        formidable: {            multipart: true,            maxFileSize: 400 * 1024 * 1024 // 設(shè)置上傳文件大小最大限制,默認(rèn)4M        }    }))
  1. 然后增加一些全局的處理
// 全局異常處理app.use(async (ctx, next) => {    try {        await next()    } catch (err) {        ctx.body = {            code: -1,            data: ctx.data,            message: ctx.msg || err.message || 服務(wù)開小差了,請(qǐng)稍后再試,            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     // 創(chuàng)建可讀流    let reader = fs.createReadStream(file.path)    let filePath = `/tmp/${file.name}`    // 創(chuàng)建可寫流    const upStream = fs.createWriteStream(filePath)    await reader.pipe(upStream);    ctx.data = file.name    ctx.code = 0    ctx.msg = 上傳驗(yàn)證文件成功    await next()});
  1. 前端的模板目錄views下面增加兩份頁(yè)面代碼。
    • auth.html文件,用于授權(quán)處理,代碼參考。
    • up.html文件,用于文件上傳。

文件上傳功能我們需要注意一點(diǎn):

  • 云函數(shù)在執(zhí)行過(guò)程中,都擁有一塊500MB的臨時(shí)磁盤空間 /tmp,用戶可以在執(zhí)行代碼時(shí)對(duì)該空間進(jìn)行一些讀寫操作,也可以創(chuàng)建子目錄,但這部分?jǐn)?shù)據(jù)在函數(shù)執(zhí)行完成后不會(huì)保留。

因?yàn)樾枰蟼饕粋€(gè)驗(yàn)證文件所以這個(gè)臨時(shí)目錄自然會(huì)有這個(gè)txt文件,但是微信需要驗(yàn)證這個(gè)文件的有效性,所以這就意味著tmp目錄下的東西需要被我們?cè)L問(wèn)到,那該怎么辦?

解決辦法當(dāng)然是有的,那就是手動(dòng)修改靜態(tài)資源目錄為tmp。

修改app.js文件:

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

這樣在上傳之后就可以直接訪問(wèn)到了。

  1. 首頁(yè)及上傳頁(yè)的路由處理:
router.get("/", async (ctx, next) => {    await ctx.render("index");});router.get("/up", async (ctx, next) => {  await ctx.render("up");});
  1. 授權(quán)頁(yè)的訪問(wèn)路由處理:
router.get("/auth.html", async (ctx, next) => {  await ctx.render("auth");});
  1. 保存代碼并配置訪問(wèn)服務(wù)。

前端接入

vue項(xiàng)目為例

插件引入:

在項(xiàng)目中加入生成回調(diào)地址的wechatAuth.js 文件。

部分代碼參考:

// 引入插件import wechatAuth from @/plugins/wechatAuth// 設(shè)置APPIDwechatAuth.setAppId(process.env.VUE_APP_WECHAT_APPID)// 使用插件生成授權(quán)回調(diào)地址wechatAuth.redirect_uri = processUrl()/** * 處理url鏈接 * @returns {string} */function processUrl() {  // 本地環(huán)境拿code  if (process.env.NODE_ENV === development) {    // 中間授權(quán)頁(yè)地址    return `${process.env.VUE_APP_WECHAT_AUTH_URL}?backUrl=${window.location.href}`  }  const url = window.location.href  // 解決多次登錄url添加重復(fù)的code與state問(wèn)題  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}

環(huán)境變量配置:

#appid 可填入申請(qǐng)的測(cè)試公眾號(hào)id或者其它準(zhǔn)備好的IDVUE_APP_WECHAT_APPID=#authUrl 網(wǎng)頁(yè)授權(quán)中間頁(yè)VUE_APP_WECHAT_AUTH_URL=云函數(shù)http訪問(wèn)服務(wù)地址/auth.html

整個(gè)授權(quán)服務(wù)的流程可概括為下圖:

因?yàn)槲覀冎皇前勋@取微信授權(quán)code的過(guò)程統(tǒng)一放到了云函數(shù)去處理,所以多個(gè)項(xiàng)目在調(diào)試時(shí)都可以使用同一個(gè)地址,減少了資源占用,不失為一個(gè)可用方案。

我們僅需要一個(gè)云函數(shù)就可以實(shí)現(xiàn)微信授權(quán)的本地調(diào)試以及幾個(gè)項(xiàng)目幾個(gè)公眾號(hào)共用一個(gè)授權(quán)服務(wù),免去獨(dú)立域名、獨(dú)立服務(wù)器的煩惱。

服務(wù)Demo演示

這里提供了一個(gè)云函數(shù)網(wǎng)頁(yè)授權(quán)服務(wù)的Demo地址:
http://cloud.xuedingmiao.com/

參考資料

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