PHP项目Odoo与PHP集成

wen PHP项目 2

本文目录导读:

PHP项目Odoo与PHP集成

  1. Odoo REST API 集成
  2. 使用第三方库
  3. REST API 扩展(Odoo 16+)
  4. 数据同步方案
  5. Webhook集成
  6. 批量操作和性能优化
  7. 错误处理和日志
  8. 最佳实践建议

我来全面介绍PHP项目与Odoo集成的各种方案。

Odoo REST API 集成

使用官方XML-RPC/JSON-RPC

// XML-RPC 连接示例
class OdooClient {
    private $url;
    private $db;
    private $username;
    private $password;
    private $uid;
    public function __construct($url, $db, $username, $password) {
        $this->url = $url;
        $this->db = $db;
        $this->username = $username;
        $this->password = $password;
        $this->authenticate();
    }
    private function authenticate() {
        $models = new \PhpXmlRpc\Client($this->url . '/xmlrpc/2/common');
        $this->uid = $models->call('authenticate', [
            $this->db,
            $this->username,
            $this->password,
            []
        ]);
    }
    public function searchAndRead($model, $domain, $fields = ['id', 'name']) {
        $models = new \PhpXmlRpc\Client($this->url . '/xmlrpc/2/object');
        return $models->call('execute_kw', [
            $this->db,
            $this->uid,
            $this->password,
            $model,
            'search_read',
            [$domain],
            ['fields' => $fields, 'limit' => 100]
        ]);
    }
    public function create($model, $data) {
        $models = new \PhpXmlRpc\Client($this->url . '/xmlrpc/2/object');
        return $models->call('execute_kw', [
            $this->db,
            $this->uid,
            $this->password,
            $model,
            'create',
            [$data]
        ]);
    }
    public function update($model, $id, $data) {
        $models = new \PhpXmlRpc\Client($this->url . '/xmlrpc/2/object');
        return $models->call('execute_kw', [
            $this->db,
            $this->uid,
            $this->password,
            $model,
            'write',
            [[$id], $data]
        ]);
    }
    public function delete($model, $id) {
        $models = new \PhpXmlRpc\Client($this->url . '/xmlrpc/2/object');
        return $models->call('execute_kw', [
            $this->db,
            $this->uid,
            $this->password,
            $model,
            'unlink',
            [[$id]]
        ]);
    }
}
// 使用示例
$odoo = new OdooClient(
    'http://localhost:8069',
    'mydb',
    'admin',
    'password'
);
// 查询客户
$customers = $odoo->searchAndRead('res.partner', 
    [['customer_rank', '>', 0]],
    ['id', 'name', 'email', 'phone']
);
// 创建订单
$orderId = $odoo->create('sale.order', [
    'partner_id' => $customerId,
    'order_line' => [[
        0, 0, [
            'product_id' => $productId,
            'product_uom_qty' => 10,
            'price_unit' => 100
        ]
    ]]
]);

使用第三方库

rikudou/odoo-api

composer require rikudou/odoo-api
// 使用示例
use Rikudou\Odoo\OdooConnection;
use Rikudou\Odoo\Odoo;
$connection = new OdooConnection(
    'localhost', 
    8069, 
    'mydb', 
    'admin', 
    'password'
);
$odoo = new Odoo($connection);
// 获取合作伙伴
$partners = $odoo->getModel('res.partner')
    ->fields(['id', 'name', 'email'])
    ->where('customer_rank', '>', 0)
    ->limit(50)
    ->execute();

Baxtian/odoo-xmlrpc

composer require baxtian/odoo-xmlrpc
use Baxtian\OdooXmlRpc\Client;
$client = new Client('http://localhost:8069', 'mydb');
$client->authenticate('admin', 'password');
// 查询产品
$products = $client->searchRead(
    'product.template',
    [['sale_ok', '=', true]],
    ['name', 'list_price', 'default_code']
);

REST API 扩展(Odoo 16+)

安装Odoo REST API模块

# 安装社区版REST API模块
pip install odoo-rest-api
# 或使用官方Web API
# Odoo 16 内置了JSON-RPC

PHP调用示例

