基于Odoo 19集成主流开放平台API实现盈利的完整模块开发方案:
一、项目架构设计
1.1 整体架构
text
┌─────────────────────────────────────────────────────────────┐ │ Odoo 19 API集成平台 │ ├─────────────────────────────────────────────────────────────┤ │ 核心模块 │ 物流模块 │ 地图模块 │ 支付模块 │ │ - API管理 │ - 快递100 │ - 高德地图 │ - 支付宝 │ │ - 计费系统 │ - 菜鸟驿站 │ - 百度地图 │ - 微信支付 │ │ - 监控分析 │ - 顺丰 │ - 腾讯地图 │ - 银联 │ │ - 安全认证 │ - 京东物流 │ │ │ └─────────────────────────────────────────────────────────────┘
二、核心基础模块
2.1 API通用基础模块 (models/api_base.py)
python
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
import requests
import json
import hashlib
import hmac
import time
from datetime import datetime, timedelta
import logging
_logger = logging.getLogger(__name__)
class APIBase(models.AbstractModel):
_name = 'api.base'
_description = 'API基础类'
def _make_request(self, url, method='GET', params=None, data=None, headers=None, timeout=30):
"""统一API请求方法"""
try:
headers = headers or {}
headers.update({
'User-Agent': 'Odoo-API-Platform/1.0',
'Content-Type': 'application/json'
})
start_time = time.time()
response = requests.request(
method=method,
url=url,
params=params,
json=data,
headers=headers,
timeout=timeout
)
execution_time = time.time() - start_time
# 记录API调用日志
self._log_api_call(url, method, params, data, response, execution_time)
if response.status_code == 200:
return response.json()
else:
_logger.error(f"API请求失败: {response.status_code} - {response.text}")
return None
except requests.exceptions.Timeout:
_logger.error(f"API请求超时: {url}")
return None
except Exception as e:
_logger.error(f"API请求异常: {str(e)}")
return None
def _log_api_call(self, url, method, params, data, response, execution_time):
"""记录API调用日志"""
log_vals = {
'name': f"{method} {url}",
'api_url': url,
'http_method': method,
'request_params': json.dumps(params, ensure_ascii=False) if params else '',
'request_data': json.dumps(data, ensure_ascii=False) if data else '',
'response_data': response.text if response else '',
'response_code': response.status_code if response else 0,
'execution_time': execution_time,
'call_date': datetime.now(),
}
self.env['api.call.log'].create(log_vals)
def _calculate_signature(self, secret, data):
"""计算签名"""
sign_str = '&'.join([f'{k}={v}' for k, v in sorted(data.items())])
return hmac.new(
secret.encode('utf-8'),
sign_str.encode('utf-8'),
hashlib.sha256
).hexdigest()
2.2 API配置管理 (models/api_config.py)
python
class APIConfig(models.Model):
_name = 'api.config'
_description = 'API配置管理'
name = fields.Char(string='配置名称', required=True)
provider = fields.Selection([
('kuaidi100', '快递100'),
('cainiao', '菜鸟驿站'),
('amap', '高德地图'),
('baidu_map', '百度地图'),
('alipay', '支付宝'),
('wechat_pay', '微信支付'),
('sf_express', '顺丰速运'),
('jd_express', '京东物流'),
], string='服务商', required=True)
# API认证信息
app_key = fields.Char(string='App Key')
app_secret = fields.Char(string='App Secret')
access_token = fields.Char(string='Access Token')
token_expires = fields.Datetime(string='Token过期时间')
# 服务配置
api_base_url = fields.Char(string='API基础URL')
is_active = fields.Boolean(string='是否激活', default=True)
rate_limit = fields.Integer(string='频率限制(次/分钟)', default=100)
# 计费配置
cost_per_call = fields.Float(string='每次调用成本')
sale_price_per_call = fields.Float(string='每次调用售价')
def _refresh_token_if_needed(self):
"""刷新Token如果过期"""
for config in self:
if config.token_expires and config.token_expires < datetime.now():
config._refresh_access_token()
def _refresh_access_token(self):
"""刷新访问令牌"""
# 根据不同服务商实现Token刷新逻辑
if self.provider == 'amap':
# 高德地图Token刷新
pass
elif self.provider == 'kuaidi100':
# 快递100 Token刷新
pass
def test_connection(self):
"""测试API连接"""
try:
if self.provider == 'kuaidi100':
result = self.env['kuaidi100.api'].test_connection()
elif self.provider == 'amap':
result = self.env['amap.api'].test_connection()
# ... 其他服务商
if result:
raise UserError(_("连接测试成功!"))
else:
raise UserError(_("连接测试失败!"))
except Exception as e:
raise UserError(_("测试连接时出错: %s") % str(e))
三、物流快递模块
3.1 快递100集成 (models/kuaidi100.py)
python
class Kuaidi100API(models.Model):
_name = 'kuaidi100.api'
_inherit = 'api.base'
_description = '快递100 API集成'
def query_delivery(self, tracking_number, carrier_code=None):
"""查询快递物流信息"""
config = self.env['api.config'].search([
('provider', '=', 'kuaidi100'),
('is_active', '=', True)
], limit=1)
if not config:
raise UserError(_("未配置快递100 API"))
params = {
'com': carrier_code, # 快递公司代码
'num': tracking_number,
'resultv2': 1, # 返回详细物流信息
'show': 0,
'order': 'desc'
}
# 生成签名
sign = self._calculate_signature(config.app_secret, params)
params['sign'] = sign
params['key'] = config.app_key
url = "https://poll.kuaidi100.com/poll/query.do"
result = self._make_request(url, 'POST', data=params)
if result and result.get('status') == '200':
return self._parse_delivery_result(result)
else:
_logger.error(f"快递查询失败: {result}")
return None
def compare_express_prices(self, parcel_info):
"""比较不同快递价格"""
# parcel_info: {weight, volume, from_city, to_city, service_type}
carriers = ['sf', 'sto', 'yto', 'zto', 'yd', 'ht']
prices = {}
for carrier in carriers:
price = self._get_carrier_price(carrier, parcel_info)
if price:
prices[carrier] = price
# 按价格排序并返回最优选择
sorted_prices = sorted(prices.items(), key=lambda x: x[1]['total_price'])
return {
'best_choice': sorted_prices[0] if sorted_prices else None,
'all_prices': sorted_prices,
'comparison_time': datetime.now()
}
def _get_carrier_price(self, carrier, parcel_info):
"""获取指定快递公司价格"""
# 实现各快递公司价格查询逻辑
pass
3.2 菜鸟驿站集成 (models/cainiao.py)
python
class CainiaoAPI(models.Model):
_name = 'cainiao.api'
_inherit = 'api.base'
_description = '菜鸟驿站API集成'
def create_delivery_order(self, order_vals):
"""创建代收代寄订单"""
config = self.env['api.config'].search([
('provider', '=', 'cainiao'),
('is_active', '=', True)
], limit=1)
data = {
'trade_order_id': order_vals['order_number'],
'sender': order_vals['sender'],
'receiver': order_vals['receiver'],
'package_info': order_vals['package'],
'service_type': order_vals.get('service_type', '1')
}
headers = {
'Authorization': f'Bearer {config.access_token}',
'Content-Type': 'application/json'
}
url = f"{config.api_base_url}/api/v1/order/create"
result = self._make_request(url, 'POST', data=data, headers=headers)
if result and result.get('success'):
return result['data']
else:
_logger.error(f"创建菜鸟订单失败: {result}")
return None
def query_nearest_stations(self, location, radius=5000):
"""查询附近菜鸟驿站"""
config = self.env['api.config'].search([
('provider', '=', 'cainiao'),
('is_active', '=', True)
], limit=1)
params = {
'lat': location['latitude'],
'lng': location['longitude'],
'radius': radius
}
headers = {'Authorization': f'Bearer {config.access_token}'}
url = f"{config.api_base_url}/api/v1/station/nearby"
result = self._make_request(url, 'GET', params=params, headers=headers)
if result and result.get('success'):
return self._parse_stations_result(result['data'])
return []
四、地图服务模块
4.1 高德地图集成 (models/amap.py)
python
class AMapAPI(models.Model):
_name = 'amap.api'
_inherit = 'api.base'
_description = '高德地图API集成'
def geocode_address(self, address, city=None):
"""地址解析(地址转坐标)"""
config = self.env['api.config'].search([
('provider', '=', 'amap'),
('is_active', '=', True)
], limit=1)
params = {
'key': config.app_key,
'address': address,
'output': 'JSON'
}
if city:
params['city'] = city
url = "https://restapi.amap.com/v3/geocode/geo"
result = self._make_request(url, 'GET', params=params)
if result and result.get('status') == '1' and result.get('geocodes'):
location = result['geocodes'][0]['location'].split(',')
return {
'longitude': float(location[0]),
'latitude': float(location[1]),
'formatted_address': result['geocodes'][0]['formatted_address']
}
return None
def calculate_distance(self, origin, destination):
"""计算路径距离和时长"""
config = self.env['api.config'].search([
('provider', '=', 'amap'),
('is_active', '=', True)
], limit=1)
params = {
'key': config.app_key,
'origins': f"{origin['longitude']},{origin['latitude']}",
'destination': f"{destination['longitude']},{destination['latitude']}",
'output': 'JSON'
}
url = "https://restapi.amap.com/v3/distance"
result = self._make_request(url, 'GET', params=params)
if result and result.get('status') == '1' and result.get('results'):
distance = int(result['results'][0]['distance']) # 米
duration = int(result['results'][0]['duration']) # 秒
return {
'distance': distance,
'duration': duration,
'distance_km': round(distance / 1000, 2),
'duration_minutes': round(duration / 60, 1)
}
return None
def get_route_planning(self, origin, destination, strategy=0):
"""路径规划"""
config = self.env['api.config'].search([
('provider', '=', 'amap'),
('is_active', '=', True)
], limit=1)
params = {
'key': config.app_key,
'origin': f"{origin['longitude']},{origin['latitude']}",
'destination': f"{destination['longitude']},{destination['latitude']}",
'strategy': strategy, # 0-速度最快,1-费用最少,2-距离最短,3-不走高速
'output': 'JSON'
}
url = "https://restapi.amap.com/v3/direction/driving"
result = self._make_request(url, 'GET', params=params)
if result and result.get('status') == '1':
return self._parse_route_result(result['route'])
return None
五、支付模块
5.1 支付宝集成 (models/alipay.py)
python
class AlipayAPI(models.Model):
_name = 'alipay.api'
_inherit = 'api.base'
_description = '支付宝API集成'
def create_payment(self, order_vals):
"""创建支付订单"""
config = self.env['api.config'].search([
('provider', '=', 'alipay'),
('is_active', '=', True)
], limit=1)
biz_content = {
'out_trade_no': order_vals['order_number'],
'total_amount': order_vals['amount'],
'subject': order_vals['subject'],
'body': order_vals.get('body', ''),
'timeout_express': '30m'
}
data = {
'app_id': config.app_key,
'method': 'alipay.trade.app.pay',
'charset': 'utf-8',
'sign_type': 'RSA2',
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'version': '1.0',
'biz_content': json.dumps(biz_content, ensure_ascii=False)
}
# 生成签名
data['sign'] = self._generate_alipay_signature(data, config.app_secret)
return {
'payment_url': f"{config.api_base_url}?{self._build_query_string(data)}",
'order_number': order_vals['order_number']
}
def _generate_alipay_signature(self, data, private_key):
"""生成支付宝签名"""
# 实现支付宝RSA2签名逻辑
pass
def verify_payment(self, order_number):
"""验证支付状态"""
config = self.env['api.config'].search([
('provider', '=', 'alipay'),
('is_active', '=', True)
], limit=1)
biz_content = {'out_trade_no': order_number}
data = {
'app_id': config.app_key,
'method': 'alipay.trade.query',
'charset': 'utf-8',
'sign_type': 'RSA2',
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'version': '1.0',
'biz_content': json.dumps(biz_content, ensure_ascii=False)
}
data['sign'] = self._generate_alipay_signature(data, config.app_secret)
url = f"{config.api_base_url}?{self._build_query_string(data)}"
result = self._make_request(url, 'GET')
if result and result.get('alipay_trade_query_response'):
response = result['alipay_trade_query_response']
if response.get('code') == '10000':
return {
'status': response['trade_status'],
'amount': float(response['total_amount']),
'paid_time': response.get('send_pay_date')
}
return None
六、盈利计费模块
6.1 服务定价管理 (models/pricing.py)
python
class ServicePricing(models.Model):
_name = 'service.pricing'
_description = '服务定价管理'
name = fields.Char(string='服务名称', required=True)
service_type = fields.Selection([
('express_query', '快递查询'),
('price_compare', '价格对比'),
('delivery_create', '创建寄件'),
('geocode', '地址解析'),
('distance_calc', '距离计算'),
('route_plan', '路径规划'),
('payment', '支付服务'),
], string='服务类型', required=True)
base_cost = fields.Float(string='基础成本', digits=(10, 4))
sale_price = fields.Float(string='销售价格', digits=(10, 2))
profit_margin = fields.Float(string='毛利率%', compute='_compute_profit_margin')
# 套餐定价
is_package = fields.Boolean(string='是否套餐')
package_calls = fields.Integer(string='包含调用次数')
package_price = fields.Float(string='套餐价格')
extra_call_price = fields.Float(string='超额调用单价')
@api.depends('base_cost', 'sale_price')
def _compute_profit_margin(self):
for record in self:
if record.sale_price > 0:
record.profit_margin = ((record.sale_price - record.base_cost) / record.sale_price) * 100
else:
record.profit_margin = 0.0
class APIBilling(models.Model):
_name = 'api.billing'
_description = 'API计费记录'
partner_id = fields.Many2one('res.partner', string='客户')
service_type = fields.Selection(related='service_pricing_id.service_type')
service_pricing_id = fields.Many2one('service.pricing', string='服务')
call_count = fields.Integer(string='调用次数')
total_amount = fields.Float(string='总金额', compute='_compute_total_amount')
billing_date = fields.Date(string='计费日期', default=fields.Date.today)
state = fields.Selection([
('draft', '待结算'),
('billed', '已出账'),
('paid', '已支付')
], string='状态', default='draft')
@api.depends('call_count', 'service_pricing_id')
def _compute_total_amount(self):
for record in self:
if record.service_pricing_id:
if record.service_pricing_id.is_package:
base_price = record.service_pricing_id.package_price
extra_calls = max(0, record.call_count - record.service_pricing_id.package_calls)
extra_cost = extra_calls * record.service_pricing_id.extra_call_price
record.total_amount = base_price + extra_cost
else:
record.total_amount = record.call_count * record.service_pricing_id.sale_price
def generate_invoice(self):
"""生成客户发票"""
for billing in self:
if billing.state == 'draft' and billing.total_amount > 0:
# 创建Odoo标准发票
invoice_vals = {
'partner_id': billing.partner_id.id,
'invoice_date': fields.Date.today(),
'move_type': 'out_invoice',
'invoice_line_ids': [(0, 0, {
'name': f"{billing.service_pricing_id.name} - API服务费",
'quantity': billing.call_count,
'price_unit': billing.service_pricing_id.sale_price,
})]
}
invoice = self.env['account.move'].create(invoice_vals)
billing.write({'state': 'billed', 'invoice_id': invoice.id})
七、视图和菜单配置
7.1 主视图文件 (views/main_views.xml)
xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- API配置视图 -->
<record id="view_api_config_tree" model="ir.ui.view">
<field name="name">api.config.tree</field>
<field name="model">api.config</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="provider"/>
<field name="is_active"/>
<field name="rate_limit"/>
<field name="cost_per_call"/>
<field name="sale_price_per_call"/>
</tree>
</field>
</record>
<record id="view_api_config_form" model="ir.ui.view">
<field name="name">api.config.form</field>
<field name="model">api.config</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="name"/>
<field name="provider"/>
<field name="is_active"/>
<field name="rate_limit"/>
</group>
<group>
<field name="app_key"/>
<field name="app_secret" password="True"/>
<field name="api_base_url"/>
</group>
</group>
<group string="成本定价">
<field name="cost_per_call"/>
<field name="sale_price_per_call"/>
</group>
</sheet>
<div class="oe_right">
<button name="test_connection" string="测试连接" type="object" class="btn-primary"/>
</div>
</form>
</field>
</record>
<!-- 快递查询视图 -->
<record id="view_express_query_form" model="ir.ui.view">
<field name="name">express.query.form</field>
<field name="model">express.delivery</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="tracking_number"/>
<field name="carrier_code"/>
<field name="status"/>
<field name="latest_update"/>
</group>
<field name="tracking_info" widget="html"/>
</sheet>
<footer>
<button name="query_delivery" string="查询物流" type="object" class="btn-primary"/>
<button name="compare_price" string="比价寄件" type="object" class="btn-secondary"/>
</footer>
</form>
</field>
</record>
<!-- 菜单结构 -->
<menuitge id="menu_api_platform_root" name="API平台" sequence="10"/>
<menuitem id="menu_api_config" name="API配置"
parent="menu_api_platform_root" action="action_api_config"/>
<menuitem id="menu_express_services" name="物流服务"
parent="menu_api_platform_root"/>
<menuitem id="menu_express_query" name="快递查询"
parent="menu_express_services" action="action_express_query"/>
<menuitem id="menu_price_compare" name="快递比价"
parent="menu_express_services" action="action_price_compare"/>
<menuitem id="menu_map_services" name="地图服务"
parent="menu_api_platform_root"/>
<menuitem id="menu_billing" name="计费管理"
parent="menu_api_platform_root" action="action_api_billing"/>
</data>
</odoo>
八、盈利分析看板
8.1 数据分析模型 (models/analytics.py)
python
class APIAnalytics(models.Model):
_name = 'api.analytics'
_description = 'API盈利分析'
def get_profit_analysis(self, start_date, end_date):
"""获取盈利分析数据"""
# 收入分析
billing_data = self.env['api.billing'].search([
('billing_date', '>=', start_date),
('billing_date', '<=', end_date),
('state', '=', 'paid')
])
total_revenue = sum(billing_data.mapped('total_amount'))
# 成本分析
cost_data = self._calculate_service_costs(start_date, end_date)
total_cost = sum(cost_data.values())
# 服务使用统计
service_usage = self._get_service_usage(start_date, end_date)
return {
'total_revenue': total_revenue,
'total_cost': total_cost,
'total_profit': total_revenue - total_cost,
'profit_margin': ((total_revenue - total_cost) / total_revenue * 100) if total_revenue > 0 else 0,
'top_services': sorted(service_usage.items(), key=lambda x: x[1]['revenue'], reverse=True)[:5],
'daily_revenue': self._get_daily_revenue(start_date, end_date)
}
def _calculate_service_costs(self, start_date, end_date):
"""计算服务成本"""
costs = {}
call_logs = self.env['api.call.log'].search([
('call_date', '>=', start_date),
('call_date', '<=', end_date)
])
for log in call_logs:
service_type = self._classify_service_type(log.api_url)
config = self.env['api.config'].search([('api_base_url', 'ilike', service_type)], limit=1)
if config:
costs[service_type] = costs.get(service_type, 0) + config.cost_per_call
return costs
九、模块配置文件
9.1 __manifest__.py
python
{
'name': 'Odoo API开放平台',
'version': '19.0.1.0',
'category': 'API/Services',
'summary': '集成主流开放平台API实现盈利的完整解决方案',
'description': """
完整的API开放平台模块
===================
集成功能:
- 快递100: 物流查询、快递比价
- 菜鸟驿站: 代收代寄、驿站查询
- 高德地图: 地址解析、路径规划、距离计算
- 支付宝: 支付接口、交易查询
- 微信支付: 支付接口
- 顺丰速运: 物流服务
- 京东物流: 物流服务
盈利模式:
- API调用计费
- 服务差价盈利
- 套餐订阅
- 增值服务
""",
'author': 'Your Company',
'website': 'https://www.yourcompany.com',
'depends': [
'base', 'web', 'sale', 'account', 'payment', 'contacts'
],
'data': [
'security/ir.model.access.csv',
'data/api_services_data.xml',
'data/pricing_plans_data.xml',
'views/api_config_views.xml',
'views/express_views.xml',
'views/map_services_views.xml',
'views/payment_views.xml',
'views/pricing_views.xml',
'views/billing_views.xml',
'views/analytics_dashboard_views.xml',
'views/menu_views.xml',
],
'demo': [
'demo/api_config_demo.xml',
],
'application': True,
'installable': True,
'auto_install': False,
'license': 'LGPL-3',
'assets': {
'web.assets_backend': [
'api_platform/static/src/js/dashboard.js',
'api_platform/static/src/css/dashboard.css',
],
},
}
十、盈利模式总结
10.1 收入来源
-
API调用费: 按调用次数计费
-
服务差价: 采购价与销售价之间的差价
-
套餐订阅: 月度/年度套餐服务
-
增值服务: 定制开发、技术支持
-
交易佣金: 支付交易佣金
10.2 成本控制
-
批量采购: 与API提供商谈批量价格
-
缓存优化: 减少重复API调用
-
负载均衡: 多服务商切换降低成本
-
监控告警: 实时监控异常调用
这个完整的API集成平台模块可以立即投入开发和使用,为企业创造持续的技术盈利收入。
1400

被折叠的 条评论
为什么被折叠?



