# 客户端自定义集成函数同步指南

## 📋 概述

自定义集成函数允许用户在NetCat平台定义自己的业务函数，这些函数可以在AI生成的脚本中调用。当脚本在客户端本地执行时，需要同步服务器生成的自定义集成代码到本地，以便正确调用这些函数。

### 核心机制
- **服务端**: 用户定义函数 → 系统生成Ruby类代码 → 存储在数据库
- **客户端**: 同步API获取代码 → 本地保存 → 脚本执行时加载使用

## 🔌 API接口

### 1. 同步端点
- **URL**: `GET /api/v1/custom_integrations/sync`
- **认证**: Bearer Token (JWT)
- **方法**: GET
- **响应格式**: JSON

### 2. 认证要求
客户端必须先通过 `/api/v1/auth/token` 接口获取JWT令牌，然后在请求头中添加：
```
Authorization: Bearer YOUR_JWT_TOKEN
```

### 3. 响应结构
```json
{
  "success": true,
  "data": {
    "generated_code": "# frozen_string_literal: true\n\nmodule NetCat\n  module Integrations\n    class GeneratedCustom < Base\n      # ...\n    end\n  end\nend",
    "updated_at": "2026-03-16T10:30:45.123Z",
    "version_hash": "7d5a8b3c6e9f1a2b4d5c7e8f9a0b1c2d"
  }
}
```

### 4. 字段说明
- `generated_code`: 生成的Ruby类代码，包含用户定义的所有函数
- `updated_at`: 代码最后更新时间，用于缓存控制
- `version_hash`: 基于 `generated_code + updated_at` 计算的MD5哈希，用于版本检查

## 🚀 客户端处理流程

### 1. 同步时机
建议在以下时机同步自定义集成代码：
- **启动时**: 客户端启动时检查是否需要更新
- **用户登录后**: 用户登录成功后立即同步
- **脚本执行前**: 执行涉及自定义函数的脚本前
- **定时任务**: 定期（如每小时）检查更新

### 2. 同步步骤

```mermaid
graph TD
    A[开始同步] --> B[检查本地缓存]
    B --> C{需要更新?}
    C -->|是| D[调用同步API]
    C -->|否| E[使用本地代码]
    D --> F{API成功?}
    F -->|是| G[保存代码到本地]
    F -->|否| H[使用缓存或默认]
    G --> I[更新版本信息]
    H --> I
    I --> J[加载代码准备使用]
```

### 3. 版本管理
客户端应该实现版本缓存机制：

```python
# 伪代码示例
class CustomIntegrationManager:
    def __init__(self):
        self.local_version_hash = None
        self.local_code = None
        self.local_updated_at = None

    def needs_update(self, server_data):
        """检查是否需要更新"""
        if not self.local_code:
            return True
        if server_data['version_hash'] != self.local_version_hash:
            return True
        return False

    def save_code(self, server_data):
        """保存代码到本地文件"""
        self.local_code = server_data['generated_code']
        self.local_version_hash = server_data['version_hash']
        self.local_updated_at = server_data['updated_at']

        # 保存到文件系统
        with open('custom_integration.rb', 'w') as f:
            f.write(self.local_code)

        # 保存版本信息到配置文件
        self.save_version_info()
```

### 4. 错误处理
- **网络错误**: 重试机制（最多3次，指数退避）
- **认证失败**: 重新获取令牌
- **服务端错误**: 使用本地缓存的代码，记录错误日志
- **空数据**: 用户没有定义自定义集成，使用默认空类

## 💻 代码集成示例