class OdooRestClient {
    private $baseUrl;
    private $token;
    public function __construct($baseUrl, $apiKey) {
        $this->baseUrl = rtrim($baseUrl, '/');
        $this->token = $apiKey;
    }
    public function get($endpoint, $params = []) {
        $url = $this->baseUrl . '/api/' . ltrim($endpoint, '/');
        if (!empty($params)) {
            $url .= '?' . http_build_query($params);
        }
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer ' . $this->token,
                'Content-Type: application/json'
            ]
        ]);
        $response = curl_exec($ch);
        curl_close($ch);
        return json_decode($response, true);
    }
    public function post($endpoint, $data = []) {
        $url = $this->baseUrl . '/api/' . ltrim($endpoint, '/');
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => json_encode($data),
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer ' . $this->token,
                'Content-Type: application/json'
            ]
        ]);
        $response = curl_exec($ch);
        curl_close($ch);
        return json_decode($response, true);
    }
}
// 使用REST API
$client = new OdooRestClient('http://localhost:8069', 'your-api-key');
$orders = $client->get('sale.order', ['limit' => 20]);

数据同步方案

双向同步实现

class OdooSyncManager {
    private $odoo;
    private $localDB;
    public function __construct($odooClient, $pdo) {
        $this->odoo = $odooClient;
        $this->localDB = $pdo;
    }
    // 从Odoo同步产品到本地
    public function syncProductsFromOdoo() {
        $lastSync = $this->getLastSyncTime('products');
        // 获取Odoo中更新的产品
        $domain = [['write_date', '>', $lastSync]];
        $products = $this->odoo->searchAndRead(
            'product.template',
            $domain,
            ['id', 'name', 'default_code', 'list_price', 'categ_id']
        );
        foreach ($products as $product) {
            $this->upsertLocalProduct($product);
        }
        $this->updateSyncTime('products');
        return count($products);
    }
    // 从本地同步订单到Odoo
    public function syncOrdersToOdoo() {
        $orders = $this->getPendingOrders();
        foreach ($orders as $order) {
            try {
                $odooOrderId = $this->odoo->create('sale.order', [
                    'partner_id' => $this->getOdooPartnerId($order['customer_id']),
                    'date_order' => $order['created_at'],
                    'order_line' => $this->buildOrderLines($order['items']),
                    'note' => $order['notes']
                ]);
                // 确认订单
                $this->odoo->executeMethod(
                    'sale.order',
                    'action_confirm',
                    [$odooOrderId]
                );
                $this->markOrderSynced($order['id'], $odooOrderId);
            } catch (Exception $e) {
                $this->logSyncError($order['id'], $e->getMessage());
            }
        }
    }
    private function upsertLocalProduct($odooProduct) {
        $stmt = $this->localDB->prepare(
            "INSERT INTO products (odoo_id, name, sku, price, category_id, updated_at)
             VALUES (:odoo_id, :name, :sku, :price, :category_id, NOW())
             ON DUPLICATE KEY UPDATE
             name = VALUES(name),
             sku = VALUES(sku),
             price = VALUES(price),
             category_id = VALUES(category_id),
             updated_at = NOW()"
        );
        return $stmt->execute([
            'odoo_id' => $odooProduct['id'],
            'name' => $odooProduct['name'],
            'sku' => $odooProduct['default_code'],
            'price' => $odooProduct['list_price'],
            'category_id' => $odooProduct['categ_id'][0] ?? null
        ]);
    }
}

Webhook集成

Odoo端配置

# odoo/custom_addons/php_integration/models/webhook.py
from odoo import models, api
class WebhookManager(models.Model):
    _name = 'webhook.manager'
    _description = 'Webhook Manager'
    def send_webhook(self, event, data):
        webhook_url = self.env['ir.config_parameter'].get_param(
            'php_webhook_url'
        )
        if webhook_url:
            import requests
            requests.post(webhook_url, json={
                'event': event,
                'timestamp': fields.Datetime.now(),
                'data': data
            })
    @api.model
    def create(self, vals):
        record = super().create(vals)
        if record._name == 'sale.order':
            self.send_webhook('sale.order.created', {
                'id': record.id,
                'name': record.name,
                'partner_id': record.partner_id.id
            })
        return record

PHP Webhook接收端

