1. 引言
Odoo作为一个功能强大的开源ERP和业务应用套件,其开放性和可扩展性是核心优势之一。接口(API)开发在Odoo生态中扮演着至关重要的角色,它使得Odoo能够与外部系统、第三方应用、移动端以及Web前端进行数据交换和功能集成。理解Odoo的接口开发机制,对于实现复杂的业务集成、构建定制化解决方案至关重要。本文档将深入探讨Odoo接口开发的实现原理,区分不同的接口类型,并提供实践指导。
2. Odoo接口开发原理与技术架构
Odoo的接口开发主要基于其稳健的Web框架和模型驱动的架构。其核心原理可以概括为以下几点:
2.1 客户端-服务器架构
Odoo采用标准的客户端-服务器(Client-Server)架构。客户端(通常是Web浏览器,也可以是其他应用程序)通过网络向Odoo服务器发送请求,服务器处理请求后返回响应。这种架构使得Odoo的功能可以通过网络被远程访问和调用。
2.2 RPC(远程过程调用)
Odoo原生支持RPC机制,允许外部应用程序像调用本地函数一样调用Odoo服务器上的方法。这是实现外部系统与Odoo交互的基础。Odoo主要支持两种RPC协议:XML-RPC和JSON-RPC。这两种协议都允许通过HTTP(S)传输结构化的请求和响应数据。
2.3 Web控制器(Controllers)
除了标准的RPC接口,Odoo还提供了一个强大的Web控制器层。开发者可以通过创建自定义控制器来定义HTTP路由(Routes),处理HTTP请求(GET, POST, PUT, DELETE等),并返回各种格式的响应(HTML, JSON, 文件等)。这使得开发者能够构建灵活的、符合RESTful风格的API,或者实现自定义的Web页面和交互逻辑。
2.4 模型驱动
无论是通过RPC还是控制器,对Odoo数据的操作最终都会落实到Odoo的模型(Models)层。Odoo的模型定义了数据结构和业务逻辑。接口调用通常会触发模型方法的执行,如create()
, search_read()
, write()
, unlink()
等,从而实现对数据的增删改查。
3. Odoo接口类型详解
Odoo提供了多种接口开发方式,以适应不同的集成需求和技术偏好。主要可以分为以下几类:
3.1 XML-RPC接口
XML-RPC是一种基于XML的RPC协议,允许通过HTTP发送方法调用请求。Odoo通过/xmlrpc/2/common
(用于认证和数据库信息)和/xmlrpc/2/object
(用于调用模型方法)两个端点提供XML-RPC服务。
原理: 客户端将方法调用信息(方法名、参数)编码为XML格式,通过HTTP POST请求发送到Odoo服务器的指定端点。服务器解析XML请求,执行相应的方法,并将结果编码为XML格式返回给客户端。
适用场景: 主要用于需要与多种异构系统(特别是那些原生支持XML-RPC的系统)进行集成的场景。虽然相对古老,但仍然是一种稳定可靠的集成方式。
实现示例 (Python):
import xmlrpc.client
url = "http://localhost:8069" # Odoo服务器地址
db = "your_database_name" # Odoo数据库名
username = "admin" # Odoo用户名
password = "your_password" # Odoo密码
# 1. 认证 获取用户ID (uid)
try:
common = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/common')
uid = common.authenticate(db, username, password, {})
if not uid:
print("认证失败!")
exit()
print(f"认证成功, UID: {uid}")
except Exception as e:
print(f"连接或认证错误: {e}")
exit()
# 2. 调用模型方法 (以查询合作伙伴为例)
models = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/object')
try:
partner_ids = models.execute_kw(db, uid, password,
'res.partner', # 模型名
'search', # 方法名
[[['is_company', '=', True]]]) # 参数 (domain)
print(f"找到的公司数量: {len(partner_ids)}")
if partner_ids:
partners_data = models.execute_kw(db, uid, password,
'res.partner',
'read', # 方法名
[partner_ids], # 参数 (ids)
{'fields': ['name', 'email', 'phone']}) # 可选参数 (指定字段)
print("合作伙伴信息:")
for partner in partners_data:
print(partner)
except xmlrpc.client.Fault as error:
print(f"XML-RPC 调用错误: {error}")
except Exception as e:
print(f"执行模型方法时出错: {e}")
优缺点:
- 优点:标准化协议,跨语言支持良好,稳定。
- 缺点:基于XML,数据传输相对冗余;相比JSON-RPC和REST,性能可能稍差。
3.2 JSON-RPC接口
JSON-RPC是一种基于JSON的轻量级RPC协议。Odoo通过/jsonrpc
端点提供JSON-RPC服务。
原理: 客户端将方法调用信息编码为JSON格式,通过HTTP POST请求发送到Odoo服务器的/jsonrpc
端点。请求体是一个包含jsonrpc
, method
, params
, id
等字段的JSON对象。服务器解析JSON请求,执行相应的方法,并将结果编码为JSON格式返回。
适用场景: 广泛用于Web前端(JavaScript可以直接处理JSON)、移动应用以及需要轻量级数据交换的现代系统集成。
实现示例 (请求格式):
POST /jsonrpc HTTP/1.1
Host: localhost:8069
Content-Type: application/json
{
"jsonrpc": "2.0",
"method": "call", // 固定为 'call'
"params": {
"service": "object", // 'common' 用于认证, 'object' 用于模型方法
"method": "execute_kw", // 调用模型方法的标准方法
"args": [
"your_database_name", // 数据库名
1, // 用户UID (认证后获得)
"your_password", // 用户密码
"res.partner", // 目标模型
"search_read", // 要调用的模型方法
[[["is_company", "=", true]]], // 方法的位置参数 (domain)
{"fields": ["name", "email"], "limit": 5} // 方法的关键字参数
]
},
"id": 1 // 请求ID,用于匹配响应
}
Python实现示例:
import requests
import json
url = "http://localhost:8069/jsonrpc"
db = "your_database_name"
username = "admin"
password = "your_password"
# 1. 认证获取UID
payload = {
"jsonrpc": "2.0",
"method": "call",
"params": {
"service": "common",
"method": "authenticate",
"args": [db, username, password, {}]
},
"id": 1
}
response = requests.post(url, data=json.dumps(payload), headers={"Content-Type": "application/json"})
result = response.json()
if "error" in result:
print(f"认证错误: {result['error']}")
exit()
uid = result["result"]
print(f"认证成功, UID: {uid}")
# 2. 调用模型方法
payload = {
"jsonrpc": "2.0",
"method": "call",
"params": {
"service": "object",
"method": "execute_kw",
"args": [
db, uid, password,
"res.partner",
"search_read",
[[["is_company", "=", True]]],
{"fields": ["name", "email", "phone"], "limit": 5}
]
},
"id": 2
}
response = requests.post(url, data=json.dumps(payload), headers={"Content-Type": "application/json"})
result = response.json()
if "error" in result:
print(f"调用错误: {result['error']}")
else:
print("合作伙伴信息:")
for partner in result["result"]:
print(partner)
优缺点:
- 优点:基于JSON,轻量、易于解析,性能较好,特别适合Web和移动端开发。
- 缺点:虽然是标准,但相比RESTful API在语义表达上可能不够直观。
3.3 自定义控制器 (RESTful API)
通过编写自定义控制器,开发者可以创建完全符合REST(Representational State Transfer)架构风格的API。RESTful API使用标准的HTTP方法(GET, POST, PUT, DELETE等)对资源进行操作,并通过URL路径标识资源。
原理: 开发者在Odoo模块的controllers
目录下创建Python文件,定义继承自odoo.http.Controller
的类。在类中使用@http.route()
装饰器来定义路由规则,包括URL路径、允许的HTTP方法、认证方式 (auth
)、响应类型 (type
)等。控制器方法内部可以使用request
对象访问请求信息(如参数、头信息)和Odoo环境 (request.env
) 来调用模型方法或执行其他业务逻辑,最后构造并返回HTTP响应。
适用场景: 构建现代Web应用、移动应用的后端API;需要对API结构、请求/响应格式、认证方式有完全控制权的场景;希望提供更直观、语义化的API接口。
实现示例 (controllers/main.py
):
from odoo import http
from odoo.http import request, Response
import json
class PartnerApiController(http.Controller):
# 获取合作伙伴列表 (JSON响应)
@http.route('/api/v1/partners', type='json', auth='user', methods=['GET'], csrf=False)
def get_partners_json(self, limit=10, offset=0, domain=None, **kwargs):
""" 获取合作伙伴列表 (需要用户认证) """
try:
# 考虑安全性,对传入的domain进行处理或限制
search_domain = [('customer_rank', '>', 0)] # 示例:只获取客户
if domain and isinstance(domain, list):
# 注意:直接使用外部传入的domain可能存在安全风险,需要校验
# search_domain.extend(domain)
pass # 实际应用中应谨慎处理外部domain
partners = request.env['res.partner'].search_read(
domain=search_domain,
fields=['id', 'name', 'email', 'phone'],
limit=int(limit),
offset=int(offset)
)
total = request.env['res.partner'].search_count(search_domain)
return {'status': 'success', 'total': total, 'data': partners}
except Exception as e:
# 记录错误日志
request.env.cr.rollback()
return {'status': 'error', 'message': str(e)}
# 获取单个合作伙伴详情 (HTTP响应, REST风格)
@http.route('/api/v1/partners/<int:partner_id>', type='http', auth='user', methods=['GET'], csrf=False)
def get_partner_http(self, partner_id, **kwargs):
""" 获取单个合作伙伴详情 (需要用户认证) """
partner = request.env['res.partner'].search_read(
domain=[('id', '=', partner_id)],
fields=['id', 'name', 'email', 'phone', 'street', 'city', 'country_id']
)
if not partner:
return Response(json.dumps({'status': 'error', 'message': 'Partner not found'}), status=404, content_type='application/json')
# 返回JSON格式的HTTP响应
return Response(json.dumps({'status': 'success', 'data': partner[0]}), content_type='application/json')
# 创建合作伙伴 (JSON响应)
@http.route('/api/v1/partners', type='json', auth='user', methods=['POST'], csrf=False)
def create_partner(self, **kwargs):
""" 创建新的合作伙伴 (需要用户认证) """
required_fields = ['name']
missing_fields = [f for f in required_fields if f not in kwargs]
if missing_fields:
return {'status': 'error', 'message': f'Missing required fields: {missing_fields}'}
try:
# 提取请求数据
partner_data = {
'name': kwargs.get('name'),
'email': kwargs.get('email'),
'phone': kwargs.get('phone'),
'street': kwargs.get('street'),
'city': kwargs.get('city'),
'customer_rank': kwargs.get('is_customer') and 1 or 0,
}
# 创建记录
partner = request.env['res.partner'].create(partner_data)
return {
'status': 'success',
'message': 'Partner created successfully',
'id': partner.id
}
except Exception as e:
request.env.cr.rollback()
return {'status': 'error', 'message': str(e)}
# 更新合作伙伴 (HTTP响应, REST风格)
@http.route('/api/v1/partners/<int:partner_id>', type='json', auth='user', methods=['PUT'], csrf=False)
def update_partner(self, partner_id, **kwargs):
""" 更新合作伙伴信息 (需要用户认证) """
partner = request.env['res.partner'].browse(partner_id)
if not partner.exists():
return {'status': 'error', 'message': 'Partner not found'}
try:
# 提取要更新的字段
update_data = {}
for field in ['name', 'email', 'phone', 'street', 'city']:
if field in kwargs:
update_data[field] = kwargs[field]
if 'is_customer' in kwargs:
update_data['customer_rank'] = kwargs['is_customer'] and 1 or 0
# 更新记录
partner.write(update_data)
return {
'status': 'success',
'message': 'Partner updated successfully'
}
except Exception as e:
request.env.cr.rollback()
return {'status': 'error', 'message': str(e)}
# 删除合作伙伴 (HTTP响应, REST风格)
@http.route('/api/v1/partners/<int:partner_id>', type='json', auth='user', methods=['DELETE'], csrf=False)
def delete_partner(self, partner_id, **kwargs):
""" 删除合作伙伴 (需要用户认证) """
partner = request.env['res.partner'].browse(partner_id)
if not partner.exists():
return {'status': 'error', 'message': 'Partner not found'}
try:
# 删除记录
partner.unlink()
return {
'status': 'success',
'message': 'Partner deleted successfully'
}
except Exception as e:
request.env.cr.rollback()
return {'status': 'error', 'message': str(e)}
优缺点:
- 优点:完全可定制,符合现代API设计标准,语义清晰,可以精确控制请求/响应格式和认证方式。
- 缺点:需要更多的代码编写,相比直接使用RPC接口,开发工作量较大。
4. 接口认证与安全
无论使用哪种接口类型,安全性都是一个关键考量。Odoo提供了多种认证和安全机制:
4.1 基本认证方式
- 用户名/密码认证:最基本的认证方式,适用于XML-RPC和JSON-RPC。
- Session认证:基于Cookie的会话认证,主要用于Web界面和控制器。
- API密钥认证:通过自定义控制器实现,更适合系统间集成。
- OAuth2认证:可以通过第三方模块实现,支持更复杂的授权流程。
4.2 控制器认证参数
在自定义控制器中,@http.route()
装饰器的auth
参数控制认证方式:
auth='none'
:无需认证,任何人都可以访问(适用于公开API)。auth='public'
:允许未登录用户访问,但会创建一个公共(匿名)用户会话。auth='user'
:需要登录用户才能访问(默认值)。auth='admin'
:仅管理员可以访问。
4.3 安全最佳实践
- CSRF保护:对于需要修改数据的请求,Odoo默认启用CSRF保护。如果是外部API调用,可以通过设置
csrf=False
来禁用。 - 数据验证:始终验证和清理输入数据,特别是来自外部的请求参数。
- 权限控制:利用Odoo的访问控制列表(ACL)和记录规则来限制数据访问。
- 使用sudo()谨慎:
sudo()
方法可以绕过权限检查,应谨慎使用,并确保在必要时恢复正常环境。 - API速率限制:考虑实现API调用速率限制,防止滥用。
- 日志记录:记录关键API调用,便于审计和故障排查。
4.4 JWT认证实现示例
JWT(JSON Web Token)是一种流行的API认证方式,特别适合无状态的RESTful API。以下是在Odoo中实现JWT认证的简化示例:
# 需要安装PyJWT库: pip install PyJWT
import jwt
import datetime
from odoo import http
from odoo.http import request, Response
import json
SECRET_KEY = "your-secret-key" # 实际应用中应存储在安全的配置中
class JWTAuthController(http.Controller):
@http.route('/api/auth/token', type='json', auth='none', methods=['POST'], csrf=False)
def get_token(self, **kwargs):
"""获取JWT令牌的接口"""
username = kwargs.get('username')
password = kwargs.get('password')
if not username or not password:
return {'status': 'error', 'message': 'Missing credentials'}
# 验证用户凭据
uid = request.env['res.users'].sudo().authenticate(request.env.cr.dbname, username, password, {})
if not uid:
return {'status': 'error', 'message': 'Invalid credentials'}
# 生成JWT令牌
payload = {
'uid': uid,
'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1), # 1天过期
'iat': datetime.datetime.utcnow(),
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
return {'status': 'success', 'token': token}
@staticmethod
def validate_token(token):
"""验证JWT令牌"""
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
return payload
except jwt.ExpiredSignatureError:
return None
except jwt.InvalidTokenError:
return None
# 使用JWT保护的API示例
class ProtectedApiController(http.Controller):
@http.route('/api/protected/data', type='http', auth='none', methods=['GET'], csrf=False)
def get_protected_data(self, **kwargs):
"""受JWT保护的API端点"""
# 从请求头获取令牌
auth_header = request.httprequest.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return Response(json.dumps({'status': 'error', 'message': 'No token provided'}),
status=401, content_type='application/json')
token = auth_header.split(' ')[1]
payload = JWTAuthController.validate_token(token)
if not payload:
return Response(json.dumps({'status': 'error', 'message': 'Invalid or expired token'}),
status=401, content_type='application/json')
# 使用令牌中的用户ID设置环境
uid = payload.get('uid')
try:
# 以特定用户身份执行操作
user = request.env['res.users'].sudo().browse(uid)
if not user.exists():
return Response(json.dumps({'status': 'error', 'message': 'User not found'}),
status=401, content_type='application/json')
# 切换到该用户的环境
env = request.env(user=uid)
# 执行受保护的操作
data = env['res.partner'].search_read(
domain=[('customer_rank', '>', 0)],
fields=['name', 'email'],
limit=5
)
return Response(json.dumps({'status': 'success', 'data': data}),
content_type='application/json')
except Exception as e:
return Response(json.dumps({'status': 'error', 'message': str(e)}),
status=500, content_type='application/json')
5. 接口的组织与最佳实践
5.1 模块结构
一个标准的Odoo API模块结构如下:
my_api_module/
├── __init__.py # 导入controllers和models
├── __manifest__.py # 模块清单
├── controllers/
│ ├── __init__.py # 导入控制器
│ ├── main.py # 主控制器
│ └── auth.py # 认证控制器
├── models/
│ ├── __init__.py # 导入模型
│ └── api_log.py # API日志模型
├── security/
│ └── ir.model.access.csv # 访问权限
└── static/
└── description/
└── icon.png # 模块图标
5.2 API版本控制
在URL路径中包含版本号是一种良好的实践,例如/api/v1/partners
。这样在API变更时,可以保持向后兼容性。
# v1版本API
@http.route('/api/v1/partners', ...)
def get_partners_v1(self, **kwargs):
# v1实现
pass
# v2版本API(新增功能或改变行为)
@http.route('/api/v2/partners', ...)
def get_partners_v2(self, **kwargs):
# v2实现
pass
5.3 响应格式标准化
保持一致的响应格式有助于客户端处理:
def standard_response(success=True, data=None, message=None, status_code=200):
"""生成标准化的API响应"""
response = {
'status': 'success' if success else 'error',
}
if data is not None:
response['data'] = data
if message:
response['message'] = message
if not success and status_code == 200:
status_code = 400 # 默认错误状态码
return Response(json.dumps(response), status=status_code, content_type='application/json')
5.4 API日志记录
记录API调用对于调试和审计非常有用:
def log_api_call(self, endpoint, request_data, response_data, success, user_id=None):
"""记录API调用"""
try:
log_data = {
'name': endpoint,
'request_data': json.dumps(request_data),
'response_data': json.dumps(response_data),
'success': success,
'user_id': user_id or request.env.uid,
'ip_address': request.httprequest.remote_addr,
}
request.env['api.log'].sudo().create(log_data)
except Exception as e:
_logger.error(f"Failed to log API call: {e}")
5.5 分页、过滤和排序
对于返回大量数据的API,应实现分页、过滤和排序功能:
@http.route('/api/v1/partners', type='http', auth='user', methods=['GET'], csrf=False)
def get_partners_paginated(self, **kwargs):
# 分页参数
limit = int(kwargs.get('limit', 10))
offset = int(kwargs.get('offset', 0))
# 过滤参数
filters = {}
for key in ['name', 'email', 'country_id']:
if key in kwargs:
filters[key] = kwargs[key]
# 构建domain
domain = []
for key, value in filters.items():
if key == 'name' or key == 'email':
domain.append((key, 'ilike', value))
else:
domain.append((key, '=', int(value)))
# 排序参数
order = kwargs.get('order', 'name asc')
# 查询数据
partners = request.env['res.partner'].search_read(
domain=domain,
fields=['name', 'email', 'phone', 'country_id'],
limit=limit,
offset=offset,
order=order
)
# 获取总记录数(用于分页)
total = request.env['res.partner'].search_count(domain)
# 构建响应
response = {
'status': 'success',
'data': partners,
'pagination': {
'total': total,
'limit': limit,
'offset': offset,
'pages': (total + limit - 1) // limit
}
}
return Response(json.dumps(response), content_type='application/json')
6. Odoo 18的接口增强
Odoo 18版本在接口开发方面引入了一些增强功能:
6.1 HTTP方法支持扩展
@http.route
装饰器现在支持更多HTTP方法的限定,包括PUT
、DELETE
、PATCH
等,使得开发RESTful API更加便捷。
@http.route('/api/resource', methods=['PATCH'], ...)
def update_resource_partial(self, **kwargs):
# 处理PATCH请求(部分更新)
pass
6.2 请求头处理增强
可以更方便地访问和处理HTTP请求头:
@http.route('/api/headers', type='http', auth='none', csrf=False)
def handle_headers(self, **kwargs):
# 获取请求头
headers = request.httprequest.headers
user_agent = headers.get('User-Agent')
content_type = headers.get('Content-Type')
# 设置响应头
response = Response(json.dumps({'status': 'success'}), content_type='application/json')
response.headers['X-Custom-Header'] = 'Custom Value'
return response
6.3 JSON请求处理改进
Odoo 18改进了对嵌套JSON数据的处理:
@http.route('/api/complex-data', type='json', auth='none', csrf=False)
def handle_complex_json(self, **kwargs):
# 直接访问嵌套的JSON数据
nested_data = kwargs.get('nested', {})
value = nested_data.get('deep', {}).get('value')
return {'received_value': value}
6.4 Webhook支持
使用auth='none'
可以轻松创建外部系统可访问的webhook端点:
@http.route('/webhook/payment-notification', type='json', auth='none', csrf=False)
def payment_webhook(self, **kwargs):
# 验证webhook签名
signature = request.httprequest.headers.get('X-Webhook-Signature')
if not self._validate_webhook_signature(signature, request.httprequest.data):
return {'status': 'error', 'message': 'Invalid signature'}
# 处理webhook数据
payment_data = kwargs
# ... 处理支付通知 ...
return {'status': 'success'}
7. 实际应用场景
7.1 与外部系统集成
7.1.1 ERP系统集成
# 在外部ERP系统中调用Odoo API
def sync_products_from_odoo():
# 使用XML-RPC连接Odoo
common = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/common')
uid = common.authenticate(ODOO_DB, ODOO_USER, ODOO_PASSWORD, {})
models = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/object')
# 获取产品数据
products = models.execute_kw(ODOO_DB, uid, ODOO_PASSWORD,
'product.product', 'search_read',
[[['sale_ok', '=', True]]],
{'fields': ['name', 'list_price', 'default_code', 'barcode']})
# 同步到本地数据库
for product in products:
update_local_product(product)
7.1.2 电子商务集成
# Odoo控制器处理来自电商平台的订单
@http.route('/api/v1/orders', type='json', auth='api_key', methods=['POST'], csrf=False)
def receive_ecommerce_order(self, **kwargs):
# 验证API密钥
api_key = request.httprequest.headers.get('X-API-Key')
if not self._validate_api_key(api_key):
return {'status': 'error', 'message': 'Invalid API key'}
# 创建销售订单
try:
order_data = kwargs.get('order', {})
customer_data = order_data.get('customer', {})
# 查找或创建客户
customer = self._find_or_create_customer(customer_data)
# 创建订单头
order_vals = {
'partner_id': customer.id,
'date_order': fields.Datetime.now(),
'external_id': order_data.get('external_id'),
'note': order_data.get('note'),
}
order = request.env['sale.order'].sudo().create(order_vals)
# 创建订单行
for line in order_data.get('lines', []):
product = request.env['product.product'].sudo().search([
('default_code', '=', line.get('sku'))
], limit=1)
if product:
line_vals = {
'order_id': order.id,
'product_id': product.id,
'product_uom_qty': line.get('quantity', 1.0),
'price_unit': line.get('price_unit'),
}
request.env['sale.order.line'].sudo().create(line_vals)
# 确认订单
if order_data.get('confirm', False):
order.action_confirm()
return {
'status': 'success',
'message': 'Order created successfully',
'order_id': order.id,
'name': order.name
}
except Exception as e:
request.env.cr.rollback()
_logger.error(f"Failed to create order: {e}")
return {'status': 'error', 'message': str(e)}
7.2 移动应用后端
# 为移动应用提供认证API
@http.route('/api/v1/mobile/login', type='json', auth='none', methods=['POST'], csrf=False)
def mobile_login(self, **kwargs):
username = kwargs.get('username')
password = kwargs.get('password')
if not username or not password:
return {'status': 'error', 'message': 'Missing credentials'}
try:
uid = request.env['res.users'].sudo().authenticate(request.env.cr.dbname, username, password, {})
if not uid:
return {'status': 'error', 'message': 'Invalid credentials'}
user = request.env['res.users'].sudo().browse(uid)
# 生成访问令牌
token_data = {
'uid': uid,
'name': user.name,
'exp': datetime.datetime.utcnow() + datetime.timedelta(days=30),
}
token = self._generate_token(token_data)
# 返回用户信息和令牌
return {
'status': 'success',
'token': token,
'user': {
'id': user.id,
'name': user.name,
'email': user.email,
'image': user.image_128 and user.image_128.decode('utf-8') or False,
}
}
except Exception as e:
_logger.error(f"Mobile login error: {e}")
return {'status': 'error', 'message': 'Login failed'}
7.3 第三方服务集成
# 集成支付网关回调
@http.route('/api/payment/callback', type='http', auth='none', methods=['POST'], csrf=False)
def payment_gateway_callback(self, **kwargs):
# 验证回调请求
signature = request.httprequest.headers.get('X-Signature')
if not self._verify_payment_signature(signature, kwargs):
return Response("Invalid signature", status=400)
# 处理支付结果
payment_reference = kwargs.get('reference')
status = kwargs.get('status')
amount = float(kwargs.get('amount', '0.0'))
try:
# 查找相关订单
order = request.env['sale.order'].sudo().search([
('payment_reference', '=', payment_reference)
], limit=1)
if not order:
return Response("Order not found", status=404)
# 更新订单状态
if status == 'paid':
# 创建支付记录
payment_vals = {
'amount': amount,
'payment_type': 'inbound',
'partner_type': 'customer',
'partner_id': order.partner_id.id,
'communication': f"Payment for {order.name}",
# 其他必要字段...
}
payment = request.env['account.payment'].sudo().create(payment_vals)
# 确认支付
payment.action_post()
# 更新订单
order.write({'payment_status': 'paid'})
# 如果订单未确认,则确认订单
if order.state == 'draft':
order.action_confirm()
return Response("Payment processed successfully", status=200)
else:
order.write({'payment_status': status})
return Response("Payment status updated", status=200)
except Exception as e:
request.env.cr.rollback()
_logger.error(f"Payment callback error: {e}")
return Response(f"Error processing payment: {e}", status=500)
8. 接口测试与调试
8.1 使用Postman测试
Postman是测试API的常用工具。以下是测试Odoo API的基本步骤:
设置环境变量:
odoo_url
: Odoo服务器URLdb_name
: 数据库名称username
: 用户名password
: 密码
创建认证请求:
- 对于XML-RPC,使用Pre-request Script转换请求
- 对于JSON-RPC,直接发送JSON请求
- 对于自定义API,根据认证方式设置
保存认证响应:
- 使用Tests脚本保存认证令牌或UID
创建API请求集合:
- 组织不同的API请求
- 使用保存的认证信息
8.2 日志调试
在Odoo服务器端启用调试日志:
import logging
_logger = logging.getLogger(__name__)
@http.route('/api/debug', ...)
def debug_api(self, **kwargs):
_logger.info("Request received: %s", kwargs)
_logger.debug("Request headers: %s", request.httprequest.headers)
# 处理请求...
_logger.info("Response sent: %s", response)
return response
8.3 使用OpenAPI/Swagger文档
通过第三方模块(如rest_api_documentation
),可以为Odoo API生成Swagger文档:
# 在控制器方法中添加OpenAPI文档注释
@http.route('/api/v1/partners', ...)
def get_partners(self, **kwargs):
"""
@api {get} /api/v1/partners 获取合作伙伴列表
@apiName GetPartners
@apiGroup Partner
@apiVersion 1.0.0
@apiParam {Number} [limit=10] 每页记录数
@apiParam {Number} [offset=0] 偏移量
@apiParam {String} [name] 按名称过滤
@apiSuccess {String} status 请求状态
@apiSuccess {Number} total 总记录数
@apiSuccess {Object[]} data 合作伙伴列表
@apiSuccess {Number} data.id 合作伙伴ID
@apiSuccess {String} data.name 合作伙伴名称
@apiSuccess {String} data.email 电子邮件
@apiError {String} status 错误状态
@apiError {String} message 错误消息
"""
# 实现代码...
9. 总结与最佳实践
9.1 选择合适的接口类型
- XML-RPC:适用于需要与多种异构系统集成,特别是那些原生支持XML-RPC的系统。
- JSON-RPC:适用于Web前端、移动应用以及现代系统集成,特别是当性能和数据量是考量因素时。
- 自定义控制器(RESTful API):适用于需要完全控制API结构、认证方式和响应格式的场景,特别是构建面向外部的API。
9.2 接口设计原则
- 一致性:保持URL结构、请求/响应格式的一致性。
- 版本控制:在URL中包含版本号,便于API演进。
- 资源导向:使用名词(而非动词)来表示资源,如
/partners
而非/getPartners
。 - HTTP方法语义:正确使用HTTP方法(GET, POST, PUT, DELETE)。
- 状态码使用:使用标准HTTP状态码表达请求结果。
- 分页与过滤:为返回大量数据的API实现分页和过滤机制。
- 错误处理:提供清晰的错误信息和状态码。
9.3 安全最佳实践
- 最小权限原则:API只应具有完成其任务所需的最小权限。
- 输入验证:始终验证和清理所有输入数据。
- HTTPS:始终使用HTTPS加密传输。
- 认证与授权:实施强大的认证机制,并确保适当的授权检查。
- API密钥轮换:定期轮换API密钥和令牌。
- 速率限制:实施API调用速率限制,防止滥用。
- 审计日志:记录关键API操作,便于审计和故障排查。
9.4 性能优化
- 字段选择:只请求和返回必要的字段。
- 批量操作:尽可能使用批量操作而非多次单独调用。
- 缓存:适当使用缓存减少数据库查询。
- 延迟加载:对于大型数据集,实现分页和延迟加载。
- 异步处理:对于耗时操作,考虑异步处理。
通过遵循这些原则和最佳实践,开发者可以创建安全、高效、易于使用的Odoo接口,满足各种集成和扩展需求。无论是构建内部系统集成,还是开发面向外部的API,Odoo的接口开发机制都提供了强大而灵活的支持。