Quellcode durchsuchen

:tada: wechat corp message supported

Song vor 4 Jahren
Ursprung
Commit
4713373c54
10 geänderte Dateien mit 184 neuen und 29 gelöschten Zeilen
  1. 2 1
      .gitignore
  2. 1 1
      app.js
  3. 18 11
      common/message.js
  4. 40 4
      common/token.js
  5. 94 0
      common/wechat-corp.js
  6. 14 7
      common/wechat.js
  7. 1 1
      models/index.js
  8. 2 2
      models/message.js
  9. 11 0
      models/user.js
  10. 1 2
      routers/user.js

+ 2 - 1
.gitignore

@@ -4,4 +4,5 @@ node_modules
 data.db-journal
 .vscode
 *.db
-package-lock.json
+package-lock.json
+yarn.lock

+ 1 - 1
app.js

@@ -25,7 +25,7 @@ app.locals.isErrorMessage = false;
 setTimeout(async () => {
   // TODO: Here we need an improvement! I have tried EventEmitter but it's not working. :(
   await initializeTokenStore();
-  await refreshToken(app);
+  await refreshToken();
   setInterval(async () => refreshToken(), 100 * 60 * 1000);
 }, 1000);
 

+ 18 - 11
common/message.js

@@ -1,30 +1,37 @@
+const { getUserDefaultMethod } = require('./token');
 const { pushWeChatMessage } = require('./wechat');
