1. 程式人生 > 實用技巧 >在專案中使用egg 的總結(持續更新文)

在專案中使用egg 的總結(持續更新文)

使用 egg 實戰微信小程式後端

微信小程式授權登入流程

官方圖示

我的理解

node 的實現

class AuthController extends BaseController {
  /**
   * 登入憑證校驗
   * @param {number} appid 小程式appid 必傳
   * @param {number} code 使用者登入憑證(有效期五分鐘)必傳 
   * @param {string} encryptedData   包含unionId的微信使用者資訊 非必傳
   * @param {string} iv 加密演算法的初始向量 -必傳
   * @param {number} timestamp 時間戳 非必傳
   *
   */
  async code2session() {
    const { ctx } = this;
    const { appid, code } = ctx.request.body;
    const { secret } = this.mpInfo;
    const { service } = ctx;
    const { MicroAppUser } = ctx.modelMa;
    try {
      ctx.validate({
        appid: 'string',
        code: 'string'
      })
    } catch (err) {
      ctx.throw(400, err)
    }
    try {
      // 微信code2Session
      const wxC2sRes =  await this._wx_code2Session(appid, secret, code)
      // 生產sid
      const sid = await this._createSessionId()

      const unionid = wxC2sRes.unionid;
      const openid = wxC2sRes.openid;
      const wxUser = await this._getWxUserForDb(openid, appid) || '';
      const created = await MicroAppUser.findOrCreate({
        defaults: {
          sid,
          sessionKey: wxC2sRes.session_key,
          openid,
          unionid,
          uid
        },
        where: {
          appid,
          openid
        }
      }).spread((row, created) => { return created })
  
      if (!created) {
        // 更新sid
        await MicroAppUser.update({
          sid,
          sessionKey: wxC2sRes.session_key,
          openid,
          unionid,
          uid
        }, {
          where: {
            appid,
            openid
          }
        })
      }

      if (wxUser) wxUser.session_key = wxC2sRes.session_key;
      ctx.body = {
        code: 200, 
        data: {
          sid,
          openid: wxC2sRes.openid,
          ypUser,
          wxUser
        }
      }
    } catch (err) {
      ctx.throw(500, err)
    }
  }
  async code2sessionAndDecode () {
    const { ctx } = this;
    const { appid, code, encryptedData, iv,} = ctx.request.body;
    const { secret } = this.mpInfo;
    const { service, helper } = ctx;
    const { MicroAppUser } = ctx.modelMa;
    try {
      ctx.validate({
        appid: 'string',
        code: 'string'
      })
    } catch (err) {
      ctx.throw(400, err)
    }
    try {
      if (!iv) {
        return ctx.body = {
          code: 400,
          msg: `缺少必要引數vi`
        }
      }

      const wxC2sRes = await this._wx_code2Session(appid, secret, code)

      let sessionKey = wxC2sRes.session_key
      if (!sessionKey) {
        ctx.throw(500, 'sessionKey is not found')
      }
      // 生產sid
      const sid = await this._createSessionId()

      // 解碼unionid獲取微信使用者資訊
      const wxUser = await helper.encodeWxEncryptedData(appid, sessionKey, encryptedData, iv);
      const openid = wxUser.openId;
      const unionId = wxUser.unionId;
      const uid = unionId ? await service.rap.dubbox.getUserIdByUnionId(unionId) : 0;
      const ypUser = uid ? await service.rap.dubbox.getUserInfoById(uid) : '';

      if (ypUser) {
        ypUser.role = await this._getCrewRole(ypUser.uid)
      }

      const created = await MicroAppUser.findOrCreate({
        defaults: {
          sid,
          sessionKey,
          openid,
          unionid: unionId,
          uid
        },
        where: {
          appid,
          openid
        }
      }).spread((row, created) => { return created })

      if (!created) {
        // 更新sid
        await MicroAppUser.update({
          sid,
          sessionKey,
          openid,
          unionid: unionId,
          uid
        }, {
          where: {
            appid,
            openid
          }
        })
      }

      ctx.body = {
        code: 200, 
        data: {
          sid,
          ypUser,
          wxUser
        }
      }
    } catch (err) {
      ctx.throw(500, err)
    }
  }
  // 儲存微信使用者資訊
  async saveUserInfo() {
    const { ctx, app } = this;
    const body = ctx.request.body;
    const { MicroAppUser } = app.modelMa;
  
    try {
      body.openid = ctx.openid || body.openId || body.openid
      body.nickname=body.nickname || body.nickName;
      const defaults = {
        appid: body.appid,
        openid: body.openid,
        unionid: body.unionid ||  body.unionId,
        sessionKey: body.session_key,
        nickname: body.nickname,
        gender: body.gender,
        avatarUrl: body.avatarUrl,
        country: body.country,
        province: body.province,
        city: body.city,
        language: body.language,
        uid: body.uid
      }
      this.logger.info(`saveUserInfo>>>${defaults}`)
  
      let result = await MicroAppUser.findOrCreate({
        raw: true,
        defaults,
        where: {
          openid: body.openid,
          appid: body.appid
        }
      })
      if (!result.created) {
        result = await MicroAppUser.update(defaults, {
          where: {
            openid: body.openid,
            appid: body.appid
          }
        })
      }
      return ctx.body = {
        code: 200,
        data: result,
        msg: '操作成功'
      }
    } catch (err) {
      ctx.throw(500, err)
    }
  }
  // 從DB獲取微信使用者資訊
  async _getWxUserForDb(openid, appid) {
    return await this.app.mysql.get('microApp').select('micro_app_user', {
      where: {
        openid,
        appid
      }
    }).then(rows => {
      return rows = rows[0]
    })
  }
  // 建立sessionid
  async _createSessionId () {
    const { ctx } = this;
    const { appid, timestamp, uid, sid } = ctx.request.body;
    var str = ''
    if (appid) str += appid
    if (timestamp) str += timestamp
    if (sid) str += sid
    if (uid) str += uid
    return md5(str)
  }
  // 微信-登入憑證校驗
  async _wx_code2Session(appid, secret, code) {
    try {
      const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appid}&secret=${secret}&js_code=${code}&grant_type=authorization_code`;
      const data = await this.ctx.curl(url, {
        method: 'get',
        contentType: 'json',
        timeout: 10000,
        dataType: 'json',
      })
      return data.data
    } catch (err) {
      this.ctx.throw(err)
    }
  }
}

利用 redis 優化註冊介面

redis 是實際上是利用記憶體在執行,所以為了防止使用者註冊兩次時間間隔太短

  • 存入 redis
  • 從 redis 讀取值存入資料庫
  • 刪除 redis 中的值
  async register() { 
    const { ctx, app, service } = this
    const { GlUser } = ctx.activityModel
    ctx.UID = await this.checkUid()
    // 插入到「我的」活動表
    let aid = this.actSetting.aid
    if (aid) {
      const result = await ctx.service.activity.activity.regActivity(
        ctx.UID,
        aid
      )
    }
    try {
      const userInfo = await ctx.service.activity.user.getUserInfoById(ctx.UID)

      // 如果頭像來源是又拍雲,則將頭像縮放至 200*200 輸出, 並改為 https 協議
      if (userInfo.avatar && userInfo.avatar.indexOf('upyun') > -1) {
        let index = userInfo.avatar.indexOf('://')
        userInfo.avatar = `https${userInfo.avatar.slice(
          index,
          userInfo.avatar.length
        )}!/sq/200`
      }

      const data = {
        uid: ctx.UID,
        nickname: userInfo.nickname,
        avatar: userInfo.avatar,
      }

      // redis是實際上是利用記憶體在執行,所以為了防止使用者註冊兩次時間間隔太短
      // 1. 存入redis 2. 從redis讀取值存入資料庫 3. 刪除redis中的值
      await service.redis.setRedis(this.redisKey + ctx.UID, data, this.settings.redisTime);
      const userData = await service.redis.getRedis(this.redisKey + ctx.UID);

      let result = await GlUser.findOrCreate({
        where: {
          uid: ctx.UID,
        },
        defaults: userData,
      }).spread((user, created) => { 
        return user
      })

      ctx.body = {
        code: 200,
        data: result,
        msg: 'success',
      }

      await service.redis.destroyRedis(this.redisKey + ctx.UID);
    } catch (error) {
      ctx.throw(500, error)
    }
  }

參考:

小程式登入