weixin.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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 reqUrl = 'https://api.weixin.qq.com/sns/oauth2/access_token';
  18. const params = {
  19. appid: wxapi.appid,
  20. secret: wxapi.appSecret,
  21. code,
  22. grant_type: 'authorization_code',
  23. };
  24. console.log('rrrr-' + params);
  25. const res = await this.httpGet(reqUrl, params);
  26. if (res.errcode && res.errcode !== 0) {
  27. this.ctx.logger.error(`[WeixinAuthService] fetch open by code fail, errcode: ${res.errcode}, errmsg: ${res.errmsg}`);
  28. throw new BusinessError(ErrorCode.SERVICE_FAULT, '获得微信认证信息失败');
  29. }
  30. // const { openid } = res;
  31. return res;
  32. }
  33. // 通过openid获得用户信息
  34. async fetchUnionID(openid) {
  35. // TODO:参数检查和默认参数处理
  36. assert(openid);
  37. const appid = 'wxdf3ed83c095be97a';
  38. const grant_type = 'client_credential';
  39. const secret = '748df7c2a75077a79ae0c971b1638244';
  40. // TODO: 获得用户信息
  41. const url = 'http://wx.cc-lotus.info/api.weixin.qq.com/cgi-bin/token?appid=' + appid + '&grant_type=' + grant_type + '&secret=' + secret;
  42. const res = await this.ctx.curl(url, {
  43. method: 'get',
  44. headers: {
  45. 'content-type': 'application/json',
  46. },
  47. dataType: 'json',
  48. });
  49. // console.debug('res: ', res);
  50. if (res.errcode && res.errcode !== 0) {
  51. this.ctx.logger.error(`[WeixinAuthService] fetch userinfo by openid fail, errcode: ${res.errcode}, errmsg: ${res.errmsg}`);
  52. throw new BusinessError(ErrorCode.SERVICE_FAULT, '获得微信用户信息失败');
  53. }
  54. const token = res.access_token;
  55. console.log(token);
  56. const urlun = 'https://api.weixin.qq.com/cgi-bin/user/info?access_token=' + token + '&openid=' + openid + '&lang=zh_CN';
  57. const result = await this.ctx.curl(urlun, {
  58. method: 'get',
  59. headers: {
  60. 'content-type': 'application/json',
  61. },
  62. dataType: 'json',
  63. });
  64. // console.debug('res: ', res);
  65. if (result.errcode && result.errcode !== 0) {
  66. this.ctx.logger.error(`[WeixinAuthService] fetch userinfo by openid fail, errcode: ${res.errcode}, errmsg: ${res.errmsg}`);
  67. throw new BusinessError(ErrorCode.SERVICE_FAULT, '获得微信用户信息失败');
  68. }
  69. return result;
  70. }
  71. async createJwt({ openid, nickname, subscribe }) {
  72. const { secret, expiresIn = '1d', issuer = 'weixin' } = this.config.jwt;
  73. const subject = openid;
  74. const userinfo = { nickname, subscribe };
  75. const token = await jwt.sign(userinfo, secret, { expiresIn, issuer, subject });
  76. return token;
  77. }
  78. /**
  79. * 创建二维码
  80. * 随机生成二维码,并保存在Redis中,状态初始为pending
  81. * 状态描述:
  82. * pending - 等待扫码
  83. * consumed - 使用二维码登录完成
  84. * scand:token - Jwt登录凭证
  85. */
  86. async createQrcode() {
  87. const qrcode = uuid();
  88. const key = `visit:qrcode:group:${qrcode}`;
  89. await this.app.redis.set(key, 'pending', 'EX', 600);
  90. return qrcode;
  91. }
  92. /**
  93. * 创建二维码
  94. * 生成群二维码
  95. * 状态描述:
  96. * pending - 等待扫码
  97. * consumed - 使用二维码登录完成
  98. * scand:token - Jwt登录凭证
  99. */
  100. async createQrcodeGroup({ groupid }) {
  101. const { authUrl = this.ctx.path } = this.app.config;
  102. let backUrl;
  103. if (authUrl.startsWith('http')) {
  104. backUrl = encodeURI(`${authUrl}?state=${groupid}`);
  105. } else {
  106. backUrl = encodeURI(`${this.ctx.protocol}://${this.ctx.host}${authUrl}?state=${groupid}`);
  107. }
  108. console.log(backUrl);
  109. return backUrl;
  110. }
  111. /**
  112. * 扫码登录确认
  113. */
  114. async scanQrcode({ qrcode, token }) {
  115. assert(qrcode, 'qrcode不能为空');
  116. assert(token, 'token不能为空');
  117. const key = `smart:qrcode:login:${qrcode}`;
  118. const status = await this.app.redis.get(key);
  119. if (!status) {
  120. throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码已过期');
  121. }
  122. if (status !== 'pending') {
  123. throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码状态无效');
  124. }
  125. // 验证Token
  126. const { secret } = this.config.jwt;
  127. const decoded = jwt.verify(token, secret, { issuer: 'weixin' });
  128. this.ctx.logger.debug(`[weixin] qrcode login - ${decoded}`);
  129. // TODO: 修改二维码状态,登录凭证保存到redis
  130. await this.app.redis.set(key, `scaned:${token}`, 'EX', 600);
  131. // TODO: 发布扫码成功消息
  132. const { mq } = this.ctx;
  133. const ex = 'qrcode.login';
  134. if (mq) {
  135. await mq.topic(ex, qrcode, 'scaned', { durable: true });
  136. } else {
  137. this.ctx.logger.error('!!!!!!没有配置MQ插件!!!!!!');
  138. }
  139. }
  140. // 使用二维码换取登录凭证
  141. async qrcodeLogin(qrcode) {
  142. assert(qrcode, 'qrcode不能为空');
  143. const key = `smart:qrcode:login:${qrcode}`;
  144. const val = await this.app.redis.get(key);
  145. if (!val) {
  146. throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码已过期');
  147. }
  148. const [ status, token ] = val.split(':', 2);
  149. if (status !== 'scaned' || !token) {
  150. throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码状态无效');
  151. }
  152. // TODO: 修改二维码状态
  153. await this.app.redis.set(key, 'consumed', 'EX', 600);
  154. return { token };
  155. }
  156. // 检查二维码状态
  157. async checkQrcode(qrcode) {
  158. assert(qrcode, 'qrcode不能为空');
  159. const key = `smart:qrcode:login:${qrcode}`;
  160. const val = await this.app.redis.get(key);
  161. if (!val) {
  162. throw new BusinessError(ErrorCode.SERVICE_FAULT, '二维码已过期');
  163. }
  164. const [ status ] = val.split(':', 2);
  165. return { status };
  166. }
  167. // 发送微信模板消息
  168. async sendTemplateMsg(templateid, openid, first, keyword1, keyword2, remark, tourl) {
  169. const url = this.ctx.app.config.sendDirMq + this.ctx.app.config.appid;
  170. let _url = '';
  171. if (tourl) {
  172. // 通过openid取得用户基本信息
  173. const user = await this.ctx.model.User.findOne({ openid });
  174. const newdata = { openid, name: user.name, title: first, detail: keyword1, date: keyword2, remark, status: '0' };
  175. const res = await this.ctx.model.Message.create(newdata);
  176. // 需要回执的时候进行保存处理
  177. _url = this.ctx.app.config.baseUrl + '/api/train/auth?state=1&redirect_uri=' + this.ctx.app.config.baseUrl + '/classinfo/&type=9&objid=' + tourl + '&msgid=' + res.id;
  178. }
  179. const requestData = { // 发送模板消息的数据
  180. touser: openid,
  181. template_id: templateid,
  182. url: _url,
  183. data: {
  184. first: {
  185. value: first,
  186. color: '#173177',
  187. },
  188. keyword1: {
  189. value: keyword1,
  190. color: '#1d1d1d',
  191. },
  192. keyword2: {
  193. value: keyword2,
  194. color: '#1d1d1d',
  195. },
  196. remark: {
  197. value: remark,
  198. color: '#173177',
  199. },
  200. },
  201. };
  202. console.log('templateid---' + templateid);
  203. console.log('openid---' + openid);
  204. console.log('requestData---' + JSON.stringify(requestData));
  205. await this.ctx.curl(url, {
  206. method: 'post',
  207. headers: {
  208. 'content-type': 'application/json',
  209. },
  210. dataType: 'json',
  211. data: JSON.stringify(requestData),
  212. });
  213. }
  214. // 发送微信模板消息自定义消息
  215. async sendTemplateDesign(templateid, openid, first, keyword1, keyword2, remark, tourl) {
  216. const url = this.ctx.app.config.sendDirMq + this.ctx.app.config.appid;
  217. const requestData = { // 发送模板消息的数据
  218. touser: openid,
  219. template_id: templateid,
  220. url: tourl,
  221. data: {
  222. first: {
  223. value: first,
  224. color: '#173177',
  225. },
  226. keyword1: {
  227. value: keyword1,
  228. color: '#1d1d1d',
  229. },
  230. keyword2: {
  231. value: keyword2,
  232. color: '#1d1d1d',
  233. },
  234. remark: {
  235. value: remark,
  236. color: '#173177',
  237. },
  238. },
  239. };
  240. console.log('templateid---' + templateid);
  241. console.log('openid---' + openid);
  242. console.log('requestData---' + JSON.stringify(requestData));
  243. await this.ctx.curl(url, {
  244. method: 'post',
  245. headers: {
  246. 'content-type': 'application/json',
  247. },
  248. dataType: 'json',
  249. data: JSON.stringify(requestData),
  250. });
  251. }
  252. }
  253. module.exports = WeixinAuthService;