|
@@ -0,0 +1,226 @@
|
|
|
+'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;
|
|
|
+ let 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 = 'wxdf3ed83c095be97a';
|
|
|
+ const grant_type = 'client_credential';
|
|
|
+ const secret = '748df7c2a75077a79ae0c971b1638244';
|
|
|
+ // 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;
|