Odoo16 微信公众号模块开发示例

news2024/12/28 3:26:19

Odoo16 微信公众号模块开发示例

本模块基于 aiohttp + asyncio 进行异步微信公众号接口开发, 仅实现了部分 API 仅供学习参考,更完善的同步接口请参考:wechatpy 或 werobot,可用来替代 模块中的 wechat client。

业务需求

  1. 小程序中需要用户先关注公众号后才能进一步操作
  2. 通过公众号给用户发送通知

功能设计

方案:

同一用户在小程序与公众号中的 openid 是不同的,通过微信 UnionId 机制, 将小程序及公众号绑定到同一开放平台(开放平台需认证缴费),把已有公众号用户同步到后台,并处理关注及取消关注事件,即在后台中同步所有关注用户,并通过 UnionId 判断用户是否已关注,未关注时小程序端展示公众号中已添加的关注引导文章。

功能点:

后端:

  • 基于 aiohttp + asyncio 开发公众号 Api 接口
  • 公众号基本配置管理
  • 关注用户信息管理
  • 批量同步公众号关注用户
  • 单个用户信息同步
  • 微信服务配置回调API
  • 关注及取消关注时同步用户信息
  • 发送微信模板消息
  • 查询用户是否关注接口

小程序端:

  • 调用接口判断用户是否已关注
  • 未关注时跳转webview打开公众号中关注引导文章

公众号客户端

  • api/get_user_info/_api.apy 依赖 request.py 封装具体请求参数,解析返回结果
  • client.py client 实例,注入配置参数,依赖 api/* 实现各公众号 API,缓存 access_token 在api间重用,进行异常处理,记录日志
  • request.py 封装 aiohttp 客户端请求。
# client.py
class WxClient(object):
    '''
    基于 aiohttp 的微信公众号异步客户端
    使用示例:
        wx_client = client_instance(wx_appid, wx_secret)
        loop = wx_client.loop
        ret = loop.run_until_complete(self.async_get_user_info(self.openid, wx_client))
    '''

    def __init__(self, app_id, secret):
        self._app_id = app_id
        self._secret = secret
        # access_token 缓存
        self._token_store = TokenStore(secret) 
        # aiohttp 共用异步 http client + 事件循环
        self._session, self._loop = init_session() 
    
    @handle_exception(bool)
    async def get_user_info(self, openid: str) -> get_user_info.WxUserInfo:
        token = await self.latest_token()
        return await get_user_info.request(self._session, token, openid)

def client_instance(app_id, secret):
	'''公众号客户端实例'''
	global client
	if not client:
	    client = WxClient(app_id, secret)
	return client
# request.py 封装 aiohttp 客户端请求, 初始化 session 、event_loop
def create_session():
    conn = aiohttp.TCPConnector(ssl=False)
    return aiohttp.ClientSession(connector=conn)

def init_session():
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    session = create_session()
    return session, loop

def check_response_error(data, error_code=0, error_msg_key='errmsg'):
    if code := int(data.get('errcode', 0)) != error_code:
        raise ServerError(code, data[error_msg_key])

async def get_response(session, url, params=None, timeout=10, response_callback=None):
    async with async_timeout.timeout(timeout):
        async with session.get(url, params=params) as response:
            if response_callback:
                return await response_callback(response)
            else:
                result = await response.json()
                check_response_error(result)
                return result

知识点:通过 odoo.tools.config['data_dir'] 获取 odoo 数据存储目录,以存储自定义接口日志。

公众号配置

在这里插入图片描述- 公众号配置管理模型

class WxConfig(models.Model):
    _name = 'wx.config'
    _description = u'公众号配置'
    
    name = fields.Char('名称')
    wx_appid = fields.Char('AppId')
    wx_secret = fields.Char('AppSecret')
    wx_url = fields.Char('URL', readonly=True, compute='_compute_wx_url', help='复制到公众号官方后台')
    wx_token = fields.Char('Token', default=_generate_token)
    wx_aeskey = fields.Char('EncodingAESKey', default='')
    reply = fields.Char(string='关注回复', default='欢迎关注!')
...

知识点:限制模型仅单条记录、展示form视图。

  1. 通过 data.xml 创建记录
<odoo>
    <data noupdate="1">
        <record id="wx_config_data_1" model="wx.config">
            <field name="name">公众号配置</field>
            <field name="wx_appid">xxxxxxx</field>
        </record>
    </data>
</odoo>
  1. 视图中 views.xml 创建窗口动作,view_mode 为 form
    <record id="list_wx_config_action" model="ir.actions.act_window">
        <field name="name">公众号设置</field>
        <field name="res_model">wx.config</field>
        <field name="view_mode">form</field>
        <field name="res_id" ref="wx_config_data_1"/>
    </record>
  1. 将模型权限设置为1100,仅读取修改权限
  2. 模型中添加模型方法读取该唯一记录:
    @api.model
    def get_config(self):
        return self.env.ref('odooer_wechat.wx_config_data_1')

批量用户同步

  1. 自定义列表视图,添加同步用户按钮

在这里插入图片描述
定义组件模板,添加同步按钮:

<xpath expr="//div[hasclass('o_list_buttons')]" position="inside">
    <button type="button" class="btn btn-secondary o_button_download_import_tmpl"
        t-on-click.stop.prevent="onClickSyncBtn">
        同步用户
    </button>
</xpath>

组件js, 定义按钮处理逻辑及组件名称 sync_wx_user_tree:

export class SyncUserController extends ListController {
    setup() {
        super.setup();
        this.orm = useService('orm');
    }
    async onClickSyncBtn() {
        const action = await this.orm.call(
            "wx.user",
            "action_sync_users"
        );
        window.location.reload();
        // this.actionService.doAction(action)
    }
}

export const SyncUserListView = {
    ...listView,
    Controller: SyncUserController,
    buttonTemplate: "SyncUser.ListView.Buttons",
};

registry.category("views").add("sync_wx_user_tree", SyncUserListView);

__manifest__.py 注册自定义组件源码:.js .xml .css

'assets': {
    'web.assets_backend': [
        'odooer_wechat/static/src/components/**/*',
    ]
},