### 1. Ruby客户端示例
```ruby
require 'net/http'
require 'json'
require 'digest'

class CustomIntegrationClient
  CACHE_FILE = 'custom_integration.rb'
  VERSION_FILE = '.custom_integration_version'

  def initialize(api_base_url, jwt_token)
    @api_base_url = api_base_url
    @jwt_token = jwt_token
  end

  def sync_if_needed
    return if !stale? && File.exist?(CACHE_FILE)

    server_data = fetch_from_server
    return unless server_data

    if needs_update?(server_data)
      save_code(server_data)
      true  # 表示有更新
    else
      false # 表示没有更新
    end
  end

  def load_code
    return '' unless File.exist?(CACHE_FILE)
    File.read(CACHE_FILE)
  end

  private

  def fetch_from_server
    uri = URI("#{@api_base_url}/api/v1/custom_integrations/sync")
    request = Net::HTTP::Get.new(uri)
    request['Authorization'] = "Bearer #{@jwt_token}"

    response = Net::HTTP.start(uri.hostname, uri.port) do |http|
      http.request(request)
    end

    if response.is_a?(Net::HTTPSuccess)
      json = JSON.parse(response.body)
      json['data'] if json['success']
    else
      puts "同步失败: #{response.code} #{response.message}"
      nil
    end
  rescue => e
    puts "同步异常: #{e.message}"
    nil
  end

  def stale?
    # 检查缓存是否过期（比如超过1小时）
    return true unless File.exist?(VERSION_FILE)

    begin
      version_info = JSON.parse(File.read(VERSION_FILE))
      cached_time = Time.parse(version_info['updated_at'])
      (Time.now - cached_time) > 3600  # 1小时
    rescue
      true
    end
  end

  def needs_update?(server_data)
    return true unless File.exist?(VERSION_FILE)

    begin
      version_info = JSON.parse(File.read(VERSION_FILE))
      server_data['version_hash'] != version_info['version_hash']
    rescue
      true
    end
  end

  def save_code(server_data)
    # 保存代码文件
    File.write(CACHE_FILE, server_data['generated_code'])

    # 保存版本信息
    version_info = {
      'version_hash' => server_data['version_hash'],
      'updated_at' => server_data['updated_at'],
      'sync_time' => Time.now.iso8601
    }
    File.write(VERSION_FILE, JSON.pretty_generate(version_info))
  end
end
```

### 2. Python客户端示例
```python
import requests
import json
import hashlib
import os
from datetime import datetime, timedelta

class CustomIntegrationClient:
    def __init__(self, api_base_url, jwt_token):
        self.api_base_url = api_base_url
        self.jwt_token = jwt_token
        self.cache_file = 'custom_integration.rb'
        self.version_file = '.custom_integration_version'

    def sync_if_needed(self):
        """如果需要则同步"""
        if not self._is_stale() and os.path.exists(self.cache_file):
            return False

        server_data = self._fetch_from_server()
        if not server_data:
            return False

        if self._needs_update(server_data):
            self._save_code(server_data)
            return True
        return False

    def load_code(self):
        """加载本地代码"""
        if not os.path.exists(self.cache_file):
            return ''
        with open(self.cache_file, 'r') as f:
            return f.read()

    def _fetch_from_server(self):
        """从服务器获取数据"""
        url = f"{self.api_base_url}/api/v1/custom_integrations/sync"
        headers = {'Authorization': f'Bearer {self.jwt_token}'}

        try:
            response = requests.get(url, headers=headers, timeout=10)
            if response.status_code == 200:
                data = response.json()
                if data.get('success'):
                    return data.get('data')
            else:
                print(f"同步失败: {response.status_code}")
        except Exception as e:
            print(f"同步异常: {e}")
        return None

    def _is_stale(self):
        """检查缓存是否过期"""
        if not os.path.exists(self.version_file):
            return True

        try:
            with open(self.version_file, 'r') as f:
                version_info = json.load(f)
            sync_time = datetime.fromisoformat(version_info['sync_time'].replace('Z', '+00:00'))
            return (datetime.now() - sync_time) > timedelta(hours=1)
        except:
            return True

    def _needs_update(self, server_data):
        """检查是否需要更新"""
        if not os.path.exists(self.version_file):
            return True

        try:
            with open(self.version_file, 'r') as f:
                version_info = json.load(f)
            return server_data['version_hash'] != version_info['version_hash']
        except:
            return True

    def _save_code(self, server_data):
        """保存代码和版本信息"""
        # 保存代码文件
        with open(self.cache_file, 'w') as f:
            f.write(server_data['generated_code'])

        # 保存版本信息
        version_info = {
            'version_hash': server_data['version_hash'],
            'updated_at': server_data['updated_at'],
            'sync_time': datetime.now().isoformat()
        }
        with open(self.version_file, 'w') as f:
            json.dump(version_info, f, indent=2)
```