+const { pushWeChatCorpMessage } = require('./wechat-corp');
 const { pushEmailMessage } = require('./email');
 const { Message } = require('../models');
 
 async function processMessage(userPrefix, message) {
   if (message.email) {
     // If message has the attribute "email", override its type.
-    message.type = '1';
+    message.type = 'email';
   }
-  if (message.content && message.type === '0') {
-    message = await Message.create(message);
+  if (!message.type) {
+    message.type = getUserDefaultMethod(userPrefix);
   }
-  let result = {
-    success: false,
-    message: `unsupported message type ${message.type}`,
-  };
+  if (message.content && message.type !== 'email') {
+    // If message is not email type, we should save it because we have to serve the page.
+    message = await Message.create(message, { raw: true });
+  }
+  let result;
   switch (message.type) {
-    case '0': // WeChat message
+    case 'test': // WeChat message
       result = await pushWeChatMessage(userPrefix, message);
       break;
-    case '1': // Email message
+    case 'email': // Email message
       result = await pushEmailMessage(userPrefix, message);
       break;
-    case '2': // HTTP GET request
-      // TODO: HTTP GET request
+    case 'corp': // WeChar corp message
+      result = await pushWeChatCorpMessage(userPrefix, message);
       break;
     default:
+      result = {
+        success: false,
+        message: `unsupported message type ${message.type}`,
+      };
       break;
   }
   return result;

+ 40 - 4
common/token.js

@@ -7,41 +7,77 @@ async function initializeTokenStore() {
   if (process.env.MODE === '1') {
     console.log('Current mode is Heroku mode.');
     let user = {
+      // Common
+      prefix: process.env.PREFIX,
+      accessToken: process.env.ACCESS_TOKEN,
+      defaultMethod: process.env.DEFAULT_METHOD,
+      // WeChat public account
       wechatAppId: process.env.WECHAT_APP_ID,
       wechatAppSecret: process.env.WECHAT_APP_SECRET,
       wechatTemplateId: process.env.WECHAT_TEMPLATE_ID,
       wechatOpenId: process.env.WECHAT_OPEN_ID,
       wechatVerifyToken: process.env.WECHAT_VERIFY_TOKEN,
-      prefix: process.env.PREFIX,
+      // Email
       email: process.env.EMAIL,
       smtpServer: process.env.SMTP_SERVER,
       smtpUser: process.env.SMTP_USER,
       smtpPass: process.env.SMTP_PASS,
+      // WeChat corporation account
+      corpId: process.env.CORP_ID,
+      corpAgentId: process.env.CORP_AGENT_ID,
+      corpAppSecret: process.env.CORP_APP_SECRET,
+      corpUserId: process.env.CORP_USER_ID,
     };
     users.push(user);
   } else {
-    users = await User.findAll();
+    users = await User.findAll({
+      raw: true,
+    });
   }
   users.forEach((user) => {
-    if (user.wechatAppId) {
+    if (user.prefix) {
       tokenStore.set(user.prefix, {
+        // Common
+        accessToken: user.accessToken,
+        defaultMethod: user.defaultMethod,
+        // WeChat test account
         wechatAppId: user.wechatAppId,
         wechatAppSecret: user.wechatAppSecret,
         wechatTemplateId: user.wechatTemplateId,
         wechatOpenId: user.wechatOpenId,
         wechatVerifyToken: user.wechatVerifyToken,
-        token: '',
+        wechatToken: '',
+        // Email
         email: user.email,
         smtpServer: user.smtpServer,
         smtpUser: user.smtpUser,
         smtpPass: user.smtpPass,
+        // WeChat corporation account
+        corpId: user.corpId,
+        corpAgentId: user.corpAgentId,
+        corpAppSecret: user.corpAppSecret,
+        corpUserId: user.corpUserId,
+        corpToken: '',
       });
     }
   });
   console.log('Token store initialized.');
 }
 
+function updateTokenStore(prefix, key, value) {
+  let user = tokenStore.get(prefix);
+  user[key] = value;
+  tokenStore.set(prefix, user);
+}
+
+function getUserDefaultMethod(prefix) {
+  let user = tokenStore.get(prefix);
+  return user.defaultMethod;
+}
+
 module.exports = {
   initializeTokenStore,
+  updateTokenStore,
+  getUserDefaultMethod,
   tokenStore,
 };

+ 94 - 0
common/wechat-corp.js

@@ -0,0 +1,94 @@
+const axios = require('axios');
+const { tokenStore, updateTokenStore } = require('./token');
+const config = require('../config');
+
+async function refreshToken() {
+  for (let [key, value] of tokenStore) {
+    if (value.corpId) {
+      value.corpToken = await requestToken(value.corpId, value.corpAppSecret);
+      tokenStore.set(key, value);
+    }
+  }
+  console.log('Token refreshed.');
+}
+
+async function requestToken(corpId, corpAppSecret) {
+  // Reference: https://work.weixin.qq.com/api/doc/90000/90135/91039
+
+  let token = '';
+  try {
+    let res = await axios.get(
+      `https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${corpId}&corpsecret=${corpAppSecret}`
+    );
+    // console.debug(res);
+    if (res && res.data) {
+      if (res.data.access_token) {
+        token = res.data.access_token;
+      } else {
+        console.error(res.data);
+      }
+    }
+  } catch (e) {
+    console.error(e);
+  }
+  return token;
+}
+
+async function pushWeChatCorpMessage(userPrefix, message) {
+  // Reference: https://work.weixin.qq.com/api/doc/90000/90135/90236
+
+  let user = tokenStore.get(userPrefix);
+  if (!user) {
+    return {
+      success: false,
+      message: `不存在的前缀:${userPrefix},请注意大小写`,
+    };
+  }
+  let access_token = user.corpToken;
+  let request_data = {
+    msgtype: 'textcard',
+    touser: user.corpUserId,
+    agentid: user.corpAgentId,
+    textcard: {
+      title: message.title,
+      description: message.description,
+    },
+  };
+  if (message.content) {
+    request_data.textcard.url = `${config.href}message/${message.id}`;
+  }
+  let requestUrl = `https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${access_token}`;
+  try {
+    let response = await axios.post(requestUrl, request_data);
+    if (response && response.data && response.data.errcode !== 0) {
+      // Failed to push message, get a new token and try again.
+      access_token = await requestToken(user.corpId, user.corpAppSecret);
+      updateTokenStore(userPrefix, 'corpToken', access_token);
+      requestUrl = `https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${access_token}`;
+      response = await axios.post(requestUrl, request_data);
+    }
+    if (response.data.errcode === 0) {
+      return {
+        success: true,
+        message: 'ok',
+      };
+    } else {
+      return {
+        success: false,
+        message: response.data.errmsg,
+      };
+    }
+  } catch (e) {
+    console.error(e);
+    return {
+      success: false,
+      message: e.message,
+    };
+  }
+}
+
+module.exports = {
+  refreshToken,
+  requestToken,
+  pushWeChatCorpMessage,
+};

+ 14 - 7
common/wechat.js

@@ -1,11 +1,16 @@
 const axios = require('axios');
-const { tokenStore } = require('./token');
+const { tokenStore, updateTokenStore } = require('./token');
 const config = require('../config');
 
 async function refreshToken() {
   for (let [key, value] of tokenStore) {
-    value.token = await requestToken(value.wechatAppId, value.wechatAppSecret);
-    tokenStore.set(key, value);
+    if (value.wechatAppId) {
+      value.wechatToken = await requestToken(
+        value.wechatAppId,
+        value.wechatAppSecret
+      );
+      tokenStore.set(key, value);
+    }
   }
   console.log('Token refreshed.');
 }
@@ -39,22 +44,24 @@ async function pushWeChatMessage(userPrefix, message) {
       message: `不存在的前缀:${userPrefix},请注意大小写`,
     };
   }
-  let access_token = user.token;
+  let access_token = user.wechatToken;
   let request_data = {
     touser: user.wechatOpenId,
     template_id: user.wechatTemplateId,
   };
   if (message.content) {
     request_data.url = `${config.href}message/${message.id}`;
-    console.debug(`http://localhost:3000/message/${message.id}`);
   }
   request_data.data = { text: { value: message.description } };
   let requestUrl = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${access_token}`;
   try {
     let response = await axios.post(requestUrl, request_data);
     if (response && response.data && response.data.errcode !== 0) {
-      await requestToken(user.wechatAppId, user.wechatAppSecret);
-      await axios.post(requestUrl, request_data);
+      // Failed to push message, get a new token and try again.
+      let token = await requestToken(user.wechatAppId, user.wechatAppSecret);
+      updateTokenStore(userPrefix, 'wechatToken', token);
+      requestUrl = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${access_token}`;
+      response = await axios.post(requestUrl, request_data);
     }
     if (response.data.errcode === 0) {
       return {

+ 1 - 1
models/index.js

@@ -5,7 +5,7 @@ const sequelize = require('../common/database');
 Message.belongsTo(User);
 
 (async () => {
-  await sequelize.sync({ alter: true });
+  await sequelize.sync();
   console.log('Database initialized.');
   const isNoAdminExisted =
     (await User.findOne({ where: { isAdmin: true } })) === null;

+ 2 - 2
models/message.js

@@ -14,8 +14,8 @@ Message.init(
     description: DataTypes.TEXT,
     content: DataTypes.TEXT,
     type: {
-      type: DataTypes.INTEGER,
-      defaultValue: 0,
+      type: DataTypes.STRING,
+      defaultValue: 'test',
     },
   },
   { sequelize }

+ 11 - 0
models/user.js

@@ -37,17 +37,28 @@ User.init(
       type: DataTypes.BOOLEAN,
       defaultValue: false,
     },
+    defaultMethod:{
+      type: DataTypes.STRING,
+      defaultValue: 'test'
+    },
+    // WeChat public account
     wechatAppId: DataTypes.STRING,
     wechatAppSecret: DataTypes.STRING,
     wechatTemplateId: DataTypes.STRING,
     wechatOpenId: DataTypes.STRING,
     wechatVerifyToken: DataTypes.STRING,
+    // Email
     smtpServer: {
       type: DataTypes.STRING,
       defaultValue: 'smtp.qq.com',
     },
     smtpUser: DataTypes.STRING,
     smtpPass: DataTypes.STRING,
+    // WeChat corporation
+    corpId: DataTypes.STRING,
+    corpAgentId: DataTypes.STRING,
+    corpAppSecret: DataTypes.STRING,
+    corpUserId: DataTypes.STRING
   },
   { sequelize }
 );

+ 1 - 2
routers/user.js

@@ -24,7 +24,6 @@ router.all('/:userPrefix/verify', (req, res, next) => {
 router.all('/:userPrefix/:description', async (req, res, next) => {
   const userPrefix = req.params.userPrefix;
   let message = {
-    type: '0',
     title: '无标题',
     description: req.params.description,
   };
@@ -34,7 +33,7 @@ router.all('/:userPrefix/:description', async (req, res, next) => {
 router.all('/:userPrefix', async (req, res, next) => {
   const userPrefix = req.params.userPrefix;
   let message = {
-    type: req.query.type || req.body.type || '0',
+    type: req.query.type || req.body.type,
     title: req.query.title || req.body.title || '无标题',
     description: req.query.description || req.body.description || '无描述',
     content: req.query.content || req.body.content,