首页 / 帮助中心 / API集成指南

会议管理系统开放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配置"中查看和生成:

  1. 登录系统管理后台
  2. 进入"系统设置 > 组织信息"
  3. 切换到"API配置"标签页
  4. 查看或重新生成AppID和AppSecret

重要提示

  • AppSecret仅在重新生成时显示一次,请妥善保管
  • 重新生成后,原凭证将立即失效
  • 如怀疑凭证泄露,请立即重新生成
  • AppID和AppSecret存储在数据库的base_organization_configs表中,系统通过AppID查询对应的AppSecret进行签名验证

签名生成方法

每次API请求都需要生成签名,签名算法如下:

  1. 构建签名字符串
   签名字符串 = 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请求为空字符串
  1. 计算HMAC-SHA256签名
   签名 = HMAC-SHA256(签名字符串, AppSecret)

将签名结果转换为十六进制字符串(小写)

  1. 设置请求头
   X-App-ID: {AppID}
   X-Timestamp: {时间戳}
   X-Signature: {签名}
   Content-Type: application/json

认证流程

系统认证流程如下:

  1. 客户端:使用AppID和AppSecret生成签名,设置请求头
  2. 服务端:从请求头获取X-App-ID,查询数据库获取对应的AppSecret
  3. 服务端:使用相同的签名算法计算期望签名
  4. 服务端:比较客户端签名和服务端签名,验证请求合法性
  5. 服务端:验证时间戳是否在有效范围内(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行)。