Egg.js+MongoDB開發小程式介面 (三) 與微信伺服器互動及使用者資訊加解密
什麼是Token
1、Token的引入:Token是在客戶端頻繁向服務端請求資料,服務端頻繁的去資料庫查詢使用者名稱和密碼並進行對比,判斷使用者名稱和密碼正確與否,並作出相應提示,在這樣的背景下,Token便應運而生。
2、Token的定義:Token是服務端生成的一串字串,以作客戶端進行請求的一個令牌,當第一次登入後,伺服器生成一個Token便將此Token返回給客戶端,以後客戶端只需帶上這個Token前來請求資料即可,無需再次帶上使用者名稱和密碼。
3、使用Token的目的:Token的目的是為了減輕伺服器的壓力,減少頻繁的查詢資料庫,使伺服器更加健壯。
#與微信伺服器互動
##獲取access_token
獲取小程式全域性唯一後臺介面呼叫憑據(
access_token
)。呼叫絕大多數後臺介面時都需使用 access_token,開發者需要進行妥善儲存。
請求地址
GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
access_token 有請求頻率和次數限制,但他有較長的時效性,所以我們可以將其儲存在MongoDB並掛載到Egg的Application上,方便在做其他請求時呼叫
我們利用MongoDB的TTL 和Egg的定時任務 來讓Application上掛載的Token 永不過期
service
app/service/wxminiprogram/index.js
用於獲取小程式開發所需的appid 和金鑰
'use strict';
const Service = require('egg').Service;
class wxmpIndexService extends Service {
constructor(ctx) {
super(ctx);
this.WXAuth = this.config.wxmpCf;
}
}
module.exports = wxmpIndexService;
app/service/wxminiprogram/auth.js
獲取 儲存 微信小程式access_token
'use strict'
const Service = require('egg').Service;
//
class wxmpAuth extends Service {
constructor(ctx) {
super(ctx);
this.WXAuth = this.config.wxmpCf;
this.MODEL = ctx.model.Auth.Token
}
//檢查Token 是否存在 給Egg定時任務用
async checkToken(type = 1) {
const ctx = this.ctx;
try {
const MR = await this.MODEL.findOne({
type
});
if (!!MR && !ctx.app.wxToken) {
const access_token = await this.getToken();
ctx.app.wxToken = access_token;
}
if (!MR) {
const RESULT = await this.MPAuth();
ctx.app.wxToken = RESULT.access_token;
}
} catch (error) {
console.log(error)
}
}
//獲取系統中Token
async getToken(type = 1) {
try {
const MB = await this.MODEL.findOne({
type
});
return !MB ? (await this.MPAuth()).access_token : MB.access_token
} catch (error) {
console.log(error);
return error
}
}
async MPAuth() {
try {
const RESULT = await this.WXMPToken();
const MR = await this.MODEL.findOneAndUpdate({
type: 1
}, {
endTime: new Date(),
...RESULT.data
}, {
upsert: true
});
return RESULT.data
} catch (error) {
console.log(error)
return error
}
}
// 從微信伺服器獲取access_token
WXMPToken() {
const ctx = this.ctx;
const {
appid,
secret
} = this.WXAuth;
return ctx.curl('https://api.weixin.qq.com/cgi-bin/token', {
dataType: 'json',
data: {
grant_type: 'client_credential',
secret,
appid,
type: 1
}
});
}
}
module.exports = wxmpAuth;
model
app/model/auth.js
module.exports = app => {
const mongoose = app.mongoose;
const Schema = mongoose.Schema;
const conn = app.mongooseDB.get('mp');
const tokenSchema = new Schema({
// TTL 過期 預設60*20
// token
access_token: {
type: String
},
// token 型別 我們在實際的專案開發中肯定不只使用騰訊的token 還會使用其他第三方的 所有這裡給token 分類方便重新整理更替 {1:騰訊小程式token}
type: {
type: Number,
},
endTime: {
type: Date,
default: Date.now,
index: {
expires: 6600
}
},
expires_in: {},
// 有效期
updateTime: {
type: Date
}
}, {
timestamps: {
createdAt: 'created',
updatedAt: 'updated'
}
});
tokenSchema.statics = {
addOne: async function(body) {
try {
return await this.model.create({...body });
} catch (error) {
return error
}
}
}
return conn.model('third_token', tokenSchema);
}
schedule 定時任務
app/schedule/token_refresh.js
const Subscription = require('egg').Subscription;
class refresh_token extends Subscription {
constructor(ctx) {
super(ctx);
this.wxAuthService = ctx.service.wxminiprogram.auth;
}
static get schedule() {
// 每10s執行一次token檢查 專案啟動時執行一次將未過期的token掛載到Application(開發時可以設定為此,開始時可能會不斷的重新整理熱更程式碼)
return {
interval: '10s',
type: 'worker',
immediate: true,
env: 'local'
};
}
async subscribe() {
const ctx = this.ctx;
try {
await this.wxAuthService.checkToken();
} catch (error) {
console.log(error)
return error;
}
}
}
module.exports = refresh_token;
#與小程式互動
建立使用者token
app/service/account/jwt.js
'use strict';
const Service = require('egg').Service;
class JwtService extends Service {
create(OBJ) {
const { app } = this
// const key = app.config.keys.replace(/_/g, '');
return app.jwt.sign({...OBJ }, app.config.jwt.secret, { algorithm: 'HS256' });
}
}
module.exports = JwtService;
##微信小程式使用者加解密小程式
###加解密函式
app/lib/WXBizDataCrypt.js
var crypto = require('crypto')
function WXBizDataCrypt(appId, sessionKey) {
this.appId = appId
this.sessionKey = sessionKey
}
WXBizDataCrypt.prototype.decryptData = function(encryptedData, iv) {
// base64 decode
var sessionKey = new Buffer(this.sessionKey, 'base64')
encryptedData = new Buffer(encryptedData, 'base64')
iv = new Buffer(iv, 'base64')
try {
// 解密
var decipher = crypto.createDecipheriv('aes-128-cbc', sessionKey, iv)
// 設定自動 padding 為 true,刪除填充補位
decipher.setAutoPadding(true)
var decoded = decipher.update(encryptedData, 'binary', 'utf8')
decoded += decipher.final('utf8')
decoded = JSON.parse(decoded)
} catch (err) {
throw new Error('Illegal Buffer')
}
if (decoded.watermark.appid !== this.appId) {
throw new Error('Illegal Buffer')
}
return decoded
}
module.exports = WXBizDataCrypt
##獲取小程式使用者加密資訊Service
app/service/wxminiprogram/user.js
'use strict'
const indexService = require('./index');
const WXBizDataCrypt = require('../../libs/WXBizDataCrypt');
//
//
class weChatTemplateService extends indexService {
constructor(ctx) {
super(ctx);
this.userMODEL = ctx.model.Account.User;
this.tokenSERVICE = ctx.service.account.jwt;
this.BASICUSER = ctx.model.Basics.User.User;
};
async createBasicUser(body, phone) {
const RBD = await this.BASICUSER.findOneAndUpdate({ phone }, body, { new: true, upsert: true });
};
// 解密資料
async descDatas(DATAS, reg = false) {
const { encryptedData = null, iv = null, js_code = null } = DATAS;
const { appid } = this.WXAuth;
try {
if (!!js_code) {
if (!!encryptedData) {
const { session_key, openId } = await this.getOpenID(js_code, (iv && !!reg) ? true : false, true);
const WXBDC = new WXBizDataCrypt(appid, session_key);
const RESULT = WXBDC.decryptData(encryptedData, iv);
return RESULT
}
}
} catch (error) {
console.log(error)
return { error: '伺服器忙,請重試!', code: 500 }
}
};
// 繫結手機
async bindPhone(DATAS, _id) {
let RESULT;
try {
RESULT = await this.descDatas(DATAS);
if (!!RESULT.error) { throw RESULT.error; return };
const { phoneNumber, countryCode } = RESULT;
const RBD = await this.userMODEL.findOneAndUpdate({ _id }, { 'telphone': phoneNumber }, { new: true });
const { telphone: phone = null, wxUserInfo: wx_UserInfo, unionid: wx_unionid } = RBD;
!!phone && await this.createBasicUser({ phone, wx_UserInfo, wx_unionid }, phone);
return { phone }
} catch (error) {
return { message: error, data: {}, code: 204 }
}
};
// 獲取步數
async getRun(DATAS) {
const RESULT = await this.descDatas(DATAS);
return RESULT;
};
async getOpenID(js_code, regUser, onlyId = false) {
const ctx = this.ctx
const { appid, secret } = this.WXAuth;
try {
const RESULT = await ctx.curl('https://api.weixin.qq.com/sns/jscode2session', {
dataType: 'json',
data: {
grant_type: 'authorization_code',
secret,
appid,
js_code
}
});
const { openid: openId = null, session_key = null, unionid = null, errcode } = RESULT.data;
if (!!onlyId) return { session_key };
if (!onlyId && !!session_key) {
const MR = await this.userMODEL.findOneAndUpdate({ openId }, { openId, unionid }, { 'upsert': true, 'new': true });
if (!regUser) {
const { _id: uid, isWXAuth, openId, wxUserInfo: userInfo, telphone = null, _merchant, _merchant: { _id: mid = null, role = null } } = MR;
// console.log(MR)
return this.setToken({ uid, isWXAuth, openId, mid, role }, { isWXAuth, 'userInfo': {...userInfo, telphone, uid, _merchant } });
} else {
return { session_key, openId }
}
} else {
return { code: 401, message: '獲取使用者資訊失敗' }
};
} catch (error) {
// console.log(error)
return error
}
};
setToken(params, other) {
return { 'token': this.tokenSERVICE.create(params), ...other }
};
async getUserInfo(DATAS) {
const ctx = this.ctx;
const { encryptedData = null, iv = null, js_code = null } = DATAS;
const { appid } = this.WXAuth;
try {
if (!!js_code) {
if (!!encryptedData && !!iv) {
const BBBB = await this.getOpenID(js_code, !!iv);
const { session_key, openId } = BBBB;
const WXBDC = new WXBizDataCrypt(appid, session_key);
const RESULT = WXBDC.decryptData(encryptedData, iv);
if (!!RESULT.openId) {
delete RESULT.openId;
delete RESULT.watermark;
// 註冊使用者
const MB = await this.userMODEL.findOneAndUpdate({ openId }, { isWXAuth: true, wxUserInfo: RESULT }, { upsert: true, new: true });
const { _id: uid, isWXAuth, wxUserInfo: userInfo, telphone = null, _merchant = null, _merchant: { _id: mid = null } } = MB;
// 初始化收藏
// await ctx.model.Account.Collect.initFn(uid);
// 返回資料
return this.setToken({ uid, isWXAuth, openId, mid }, { isWXAuth, 'userInfo': {...userInfo, telphone, uid, _merchant } });
}
} else {
return await this.getOpenID(js_code);
}
}
} catch (error) {
console.log(error)
return error
}
}
}
module.exports = weChatTemplateService;
##控制器
app/controller/mp/account/user.js
'use strict';
const Controller = require('../../index');
class UserController extends Controller {
constructor(ctx) {
super(ctx);
this.MODEL = ctx.model.Account.User;
this.SERVICE = ctx.service.wxminiprogram.user
};
// 重新整理token
async refreshToken() {
const ctx = this.ctx;
const {
uid: _id
} = ctx;
const {
mid,
openId,
isWXAuth,
userInfo: {
uid
},
userInfo
} = await this.show(null, true, _id);
ctx.body = this.SERVICE.setToken({
uid,
isWXAuth,
openId,
mid
}, {
isWXAuth,
userInfo
})
};
// 獲取使用者自己資料
async show(e, refresh = false, _uid = null) {
const ctx = this.ctx
const {
uid: _id
} = ctx;
const {
_id: uid,
telphone,
_merchant,
_merchant: {
_id: mid
},
isWXAuth,
openId,
wxUserInfo: {
nickName,
avatarUrl,
gender,
city,
province,
country
}
} = await this.MODEL.findOne({
_id: _id ? _id : _uid
}, 'wxUserInfo isWXAuth telphone _merchant openId');
const BODY = {
...refresh ? {
mid,
openId
} : {},
isWXAuth,
userInfo: {
uid,
telphone,
_merchant,
nickName,
avatarUrl,
gender,
city,
province,
country
}
}
try {
if (!!refresh) {
return BODY
};
ctx.body = BODY
} catch (error) {
console.log(error)
}
};
// 建立使用者(臨時使用者)(jscode:小程式獲取jscode 使用者資料 encryptedData iv)
async create() {
const ctx = this.ctx
let { jscode: js_code, encryptedData = null, iv = null } = ctx.request.body;
try {
ctx.body = await this.SERVICE.getUserInfo(this.DUFN({ js_code, encryptedData, iv }))
} catch (error) {
console.log(error)
}
// ctx.body = await this.SERVICE.getUserInfo(this.DUFN({ js_code, encryptedData, iv }))
};
// 繫結使用者手機
async bindPhone() {
const ctx = this.ctx;
const { uid = null } = ctx;
let { jscode: js_code, encryptedData = null, iv = null } = ctx.request.body;
try {
const RBD = await this.SERVICE.bindPhone(this.DUFN({ js_code, encryptedData, iv }), uid);
ctx.body = RBD
} catch (error) {
console.log('bindPhone_ctrl', error)
}
};
}
module.exports = UserController;
##建立路由
建立使用者登入相關router
app/router/mp/account.js
module.exports = app => {
const { router, controller } = app;
/**
* @name 微信使用者體系
*/
// 使用者登入
router.post('wxmp', '/api/v1/mp/account/user/auth', controller.mp.account.user.create);
// 使用者繫結手機
router.post('wxmpBindPhone', '/api/v1/mp/account/user/bindPhone', app.jwt, controller.mp.account.user.bindPhone);
// 獲取使用者資訊
router.get('getUserInfo', '/api/v1/mp/account/user/info', app.jwt, controller.mp.account.user.show);
// 重新整理token
router.get('refreshToken', '/api/v1/mp/account/refreshToken', app.jwt, controller.mp.account.user.refreshToken)
}
在主路由註冊
app/router.js
'use strict';
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
...
require('./router/mp/account')(app);
...
};
相關文章
- 微信開發與小程式
- 微信小程式分享及使用者資訊授權等介面能力的調整通知微信小程式
- 【微信小程式】微信小程式掉進的坑之與後臺資料互動微信小程式
- 微信小程式三種獲取使用者資訊的方式微信小程式
- 微信小程式開發系列教程三:微信小程式的除錯方法微信小程式除錯
- [開發教程]第1講:Bootstrap使用者介面與互動架構boot架構
- 微信小程式 獲取使用者資訊微信小程式
- 微信小程式之間的跳轉及如何傳參互動微信小程式
- 微信小程式開發:接入阿里雲人像動漫化api介面微信小程式阿里API
- 微信小程式搭配小白介面,自己沒有伺服器也能開發哦微信小程式伺服器
- 從前端介面開發談微信小程式體驗前端微信小程式
- 微信小程式開發總結與心得微信小程式
- 微信小程式開發微信小程式
- 微信小程式獲取使用者資訊方法微信小程式
- 微信小程式開發小記微信小程式
- 微信小程式開發系列(一) :開發環境搭建和微信小程式的檢視設計與開發微信小程式開發環境
- 填寫設定小程式資訊-微信小程式開發-視訊教程3微信小程式
- 微信小程式入門開發及問題整理微信小程式
- h5與微信小程式直播開發H5微信小程式
- 微信小程式開發入門與實踐微信小程式
- 微信小程式維護登入態與獲取使用者資訊微信小程式
- 微信小程式開發系列五:微信小程式中如何響應使用者輸入事件微信小程式事件
- 【小程式】微信小程式開發實踐微信小程式
- 【小程式】微信小程式開發準備微信小程式
- JAVA解密微信小程式使用者資訊encryptedData方案Java解密微信小程式
- 微信小程式開發教程微信小程式
- 微信小程式開發2微信小程式
- 快速開發微信小程式微信小程式
- 微信小程式API互動的自定義封裝微信小程式API封裝
- Laravel 微信小程式獲取『使用者詳細資訊』及『帶引數小程式碼』擴充套件Laravel微信小程式套件
- 編輯修改小程式資訊-微信小程式開發-視訊教程6微信小程式
- 微信程式開發系列教程(三)使用微信API給微信使用者發文字訊息API
- Hybrid小程式混合開發之路 – 資料互動
- 微信小程式 getUserProfile 獲取使用者資訊微信小程式
- Node.js 微信小程式獲取使用者資訊Node.js微信小程式
- 微信小程式Taro開發(2):生命週期及開發中注意點微信小程式
- 微信小程式開發總結微信小程式
- 開發微信小程式的作用微信小程式