会议管理系统开放API接口文档
API概述
会议管理系统提供了一套完整的RESTful API,用于与第三方系统进行集成。API使用JSON格式进行数据交换,支持标准的HTTP方法(GET、POST、PUT、DELETE)。
基础URL
http(s)://{服务器地址}/api/v1/open
路径说明:
- OPENAPI:
/api/v1/open/*- 使用AppID/AppSecret认证
API版本
当前API版本:v1
所有API请求都需要在URL中包含版本号 /api/v1。
支持的HTTP方法
- GET:查询资源
- POST:创建资源或执行操作
- PUT:更新资源
- DELETE:删除资源
认证机制
外部API使用AppID/AppSecret进行API认证,通过HMAC-SHA256签名验证请求的合法性。这与内部API使用的JWT认证方式不同。
AppID和AppSecret说明
AppID和AppSecret存储在系统的组织配置表(base_organization_configs)中,每个组织(企业、政府机构、事业单位等)都有独立的API凭证:
- AppID:64字符长度的应用标识符,用于标识调用方
- AppSecret:128字符长度的应用密钥,用于签名计算,必须严格保密
获取AppID和AppSecret
AppID和AppSecret在系统管理后台的"系统设置 > 组织信息 > API配置"中查看和生成:
- 登录系统管理后台
- 进入"系统设置 > 组织信息"
- 切换到"API配置"标签页
- 查看或重新生成AppID和AppSecret
重要提示:
- AppSecret仅在重新生成时显示一次,请妥善保管
- 重新生成后,原凭证将立即失效
- 如怀疑凭证泄露,请立即重新生成
- AppID和AppSecret存储在数据库的
base_organization_configs表中,系统通过AppID查询对应的AppSecret进行签名验证
签名生成方法
每次API请求都需要生成签名,签名算法如下:
- 构建签名字符串:
签名字符串 = HTTP方法 + "\n" + URI + "\n" + 时间戳 + "\n" + 请求体
说明:
- HTTP方法:GET、POST、PUT、DELETE等(必须大写)
- URI:完整的请求路径,包含查询参数(如:
/api/v1/open/users?page=1&pageSize=10) - 时间戳:Unix时间戳(秒),例如:
1690123456 - 请求体:POST/PUT请求的JSON字符串(原始字符串,不进行格式化),GET/DELETE请求为空字符串
- 计算HMAC-SHA256签名:
签名 = HMAC-SHA256(签名字符串, AppSecret)
将签名结果转换为十六进制字符串(小写)
- 设置请求头:
X-App-ID: {AppID}
X-Timestamp: {时间戳}
X-Signature: {签名}
Content-Type: application/json
认证流程
系统认证流程如下:
- 客户端:使用AppID和AppSecret生成签名,设置请求头
- 服务端:从请求头获取
X-App-ID,查询数据库获取对应的AppSecret - 服务端:使用相同的签名算法计算期望签名
- 服务端:比较客户端签名和服务端签名,验证请求合法性
- 服务端:验证时间戳是否在有效范围内(5分钟内)
认证失败情况:
- 缺少必要的认证头信息(
MISSING_AUTH_HEADERS) - 时间戳格式错误(
INVALID_TIMESTAMP) - 请求已过期(超过5分钟,
REQUEST_EXPIRED) - 无效的AppID(
INVALID_APP_ID) - 签名验证失败(
INVALID_SIGNATURE)
时间戳要求
- 时间戳必须为Unix时间戳(秒),例如:
1690123456 - 时间戳与服务器时间的差值不能超过5分钟(300秒)
- 超过时间限制的请求将被拒绝,返回错误码
REQUEST_EXPIRED,防止重放攻击 - 建议客户端使用服务器时间同步,确保时间戳准确
提示:详细的签名示例、代码工具类和测试工具使用方法请参考文档末尾的附录部分。
通用数据结构
响应格式
所有API响应均使用以下统一格式:
{
"code": 200,
"message": "操作成功",
"data": {
// 响应数据
}
}
分页响应格式
带分页的列表数据使用以下格式:
{
"code": 200,
"message": "查询成功",
"data": {
"list": [
// 数据项列表
],
"total": 100,
"page": 1,
"pageSize": 10
}
}
错误码说明
| 错误码 | 说明 |
|---|---|
| 200 | 成功 |
| 400 | 请求参数错误 |
| 401 | 未授权或Token无效 |
| 403 | 权限不足 |
| 404 | 资源不存在 |
| 500 | 服务器内部错误 |
| 1001 | 用户名或密码错误 |
| 1002 | 账户已禁用 |
| 2001 | 会议不存在 |
| 2002 | 会议已被预订 |
| 3001 | 用户不存在 |
| 3002 | 部门不存在 |
| 4001 | 审批不存在 |
| 4002 | 审批状态错误 |
| 5001 | 节目不存在 |
| 5002 | 发布失败 |
集成示例
JavaScript集成示例
// 登录并获取Token
async function login(username, password) {
try {
const response = await fetch('http://api.example.com/api/v1/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
const result = await response.json();
if (result.code === 200) {
localStorage.setItem('token', result.data.token);
return result.data;
} else {
throw new Error(result.message);
}
} catch (error) {
console.error('登录失败:', error);
throw error;
}
}
// 获取用户列表
async function getUsers(page = 1, pageSize = 10, name = '', departmentId = '') {
const params = new URLSearchParams({
page: page.toString(),
pageSize: pageSize.toString()
});
if (name) params.append('name', name);
if (departmentId) params.append('departmentId', departmentId);
const uri = `/api/v1/users?${params}`;
return await sendApiRequest('GET', uri);
}
// 创建会议
async function createMeeting(meetingData) {
return await sendApiRequest('POST', '/api/v1/meetings', meetingData);
}
// 获取审批列表
async function getApprovals(page = 1, pageSize = 20, status = '', type = '') {
const params = new URLSearchParams({
page: page.toString(),
pageSize: pageSize.toString()
});
if (status) params.append('status', status);
if (type) params.append('type', type);
const uri = `/api/v1/approval?${params}`;
return await sendApiRequest('GET', uri);
}
// 处理审批
async function processApproval(approvalId, action, comment = '') {
return await sendApiRequest('PUT', `/api/v1/approval/${approvalId}/process`, { action, comment });
}
// 获取节目列表
async function getPrograms(page = 1, pageSize = 10, status = '', keyword = '') {
const params = new URLSearchParams({
page: page.toString(),
pageSize: pageSize.toString()
});
if (status) params.append('status', status);
if (keyword) params.append('keyword', keyword);
const uri = `/api/v1/etv/programs?${params}`;
return await sendApiRequest('GET', uri);
}
// 创建发布
async function createPublish(publishData) {
return await sendApiRequest('POST', '/api/v1/etv/publish', publishData);
}
// 使用示例
async function example() {
try {
// 获取用户列表
const usersData = await getUsers(1, 10);
console.log('用户列表:', usersData);
// 创建会议
const meetingData = {
title: '项目启动会',
roomId: 1,
startTime: '2025-07-24T14:00:00Z',
endTime: '2025-07-24T16:00:00Z',
description: '项目启动讨论',
participants: [10, 11, 12],
notifyAttendees: true
};
const newMeeting = await createMeeting(meetingData);
console.log('会议创建成功:', newMeeting);
// 获取审批列表
const approvalsData = await getApprovals(1, 20, 'pending', 'meeting');
console.log('审批列表:', approvalsData);
// 处理审批
if (approvalsData.list.length > 0) {
await processApproval(approvalsData.list[0].id, 'approve', '审批通过');
console.log('审批处理成功');
}
// 获取节目列表
const programsData = await getPrograms(1, 10, 'approved');
console.log('节目列表:', programsData);
// 创建发布
if (programsData.list.length > 0) {
const publishData = {
name: '大厅欢迎屏发布',
programId: programsData.list[0].id,
target: {
type: 'terminal',
smartboardIds: [1, 2, 3]
},
startTime: '2025-07-24T09:00:00Z',
endTime: '2025-07-24T18:00:00Z',
priority: 1,
playMode: 'replace',
startImmediately: true
};
const newPublish = await createPublish(publishData);
console.log('发布创建成功:', newPublish);
}
} catch (error) {
console.error('示例执行失败:', error);
}
}
// 执行示例
example();
Python集成示例
import requests
import hmac
import hashlib
import time
import json
class ApiClient:
def __init__(self, base_url, app_id, app_secret):
self.base_url = base_url
self.app_id = app_id
self.app_secret = app_secret
def generate_signature(self, method, uri, timestamp, body):
"""生成签名"""
sign_string = f"{method}\n{uri}\n{timestamp}\n{body or ''}"
signature = hmac.new(
self.app_secret.encode('utf-8'),
sign_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature
def send_request(self, method, uri, body=None):
"""发送API请求"""
timestamp = str(int(time.time()))
body_string = json.dumps(body) if body else ''
signature = self.generate_signature(method, uri, timestamp, body_string)
headers = {
'X-App-ID': self.app_id,
'X-Timestamp': timestamp,
'X-Signature': signature,
'Content-Type': 'application/json'
}
url = f"{self.base_url}{uri}"
if method == 'GET':
response = requests.get(url, headers=headers, params=body if isinstance(body, dict) else None)
elif method == 'POST':
response = requests.post(url, headers=headers, json=body)
elif method == 'PUT':
response = requests.put(url, headers=headers, json=body)
elif method == 'DELETE':
response = requests.delete(url, headers=headers)
else:
raise ValueError(f"不支持的HTTP方法: {method}")
data = response.json()
if data.get("code") == 200:
return data.get("data")
else:
raise Exception(f"API请求失败: {data.get('message') or data.get('error')}")
def get_users(self, page=1, page_size=10, name=None, department_id=None):
"""获取用户列表"""
params = {
"page": page,
"pageSize": page_size
}
if name:
params["name"] = name
if department_id:
params["departmentId"] = department_id
uri = f"/api/v1/users?{'&'.join([f'{k}={v}' for k, v in params.items()])}"
return self.send_request('GET', uri)
def get_departments(self, include_member_count=False):
"""获取部门列表"""
uri = "/api/v1/departments"
if include_member_count:
uri += "?includeMemberCount=true"
return self.send_request('GET', uri)
def create_meeting(self, title, room_id, start_time, end_time, description=None, participants=None, notify_attendees=True):
"""创建会议"""
payload = {
"title": title,
"roomId": room_id,
"startTime": start_time,
"endTime": end_time,
"notifyAttendees": notify_attendees
}
if description:
payload["description"] = description
if participants:
payload["participants"] = participants
return self.send_request('POST', '/api/v1/meetings', payload)
def get_approvals(self, page=1, page_size=20, status=None, business_type=None):
"""获取审批列表"""
params = {
"page": page,
"pageSize": page_size
}
if status:
params["status"] = status
if business_type:
params["type"] = business_type
uri = f"/api/v1/approval?{'&'.join([f'{k}={v}' for k, v in params.items()])}"
return self.send_request('GET', uri)
def process_approval(self, approval_id, action, comment=""):
"""处理审批"""
payload = {
"action": action,
"comment": comment
}
return self.send_request('PUT', f'/api/v1/approval/{approval_id}/process', payload)
def get_programs(self, page=1, page_size=10, status=None, keyword=None):
"""获取节目列表"""
params = {
"page": page,
"pageSize": page_size
}
if status:
params["status"] = status
if keyword:
params["keyword"] = keyword
uri = f"/api/v1/etv/programs?{'&'.join([f'{k}={v}' for k, v in params.items()])}"
return self.send_request('GET', uri)
def create_publish(self, name, program_id, target, start_time, end_time=None, priority=0, play_mode="replace", start_immediately=False):
"""创建发布"""
payload = {
"name": name,
"programId": program_id,
"target": target,
"startTime": start_time,
"priority": priority,
"playMode": play_mode,
"startImmediately": start_immediately
}
if end_time:
payload["endTime"] = end_time
return self.send_request('POST', '/api/v1/etv/publish', payload)
# 使用示例
def main():
# 创建API客户端(需要提供AppID和AppSecret)
client = ApiClient(
base_url="http://api.example.com",
app_id="your-app-id",
app_secret="your-app-secret"
)
try:
# 获取用户列表
users_data = client.get_users(page=1, page_size=10)
print(f"用户总数: {users_data['total']}")
# 获取部门列表
departments_data = client.get_departments(include_member_count=True)
print(f"部门数量: {len(departments_data)}")
# 创建会议
from datetime import datetime, timedelta
import pytz
utc = pytz.UTC
now = datetime.now(utc)
start_time = (now + timedelta(days=1)).isoformat()
end_time = (now + timedelta(days=1, hours=2)).isoformat()
meeting_data = client.create_meeting(
title="Python集成测试会议",
room_id=1,
start_time=start_time,
end_time=end_time,
description="Python API集成测试",
participants=[10, 11, 12],
notify_attendees=True
)
print(f"会议创建成功: {meeting_data}")
# 获取审批列表
approvals_data = client.get_approvals(page=1, page_size=20, status="pending", business_type="meeting")
print(f"待审批数量: {approvals_data['total']}")
# 处理审批
if approvals_data["list"]:
result = client.process_approval(approvals_data["list"][0]["id"], "approve", "审批通过")
print("审批处理成功")
# 获取节目列表
programs_data = client.get_programs(page=1, page_size=10, status="approved")
print(f"节目总数: {programs_data['total']}")
# 创建发布
if programs_data["list"]:
publish_data = client.create_publish(
name="大厅欢迎屏发布",
program_id=programs_data["list"][0]["id"],
target={
"type": "terminal",
"smartboardIds": [1, 2, 3]
},
start_time=start_time,
end_time=end_time,
priority=1,
play_mode="replace",
start_immediately=True
)
print(f"发布创建成功: {publish_data}")
except Exception as e:
print(f"错误: {e}")
if __name__ == "__main__":
main()
---
附录
签名工具类(推荐使用)
为了方便第三方系统集成,我们提供了以下语言的签名工具类,可以直接使用:
JavaScript签名工具类(Node.js)
const crypto = require('crypto');
const https = require('https');
const http = require('http');
/**
* 会议管理系统API客户端
*/
class MeetingAPIClient {
constructor(baseUrl, appId, appSecret) {
this.baseUrl = baseUrl.replace(/\/$/, ''); // 移除末尾斜杠
this.appId = appId;
this.appSecret = appSecret;
}
/**
* 生成签名
* @param {string} method HTTP方法
* @param {string} uri 请求URI(包含查询参数)
* @param {string} timestamp 时间戳
* @param {string} body 请求体
* @returns {string} 签名
*/
generateSignature(method, uri, timestamp, body) {
const signString = `${method}\n${uri}\n${timestamp}\n${body || ''}`;
const signature = crypto
.createHmac('sha256', this.appSecret)
.update(signString)
.digest('hex');
return signature;
}
/**
* 生成请求头
* @param {string} method HTTP方法
* @param {string} uri 请求URI
* @param {string} body 请求体
* @returns {object} 请求头
*/
generateHeaders(method, uri, body) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature = this.generateSignature(method, uri, timestamp, body);
return {
'X-App-ID': this.appId,
'X-Timestamp': timestamp,
'X-Signature': signature,
'Content-Type': 'application/json'
};
}
/**
* 发送HTTP请求
* @param {string} method HTTP方法
* @param {string} path 请求路径(如:/api/v1/open/users)
* @param {object} params 查询参数(可选)
* @param {object} body 请求体(可选)
* @returns {Promise} 响应数据
*/
async request(method, path, params = null, body = null) {
return new Promise((resolve, reject) => {
// 构建URI
let uri = path;
if (params) {
const queryString = Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&');
uri += `?${queryString}`;
}
// 构建完整URL
const url = new URL(this.baseUrl + uri);
const bodyString = body ? JSON.stringify(body) : '';
// 生成请求头
const headers = this.generateHeaders(method, uri, bodyString);
// 选择HTTP模块
const httpModule = url.protocol === 'https:' ? https : http;
const options = {
hostname: url.hostname,
port: url.port || (url.protocol === 'https:' ? 443 : 80),
path: url.pathname + url.search,
method: method,
headers: headers
};
const req = httpModule.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const result = JSON.parse(data);
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(result);
} else {
reject(new Error(`请求失败: ${result.message || data}`));
}
} catch (e) {
reject(new Error(`解析响应失败: ${data}`));
}
});
});
req.on('error', (error) => {
reject(error);
});
if (bodyString) {
req.write(bodyString);
}
req.end();
});
}
// 便捷方法
async get(path, params) {
return this.request('GET', path, params);
}
async post(path, body, params) {
return this.request('POST', path, params, body);
}
async put(path, body, params) {
return this.request('PUT', path, params, body);
}
async delete(path, params) {
return this.request('DELETE', path, params);
}
}
// 使用示例
async function main() {
const client = new MeetingAPIClient(
'https://mms.ehuitong.cn',
'your-app-id',
'your-app-secret'
);
try {
// GET请求示例
const users = await client.get('/api/v1/open/users', { page: 1, pageSize: 10 });
console.log('用户列表:', users);
// POST请求示例
const newUser = await client.post('/api/v1/open/users', {
loginName: 'testuser',
name: '测试用户',
departmentId: 1,
password: '123456'
});
console.log('创建用户:', newUser);
// PUT请求示例
const updatedUser = await client.put('/api/v1/open/users/1', {
name: '更新后的用户名'
});
console.log('更新用户:', updatedUser);
// DELETE请求示例
await client.delete('/api/v1/open/users/1');
console.log('删除用户成功');
} catch (error) {
console.error('请求失败:', error.message);
}
}
// 如果直接运行此文件
if (require.main === module) {
main();
}
module.exports = MeetingAPIClient;
Java签名工具类
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 会议管理系统API客户端
*/
public class MeetingAPIClient {
private String baseUrl;
private String appId;
private String appSecret;
public MeetingAPIClient(String baseUrl, String appId, String appSecret) {
this.baseUrl = baseUrl.replaceAll("/$", ""); // 移除末尾斜杠
this.appId = appId;
this.appSecret = appSecret;
}
/**
* 生成签名
*/
private String generateSignature(String method, String uri, String timestamp, String body)
throws NoSuchAlgorithmException, InvalidKeyException {
String signString = String.join("\n", method, uri, timestamp, body != null ? body : "");
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(
appSecret.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
mac.init(secretKeySpec);
byte[] hash = mac.doFinal(signString.getBytes(StandardCharsets.UTF_8));
// 转换为十六进制字符串
StringBuilder sb = new StringBuilder();
for (byte b : hash) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
/**
* 生成请求头
*/
private Map<String, String> generateHeaders(String method, String uri, String body)
throws NoSuchAlgorithmException, InvalidKeyException {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String signature = generateSignature(method, uri, timestamp, body);
Map<String, String> headers = new HashMap<>();
headers.put("X-App-ID", appId);
headers.put("X-Timestamp", timestamp);
headers.put("X-Signature", signature);
headers.put("Content-Type", "application/json");
return headers;
}
/**
* 构建查询字符串
*/
private String buildQueryString(Map<String, Object> params) {
if (params == null || params.isEmpty()) {
return "";
}
return params.entrySet().stream()
.map(e -> e.getKey() + "=" + java.net.URLEncoder.encode(
String.valueOf(e.getValue()), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
}
/**
* 发送HTTP请求
*/
public String request(String method, String path, Map<String, Object> params, String body)
throws Exception {
// 构建URI
String uri = path;
String queryString = buildQueryString(params);
if (!queryString.isEmpty()) {
uri += "?" + queryString;
}
// 构建完整URL
URL url = new URL(baseUrl + uri);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(method);
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
// 设置请求头
Map<String, String> headers = generateHeaders(method, uri, body);
for (Map.Entry<String, String> entry : headers.entrySet()) {
conn.setRequestProperty(entry.getKey(), entry.getValue());
}
// 写入请求体
if (body != null && !body.isEmpty()) {
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
os.write(body.getBytes(StandardCharsets.UTF_8));
}
}
// 读取响应
int responseCode = conn.getResponseCode();
BufferedReader reader;
if (responseCode >= 200 && responseCode < 300) {
reader = new BufferedReader(
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
} else {
reader = new BufferedReader(
new InputStreamReader(conn.getErrorStream(), StandardCharsets.UTF_8));
}
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
if (responseCode >= 200 && responseCode < 300) {
return response.toString();
} else {
throw new Exception("请求失败: " + responseCode + " - " + response.toString());
}
}
// 便捷方法
public String get(String path, Map<String, Object> params) throws Exception {
return request("GET", path, params, null);
}
public String post(String path, String body, Map<String, Object> params) throws Exception {
return request("POST", path, params, body);
}
public String put(String path, String body, Map<String, Object> params) throws Exception {
return request("PUT", path, params, body);
}
public String delete(String path, Map<String, Object> params) throws Exception {
return request("DELETE", path, params, null);
}
// 使用示例
public static void main(String[] args) {
MeetingAPIClient client = new MeetingAPIClient(
"https://mms.ehuitong.cn",
"your-app-id",
"your-app-secret"
);
try {
// GET请求示例
Map<String, Object> params = new HashMap<>();
params.put("page", 1);
params.put("pageSize", 10);
String users = client.get("/api/v1/open/users", params);
System.out.println("用户列表: " + users);
// POST请求示例
String newUserJson = "{\"loginName\":\"testuser\",\"name\":\"测试用户\",\"departmentId\":1,\"password\":\"123456\"}";
String newUser = client.post("/api/v1/open/users", newUserJson, null);
System.out.println("创建用户: " + newUser);
// PUT请求示例
String updateJson = "{\"name\":\"更新后的用户名\"}";
String updatedUser = client.put("/api/v1/open/users/1", updateJson, null);
System.out.println("更新用户: " + updatedUser);
// DELETE请求示例
client.delete("/api/v1/open/users/1", null);
System.out.println("删除用户成功");
} catch (Exception e) {
System.err.println("请求失败: " + e.getMessage());
e.printStackTrace();
}
}
}
Python签名工具类
import hmac
import hashlib
import time
import json
import urllib.parse
from typing import Optional, Dict, Any
import requests
class MeetingAPIClient:
"""会议管理系统API客户端"""
def __init__(self, base_url: str, app_id: str, app_secret: str):
"""
初始化客户端
:param base_url: 服务器地址(如:https://mms.ehuitong.cn)
:param app_id: AppID
:param app_secret: AppSecret
"""
self.base_url = base_url.rstrip('/')
self.app_id = app_id
self.app_secret = app_secret
def generate_signature(self, method: str, uri: str, timestamp: str, body: str = '') -> str:
"""
生成签名
:param method: HTTP方法
:param uri: 请求URI(包含查询参数)
:param timestamp: 时间戳
:param body: 请求体
:return: 签名
"""
sign_string = f"{method}\n{uri}\n{timestamp}\n{body or ''}"
signature = hmac.new(
self.app_secret.encode('utf-8'),
sign_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature
def generate_headers(self, method: str, uri: str, body: str = '') -> Dict[str, str]:
"""
生成请求头
:param method: HTTP方法
:param uri: 请求URI
:param body: 请求体
:return: 请求头字典
"""
timestamp = str(int(time.time()))
signature = self.generate_signature(method, uri, timestamp, body)
return {
'X-App-ID': self.app_id,
'X-Timestamp': timestamp,
'X-Signature': signature,
'Content-Type': 'application/json'
}
def request(self, method: str, path: str, params: Optional[Dict[str, Any]] = None,
body: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""
发送HTTP请求
:param method: HTTP方法
:param path: 请求路径(如:/api/v1/open/users)
:param params: 查询参数
:param body: 请求体
:return: 响应数据
"""
# 构建URI
uri = path
if params:
query_string = urllib.parse.urlencode(params)
uri += f"?{query_string}"
# 构建完整URL
url = f"{self.base_url}{uri}"
# 构建请求体
body_string = ''
if body:
body_string = json.dumps(body, ensure_ascii=False)
# 生成请求头
headers = self.generate_headers(method, uri, body_string)
# 发送请求
response = requests.request(
method=method,
url=url,
headers=headers,
data=body_string if body_string else None,
timeout=30
)
# 检查响应状态
response.raise_for_status()
return response.json()
# 便捷方法
def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""GET请求"""
return self.request('GET', path, params=params)
def post(self, path: str, body: Optional[Dict[str, Any]] = None,
params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""POST请求"""
return self.request('POST', path, params=params, body=body)
def put(self, path: str, body: Optional[Dict[str, Any]] = None,
params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""PUT请求"""
return self.request('PUT', path, params=params, body=body)
def delete(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""DELETE请求"""
return self.request('DELETE', path, params=params)
# 使用示例
if __name__ == '__main__':
client = MeetingAPIClient(
base_url='https://mms.ehuitong.cn',
app_id='your-app-id',
app_secret='your-app-secret'
)
try:
# GET请求示例
users = client.get('/api/v1/open/users', params={'page': 1, 'pageSize': 10})
print('用户列表:', users)
# POST请求示例
new_user = client.post('/api/v1/open/users', body={
'loginName': 'testuser',
'name': '测试用户',
'departmentId': 1,
'password': '123456'
})
print('创建用户:', new_user)
# PUT请求示例
updated_user = client.put('/api/v1/open/users/1', body={
'name': '更新后的用户名'
})
print('更新用户:', updated_user)
# DELETE请求示例
client.delete('/api/v1/open/users/1')
print('删除用户成功')
except Exception as e:
print(f'请求失败: {e}')
Go签名工具类
package main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
// MeetingAPIClient 会议管理系统API客户端
type MeetingAPIClient struct {
BaseURL string
AppID string
AppSecret string
Client *http.Client
}
// NewMeetingAPIClient 创建API客户端
func NewMeetingAPIClient(baseURL, appID, appSecret string) *MeetingAPIClient {
baseURL = strings.TrimSuffix(baseURL, "/")
return &MeetingAPIClient{
BaseURL: baseURL,
AppID: appID,
AppSecret: appSecret,
Client: &http.Client{Timeout: 30 * time.Second},
}
}
// GenerateSignature 生成签名
func (c *MeetingAPIClient) GenerateSignature(method, uri, timestamp, body string) string {
signString := strings.Join([]string{method, uri, timestamp, body}, "\n")
h := hmac.New(sha256.New, []byte(c.AppSecret))
h.Write([]byte(signString))
return hex.EncodeToString(h.Sum(nil))
}
// GenerateHeaders 生成请求头
func (c *MeetingAPIClient) GenerateHeaders(method, uri, body string) map[string]string {
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
signature := c.GenerateSignature(method, uri, timestamp, body)
return map[string]string{
"X-App-ID": c.AppID,
"X-Timestamp": timestamp,
"X-Signature": signature,
"Content-Type": "application/json",
}
}
// Request 发送HTTP请求
func (c *MeetingAPIClient) Request(method, path string, params map[string]interface{}, body interface{}) (map[string]interface{}, error) {
// 构建URI
uri := path
if len(params) > 0 {
values := url.Values{}
for k, v := range params {
values.Add(k, fmt.Sprintf("%v", v))
}
uri += "?" + values.Encode()
}
// 构建完整URL
fullURL := c.BaseURL + uri
// 构建请求体
var bodyBytes []byte
var bodyString string
if body != nil {
var err error
bodyBytes, err = json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("序列化请求体失败: %w", err)
}
bodyString = string(bodyBytes)
}
// 创建请求
req, err := http.NewRequest(method, fullURL, bytes.NewBuffer(bodyBytes))
if err != nil {
return nil, fmt.Errorf("创建请求失败: %w", err)
}
// 设置请求头
headers := c.GenerateHeaders(method, uri, bodyString)
for k, v := range headers {
req.Header.Set(k, v)
}
// 发送请求
resp, err := c.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("发送请求失败: %w", err)
}
defer resp.Body.Close()
// 读取响应
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应失败: %w", err)
}
// 解析响应
var result map[string]interface{}
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("解析响应失败: %w", err)
}
// 检查状态码
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("请求失败: %d - %v", resp.StatusCode, result)
}
return result, nil
}
// Get GET请求
func (c *MeetingAPIClient) Get(path string, params map[string]interface{}) (map[string]interface{}, error) {
return c.Request("GET", path, params, nil)
}
// Post POST请求
func (c *MeetingAPIClient) Post(path string, body interface{}, params map[string]interface{}) (map[string]interface{}, error) {
return c.Request("POST", path, params, body)
}
// Put PUT请求
func (c *MeetingAPIClient) Put(path string, body interface{}, params map[string]interface{}) (map[string]interface{}, error) {
return c.Request("PUT", path, params, body)
}
// Delete DELETE请求
func (c *MeetingAPIClient) Delete(path string, params map[string]interface{}) (map[string]interface{}, error) {
return c.Request("DELETE", path, params, nil)
}
// 使用示例
func main() {
client := NewMeetingAPIClient(
"https://mms.ehuitong.cn",
"your-app-id",
"your-app-secret",
)
// GET请求示例
users, err := client.Get("/api/v1/open/users", map[string]interface{}{
"page": 1,
"pageSize": 10,
})
if err != nil {
fmt.Printf("请求失败: %v\n", err)
return
}
fmt.Printf("用户列表: %+v\n", users)
// POST请求示例
newUser, err := client.Post("/api/v1/open/users", map[string]interface{}{
"loginName": "testuser",
"name": "测试用户",
"departmentId": 1,
"password": "123456",
}, nil)
if err != nil {
fmt.Printf("创建用户失败: %v\n", err)
return
}
fmt.Printf("创建用户: %+v\n", newUser)
// PUT请求示例
updatedUser, err := client.Put("/api/v1/open/users/1", map[string]interface{}{
"name": "更新后的用户名",
}, nil)
if err != nil {
fmt.Printf("更新用户失败: %v\n", err)
return
}
fmt.Printf("更新用户: %+v\n", updatedUser)
// DELETE请求示例
_, err = client.Delete("/api/v1/open/users/1", nil)
if err != nil {
fmt.Printf("删除用户失败: %v\n", err)
return
}
fmt.Println("删除用户成功")
}
签名示例
示例1:GET请求
HTTP方法: GET
URI: /api/v1/open/users?page=1&pageSize=10
时间戳: 1690123456
请求体: (空)
签名字符串:
GET
/api/v1/open/users?page=1&pageSize=10
1690123456
使用AppSecret计算HMAC-SHA256签名
示例2:POST请求
HTTP方法: POST
URI: /api/v1/open/meetings
时间戳: 1690123456
请求体: {"title":"会议标题","roomId":1,"startTime":"2025-07-23T09:00:00Z","endTime":"2025-07-23T10:00:00Z"}
签名字符串:
POST
/api/v1/open/meetings
1690123456
{"title":"会议标题","roomId":1,"startTime":"2025-07-23T09:00:00Z","endTime":"2025-07-23T10:00:00Z"}
使用AppSecret计算HMAC-SHA256签名
代码示例(原始方法,供参考)
JavaScript签名生成示例
const crypto = require('crypto');
function generateSignature(method, uri, timestamp, body, appSecret) {
// 构建签名字符串
const signString = `${method}\n${uri}\n${timestamp}\n${body || ''}`;
// 计算HMAC-SHA256签名
const signature = crypto
.createHmac('sha256', appSecret)
.update(signString)
.digest('hex');
return signature;
}
// 使用示例
const appID = 'your-app-id';
const appSecret = 'your-app-secret';
const timestamp = Math.floor(Date.now() / 1000).toString();
const method = 'GET';
const uri = '/api/v1/open/users?page=1&pageSize=10';
const body = '';
const signature = generateSignature(method, uri, timestamp, body, appSecret);
// 设置请求头
const headers = {
'X-App-ID': appID,
'X-Timestamp': timestamp,
'X-Signature': signature,
'Content-Type': 'application/json'
};
Python签名生成示例
import hmac
import hashlib
import time
def generate_signature(method, uri, timestamp, body, app_secret):
# 构建签名字符串
sign_string = f"{method}\n{uri}\n{timestamp}\n{body or ''}"
# 计算HMAC-SHA256签名
signature = hmac.new(
app_secret.encode('utf-8'),
sign_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature
# 使用示例
app_id = 'your-app-id'
app_secret = 'your-app-secret'
timestamp = str(int(time.time()))
method = 'GET'
uri = '/api/v1/open/users?page=1&pageSize=10'
body = ''
signature = generate_signature(method, uri, timestamp, body, app_secret)
# 设置请求头
headers = {
'X-App-ID': app_id,
'X-Timestamp': timestamp,
'X-Signature': signature,
'Content-Type': 'application/json'
}
Go签名生成示例
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
"time"
)
func generateSignature(method, uri, timestamp, body, appSecret string) string {
// 构建签名字符串
signString := strings.Join([]string{method, uri, timestamp, body}, "\n")
// 计算HMAC-SHA256签名
h := hmac.New(sha256.New, []byte(appSecret))
h.Write([]byte(signString))
signature := hex.EncodeToString(h.Sum(nil))
return signature
}
// 使用示例
func main() {
appID := "your-app-id"
appSecret := "your-app-secret"
timestamp := fmt.Sprintf("%d", time.Now().Unix())
method := "GET"
uri := "/api/v1/open/users?page=1&pageSize=10"
body := ""
signature := generateSignature(method, uri, timestamp, body, appSecret)
// 设置请求头
headers := map[string]string{
"X-App-ID": appID,
"X-Timestamp": timestamp,
"X-Signature": signature,
"Content-Type": "application/json",
}
fmt.Printf("Headers: %+v\n", headers)
}
Java签名生成示例
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;
public class ApiSignature {
/**
* 生成签名
* @param method HTTP方法(GET、POST、PUT、DELETE等)
* @param uri 请求URI(包含查询参数)
* @param timestamp Unix时间戳(秒)
* @param body 请求体(GET/DELETE请求为空字符串)
* @param appSecret AppSecret密钥
* @return HMAC-SHA256签名的十六进制字符串
*/
public static String generateSignature(String method, String uri, String timestamp, String body, String appSecret)
throws NoSuchAlgorithmException, InvalidKeyException {
// 构建签名字符串
String signString = String.join("\n", method, uri, timestamp, body != null ? body : "");
// 计算HMAC-SHA256签名
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(
appSecret.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
mac.init(secretKeySpec);
byte[] hash = mac.doFinal(signString.getBytes(StandardCharsets.UTF_8));
// 转换为十六进制字符串
Formatter formatter = new Formatter();
for (byte b : hash) {
formatter.format("%02x", b);
}
String signature = formatter.toString();
formatter.close();
return signature;
}
// 使用示例
public static void main(String[] args) {
try {
String appID = "your-app-id";
String appSecret = "your-app-secret";
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String method = "GET";
String uri = "/api/v1/open/users?page=1&pageSize=10";
String body = "";
String signature = generateSignature(method, uri, timestamp, body, appSecret);
// 设置请求头
System.out.println("X-App-ID: " + appID);
System.out.println("X-Timestamp: " + timestamp);
System.out.println("X-Signature: " + signature);
System.out.println("Content-Type: application/json");
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试工具使用
详细的测试工具使用说明请参考 第三方工具测试指南.md 文档,包括:
- Postman:配置环境变量和Pre-request Script
- Apifox:配置前置脚本(推荐使用
Apifox签名脚本-最简单版.js) - Insomnia:配置请求脚本
- cURL:命令行工具使用示例
- HTTPie:命令行工具使用示例
集成示例
详细的集成示例代码请参考文档中的集成示例部分(位于第3117行)。