通过 js_class="sync_wx_user_tree" 指定使用列表自定义组件。

<record id="view_wx_user_list" model="ir.ui.view">
     <field name="name">wx.user.list</field>
     <field name="model">wx.user</field>
     <field name="arch" type="xml">
          <tree js_class="sync_wx_user_tree">
     .......
  1. 向前端页面推送消息
    在这里插入图片描述
	 self.env['bus.bus']._sendone(self.env.user.partner_id, 'simple_notification', {
	     'title': '同步关注用户信息',
	     'message': '开始同步公众号关注者信息',
	     'warning': True
	 })
  1. 在线程中处理长时间任务防止界面阻塞等待
	thread = threading.Thread(target=_task)
	thread.start() # 启动线程执行任务
	def _task():
		with self.env.registry.cursor() as cr: # 保持使用该游标创建新的环境变量
		    env = api.Environment(cr, uid, {})

事件处理

https://**/wx_handler 复制到微信公众号后台服务配置URL,并启用配置。在 controller 中处理接收到的各种事件及消息。

    @http.route('/wx_handler', type='http', auth='none', methods=['GET', 'POST'], csrf=False)
    def handle(self, **kwargs):
        # 其他...
        ret = ''
        if msg.type in ['text', 'image', 'voice', 'video', 'location', 'link']:
            ret = input_handle(request, msg)
        elif msg.type == 'event':
            if msg.event == 'subscribe':
                ret = subscribe(request, msg)
            elif msg.event == 'unsubscribe':
                ret = unsubscribe(request, msg)

模块源码

未严格测试,请勿在生产环境直接使用

点击查看 https://apps.odoo.com/apps/modules/16.0/odooer_wechat/