// webhook_handler.php
class OdooWebhookHandler {
    private $secretKey;
    public function __construct($secretKey) {
        $this->secretKey = $secretKey;
    }
    public function handle() {
        $payload = json_decode(file_get_contents('php://input'), true);
        if (!$this->validateSignature($payload)) {
            http_response_code(401);
            exit('Invalid signature');
        }
        switch ($payload['event']) {
            case 'sale.order.created':
                $this->handleOrderCreated($payload['data']);
                break;
            case 'product.updated':
                $this->handleProductUpdated($payload['data']);
                break;
            case 'stock.movement':
                $this->handleStockMovement($payload['data']);
                break;
        }
        http_response_code(200);
        echo json_encode(['status' => 'success']);
    }
    private function handleOrderCreated($data) {
        // 更新本地数据库
        $this->updateLocalOrder($data);
        // 通知仓库系统
        $this->notifyWarehouse($data);
        // 发送邮件通知
        $this->sendNotification($data);
    }
    private function validateSignature($payload) {
        $signature = $_SERVER['HTTP_X_ODOO_SIGNATURE'] ?? '';
        $expected = hash_hmac('sha256', json_encode($payload), $this->secretKey);
        return hash_equals($expected, $signature);
    }
}
$handler = new OdooWebhookHandler('your-secret-key');
$handler->handle();

批量操作和性能优化

class OdooBatchProcessor {
    private $odoo;
    private $batchSize = 100;
    public function batchCreate($model, $records) {
        $chunks = array_chunk($records, $this->batchSize);
        $results = [];
        foreach ($chunks as $chunk) {
            try {
                // 批量创建
                $ids = $this->odoo->executeMethod(
                    $model,
                    'create',
                    [$chunk]
                );
                $results = array_merge($results, $ids);
            } catch (Exception $e) {
                // 单个重试
                foreach ($chunk as $record) {
                    try {
                        $results[] = $this->odoo->create($model, $record);
                    } catch (Exception $e) {
                        $this->logError($record, $e);
                    }
                }
            }
        }
        return $results;
    }
    public function batchRead($model, $ids) {
        $chunks = array_chunk($ids, $this->batchSize);
        $results = [];
        foreach ($chunks as $chunk) {
            $records = $this->odoo->searchAndRead(
                $model,
                [['id', 'in', $chunk]],
                ['id', 'name', 'write_date']
            );
            $results = array_merge($results, $records);
        }
        return $results;
    }
    // 使用缓存
    public function getCachedData($model, $domain, $fields, $ttl = 3600) {
        $cacheKey = md5($model . serialize($domain) . serialize($fields));
        $cached = apcu_fetch($cacheKey);
        if ($cached !== false) {
            return $cached;
        }
        $data = $this->odoo->searchAndRead($model, $domain, $fields);
        apcu_store($cacheKey, $data, $ttl);
        return $data;
    }
}

错误处理和日志

class OdooLogger {
    private $logFile;
    private $context;
    public function __construct($logFile = 'odoo_integration.log') {
        $this->logFile = $logFile;
        $this->context = [
            'timestamp' => date('Y-m-d H:i:s'),
            'server' => $_SERVER['SERVER_NAME'] ?? 'cli',
            'user' => $_SESSION['user_id'] ?? 'system'
        ];
    }
    public function logApiCall($method, $params, $result, $duration) {
        $entry = [
            'type' => 'API_CALL',
            'method' => $method,
            'params' => $this->sanitizeParams($params),
            'result' => $result,
            'duration' => $duration,
            'success' => !empty($result),
            'context' => $this->context
        ];
        $this->writeEntry($entry);
    }
    public function logError($error, $additionalData = []) {
        $entry = [
            'type' => 'ERROR',
            'message' => $error->getMessage(),
            'trace' => $error->getTraceAsString(),
            'additional_data' => $additionalData,
            'context' => $this->context
        ];
        $this->writeEntry($entry);
        // 发送告警
        if ($error->getCode() >= 500) {
            $this->sendAlert($entry);
        }
    }
    private function sanitizeParams($params) {
        // 移除敏感信息
        if (isset($params['password'])) {
            $params['password'] = '***';
        }
        if (isset($params['api_key'])) {
            $params['api_key'] = '***';
        }
        return $params;
    }
    private function writeEntry($entry) {
        $line = json_encode($entry) . PHP_EOL;
        file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
    }
}

最佳实践建议

  1. 连接管理:使用连接池,避免频繁创建连接
  2. 错误处理:实现重试机制和降级策略
  3. 安全考虑
    • 使用HTTPS传输
    • 限制API访问IP
    • 使用强密码和密钥
  4. 性能优化
    • 批量操作(bulk create/write)
    • 数据缓存
    • 异步处理
  5. 监控告警
    • 监控API响应时间
    • 设置错误告警
    • 记录同步状态

需要我详细说明某个具体集成方案吗?

抱歉,评论功能暂时关闭!