Flutter对接Odoo后端:RPC重复调用合理性及端点获取咨询
问题描述
我此前有REST API开发经验,常用http或dio包实现GET、POST、PUT等方法集成。现在要开发一款基于Odoo后端的电商类Flutter应用,了解到odoo_rpc包可实现Flutter与Odoo的连接及认证,但不确定在电商APP中反复调用RPC是否为最佳实践。同时想知道如何获取类似HTTP形式的端点(例如domainname/api/authentication),附上相关代码如下:
try { final session = await client.authenticate('database_name', _username, _password); final res = await client.callKw({ 'model': 'res.partner', 'method': 'search_read', 'args': [], 'kwargs': { 'context': {'bin_size': true}, 'domain': [], 'fields': [ 'name', 'email'], 'limit': 80, }}); log('Installed modules: \n' + res.toString()); print('session$session'); final response = await client.checkSession(); print('response$response'); Future<void> printSessionResult() async { var sessionResult = await client.checkSession(); print('Session Result: $sessionResult'); } printSessionResult(); void sessionChanged(OdooSession sessionId) async { print('We got new session ID: ${sessionId.id}'); // write to persistent storage } setState(() { isLoading = true; }); dataList = List<Map<String, dynamic>>.from(res); // for (var data in dataList) { // print('{id: ${data['id']}, name: ${data['name']}, email: ${data['email']}}'); // } log(dataList.toString()); Provider.of<DataProvider>(context, listen: false).setDataList(dataList); Navigator.pushNamed(context, '/get_data'); } catch (e) { print('Exception ${e.toString()}'); setState(() { isLoading = false; }); // client.close(); // exit(-1); }
解决方案与建议
一、Odoo RPC调用的最佳实践
针对电商APP场景,RPC调用可遵循以下优化方案:
- 复用全局客户端实例:不要每次请求都创建新的
OdooClient,全局维护一个已认证的实例,避免重复认证的开销。可通过Provider、GetX等状态管理工具将客户端注入全局环境。 - 会话持久化:将认证后的
OdooSession序列化存储到本地(如SharedPreferences、Hive),APP启动时优先尝试恢复会话,失效后再触发重新认证。你的代码中已有的sessionChanged回调,可在此处完善存储逻辑。 - 请求缓存与防抖:对于商品列表、分类这类低频更新数据,添加本地缓存减少重复RPC调用;针对搜索、实时库存这类高频请求,实现防抖逻辑避免无效调用。
- 统一错误处理:封装RPC请求的错误处理逻辑,针对会话过期、网络异常等场景做自动重试或跳转登录页的处理,提升用户体验。
二、Odoo REST风格端点配置
Odoo官方提供REST API支持,需通过以下步骤启用:
- 安装必要模块:在Odoo后台安装
base_rest(基础REST框架)、base_rest_auth_jwt(JWT认证支持)、sale_rest(电商相关REST接口)等模块,按需选择扩展。 - 配置JWT认证:在Odoo设置中生成JWT密钥,配置认证规则(如允许的用户组、token过期时间)。
- 使用REST端点:
- 认证端点:
POST {domainname}/api/auth/jwt/login,传入username和password获取JWT Token - 电商业务端点:比如商品列表接口
GET {domainname}/api/sale/product,具体路径根据安装的模块和配置调整 - 自定义端点:若官方接口无法满足需求,可通过
base_rest框架自定义REST服务,继承RestService类实现业务逻辑并配置路由。
- 认证端点:
三、优化后的代码示例
以下是整合了会话持久化、全局客户端管理的代码:
import 'package:odoo_rpc/odoo_rpc.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; // 全局维护Odoo客户端实例 late OdooClient odooClient; // 初始化Odoo客户端,尝试恢复本地会话 Future<void> initOdooClient() async { final prefs = await SharedPreferences.getInstance(); final savedSession = prefs.getString('odoo_session'); odooClient = OdooClient('https://your-odoo-domain.com'); if (savedSession != null) { try { final session = OdooSession.fromJson(savedSession); odooClient.session = session; // 验证会话有效性 await odooClient.checkSession(); } catch (_) { // 会话失效,清除本地存储 await prefs.remove('odoo_session'); } } // 监听会话变化,自动持久化 odooClient.sessionStream.listen((newSession) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('odoo_session', newSession.toJson()); }); } // 认证方法 Future<void> odooAuthenticate(String dbName, String username, String password) async { try { await odooClient.authenticate(dbName, username, password); } catch (e) { print('认证失败: $e'); rethrow; } } // 获取合作伙伴列表 Future<List<Map<String, dynamic>>> fetchPartners() async { try { final result = await odooClient.callKw({ 'model': 'res.partner', 'method': 'search_read', 'args': [], 'kwargs': { 'context': {'bin_size': true}, 'domain': [], 'fields': ['name', 'email'], 'limit': 80, } }); return List<Map<String, dynamic>>.from(result); } catch (e) { print('获取数据失败: $e'); // 处理会话过期 if (e is OdooSessionExpiredException) { rethrow; } rethrow; } } // 页面示例 class PartnerPage extends StatefulWidget { const PartnerPage({super.key}); @override State<PartnerPage> createState() => _PartnerPageState(); } class _PartnerPageState extends State<PartnerPage> { bool isLoading = false; List<Map<String, dynamic>> dataList = []; @override void initState() { super.initState(); // 初始化客户端后尝试获取数据 initOdooClient().then((_) => _loadPartners()); } Future<void> _loadPartners() async { setState(() => isLoading = true); try { dataList = await fetchPartners(); Provider.of<DataProvider>(context, listen: false).setDataList(dataList); if (!mounted) return; Navigator.pushNamed(context, '/get_data'); } catch (e) { print('请求异常: $e'); // 会话过期跳转登录页 if (e is OdooSessionExpiredException && mounted) { Navigator.pushNamed(context, '/login'); } } finally { if (mounted) setState(() => isLoading = false); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('合作伙伴列表')), body: isLoading ? const Center(child: CircularProgressIndicator()) : ListView.builder( itemCount: dataList.length, itemBuilder: (context, index) { final partner = dataList[index]; return ListTile( title: Text(partner['name'] ?? ''), subtitle: Text(partner['email'] ?? ''), ); }, ), ); } } // 数据Provider示例 class DataProvider with ChangeNotifier { List<Map<String, dynamic>> _dataList = []; List<Map<String, dynamic>> get dataList => _dataList; void setDataList(List<Map<String, dynamic>> list) { _dataList = list; notifyListeners(); } }
内容的提问来源于stack exchange,提问作者surajsynconicsworkspace
相关产品推荐
相关产品推荐

