本文目录导读:

我来全面介绍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);
}
}
最佳实践建议
- 连接管理:使用连接池,避免频繁创建连接
- 错误处理:实现重试机制和降级策略
- 安全考虑:
- 使用HTTPS传输
- 限制API访问IP
- 使用强密码和密钥
- 性能优化:
- 批量操作(bulk create/write)
- 数据缓存
- 异步处理
- 监控告警:
- 监控API响应时间
- 设置错误告警
- 记录同步状态
需要我详细说明某个具体集成方案吗?