其他参考

  • Odoo13 公众号模块:https://apps.odoo.com/apps/modules/13.0/oejia_wx2/
  • aiohttp 官网: https://docs.aiohttp.org/en/stable/client.html
  • asyncio官网: https://docs.python.org/3/library/asyncio.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/706278.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

std::bind的讲解

一、在讲解std::bind之前&#xff0c;我们先来复习下std::function。 std::function 是一个“可调用对象”包装器&#xff0c;是一个类模板&#xff0c;可以容纳除了类成员函数指针之外的所有可调用对象&#xff0c;它可以用统一的方式处理函数、函数对象、函数指针&#xff0…

async创建异步任务

想让线程之间可以有两个数据的交换。之前一直采用的是全局变量互斥锁的方法。到目前为止&#xff0c;线程运行完之后还无法提供一个返回值。 此时引入 future 和 async。 sync为同步的意思&#xff0c;async为异步任务。同步任务前文已经结束过&#xff1a;他指的是两个人协同…

SpringBoot 日志文件:日志的作用?为什么要写日志?

文章目录 &#x1f387;前言1.日志长什么样子&#xff1f;2.自定义打印日志2.1 在程序中得到日志对象2.2 使用日志对象打印日志 3.日志级别3.1 日志级别的分类与使用3.2 日志级别有什么用呢&#xff1f;3.3 日志级别的设置 4.日志持久化保存5.更方便的日志输出5.1 添加 lombok …

Celery分布式异步框架

