weixin.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. 'use strict';
  2. const assert = require('assert');
  3. const uuid = require('uuid');
  4. const _ = require('lodash');
  5. const { BusinessError, ErrorCode } = require('naf-core').Error;
  6. const jwt = require('jsonwebtoken');
  7. const { AxiosService } = require('naf-framework-mongoose/lib/service');
  8. class WeixinAuthService extends AxiosService {
  9. constructor(ctx) {
  10. super(ctx, {}, _.get(ctx.app.config, 'wxapi'));
  11. }
  12. // 通过认证码获得用户信息
  13. async fetch(code) {
  14. // TODO:参数检查和默认参数处理
  15. assert(code);
  16. // const { wxapi } = this.app.config;
  17. const res = await this.httpGet('/api/fetch', { code });
  18. if (res.errcode && res.errcode !== 0) {
  19. this.ctx.logger.error(`[WeixinAuthService] fetch open by code fail, errcode: ${res.errcode}, errmsg: ${res.errmsg}`);
  20. throw new BusinessError(ErrorCode.SERVICE_FAULT, '获得微信认证信息失败');
  21. }
  22. // const { openid } = res;
  23. // // TODO: 获得用户信息
  24. // res = await this.httpGet('/api.weixin.qq.com/cgi-bin/user/info?lang=zh_CN', { appid: wxapi.appid, openid });
  25. // // console.debug('res: ', res);
  26. // if (res.errcode && res.errcode !== 0) {
  27. // this.ctx.logger.error(`[WeixinAuthService] fetch userinfo by openid fail, errcode: ${res.errcode}, errmsg: ${res.errmsg}`);
  28. // throw new BusinessError(ErrorCode.SERVICE_FAULT, '获得微信用户信息失败');
  29. // }
  30. return res;
  31. }
  32. // 通过openid获得用户信息
  33. async fetchUnionID(openid) {
  34. // TODO:参数检查和默认参数处理
  35. assert(openid);
  36. const appid = 'wx6db5d25b3e7cfc14';
  37. const grant_type = 'client_credential';
  38. const secret = 'c4e3c4f74f5c1583e3b6a8ae77925d71';
  39. // TODO: 获得用户信息
  40. const url = 'http://wx.cc-lotus.info/api.weixin.qq.com/cgi-bin/token?appid=' + appid + '&grant_type=' + grant_type + '&secret=' + secret;
  41. const res = await this.ctx.curl(url, {
  42. method: 'get',
  43. headers: {
  44. 'content-type': 'application/json',
  45. },
  46. dataType: 'json',
  47. });
  48. // console.debug('res: ', res);
  49. if (res.errcode && res.errcode !== 0) {
  50. this.ctx.logger.error(`[WeixinAuthService] fetch userinfo by openid fail, errcode: ${res.errcode}, errmsg: ${res.errmsg}`);
  51. throw new BusinessError(ErrorCode.SERVICE_FAULT, '获得微信用户信息失败');
  52. }
  53. const token = res.access_token;
  54. console.log(token);
  55. const urlun = 'https://api.weixin.qq.com/cgi-bin/user/info?access_token=' + token + '&openid=' + openid + '&lang=zh_CN';
  56. const result = await this.ctx.curl(urlun, {
  57. method: 'get',
  58. headers: {
  59. 'content-type': 'application/json',
  60. },
  61. dataType: 'json',
  62. });
  63. // console.debug('res: ', res);
  64. if (result.errcode && result.errcode !== 0) {
  65. this.ctx.logger.error(`[WeixinAuthService] fetch userinfo by openid fail, errcode: ${res.errcode}, errmsg: ${res.errmsg}`);
  66. throw new BusinessError(ErrorCode.SERVICE_FAULT, '获得微信用户信息失败');
  67. }
  68. return result;
  69. }
  70. async createJwt({ openid, nickname, subscribe }) {
  71. const { secret, expiresIn = '1d', issuer = 'weixin' } = this.config.jwt;
  72. const subject = openid;
  73. const userinfo = { nickname, subscribe };
  74. const token = await jwt.sign(userinfo, secret, { expiresIn, issuer, subject });
  75. return token;
  76. }
  77. /**
  78. * 创建二维码
  79. * 随机生成二维码,并保存在Redis中,状态初始为pending
  80. * 状态描述:
  81. * pending - 等待扫码
  82. * consumed - 使用二维码登录完成
  83. * scand:token - Jwt登录凭证
  84. */
  85. async createQrcode() {
  86. const qrcode = uuid();
  87. const key = `visit:qrcode:group:${qrcode}`;
  88. await this.app.redis.set(key, 'pending', 'EX', 600);
  89. return qrcode;
  90. }
  91. /**
  92. * 创建二维码
  93. * 生成群二维码
  94. * 状态描述:
  95. * pending - 等待扫码
  96. * consumed - 使用二维码登录完成
  97. * scand:token - Jwt登录凭证
  98. */
  99. async createQrcodeGroup({ groupid }) {
  100. const { authUrl = this.ctx.path } = this.app.config;
  101. let backUrl;
  102. if (authUrl.startsWith('http')) {
  103. backUrl = encodeURI(`${authUrl}?state=${groupid}`);
  104. } else {
  105. backUrl = encodeURI(`${this.ctx.protocol}://${this.ctx.host}${authUrl}?state=${groupid}`);
  106. }
  107. console.log(backUrl);
  108. return backUrl;
  109. }
  110. /**
  111. * 扫码登录确认
  112. */
  113. async scanQrcode({ qrcode, token }) {
  114. assert(qrcode, 'qrcode不能为空');
  115. assert(token, 'token不能为空');
  116. const key = `smart:qrcode:login:${qrcode}`;
  117. const status = await this.app.redis.get(key);
  118. if (!status) {
  119. throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码已过期');
  120. }
  121. if (status !== 'pending') {
  122. throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码状态无效');
  123. }
  124. // 验证Token
  125. const { secret } = this.config.jwt;
  126. const decoded = jwt.verify(token, secret, { issuer: 'weixin' });
  127. this.ctx.logger.debug(`[weixin] qrcode login - ${decoded}`);
  128. // TODO: 修改二维码状态,登录凭证保存到redis
  129. await this.app.redis.set(key, `scaned:${token}`, 'EX', 600);
  130. // TODO: 发布扫码成功消息
  131. const { mq } = this.ctx;
  132. const ex = 'qrcode.login';
  133. if (mq) {
  134. await mq.topic(ex, qrcode, 'scaned', { durable: true });
  135. } else {
  136. this.ctx.logger.error('!!!!!!没有配置MQ插件!!!!!!');
  137. }
  138. }
  139. // 使用二维码换取登录凭证
  140. async qrcodeLogin(qrcode) {
  141. assert(qrcode, 'qrcode不能为空');
  142. const key = `smart:qrcode:login:${qrcode}`;
  143. const val = await this.app.redis.get(key);
  144. if (!val) {
  145. throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码已过期');
  146. }
  147. const [ status, token ] = val.split(':', 2);
  148. if (status !== 'scaned' || !token) {
  149. throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码状态无效');
  150. }
  151. // TODO: 修改二维码状态
  152. await this.app.redis.set(key, 'consumed', 'EX', 600);
  153. return { token };
  154. }
  155. // 检查二维码状态
  156. async checkQrcode(qrcode) {
  157. assert(qrcode, 'qrcode不能为空');
  158. const key = `smart:qrcode:login:${qrcode}`;
  159. const val = await this.app.redis.get(key);
  160. if (!val) {
  161. throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码已过期');
  162. }
  163. const [ status ] = val.split(':', 2);
  164. return { status };
  165. }
  166. // 发送微信模板消息
  167. async sendTemplateMsg(templateid, openid, first, keyword1, keyword2, remark, tourl) {
  168. const url = this.ctx.app.config.sendDirMq + this.ctx.app.config.appid;
  169. let _url = '';
  170. if (tourl) {
  171. _url = this.ctx.app.config.baseUrl + '/api/auth?state=1&redirect_uri=' + this.ctx.app.config.baseUrl + '/classinfo/&type=template&objid=' + tourl;
  172. }
  173. const requestData = { // 发送模板消息的数据
  174. touser: openid,
  175. template_id: templateid,
  176. url: _url,
  177. data: {
  178. first: {
  179. value: first,
  180. color: '#173177',
  181. },
  182. keyword1: {
  183. value: keyword1,
  184. color: '#1d1d1d',
  185. },
  186. keyword2: {
  187. value: keyword2,
  188. color: '#1d1d1d',
  189. },
  190. remark: {
  191. value: remark,
  192. color: '#173177',
  193. },
  194. },
  195. };
  196. console.log('templateid---' + templateid);
  197. console.log('openid---' + openid);
  198. console.log('requestData---' + JSON.stringify(requestData));
  199. await this.ctx.curl(url, {
  200. method: 'post',
  201. headers: {
  202. 'content-type': 'application/json',
  203. },
  204. dataType: 'json',
  205. data: JSON.stringify(requestData),
  206. });
  207. }
  208. }
  209. module.exports = WeixinAuthService;