From b39d345da9a941bbcb2220060238c51b0e2047b5 Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Fri, 23 Jan 2026 17:08:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=8E=A5=E5=8F=A3=E5=8D=9A=E5=AE=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.vitepress/theme/router/index.ts | 1 + docs/Others/WeCom.md | 451 ++++++++++++++++++++++++++ 2 files changed, 452 insertions(+) create mode 100644 docs/Others/WeCom.md diff --git a/docs/.vitepress/theme/router/index.ts b/docs/.vitepress/theme/router/index.ts index 6310e61..4df5520 100644 --- a/docs/.vitepress/theme/router/index.ts +++ b/docs/.vitepress/theme/router/index.ts @@ -87,6 +87,7 @@ export const routers = [ text: '🔄 其他', items: [ { text: 'VitePress使用和简介', link: '/Others/VitePress' }, + { text: '企业微信工作台开发', link: '/Others/WeCom' }, ] } ]; \ No newline at end of file diff --git a/docs/Others/WeCom.md b/docs/Others/WeCom.md new file mode 100644 index 0000000..1c19331 --- /dev/null +++ b/docs/Others/WeCom.md @@ -0,0 +1,451 @@ +--- + title: 企业微信工作台开发 + date: 2026-01-23 +--- + + 接口文档地址:[接口](https://developer.work.weixin.qq.com/document/path/90664) + +# 一、建立工作台 +1. 进入企业微信后台管理系统,选择应用管理->应用管理。 +2. 选择自建->创建应用。 +3. 输入名称、图标等信息,分配可见范围,完成应用创建. +4. 点击应用,选择开发者接口->网页授权,配置可信域名和企业可信IP。 +5. 查看并保存AgentId和Secret。 + +::: tip +可信域名不能是IP形式,必须是和企业微信备案主体一致的服务器。 +::: + +# 二、JS-SDK开发 +## 2.1 接口鉴权 + [官方文档](https://developer.work.weixin.qq.com/document/path/90514) + 参考第三节企业微信接口开发 + +## 2.2 打开默认浏览器 + 使用系统浏览器打开指定 URL,支持传入 oauth2 链接,从而实现在系统浏览器内免登录的效果。 + +# 三、接口开发 + 本教程以node.js为例。 +## 3.1 通用企业微信服务端API +```js +const axios = require('axios'); + +const TOKEN_URL = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken' +const JSAPI_TICKET_URL = 'https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket' +const MESSAGE_URL = 'https://qyapi.weixin.qq.com/cgi-bin/message/send' + +// 获取access_token +async function getAccessTokenApi(corpId, corpSecret) { + const now = Date.now(); + + try { + const url = `${TOKEN_URL}?corpid=${corpId}&corpsecret=${corpSecret}` + const response = await axios.get(url); + + if (response.data.errcode === 0) { + return { + accessToken: response.data.access_token, + expires: now + (response.data.expires_in - 300) * 1000 // 提前5分钟过期 + }; + } else { + throw new Error(`获取access_token失败: ${response.data.errmsg}`); + } + } catch (error) { + console.error('获取access_token错误:', error); + throw error; + } +} + +// 获取jsapi_ticket +async function getJsapiTicketApi(accessToken) { + try { + const url = `${JSAPI_TICKET_URL}?access_token=${accessToken}` + const response = await axios.get(url); + + if (response.data.errcode === 0) { + return response.data.ticket; + } else { + throw new Error(`获取jsapi_ticket失败: ${response.data.errmsg}`); + } + } catch (error) { + console.error('获取jsapi_ticket错误:', error); + throw error; + } +} + +// 发送应用消息 +async function sendMessageApi(agentid, accessToken, touser, content) { + try { + const url = `${MESSAGE_URL}?access_token=${accessToken}` + const response = await axios.post(url, { + touser: touser, + msgtype: 'text', + agentid: agentid, + text: { + content: content + } + }); + + if (response.data.errcode === 0) { + return true; + } else { + throw new Error(`发送应用消息失败: ${response.data.errmsg}`); + } + } catch (error) { + console.error('发送应用消息错误:', error); + throw error; + } +} + +module.exports = { + getAccessTokenApi, + getJsapiTicketApi, + sendMessageApi +}; +``` + +## 3.2 自建应用鉴权接口 +```js +const {getAccessTokenApi, getJsapiTicketApi, sendMessageApi} = require("./wecom"); + +const config = { + corpId: '', // 企业id + corpSecret: '', // 应用密码 + agentId: '', // 应用id + domain: '', // 跳转域名 + password: '' // 登录默认密码 +}; + +// 存储access_token和jsapi_ticket +let tokenCache = { + accessToken: null, + jsapiTicket: null, + expires: 0 +}; + +// 获取access_token +async function getRepairAccessToken() { + const now = Date.now(); + + if (tokenCache.accessToken && tokenCache.expires > now) { + return tokenCache.accessToken; + } + + const result = await getAccessTokenApi(config.corpId, config.corpSecret) + tokenCache.accessToken = result.accessToken; + tokenCache.expires = result.expires +} + +// 获取jsapi_ticket +async function getRepairJsapiTicket() { + const now = Date.now(); + + if (tokenCache.jsapiTicket && tokenCache.expires > now) { + return tokenCache.jsapiTicket; + } + + await getRepairAccessToken(); + tokenCache.jsapiTicket = await getJsapiTicketApi(tokenCache.accessToken); +} + +// 发送应用消息 +async function sendRepairMessage(touser, content) { + await getRepairAccessToken(); + await sendMessageApi(config.agentId, tokenCache.accessToken, touser, content); +} + +function getRepairDomain() { + return config.domain +} + +function getRepairPassword() { + return config.password +} + +module.exports = { + getRepairAccessToken, + getRepairJsapiTicket, + sendRepairMessage, + getRepairDomain, + getRepairPassword +}; +``` + +## 3.3 NodeJS API +```js +const express = require('express'); +const axios = require('axios'); +const cors = require('cors'); +const {generateNonceStr, generateSignature} = require("./utils"); +const {sendRepairMessage, getRepairJsapiTicket, getRepairAccessToken, getRepairDomain, getRepairPassword} = require("./repair"); + +const app = express(); +const PORT = 3000; + +app.use(cors()); +app.use(express.json()); + +const corpId = 'ww48932728388c9e3a' +const USER_INFO_URL = 'https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo' + +// API接口:生成JS-SDK配置 +app.post('/auth/config/repair', async (req, res) => { + await configService(req, res, 'repair') +}); + +async function configService(req, res, appName) { + try { + const {url} = req.body; + + if (!url) { + return res.status(400).json({errcode: -1, errmsg: '缺少url参数'}); + } + + let jsapiTicket = '' + + if (appName === 'repair') { + jsapiTicket = await getRepairJsapiTicket(); + } + + const timestamp = Math.floor(Date.now() / 1000); + const noncestr = generateNonceStr(); + const signature = generateSignature(jsapiTicket, noncestr, timestamp, url); + + res.json({ + errcode: 0, + errmsg: 'ok', + appId: corpId, + timestamp, + nonceStr: noncestr, + signature + }); + } catch (error) { + console.error('生成JS-SDK配置错误:', error); + res.status(500).json({errcode: -1, errmsg: '服务器内部错误'}); + } +} + +// API接口:OAuth回调处理 +app.get('/auth/callback/repair', async (req, res) => { + await callback(req, res, 'repair') +}); + +async function callback(req, res, appName) { + try { + const {code, state} = req.query; + + if (!code) { + return res.status(400).send('缺少code参数'); + } + + // 通过code获取用户信息 + let accessToken = '' + if (appName === 'repair') { + accessToken = await getRepairAccessToken(); + } + + const url = `${USER_INFO_URL}?access_token=${accessToken}&code=${code}` + const userResponse = await axios.get(url); + + if (userResponse.data.errcode !== 0) { + throw new Error(`获取用户信息失败: ${userResponse.data.errmsg}`); + } + + const userInfo = userResponse.data; + + // 重定向到前端页面 + let redirectUrl = '' + if (appName === 'repair') { + redirectUrl = `${getRepairDomain()}?username=${userInfo.UserId}&password=${getRepairPassword()}&type=repair`; + } + + res.redirect(redirectUrl); + + } catch (error) { + console.error('OAuth回调处理错误:', error); + res.status(500).send(` + +
+${error.message}
+ + + `); + } +} + +// API接口:发现应用消息 +app.post('/message/repair', async (req, res) => { + const {touser, content} = req.body; + await sendRepairMessage(touser, content) + res.json({ + errcode: 0, + errmsg: 'ok' + }); +}); + +// 启动服务 +app.listen(PORT, () => { + console.log(`企业微信登录服务运行在端口 ${PORT}`); +}); + +module.exports = app; +``` + +## 3.4 工具类 +```js +const crypto = require('crypto'); + +// 工具函数:生成随机字符串 +function generateNonceStr(length = 16) { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; +} + +// 工具函数:生成签名 +function generateSignature(jsapiTicket, noncestr, timestamp, url) { + const string = `jsapi_ticket=${jsapiTicket}&noncestr=${noncestr}×tamp=${timestamp}&url=${url}`; + return crypto.createHash('sha1').update(string).digest('hex'); +} + +module.exports = { + generateNonceStr, + generateSignature +}; +``` + +# 四、应用主页 +```html + + + + + +