wechat_controller.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. 'use strict';
  2. /**
  3. *
  4. *
  5. * @author Ellisran
  6. * @date 2020/7/2
  7. * @version
  8. */
  9. const moment = require('moment');
  10. // const Controller = require('egg').Controller;
  11. const crypto = require('crypto');
  12. const qywxCrypto = require('@wecom/crypto');
  13. const getRawBody = require('raw-body');
  14. const maintainConst = require('../const/maintain');
  15. const wxConst = require('../const/wechat_template.js');
  16. const smsTypeConst = require('../const/sms_type');
  17. const wxWork = require('../lib/wx_work');
  18. const { parseStringXml } = require('../lib/common');
  19. module.exports = app => {
  20. class WechatController extends app.BaseController {
  21. /**
  22. * 接入微信
  23. *
  24. * @param {Object} ctx - egg全局页面
  25. * @return {void}
  26. */
  27. async index(ctx) {
  28. try {
  29. const signature = ctx.query.signature;
  30. const timestamp = ctx.query.timestamp;
  31. const nonce = ctx.query.nonce;
  32. const echostr = ctx.query.echostr;
  33. const array = [ctx.app.config.wechatAll.token, timestamp, nonce];
  34. array.sort();
  35. const tempStr = array.join('');
  36. const hashCode = crypto.createHash('sha1');
  37. const resultCode = hashCode.update(tempStr, 'utf8').digest('hex');
  38. if (resultCode === signature) {
  39. ctx.body = echostr;
  40. // res.send(echostr);
  41. } else {
  42. throw '验证失败';
  43. }
  44. } catch (e) {
  45. console.log(e);
  46. }
  47. }
  48. /**
  49. * 微信自动回复
  50. *
  51. * @param {Object} ctx - egg全局页面
  52. * @return {void}
  53. */
  54. async replyMessage(ctx) {
  55. const xml = ctx.request.body;
  56. try {
  57. const {
  58. createTime, msgType, toUserName, toFromName, event, msgContent,
  59. } = await parseStringXml(xml);
  60. let body = '';
  61. if (msgType === 'text') {
  62. const reply_msg = '如有问题需要咨询,请电话联系0756-3850888;或添加企业QQ:800003850。';
  63. body = `
  64. <xml>
  65. <ToUserName><![CDATA[${toFromName}]]></ToUserName>
  66. <FromUserName><![CDATA[${toUserName}]]></FromUserName>
  67. <CreateTime>${createTime}</CreateTime>
  68. <MsgType><![CDATA[text]]></MsgType>
  69. <Content><![CDATA[${reply_msg}]]></Content>
  70. <MediaId><![CDATA[media_id]]></MediaId>
  71. </xml>
  72. `;
  73. }
  74. ctx.body = body;
  75. } catch (e) {
  76. console.log(e);
  77. }
  78. }
  79. /**
  80. * 微信登录验证
  81. *
  82. * @param {Object} ctx - egg全局页面
  83. * @return {void}
  84. */
  85. async oauth(ctx) {
  86. const redirect_uri = ctx.query.redirect_uri;
  87. const url = await app.wechat.oauth.getAuthorizeURL(redirect_uri, '', 'snsapi_userinfo');
  88. ctx.redirect(url);
  89. }
  90. /**
  91. * 绑定页面
  92. *
  93. * @param {Object} ctx - egg全局页面
  94. * @return {void}
  95. */
  96. async bind(ctx) {
  97. try {
  98. const user = await app.wechat.oauth.getUser(ctx.session.wechatToken.openid);
  99. const errorMessage = ctx.session.loginError;
  100. // 显示完删除
  101. ctx.session.loginError = null;
  102. // 获取系统维护信息
  103. const maintainData = await ctx.service.maintain.getDataById(1);
  104. const renderData = {
  105. maintainData,
  106. maintainConst,
  107. errorMessage,
  108. user,
  109. };
  110. await ctx.render('wechat/bind.ejs', renderData);
  111. } catch (e) {
  112. const renderData = {
  113. status: 1,
  114. msg: e,
  115. };
  116. await ctx.render('wechat/tips.ejs', renderData);
  117. }
  118. }
  119. async bindwx(ctx) {
  120. try {
  121. const result = await ctx.service.projectAccount.accountCheck(ctx.request.body);
  122. if (!result) {
  123. throw '用户名或密码错误';
  124. }
  125. if (result === 2) {
  126. // 查找项目数据
  127. const projectData = await this.ctx.service.project.getProjectByCode(ctx.request.body.project.toString().trim());
  128. // 判断是否有设置停用提示,有则展示
  129. const msg = await ctx.service.projectStopmsg.getMsg(projectData.id);
  130. throw msg;
  131. }
  132. const accountData = result;
  133. if (accountData.wx_openid || ctx.session.wechatToken.openid === accountData.wx_openid) {
  134. throw '该账号已经绑定过微信';
  135. }
  136. const wxAccountData = await ctx.service.projectAccount.getDataByCondition({ project_id: accountData.project_id, wx_openid: ctx.session.wechatToken.openid });
  137. if (wxAccountData) {
  138. throw '该微信号已绑定过本项目账号';
  139. }
  140. const user = await app.wechat.api.getUser(ctx.session.wechatToken.openid);
  141. if (user.subscribe === 0) {
  142. throw '先关注公众号才能绑定项目';
  143. }
  144. const result2 = await ctx.service.projectAccount.bindWx(accountData.id, ctx.session.wechatToken.openid, user.nickname, user.unionid);
  145. if (!result2) {
  146. throw '绑定失败';
  147. }
  148. const projectData = await ctx.service.project.getDataById(accountData.project_id);
  149. // 绑定成功通知
  150. const templateId = 'JGJeWU2FT4syWKUE5haEf3iiqaRJ1XrsxY1PKixqLpw';
  151. const url = '';
  152. const msgData = {
  153. first: {
  154. value: '您好,纵横云计量与微信绑定成功。',
  155. },
  156. keyword1: {
  157. value: projectData.code,
  158. },
  159. keyword2: {
  160. value: accountData.account,
  161. },
  162. keyword3: {
  163. value: moment(new Date()).format('YYYY-MM-DD'),
  164. },
  165. remark: {
  166. value: '感谢您的使用。',
  167. },
  168. };
  169. await app.wechat.api.sendTemplate(ctx.session.wechatToken.openid, templateId, url, '', msgData);
  170. const renderData = {
  171. status: 0,
  172. msg: '绑定成功',
  173. };
  174. await ctx.render('wechat/tips.ejs', renderData);
  175. } catch (error) {
  176. this.log(error);
  177. ctx.session.loginError = error;
  178. ctx.redirect('/wx/bind');
  179. }
  180. }
  181. // 设置用户微信登录项目,跳转到对应wap页面
  182. async url2wap(ctx) {
  183. try {
  184. if (!ctx.query.project || !ctx.query.url) {
  185. throw '参数有误';
  186. }
  187. const code = ctx.query.project;
  188. // 查找项目数据
  189. const projectData = await ctx.service.project.getProjectByCode(code.toString().trim());
  190. if (projectData === null) {
  191. throw '不存在项目数据';
  192. }
  193. const pa = await ctx.service.projectAccount.getDataByCondition({ project_id: projectData.id, wx_openid: ctx.session.wechatToken.openid });
  194. if (!pa) {
  195. throw '该微信号未绑定此项目';
  196. }
  197. if (pa.enable !== 1) {
  198. // 判断是否有设置停用提示,有则展示
  199. const msg = await ctx.service.projectStopmsg.getMsg(projectData.id);
  200. throw msg;
  201. }
  202. // 设置项目和用户session记录
  203. const result = await ctx.service.projectAccount.accountLogin({ project: projectData, accountData: pa }, 3);
  204. if (!result) {
  205. throw '登录出错';
  206. }
  207. ctx.redirect(ctx.query.url);
  208. } catch (error) {
  209. const renderData = {
  210. status: 1,
  211. msg: error,
  212. };
  213. await ctx.render('wechat/tips.ejs', renderData);
  214. }
  215. }
  216. async project(ctx) {
  217. try {
  218. // const user = await app.wechat.oauth.getUser(ctx.session.wechatToken.openid);
  219. const openid = ctx.session.wechatToken.openid;
  220. // const openid = 'fasdfklahsdklf';
  221. const paList = await ctx.service.projectAccount.getAllDataByCondition({ where: { wx_openid: openid } });
  222. const pidList = ctx.app._.uniq(ctx.app._.map(paList, 'project_id'));
  223. const pList = [];
  224. const redirect_url = ctx.protocol + '://' + ctx.host + '/wap/dashboard';
  225. for (const p of pidList) {
  226. const pro = await ctx.service.project.getDataById(p);
  227. pList.push(pro);
  228. }
  229. if (pList.length === 0) {
  230. throw '该微信号未绑定任何项目';
  231. }
  232. // 获取系统维护信息
  233. const maintainData = await ctx.service.maintain.getDataById(1);
  234. const renderData = {
  235. maintainData,
  236. maintainConst,
  237. // user,
  238. pList,
  239. redirect_url,
  240. };
  241. // ctx.body = renderData;
  242. await ctx.render('wechat/project.ejs', renderData);
  243. } catch (e) {
  244. const renderData = {
  245. status: 1,
  246. msg: e,
  247. };
  248. await ctx.render('wechat/tips.ejs', renderData);
  249. }
  250. }
  251. async oauthTxt(ctx) {
  252. ctx.body = 't3MkWAMqplVxPjmr';
  253. }
  254. async testwx(ctx) {
  255. try {
  256. const sck = 'https://scn.ink/';
  257. // 微信模板通知
  258. const tender = {
  259. data: {
  260. name: 'XXX标段',
  261. },
  262. info: {
  263. deal_info: {
  264. buildName: 'XX项目',
  265. },
  266. },
  267. };
  268. ctx.tender = tender;
  269. const stageInfo = await ctx.service.stage.getDataById(1704);
  270. const shenpiUrl = await ctx.helper.urlToShort(ctx.protocol + '://' + ctx.host + '/wap/tender/1998/stage/' + stageInfo.order);
  271. const wechatData = {
  272. wap_url: sck + shenpiUrl,
  273. qi: stageInfo.order,
  274. status: wxConst.status.success,
  275. tips: wxConst.tips.success,
  276. code: 'P1002',
  277. };
  278. // ctx.body = { tender, wechatData };
  279. await ctx.helper.sendWechat(133, smsTypeConst.const.JL, smsTypeConst.judge.result.toString(), wxConst.template.stage, wechatData);
  280. ctx.body = 'success';
  281. } catch (error) {
  282. console.log(error);
  283. ctx.body = error;
  284. }
  285. }
  286. // 企业微信功能
  287. // 回调方法
  288. async command(ctx) {
  289. try {
  290. const msg_signature = ctx.query.msg_signature;
  291. const timestamp = ctx.query.timestamp;
  292. const nonce = ctx.query.nonce;
  293. const echostr = ctx.query.echostr;
  294. const signature = qywxCrypto.getSignature(ctx.app.config.qywx.token, timestamp, nonce, echostr);
  295. if (signature === msg_signature) {
  296. const aeskey = ctx.app.config.qywx.encodingAESKey;
  297. const { message } = qywxCrypto.decrypt(aeskey, echostr);
  298. ctx.body = message;
  299. // res.send(message);
  300. } else {
  301. throw '验证失败';
  302. }
  303. } catch (e) {
  304. console.log(e);
  305. }
  306. }
  307. // 获取suite_ticket方法
  308. async postCommand(ctx) {
  309. try {
  310. // ctx.req才能获取到rawbody
  311. const wholeXML = await getRawBody(ctx.req, {
  312. length: ctx.headers['content-length'],
  313. limit: '1mb',
  314. encoding: 'utf-8',
  315. });
  316. const formatJson = await ctx.helper.parseXML(wholeXML);
  317. const messageXML = qywxCrypto.decrypt(ctx.app.config.qywx.encodingAESKey, formatJson.Encrypt);
  318. const callbackDataBody = await ctx.helper.parseXML(messageXML.message);
  319. console.log('CallbackData', callbackDataBody);
  320. const qywx = new wxWork(ctx);
  321. switch (callbackDataBody.InfoType) {
  322. case 'suite_ticket':
  323. // 刷新
  324. console.log('SuiteTicket', callbackDataBody.SuiteTicket);
  325. await qywx.setSuiteTicket(callbackDataBody.SuiteTicket);
  326. // await app.redis.set('suite_ticket', callbackDataBody.SuiteTicket, 'EX', 1500);
  327. break;
  328. case 'reset_permanent_code':
  329. case 'create_auth':
  330. console.log('AuthCode', callbackDataBody.AuthCode);
  331. await qywx.savePermanentCode(callbackDataBody.AuthCode);
  332. qywx.setPermanentCode();// 不用马上执行,有执行就行
  333. break;
  334. case 'cancel_auth':
  335. // 企业管理员删除应用
  336. await ctx.service.wxWork.delCorp(callbackDataBody.AuthCorpId);
  337. break;
  338. default:
  339. break;
  340. }
  341. // 很重要,一定要返回 success 字符串
  342. ctx.body = 'success';
  343. } catch (e) {
  344. console.log(e);
  345. }
  346. }
  347. async oauthWxWorkTxt(ctx) {
  348. ctx.body = 'CZwGPbI7BRGOBUX1';
  349. }
  350. /**
  351. * 企业微信登录验证
  352. *
  353. * @param {Object} ctx - egg全局页面
  354. * @return {void}
  355. */
  356. async workOauth(ctx) {
  357. const corpid = ctx.params.corpid;
  358. const redirect_uri = encodeURIComponent(ctx.query.redirect_uri);
  359. const corpInfo = await ctx.service.wxWork.getDataByCondition({ corpid });
  360. const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${corpid}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_privateinfo&state=STATE&agentid=${corpInfo.agentid}#wechat_redirect`;
  361. ctx.redirect(url);
  362. }
  363. async workBind(ctx) {
  364. try {
  365. const qywx = new wxWork(ctx);
  366. const token = await qywx.getCorpAccessToken(ctx.params.corpid);
  367. const user = await qywx.getCorpUser(token, ctx.query.code);
  368. if (!user) {
  369. throw '获取企业用户信息失败';
  370. }
  371. const errorMessage = ctx.session.loginError;
  372. // 显示完删除
  373. ctx.session.loginError = null;
  374. // 获取系统维护信息
  375. const maintainData = await ctx.service.maintain.getDataById(1);
  376. const renderData = {
  377. maintainData,
  378. maintainConst,
  379. errorMessage,
  380. user,
  381. corpid: ctx.params.corpid,
  382. };
  383. await ctx.render('wechat/work_bind.ejs', renderData);
  384. } catch (error) {
  385. console.log(error);
  386. const renderData = {
  387. status: 1,
  388. msg: error,
  389. };
  390. await ctx.render('wechat/tips.ejs', renderData);
  391. }
  392. }
  393. async workBindwx(ctx) {
  394. const corpid = ctx.request.body.corpid ? ctx.request.body.corpid : null;
  395. try {
  396. const result = await ctx.service.projectAccount.accountCheck(ctx.request.body);
  397. if (!result) {
  398. throw '用户名或密码错误';
  399. }
  400. if (result === 2) {
  401. // 查找项目数据
  402. const projectData = await this.ctx.service.project.getProjectByCode(ctx.request.body.project.toString().trim());
  403. // 判断是否有设置停用提示,有则展示
  404. const msg = await ctx.service.projectStopmsg.getMsg(projectData.id);
  405. throw msg;
  406. }
  407. const accountData = result;
  408. const qywx_userid = ctx.request.body.userid;
  409. if (!qywx_userid || !corpid) {
  410. throw '参数有误';
  411. }
  412. if (accountData.qywx_userid || qywx_userid === accountData.qywx_userid) {
  413. throw '该账号已经绑定过企业微信';
  414. }
  415. const wxAccountData = await ctx.service.projectAccount.getDataByCondition({ project_id: accountData.project_id, qywx_userid });
  416. if (wxAccountData) {
  417. throw '该企业微信号已绑定过本项目其它账号';
  418. }
  419. const qywx = new wxWork(ctx);
  420. const token = await qywx.getCorpAccessToken(corpid);
  421. const user = await qywx.getCorpUserCommonData(token, qywx_userid, corpid);
  422. if (!user) {
  423. throw '获取企业用户信息失败';
  424. }
  425. user.avatar = ctx.request.body.avatar !== undefined ? ctx.request.body.avatar : null;
  426. user.gender = ctx.request.body.gender !== undefined ? ctx.request.body.gender : null;
  427. const result2 = await ctx.service.projectAccount.bindWx4Work(accountData.id, corpid, qywx_userid, user);
  428. if (!result2) {
  429. throw '绑定失败';
  430. }
  431. const projectData = await ctx.service.project.getDataById(accountData.project_id);
  432. const desc = '您好,纵横云计量与企业微信绑定成功。';
  433. const content = [
  434. {
  435. keyname: '项目编号',
  436. value: projectData.code,
  437. },
  438. {
  439. keyname: '账号',
  440. value: accountData.account,
  441. },
  442. {
  443. keyname: '绑定时间',
  444. value: moment(new Date()).format('YYYY-MM-DD'),
  445. },
  446. {
  447. keyname: '备注',
  448. value: '感谢您的使用。',
  449. },
  450. ];
  451. const url = ctx.protocol + '://' + ctx.host + `/wx/work/${corpid}/project`;
  452. await qywx.sendTemplateCard([qywx_userid], corpid, '账号绑定成功通知', desc, content, url, '登录项目');
  453. const renderData = {
  454. status: 0,
  455. msg: '绑定成功',
  456. };
  457. await ctx.render('wechat/tips.ejs', renderData);
  458. } catch (error) {
  459. this.log(error);
  460. ctx.session.loginError = error;
  461. const returnUrl = corpid ? `/wx/work/${corpid}/bind` : '/';
  462. ctx.redirect(returnUrl);
  463. }
  464. }
  465. // 设置用户企业微信登录项目,跳转到对应wap页面
  466. async url2wap4work(ctx) {
  467. try {
  468. if (!ctx.query.project || !ctx.query.url) {
  469. throw '参数有误';
  470. }
  471. const code = ctx.query.project;
  472. // 查找项目数据
  473. const projectData = await ctx.service.project.getProjectByCode(code.toString().trim());
  474. if (projectData === null) {
  475. throw '不存在项目数据';
  476. }
  477. const qywx = new wxWork(ctx);
  478. const token = await qywx.getCorpAccessToken(ctx.params.corpid);
  479. const user = await qywx.getCorpUser(token, ctx.query.code);
  480. if (!user) {
  481. throw '获取企业用户信息失败';
  482. }
  483. const pa = await ctx.service.projectAccount.getDataByCondition({ project_id: projectData.id, qywx_userid: user.userid });
  484. if (!pa) {
  485. throw '该企业微信号未绑定此项目';
  486. }
  487. if (pa.enable !== 1) {
  488. // 判断是否有设置停用提示,有则展示
  489. const msg = await ctx.service.projectStopmsg.getMsg(projectData.id);
  490. throw msg;
  491. }
  492. // 设置项目和用户session记录
  493. const result = await ctx.service.projectAccount.accountLogin({ project: projectData, accountData: pa }, 3);
  494. if (!result) {
  495. throw '登录出错';
  496. }
  497. ctx.redirect(ctx.query.url);
  498. } catch (error) {
  499. const renderData = {
  500. status: 1,
  501. msg: error,
  502. };
  503. await ctx.render('wechat/tips.ejs', renderData);
  504. }
  505. }
  506. async workProject(ctx) {
  507. try {
  508. // const user = await app.wechat.oauth.getUser(ctx.session.wechatToken.openid);
  509. const qywx = new wxWork(ctx);
  510. const token = await qywx.getCorpAccessToken(ctx.params.corpid);
  511. const user = await qywx.getCorpUser(token, ctx.query.code);
  512. if (!user) {
  513. throw '获取企业用户信息失败';
  514. }
  515. const paList = await ctx.service.projectAccount.getAllDataByCondition({ where: { qywx_userid: user.userid } });
  516. const pidList = ctx.app._.uniq(ctx.app._.map(paList, 'project_id'));
  517. const pList = [];
  518. const isWap = ctx.helper.isMobile(ctx.request.header['user-agent']) ? '/wap' : '';
  519. const redirect_url = ctx.protocol + '://' + ctx.host + isWap + '/dashboard';
  520. for (const p of pidList) {
  521. const pro = await ctx.service.project.getDataById(p);
  522. pList.push(pro);
  523. }
  524. if (pList.length === 0) {
  525. throw '该企业微信号未绑定任何项目';
  526. }
  527. // 获取系统维护信息
  528. const maintainData = await ctx.service.maintain.getDataById(1);
  529. const renderData = {
  530. maintainData,
  531. maintainConst,
  532. // user,
  533. pList,
  534. redirect_url,
  535. corpid: ctx.params.corpid,
  536. };
  537. // ctx.body = renderData;
  538. await ctx.render('wechat/work_project.ejs', renderData);
  539. } catch (e) {
  540. const renderData = {
  541. status: 1,
  542. msg: e,
  543. };
  544. await ctx.render('wechat/tips.ejs', renderData);
  545. }
  546. }
  547. async workTest(ctx) {
  548. try {
  549. // const user = await app.wechat.oauth.getUser(ctx.session.wechatToken.openid);
  550. const qywx = new wxWork(ctx);
  551. const result = await qywx.getUserList(ctx.params.corpid);
  552. ctx.body = result;
  553. } catch (e) {
  554. const renderData = {
  555. status: 1,
  556. msg: e,
  557. };
  558. await ctx.render('wechat/tips.ejs', renderData);
  559. }
  560. }
  561. async tips(ctx) {
  562. const renderData = {
  563. status: 0,
  564. msg: ctx.query.msg,
  565. };
  566. await ctx.render('wechat/tips.ejs', renderData);
  567. }
  568. }
  569. return WechatController;
  570. };