1、企微工作台配置应用
1.1 准备工作
1)需要找企业管理员开通管理员权限,这样可以登录企业微信后台进行后续操作。
2)发布前端项目,需要域名访问,并且公共网络可访问到。
1.2 配置应用
登录到企业微信的管理后台:work.weixin.qq.com/wework_admi…
打开应用管理,找到自建的部分,点击【创建应用】。
填写应用名称、介绍和可见范围后,进入应用的配置界面。H5应用选择网页类型,网页网址的配置需要根据企微应用开发文档中的介绍进行配置,配置链接规则为:open.weixin.qq.com/connect/oau…
构造网页授权链接文档地址: developer.work.weixin.qq.com/document/pa…
配置的网页回调链接需要配置可信域名,找到开发者接口中的【网页授权及JS-SDK】,设置域名,点击【申请校验域名】,按照提示操作。可信域名中填写网页域名,例如:xxx.wangye.com。
2、企微服务端API
调用所有企微服务端的接口都需要access_token,获取token文档地址:developer.work.weixin.qq.com/document/pa…
调用企微的接口,需要将 调用方的IP地址在应用配置的企业可信IP中进行添加。
这里给出一个使用node.js编写的获取访问用户身份的接口的示例。ps:可以借助AI工具,把企微开发文档地址发给AI工具,让AI生成代码。
const express = require('express');
const axios = require('axios');
const app = express();
const port = process.env.PORT || 3000;
// 中间件
app.use(express.json());
// 企业微信配置信息
const config = {
corpId: 'YOUR_CORP_ID', // 企业ID
agentId: 'YOUR_AGENT_ID', // 应用ID
secret: 'YOUR_SECRET', // 应用Secret
};
// 缓存对象
let cache = {
accessToken: null,
accessTokenExpires: 0,
corpJsapiTicket: null,
corpJsapiTicketExpires: 0,
agentJsapiTicket: null,
agentJsapiTicketExpires: 0
};
/**
* 获取access_token(带缓存功能)
* 文档: https://developer.work.weixin.qq.com/document/path/91039
*/
async function getAccessToken() {
// 检查缓存中是否有未过期的access_token
if (cache.accessToken && Date.now() < cache.accessTokenExpires) {
return cache.accessToken;
}
try {
const response = await axios.get(
`https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${config.corpId}&corpsecret=${config.secret}`
);
if (response.data.errcode === 0) {
// 缓存access_token,提前5分钟过期
cache.accessToken = response.data.access_token;
cache.accessTokenExpires = Date.now() + (response.data.expires_in - 300) * 1000;
return cache.accessToken;
} else {
throw new Error(`获取access_token失败: ${response.data.errmsg}`);
}
} catch (error) {
console.error('获取access_token错误:', error);
throw error;
}
}
/**
* 使用code获取用户信息
* 文档: https://developer.work.weixin.qq.com/document/path/96442
*/
async function getUserInfoByCode(code) {
try {
const accessToken = await getAccessToken();
const response = await axios.get(
`https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=${accessToken}&code=${code}`
);
return response.data;
} catch (error) {
console.error('获取用户信息错误:', error);
throw error;
}
}
/**
* 获取企业的jsapi_ticket
* 文档: https://developer.work.weixin.qq.com/document/path/96909#获取企业-jsapi-ticket
*/
async function getCorpJsapiTicket() {
// 检查缓存中是否有未过期的企业jsapi_ticket
if (cache.corpJsapiTicket && Date.now() < cache.corpJsapiTicketExpires) {
return cache.corpJsapiTicket;
}
try {
const accessToken = await getAccessToken();
const response = await axios.get(
`https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=${accessToken}`
);
if (response.data.errcode === 0) {
// 缓存企业jsapi_ticket,提前5分钟过期
cache.corpJsapiTicket = response.data.ticket;
cache.corpJsapiTicketExpires = Date.now() + (response.data.expires_in - 300) * 1000;
return cache.corpJsapiTicket;
} else {
throw new Error(`获取企业jsapi_ticket失败: ${response.data.errmsg}`);
}
} catch (error) {
console.error('获取企业jsapi_ticket错误:', error);
throw error;
}
}
/**
* 获取应用的jsapi_ticket
* 文档: https://developer.work.weixin.qq.com/document/path/96909#获取应用的-jsapi-ticket
*/
async function getAgentJsapiTicket() {
// 检查缓存中是否有未过期的应用jsapi_ticket
if (cache.agentJsapiTicket && Date.now() < cache.agentJsapiTicketExpires) {
return cache.agentJsapiTicket;
}
try {
const accessToken = await getAccessToken();
const response = await axios.get(
`https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=${accessToken}&type=agent_config`
);
if (response.data.errcode === 0) {
// 缓存应用jsapi_ticket,提前5分钟过期
cache.agentJsapiTicket = response.data.ticket;
cache.agentJsapiTicketExpires = Date.now() + (response.data.expires_in - 300) * 1000;
return cache.agentJsapiTicket;
} else {
throw new Error(`获取应用jsapi_ticket失败: ${response.data.errmsg}`);
}
} catch (error) {
console.error('获取应用jsapi_ticket错误:', error);
throw error;
}
}
// 路由定义
/**
* 直接通过code获取用户信息的API接口(前端调用)
*/
app.post('/api/wecom/getUserInfo', async (req, res) => {
try {
const { code } = req.body;
if (!code) {
return res.status(400).json({ error: '缺少code参数' });
}
const userInfo = await getUserInfoByCode(code);
res.json({ success: true, data: userInfo });
} catch (error) {
console.error('获取用户信息错误:', error);
res.status(500).json({
success: false,
error: '获取用户信息失败',
message: error.message
});
}
});
/**
* 获取企业JSAPI ticket接口
* 文档: https://developer.work.weixin.qq.com/document/path/96909#获取企业-jsapi-ticket
*/
app.get('/api/wecom/ticket/corp', async (req, res) => {
try {
const ticket = await getCorpJsapiTicket();
res.json({
success: true,
data: {
ticket: ticket,
expires_in: Math.floor((cache.corpJsapiTicketExpires - Date.now()) / 1000)
}
});
} catch (error) {
console.error('获取企业JSAPI ticket错误:', error);
res.status(500).json({
success: false,
error: '获取企业JSAPI ticket失败',
message: error.message
});
}
});
/**
* 获取应用JSAPI ticket接口
* 文档: https://developer.work.weixin.qq.com/document/path/96909#获取应用的-jsapi-ticket
*/
app.get('/api/wecom/ticket/agent', async (req, res) => {
try {
const ticket = await getAgentJsapiTicket();
res.json({
success: true,
data: {
ticket: ticket,
expires_in: Math.floor((cache.agentJsapiTicketExpires - Date.now()) / 1000)
}
});
} catch (error) {
console.error('获取应用JSAPI ticket错误:', error);
res.status(500).json({
success: false,
error: '获取应用JSAPI ticket失败',
message: error.message
});
}
});
// 启动服务器
app.listen(port, () => {
console.log(`服务器运行在端口 ${port}`);
console.log('可用接口:');
console.log('POST /api/wecom/getUserInfo - 通过code获取用户信息');
console.log('GET /api/wecom/ticket/corp - 获取企业JSAPI ticket');
console.log('GET /api/wecom/ticket/agent - 获取应用JSAPI ticket');
});
3、集成企微JS-SDK
如果需要使用企微提供的方法,例如预览图片、内置地图,获取定位等方法,则需要集成企微的JS-SDK。接入方法文档地址:developer.work.weixin.qq.com/document/pa…
3.1 获取企业 / 应用 jsapi_ticket
服务端需要提供获取企业 / 应用 jsapi_ticket的接口,上文中的代码中已给出了示例,企微文档地址:developer.work.weixin.qq.com/document/pa…
3.2 前端注册企微JSSDK
这里给出一个前端集成代码示例,先安装npm install @wecom/jssdk,创建一个weJsSDK.js文件,编写企微的集成方法。注意需要将需要使用的JS接口列表配置到ww.register中的jsApiList中。
// src\utils\weJsSDK.js
import * as ww from '@wecom/jssdk'
// 这两个方法是分别是获取企业jsapi_ticket和获取应用jsapi_ticket的,需要调用上文中的两个接口
import { getJsapiTicket, getJsapiAgentTicket } from '@/api/server'
// 保存注册配置的Promise,避免重复注册
let registrationPromise = null;
/**
* 初始化企业微信JS-SDK
* @returns {Promise} 注册结果的Promise
*/
async function initWeComSDK() {
// 如果已经在注册中或已注册,返回同一个Promise
if (registrationPromise) {
return registrationPromise;
}
registrationPromise = (async () => {
try {
// 获取当前页面的URL(去除#后面的部分)
const url = getCurrentUrl();
// 注册SDK
await ww.register({
corpId: '企业的corpId',
agentId: '应用的agentId',
jsApiList: ['getExternalContact', 'openLocation'], // 需要使用的JS接口列表
getConfigSignature: () => getConfigSignature(url),
getAgentConfigSignature: () => getAgentConfigSignature(url)
});
console.log('企业微信JS-SDK注册成功');
return true;
} catch (error) {
console.error('企业微信JS-SDK注册失败:', error);
registrationPromise = null; // 重置以便重试
throw error;
}
})();
return registrationPromise;
}
/**
* 获取当前页面URL(去除#及其后面部分)
*/
function getCurrentUrl() {
return window.location.href.split('#')[0];
}
async function getConfigSignature(url) {
// 根据 url 生成企业签名
const res = await getJsapiTicket(); // 你的获取企业jsapi_ticket的方法
return generateSignature(res.ticket, url);
}
async function getAgentConfigSignature(url) {
// 根据 url 生成应用签名,生成方法同上,注意此处使用应用的 jsapi_ticket
const res = await getJsapiAgentTicket(); // 你的获取企业jsapi_ticket的方法
return generateSignature(res.ticket, url);
}
/**
* 生成JS-SDK签名
* @param {String} jsapiTicket - 通过接口获取的jsapi_ticket(企业或应用的)
* @param {String} url - 当前网页的URL,不包含#及其后面部分
* @returns {Object} 签名结果对象,包含timestamp, nonceStr, signature
*/
async function generateSignature(jsapiTicket, url) {
// 1. 生成随机字符串 (16位)
const nonceStr = generateNonceStr(16);
// 2. 生成时间戳 (秒级)
const timestamp = Math.floor(Date.now() / 1000);
// 3. 参数排序并拼接字符串
const string1 = generateSortedString(jsapiTicket, nonceStr, timestamp, url);
// 4. 使用SHA1加密生成签名
const signature = await sha1Hash(string1);
return {
timestamp,
nonceStr,
signature
};
}
/**
* 生成随机字符串
* @param {Number} length - 随机字符串长度
* @returns {String} 随机字符串
*/
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;
}
/**
* 生成排序后的参数字符串
* @param {String} jsapiTicket - jsapi_ticket
* @param {String} nonceStr - 随机字符串
* @param {Number} timestamp - 时间戳
* @param {String} url - 当前网页URL
* @returns {String} 排序拼接后的字符串
*/
function generateSortedString(jsapiTicket, nonceStr, timestamp, url) {
// 参数对象(key必须小写)
const params = {
jsapi_ticket: jsapiTicket,
noncestr: nonceStr,
timestamp: timestamp.toString(),
url: url
};
// 按照key的ASCII码从小到大排序(字典序)
const sortedKeys = Object.keys(params).sort();
// 拼接成URL键值对格式
const string1 = sortedKeys.map(key => `${key}=${params[key]}`).join('&');
return string1;
}
async function sha1Hash(str) {
// 将字符串编码为 Uint8Array
const encoder = new TextEncoder();
const data = encoder.encode(str);
// 使用 Web Crypto API 计算 SHA-1 哈希
const hashBuffer = await window.crypto.subtle.digest('SHA-1', data);
// 将 ArrayBuffer 转换为十六进制字符串
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex;
}
// 导出SDK实例和初始化方法
export { ww, initWeComSDK };
在前端项目入口文件中,调用初始化方法。
import { ww, initWeComSDK } from '@/utils/weJsSDK';
initWeComSDK()
.then(() => {
console.log('企业微信SDK已就绪')
})
.catch((error) => {
console.warn('企业微信SDK初始化失败:', error);
})
这样完成之后,即可调用企微提供的客户端方法,下面给出一个调用企微内置地图打开位置的一个方法。
const openWeLocation = (data) => {
// 使用企业微信内置地图打开位置
ww.openLocation({
latitude: Number(data.latitude), // 维度
longitude: Number(data.longitude), // 经度
name: '位置名称',
address: '当前地址',
scale: 18, // 更合适的缩放级别
success(result:any) {
console.log('打开地图成功');
},
fail(result:any) {
console.log('打开地图失败');
showToast('打开地图失败')
},
complete(result:any) {
console.log('打开地图完成');
}
})
}