在项目中使用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)
}
}
参考: