weixin.js 8.9 KB

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