wechat_controller.js 22 KB

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