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

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

猜你喜欢

转载自blog.csdn.net/LifeRiver/article/details/131472979