You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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支持,需通过以下步骤启用:

  1. 安装必要模块:在Odoo后台安装base_rest(基础REST框架)、base_rest_auth_jwt(JWT认证支持)、sale_rest(电商相关REST接口)等模块,按需选择扩展。
  2. 配置JWT认证:在Odoo设置中生成JWT密钥,配置认证规则(如允许的用户组、token过期时间)。
  3. 使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 09:34:58