前言
微信作为目前最热门的社交媒体之一,已经成为了企业展示、沟通的一种重要方式。而在企业内部沟通中,企业号则是微信的重要组成部分。利用企业号,企业可以通过微信平台来对内、对外进行沟通和管理。
在本文中,我们将探讨如何使用 Serverless 技术来实现微信企业号应用的开发。Serverless 是一种基于云计算的应用部署方式,它不需要我们花费大量的时间和精力来管理服务器,而是使我们可以快速、高效地编写和部署应用程序。
Serverless 架构
在 Serverless 架构中,没有服务器的概念,而是将所有的应用程序组成部分均分散在了多个服务上。这些服务可以在云环境下进行部署和管理。Serverless 架构的核心是 FaaS(Functions as a Service),即以函数为基础的服务。
使用 Serverless 架构的好处有:
- 提供高度可伸缩性,减少或增加服务器无需任何的人工干预。
- 降低了管理和维护服务器所需的时间和人力成本。
- 对于应用程序的部署和更新过程,Serverless 提供了全自动化的处理。
微信企业号开发
开发环境
在开始使用 Serverless 进行微信企业号的开发之前,我们需要先搭建好相应的开发环境。我们需要以下三个工具:
- 微信企业号
- Serverless Framework
- Node.js
注册企业号
如果您还没有微信企业号,您可以通过微信企业号官网进行注册。注册完成之后,您还需要通过审核才能获得可用的 API。
安装 Serverless Framework
Serverless Framework 是一个 Serverless 应用程序开发平台,它支持多个云服务商。在本文中,我们将使用 Serverless Framework 的阿里云函数计算插件来完成微信企业号应用的开发。
# 安装 Serverless Framework npm install -g serverless # 安装阿里云函数计算插件 npm install -g serverless-plugin-alibabacloud
配置微信企业号
完成前两个准备工作之后,我们需要在微信企业号后台中创建应用,并配置获取接口的 URL。
实现微信企业号应用
接下来,我们需要创建一个 Serverless 项目,并在其中实现微信企业号应用的功能。
创建 Serverless 项目
# 创建 Serverless 项目 serverless create --template aliyun-nodejs --path serverless-wechat-enterprise # 进入项目 cd serverless-wechat-enterprise
配置项目
在 serverless.yml
文件中,我们需要添加以下内容:
# serverless.yml service: serverless-wechat-enterprise # serverless 配置 provider: name: aliyun runtime: nodejs12 # 插件配置 plugins: - serverless-plugin-alibabacloud # 函数配置 functions: wechat: handler: index.handler # 函数 events: - http: path: / method: POST # POST 请求
在 index.handler
中,我们将实现微信企业号应用的功能。
实现应用功能
在 index.js
文件中,我们需要实现以下几个功能:
- 验证请求是否来自微信服务器
- 解密微信服务器发送的消息
- 处理微信服务器发送的消息
- 加密消息并返回给微信服务器
验证请求消息
// index.js const TOKEN = 'YOUR_TOKEN'; // 填写你的 token,与微信后台保持一致 exports.handler = async function (event, context, callback) { const { query, body } = event; try { const { signature, timestamp, nonce, echostr } = query; if (signature && timestamp && nonce && echostr) { // 计算签名 const sortedArr = [TOKEN, timestamp, nonce].sort(); const str = sortedArr.join(''); const hash = crypto.createHash('sha1').update(str).digest('hex'); // 验证签名 if (signature === hash) { console.log('验证成功'); return { isBase64Encoded: false, statusCode: 200, body: echostr }; } console.warn('签名校验失败'); return { isBase64Encoded: false, statusCode: 403, body: '签名校验失败' }; } // 其他请求消息 console.log(BODY_XML.parse(body)); // TODO: 处理其他类型的请求消息 return { isBase64Encoded: false, statusCode: 200, body: 'success' }; } catch (err) { console.error(err.stack); return { isBase64Encoded: false, statusCode: 502, body: 'Bad Gateway' }; } };
解密消息
微信服务器向我们的应用发送的消息有加密和未加密两种,我们可以通过判断消息体中是否有 Encrypt
字段来判断当前消息是否加密。如果是加密的消息,我们需要先对消息进行解密。
加密消息的解密算法如下:
// messageDecrypt.js const crypto = require('crypto'); const zlib = require('zlib'); /** * 解密微信商户平台消息 * @param {String} appId 商户号 `app_id` * @param {String} aesKey 商户号 `api_secret` * @param {String} encrypted 加密的消息 * @return {String} 解密后的消息 */ module.exports = function messageDecrypt(appId, aesKey, encrypted) { const buf = Buffer.from(encrypted, 'base64'); const decipher = crypto.createDecipheriv('aes-256-cbc', aesKey.slice(0, 32), buf.slice(0, 16)); decipher.setAutoPadding(false); try { let decrypted = decipher.update(buf.slice(16), 'binary', 'utf8'); decrypted += decipher.final('utf8'); decrypted = decrypted.replace(/[\x00-\x20]+/g, ''); // 去除填充数据 const result = decrypted.match(new RegExp(`^.*<AppId><!\\[CDATA\\[(.*)\\]\\]><\\/AppId><Encrypt><!\\[CDATA\\[(.*)\\]\\]><\\/Encrypt>$`)); if (!result || result.length !== 3) { throw new Error('Decryption failed: Invalid response format'); } if (result[1] !== appId) { throw new Error('Decryption failed: Invalid appid'); } const decryptedXML = zlib.inflateRawSync(Buffer.from(result[2], 'base64')).toString('utf8'); return decryptedXML; } catch (err) { console.error(err.stack); throw new Error('Decryption failed'); } };
处理消息
解密消息之后,我们就可以对消息进行处理了。在处理时,我们需要根据 MsgType
字段来区分不同类型的消息,并根据需求来作出回应。
// index.js const parseXML = require('xml2js').parseStringPromise; const messageDecrypt = require('./messageDecrypt'); exports.handler = async function (event, context, callback) { const { query, body } = event; try { const { signature, timestamp, nonce, echostr } = query; if (signature && timestamp && nonce && echostr) { // 计算签名 const sortedArr = [TOKEN, timestamp, nonce].sort(); const str = sortedArr.join(''); const hash = crypto.createHash('sha1').update(str).digest('hex'); // 验证签名 if (signature === hash) { console.log('验证成功'); return { isBase64Encoded: false, statusCode: 200, body: echostr }; } console.warn('签名校验失败'); return { isBase64Encoded: false, statusCode: 403, body: '签名校验失败' }; } // 解密消息 const cryptMsg = BODY_XML.parse(body); let message = cryptMsg.xml; if (cryptMsg.xml && cryptMsg.xml.Encrypt) { const decryptedMsg = messageDecrypt(APP_ID, API_SECRET, cryptMsg.xml.Encrypt); message = await parseXML(decryptedMsg, { explicitArray: false }).then(res => res.xml); } console.log(message); switch (message.MsgType) { case 'text': // 处理文本消息 const textResp = new TextResponse(message.FromUserName, message.ToUserName, '你好!欢迎关注本公众号'); console.log(textResp); break; case 'event': // 处理事件消息 if (message.Event === 'subscribe') { const eventResp = new TextResponse(message.FromUserName, message.ToUserName, '欢迎关注本公众号'); console.log(eventResp); } break; default: break; } // 返回成功消息 const encryptedResp = messageEncrypt(APP_ID, API_SECRET, textResp.toXML()); const timestamp = parseInt(new Date().getTime() / 1000, 10).toString(); const sortedArr = [TOKEN, timestamp, nonce, encryptedResp].sort(); const str = sortedArr.join(''); const hash = crypto.createHash('sha1').update(str).digest('hex'); const nonceConsistent = '1234567890'; // 随机字符串 const resp = SOCKET_XML.buildObject({ xml: { Encrypt: encryptedResp, MsgSignature: hash, TimeStamp: timestamp, Nonce: nonceConsistent, }, }); console.log(resp); return { isBase64Encoded: false, statusCode: 200, body: resp }; } catch (err) { console.error(err.stack); return { isBase64Encoded: false, statusCode: 502, body: 'Bad Gateway' }; } };
加密消息并返回
在对消息进行处理之后,我们需要将回应消息进行加密,并返回给微信服务器。
加密消息的加密算法如下:
// messageEncrypt.js const crypto = require('crypto'); const zlib = require('zlib'); /** * 加密微信商户平台消息 * @param {String} appId 商户号 `app_id` * @param {String} aesKey 商户号 `api_secret` * @param {String} message 加密的消息 * @return {String} 加密后的消息 */ module.exports = function messageEncrypt(appId, aesKey, message) { const randStr = crypto.randomBytes(16).toString('hex'); const prefix = Buffer.from(randStr).toString('base64') + Buffer.from(message.length.toString()).toString('base64'); const msg = prefix + message; // 消息体 const str = Buffer.from(message).toString('binary'); const x16encoded = aesEncrypt(appId, aesKey, str); const x64encoded = Buffer.from(x16encoded, 'binary').toString('base64'); const msgSignature = sha1([aesKey, randStr, prefix, message.length.toString(), message].sort().join('')); const reply = "<xml>" + "<Encrypt><![CDATA[" + x64encoded + "]]></Encrypt>" + "<MsgSignature><![CDATA[" + msgSignature + "]]></MsgSignature>" + "<TimeStamp>" + parseInt(new Date().getTime() / 1000, 10).toString() + "</TimeStamp>" + "<Nonce><![CDATA[" + '1234567890' + "]]></Nonce>" + "</xml>"; return reply; }; function aesEncrypt(appId, aesKey, str) { const key = Buffer.from(aesKey + '=', 'base64'); const iv = key.slice(0, 16); const cipher = crypto.createCipheriv('aes-256-cbc', key.slice(0, 32), iv); const addLen = 32 - (str.length % 32); const pad = String.fromCharCode(addLen).repeat(addLen); const encryptText = cipher.update(str + pad) + cipher.final(); return encryptText.toString('base64'); } function sha1(str) { const shasum = crypto.createHash('sha1'); shasum.update(str); return shasum.digest('hex'); }
处理完成之后,我们就可以将加密后的消息返回给微信服务器了。
总结
通过本文的介绍和示例代码,您应该已经了解了如何使用 Serverless Framework 来开发微信企业号应用。使用 Serverless 架构,不但可以更加高效地开发应用程序,还能够减轻服务器管理和维护的负担。
在进行开发时,您还需要注意以下几点:
- Serverless 架构中没有服务器,因此您需要注意对应用程序进行监控和调试。
- 微信企业号的接口可能随时发生变化,请根据最新的接口文档进行开发。
- 在开发过程中需要注意应用程序的安全性,如签名算法、消息加解密等。
感谢您的阅读,希望能对您有所帮助!
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65a8980eadd4f0e0ff1bf4da