Fork me on github

在项目中使用egg 的总结(持续更新文)

使用 egg 实战微信小程序后端

微信小程序授权登录流程

官方图示

我的理解

.jpg

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)
    }
  }

参考:

小程序登录

posted @ 2020-07-18 10:58  Zenquan  阅读(455)  评论(0编辑  收藏  举报