### 3. JavaScript/Node.js客户端示例
```javascript
const fs = require('fs').promises;
const path = require('path');
const crypto = require('crypto');

class CustomIntegrationClient {
  constructor(apiBaseUrl, jwtToken) {
    this.apiBaseUrl = apiBaseUrl;
    this.jwtToken = jwtToken;
    this.cacheFile = 'custom_integration.rb';
    this.versionFile = '.custom_integration_version';
  }

  async syncIfNeeded() {
    if (!(await this.isStale()) && await this.fileExists(this.cacheFile)) {
      return false;
    }

    const serverData = await this.fetchFromServer();
    if (!serverData) {
      return false;
    }

    if (await this.needsUpdate(serverData)) {
      await this.saveCode(serverData);
      return true;
    }
    return false;
  }

  async loadCode() {
    try {
      return await fs.readFile(this.cacheFile, 'utf8');
    } catch {
      return '';
    }
  }

  async fetchFromServer() {
    const url = `${this.apiBaseUrl}/api/v1/custom_integrations/sync`;
    const headers = {
      'Authorization': `Bearer ${this.jwtToken}`,
      'Content-Type': 'application/json'
    };

    try {
      const response = await fetch(url, { headers });
      if (response.ok) {
        const data = await response.json();
        if (data.success) {
          return data.data;
        }
      } else {
        console.error(`同步失败: ${response.status}`);
      }
    } catch (error) {
      console.error('同步异常:', error);
    }
    return null;
  }

  async isStale() {
    try {
      const versionInfo = await this.readVersionFile();
      const syncTime = new Date(versionInfo.sync_time);
      return (Date.now() - syncTime) > 3600000; // 1小时
    } catch {
      return true;
    }
  }

  async needsUpdate(serverData) {
    try {
      const versionInfo = await this.readVersionFile();
      return serverData.version_hash !== versionInfo.version_hash;
    } catch {
      return true;
    }
  }

  async saveCode(serverData) {
    // 保存代码文件
    await fs.writeFile(this.cacheFile, serverData.generated_code);

    // 保存版本信息
    const versionInfo = {
      version_hash: serverData.version_hash,
      updated_at: serverData.updated_at,
      sync_time: new Date().toISOString()
    };
    await fs.writeFile(this.versionFile, JSON.stringify(versionInfo, null, 2));
  }

  async fileExists(filePath) {
    try {
      await fs.access(filePath);
      return true;
    } catch {
      return false;
    }
  }

  async readVersionFile() {
    const content = await fs.readFile(this.versionFile, 'utf8');
    return JSON.parse(content);
  }
}
```

## 🔧 在脚本中使用

### 1. 代码加载和动态执行
客户端需要在执行脚本前加载自定义集成代码。具体方法取决于客户端的实现：

```ruby
# Ruby客户端加载示例
class ScriptExecutor
  def initialize
    @integration_client = CustomIntegrationClient.new(
      ENV['API_BASE_URL'],
      ENV['JWT_TOKEN']
    )
  end

  def execute_script(script_content, device_connection)
    # 同步最新的自定义集成代码
    @integration_client.sync_if_needed

    # 加载自定义集成代码
    custom_code = @integration_client.load_code

    # 创建执行环境，注入自定义集成
    execution_env = create_execution_env(device_connection, custom_code)

    # 执行脚本
    eval(script_content, execution_env)
  end

  def create_execution_env(connection, custom_code)
    # 创建绑定环境，包含自定义集成
    binding = Binding.new

    # 首先执行自定义集成代码，定义 GeneratedCustom 类
    eval(custom_code, binding) if custom_code && !custom_code.empty?

    # 创建 custom 对象
    if defined?(NetCat::Integrations::GeneratedCustom)
      custom_instance = NetCat::Integrations::GeneratedCustom.new(
        config: {user: current_user},
        logger: logger
      )
      binding.local_variable_set(:custom, custom_instance)
    end

    binding
  end
end
```

