'use strict'; const assert = require('assert'); const uuid = require('uuid'); const _ = require('lodash'); const { BusinessError, ErrorCode } = require('naf-core').Error; const jwt = require('jsonwebtoken'); const { AxiosService } = require('naf-framework-mongoose/lib/service'); class WeixinAuthService extends AxiosService { constructor(ctx) { super(ctx, {}, _.get(ctx.app.config, 'wxapi')); } // 通过认证码获得用户信息 async fetch(code) { // TODO:参数检查和默认参数处理 assert(code); // const { wxapi } = this.app.config; const res = await this.httpGet('/api/fetch', { code }); if (res.errcode && res.errcode !== 0) { this.ctx.logger.error(`[WeixinAuthService] fetch open by code fail, errcode: ${res.errcode}, errmsg: ${res.errmsg}`); throw new BusinessError(ErrorCode.SERVICE_FAULT, '获得微信认证信息失败'); } // const { openid } = res; // // TODO: 获得用户信息 // res = await this.httpGet('/api.weixin.qq.com/cgi-bin/user/info?lang=zh_CN', { appid: wxapi.appid, openid }); // // console.debug('res: ', res); // if (res.errcode && res.errcode !== 0) { // this.ctx.logger.error(`[WeixinAuthService] fetch userinfo by openid fail, errcode: ${res.errcode}, errmsg: ${res.errmsg}`); // throw new BusinessError(ErrorCode.SERVICE_FAULT, '获得微信用户信息失败'); // } return res; } // 通过openid获得用户信息 async fetchUnionID(openid) { // TODO:参数检查和默认参数处理 assert(openid); const appid = 'wx6db5d25b3e7cfc14'; const grant_type = 'client_credential'; const secret = 'c4e3c4f74f5c1583e3b6a8ae77925d71'; // TODO: 获得用户信息 const url = 'http://wx.cc-lotus.info/api.weixin.qq.com/cgi-bin/token?appid=' + appid + '&grant_type=' + grant_type + '&secret=' + secret; const res = await this.ctx.curl(url, { method: 'get', headers: { 'content-type': 'application/json', }, dataType: 'json', }); // console.debug('res: ', res); if (res.errcode && res.errcode !== 0) { this.ctx.logger.error(`[WeixinAuthService] fetch userinfo by openid fail, errcode: ${res.errcode}, errmsg: ${res.errmsg}`); throw new BusinessError(ErrorCode.SERVICE_FAULT, '获得微信用户信息失败'); } const token = res.access_token; console.log(token); const urlun = 'https://api.weixin.qq.com/cgi-bin/user/info?access_token=' + token + '&openid=' + openid + '&lang=zh_CN'; const result = await this.ctx.curl(urlun, { method: 'get', headers: { 'content-type': 'application/json', }, dataType: 'json', }); // console.debug('res: ', res); if (result.errcode && result.errcode !== 0) { this.ctx.logger.error(`[WeixinAuthService] fetch userinfo by openid fail, errcode: ${res.errcode}, errmsg: ${res.errmsg}`); throw new BusinessError(ErrorCode.SERVICE_FAULT, '获得微信用户信息失败'); } return result; } async createJwt({ openid, nickname, subscribe }) { const { secret, expiresIn = '1d', issuer = 'weixin' } = this.config.jwt; const subject = openid; const userinfo = { nickname, subscribe }; const token = await jwt.sign(userinfo, secret, { expiresIn, issuer, subject }); return token; } /** * 创建二维码 * 随机生成二维码,并保存在Redis中,状态初始为pending * 状态描述: * pending - 等待扫码 * consumed - 使用二维码登录完成 * scand:token - Jwt登录凭证 */ async createQrcode() { const qrcode = uuid(); const key = `visit:qrcode:group:${qrcode}`; await this.app.redis.set(key, 'pending', 'EX', 600); return qrcode; } /** * 创建二维码 * 生成群二维码 * 状态描述: * pending - 等待扫码 * consumed - 使用二维码登录完成 * scand:token - Jwt登录凭证 */ async createQrcodeGroup({ groupid }) { const { authUrl = this.ctx.path } = this.app.config; let backUrl; if (authUrl.startsWith('http')) { backUrl = encodeURI(`${authUrl}?state=${groupid}`); } else { backUrl = encodeURI(`${this.ctx.protocol}://${this.ctx.host}${authUrl}?state=${groupid}`); } console.log(backUrl); return backUrl; } /** * 扫码登录确认 */ async scanQrcode({ qrcode, token }) { assert(qrcode, 'qrcode不能为空'); assert(token, 'token不能为空'); const key = `smart:qrcode:login:${qrcode}`; const status = await this.app.redis.get(key); if (!status) { throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码已过期'); } if (status !== 'pending') { throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码状态无效'); } // 验证Token const { secret } = this.config.jwt; const decoded = jwt.verify(token, secret, { issuer: 'weixin' }); this.ctx.logger.debug(`[weixin] qrcode login - ${decoded}`); // TODO: 修改二维码状态,登录凭证保存到redis await this.app.redis.set(key, `scaned:${token}`, 'EX', 600); // TODO: 发布扫码成功消息 const { mq } = this.ctx; const ex = 'qrcode.login'; if (mq) { await mq.topic(ex, qrcode, 'scaned', { durable: true }); } else { this.ctx.logger.error('!!!!!!没有配置MQ插件!!!!!!'); } } // 使用二维码换取登录凭证 async qrcodeLogin(qrcode) { assert(qrcode, 'qrcode不能为空'); const key = `smart:qrcode:login:${qrcode}`; const val = await this.app.redis.get(key); if (!val) { throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码已过期'); } const [ status, token ] = val.split(':', 2); if (status !== 'scaned' || !token) { throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码状态无效'); } // TODO: 修改二维码状态 await this.app.redis.set(key, 'consumed', 'EX', 600); return { token }; } // 检查二维码状态 async checkQrcode(qrcode) { assert(qrcode, 'qrcode不能为空'); const key = `smart:qrcode:login:${qrcode}`; const val = await this.app.redis.get(key); if (!val) { throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码已过期'); } const [ status ] = val.split(':', 2); return { status }; } // 发送微信模板消息 async sendTemplateMsg(templateid, openid, first, keyword1, keyword2, remark, tourl) { const url = this.ctx.app.config.sendDirMq + this.ctx.app.config.appid; let _url = ''; if (tourl) { _url = this.ctx.app.config.baseUrl + '/api/auth?state=1&redirect_uri=' + this.ctx.app.config.baseUrl + '/classinfo/&type=template&objid=' + tourl; } const requestData = { // 发送模板消息的数据 touser: openid, template_id: templateid, url: _url, data: { first: { value: first, color: '#173177', }, keyword1: { value: keyword1, color: '#1d1d1d', }, keyword2: { value: keyword2, color: '#1d1d1d', }, remark: { value: remark, color: '#173177', }, }, }; console.log('templateid---' + templateid); console.log('openid---' + openid); console.log('requestData---' + JSON.stringify(requestData)); await this.ctx.curl(url, { method: 'post', headers: { 'content-type': 'application/json', }, dataType: 'json', data: JSON.stringify(requestData), }); } } module.exports = WeixinAuthService;