Celery异步任务框架 """ 1&#xff09;可以不依赖任何服务器&#xff0c;通过自身命令&#xff0c;启动服务(内部支持socket) 2&#xff09;celery服务为为其他项目服务提供异步解决任务需求的 注&#xff1a;会有两个服务同时运行&#xff0c;一个是项目服务&a…

Android 自定义View和事件分派 图解

Android 自定义View和事件分派 图解_猎羽的博客-CSDN博客https://blog.csdn.net/feather_wch/article/details/131487012

并查集和LRUCache

目录 1. 并查集 1.1原理 1.2实现 1.3应用 1.3.1省份数量 1.3.2等式方程的可满足性 2.LRUCache 1.概念 2.实现 3.JDK中类似LRUCahe的数据结构LinkedHashMap 4.LRU Cache的OJ 1. 并查集 1.1原理 把不同的元素划分到不想交的集合.开始时,每个元素自成一个单元集合,然后…

OSGI-Bundle:概念和入门

OSGI(Open Service gateway initactive)是java动态化模块系统的一系列规范。即一个系统应用上可以有很多可插拔的小应用&#xff0c;整个应用能运行和协调&#xff0c;小应用之间也可以相互交互完成业务需求。 Bundle: bundle 是以 jar 包形式存在的一个模块化物理单元&#x…

Ceph:关于 Ceph 用户认证授权管理的一些笔记

写在前面 准备考试&#xff0c;整理 Ceph 相关笔记博文内容涉及, Ceph 用户管理&#xff0c;认证管理&#xff0c;权限管理 以及相关 Demo理解不足小伙伴帮忙指正 对每个人而言&#xff0c;真正的职责只有一个&#xff1a;找到自我。然后在心中坚守其一生&#xff0c;全心全意&…

antdesginVue a-date-picker(日期时间选择器)禁用当前时间之前的时间,包含时分秒

antdesginVue a-date-picker(日期时间选择器)禁用当前时间之前的时间&#xff0c;包含时分秒 话不多说直接上效果 <a-form-item label"发生时间" name"start_time"><a-date-pickerstyle"width: 100%"allowClearv-model:value"f…

C++模板进阶知识

文章目录 前言模板进阶1.非类型模板参数2.模板的特化2.1概念2.2函数模板特化2.3类模板特化2.3.1 全特化2.3.2 偏特化2.3.3 类模板特化应用示例 3.模板的分离编译3.1 什么是分离编译3.2 模板的分离编译3.3 解决方法 4 模板总结 后记 前言 之前我们讲过模板初阶的知识&#xff0…

Linux 解决root用户被限制连接服务器

Linux 解决root用户被限制连接服务器 1. 问题描述2. 解决问题2.1 方式一&#xff08;忘记root密码的情况&#xff09;2.2 方式二&#xff08;知道root密码的情况&#xff09; 3. 其他 1. 问题描述 使用 root 用户不能链接服务器&#xff0c;密码对&#xff0c;就是连接不上&am…

uniapp:分享一个自定义侧滑样例

首先看html,分为两部分&#xff0c;主体内容部分和功能部分&#xff0c;功能部分在css中定位到主体部分的右边 <view class"section" ref"box_center" touchstart"drawStart" touchmove"drawMove($event)"touchend"drawEnd($…

晨控智能UWB室内定位:工厂智能化的新引擎

晨控智能UWB室内定位&#xff1a;工厂智能化的新引擎 工厂是一个复杂而庞大的环境&#xff0c;通常包括多个车间、设备、人员以及大量的物料和产品。需要实时、准确的定位数据来支持各项运营活动。然而&#xff0c;传统的定位技术无法满足工厂内部的高精度定位需求。而UWB室内…

u-boot的烧写及使用,u-boot-2013.01的移植 6.30

1.将Linux的执行文件放到板子上运行 嵌入式系统 1.嵌入式系统 定制2.硬件&#xff1a;核心芯片底板软件&#xff1a;驱动应用 驱动系统应用&#xff08;并发&#xff0c;网络&#xff0c;文件。。。&#xff09;3.系统&#xff1a;linux 开源 模块化 支持芯片众多 功能…

针对字符串输入之间有空格的问题相关的问题

先说结论&#xff1a; bool flag true;while (cin >> s) {if (flag) {flag false;cout << s.size();} else {cout << , << s.size();}} 即用while&#xff08;cin>>s&#xff09;来输入&#xff0c;一段单词一段单词的来做&#xff08;遇到ci…

第十一章 原理篇:transformer模型入门

说在前面的话&#xff1a; 找工作面试不是特别顺利。进了目标公司的二面&#xff0c;但是一面面试官问的一些“新技术”问题答得不太好&#xff0c;尤其是transformer相关的。这一点确实是自己的问题&#xff0c;在工作后总是面向业务学习&#xff0c;对很多算法都是处于“听说…

AD从原理图到PCB超详细教程

AD超详细教程 前言一、建立一个工程模板二、原理图1.设计原理图。2.使用AD自带库和网上开源原理图库3.画原理图库4.编译原理图 三、PCB1.确定元器件尺寸大小2.绘制PCB Library①使用元器件向导绘制元件库②原理图与PCB的映射 3.绘制PCB①更新PCB②调整元件位置③布线④漏线检查…

库操作和表操作(数据库系列2)

目录 前言&#xff1a; 1.数据库的操作 1.1显示当前的数据库 1.2创建数据库 1.3使用数据库 1.4删除数据库 2.常用数据类型 2.1数值类型 2.2字符串类型 2.3日期类型 3.表的操作 3.1查看表结构 3.2创建表 3.3查看表 3.4删除表 结束语&#xff1a; 前言&#xff1…

【硬件自动化测试--测试软件的设计及实现】如何设计并实现!

今天来聊聊关于硬件方向的自动化软件设计及实现,后面我会用实例来让我们更加深入的了解硬件自动化,首先开发工具选择的是python语言,为啥选择python语言呢,因为他的语法比较简洁,外置库非常多,反正就是对于做自动化方面很实用就对了。 1.硬件自动化测试大致分为三个阶段实…

拓展:IDEA如何使用不同版本的JDK?(改了还报错很可能因为没改全,以mac为例)

以下面的案例为例 Enhanced ‘switch’ blocks are not supported at language level ‘8’ 后面知道是因为Spring的版本和JDK的版本不对应&#xff0c;结果网上找到的解决方案都很简单。下载了一个新版本的JDK&#xff0c;然后IDEA里面Project Structure的Project标签里把SDK给…