### 2. 脚本执行流程
```mermaid
sequenceDiagram
    participant C as Client
    participant S as Server
    participant E as Executor

    C->>S: GET /api/v1/custom_integrations/sync
    S-->>C: 返回生成的代码
    C->>E: 加载自定义集成代码
    E->>E: 创建执行环境
    E->>E: 注入custom对象
    E->>E: 执行用户脚本
    Note over E: 脚本中调用 custom.send_alarm()
    E-->>C: 返回执行结果
```

## 📁 文件存储建议

### 建议的目录结构
```
client_app/
├── lib/
│   ├── custom_integration.rb          # 同步的集成代码
│   └── custom_integration_client.rb   # 同步客户端
├── config/
│   └── .custom_integration_version    # 版本信息
└── scripts/
    └── user_scripts/                  # 用户脚本
```

### 文件命名约定
- `custom_integration.rb` - 生成的集成代码文件
- `.custom_integration_version` - 版本信息（隐藏文件）
- `custom_integration_client.{rb,py,js}` - 客户端同步代码

## ⚠️ 注意事项

### 1. 安全性
- **代码验证**: 客户端应对从服务器下载的代码进行基本语法验证
- **沙箱执行**: 建议在受限环境中执行自定义集成代码
- **权限控制**: 自定义函数不应有系统级访问权限

### 2. 性能优化
- **缓存策略**: 使用版本哈希避免不必要的同步
- **增量更新**: 如果支持，可以实现增量更新机制
- **并行加载**: 可以在应用启动时预加载集成代码

### 3. 错误恢复
- **降级策略**: 当自定义集成不可用时，脚本应有降级处理
- **错误隔离**: 自定义函数错误不应影响脚本核心逻辑
- **日志记录**: 详细记录同步和执行过程中的错误

### 4. 兼容性
- **Ruby版本**: 确保生成的代码与客户端的Ruby版本兼容
- **依赖检查**: 自定义集成代码不应依赖未安装的gem
- **API变更**: 客户端应处理API响应结构的变化

## 🔄 更新策略

### 建议的更新频率
| 场景 | 更新频率 | 说明 |
|------|----------|------|
| 启动时 | 每次启动 | 检查是否有更新 |
| 定时任务 | 每小时 | 定期检查 |
| 用户操作 | 手动触发 | 用户点击"刷新"时 |
| 脚本错误 | 出错时 | 自定义函数调用失败时 |

### 版本冲突处理
如果多个客户端同时使用同一账户：
1. **最后写入优先**: 以服务器端的最新版本为准
2. **版本提示**: 当检测到版本冲突时提示用户
3. **手动同步**: 提供手动同步按钮

## 📝 调试建议

### 1. 日志记录
记录以下关键信息：
- 同步请求和响应时间
- 版本哈希变化
- 自定义函数调用次数和结果
- 错误详情和堆栈跟踪

### 2. 测试工具
建议开发测试工具：
```bash
# 测试同步功能
./test_custom_sync.rb --token YOUR_JWT_TOKEN

# 验证生成的代码
./validate_integration_code.rb custom_integration.rb

# 模拟函数调用
./test_custom_function.rb --function send_alarm --params '{"cpu": 90}'
```

### 3. 监控指标
监控以下关键指标：
- 同步成功率
- 同步延迟
- 自定义函数调用成功率
- 代码加载时间

## 🔗 相关资源

### API文档
- [认证API](/api/v1/auth/token) - 获取JWT令牌
- [自定义集成API](/api/v1/custom_integrations/sync) - 同步接口

### 服务端文档
- [AI自定义集成指南](client_custom_ai_guide.md) - 服务端AI如何生成使用自定义集成的脚本
- [自定义集成模型](../app/models/custom_integration.rb) - 数据库模型和提示词方法

### 示例代码
- [Ruby客户端示例](#1-ruby客户端示例) - 完整的Ruby实现
- [Python客户端示例](#2-python客户端示例) - 完整的Python实现
- [JavaScript客户端示例](#3-javascriptnodejs客户端示例) - 完整的Node.js实现

---

*文档更新时间: 2026-03-16*
*版本: 1.0.0*

通过遵循本指南，客户端开发者可以正确实现自定义集成函数的同步和使用功能，确保AI生成的脚本能够在客户端本地正确调用用户定义的自定义函数。