1. 项目缘起从文本到卡片企业微信消息的体验升级在企业内部系统集成领域消息推送是连接业务系统与员工的关键桥梁。过去我们通过ABAP程序调用企业微信机器人发送文本消息虽然功能实现了但消息样式单一、信息密度低在移动端阅读体验不佳。尤其当需要推送包含多字段、带格式的业务通知如采购订单审批、生产异常预警、日报汇总时一长串纯文本显得杂乱且重点不突出。企业微信机器人提供的“模版卡片”消息类型正是为了解决这个问题而生。它允许我们以结构化的卡片形式发送消息支持标题、描述、图片、按钮、关键数据字段等多种元素视觉上更清晰交互上也支持用户点击按钮进行快捷操作如“同意”、“查看详情”。对于SAP ABAP开发者而言掌握如何构造并发送模版卡片消息意味着能将枯燥的系统后台日志转化为前台用户乐于接收、一目了然的业务通知。本次分享的核心就是深入探讨如何在ABAP环境中构造并发送企业微信机器人的模版卡片消息。我们将从最基础的文本型卡片开始逐步深入到图文展示、按钮交互等复杂类型并重点拆解其中的数据结构、参数含义以及在实际编码中极易踩坑的细节。无论你是需要推送一张简单的数据汇总卡片还是构建一个带确认按钮的审批提醒这里的内容都将提供可直接复现的路径。2. 模版卡片基础类型解析与数据结构拆解企业微信机器人支持的模版卡片主要分为两大类文本通知型模版卡片和图文展示型模版卡片。在官方文档中它们有更具体的类型标识但对于ABAP开发者我们更关心其数据结构和适用场景。2.1 核心消息类型template_card在调用企业微信机器人Webhook接口时我们需要将msgtype参数设置为template_card。这是启用卡片消息的开关。整个消息体content的结构将围绕template_card这个对象展开。与简单的text或markdown类型相比template_card的结构是嵌套的、层次化的这也是编码复杂度的主要来源。一个最简化的template_card消息体JSON结构如下所示{ msgtype: template_card, template_card: { card_type: text_notice, source: { icon_url: https://example.com/icon.png, desc: 消息来源 }, main_title: { title: 主标题, desc: 主标题描述 }, emphasis_content: { title: 关键数据, desc: 100.00 }, quote_area: { type: 1, url: https://example.com/detail, title: 引用文献标题, quote_text: 引用文献内容 }, sub_title_text: 副标题文本, horizontal_content_list: [ { keyname: 字段1, value: 值1 }, { “type”: 1, “keyname”: “带链接字段”, “value”: “点击查看”, “url”: “https://example.com” } ], jump_list: [ { type: 1, title: 跳转链接1, url: https://example.com/link1 } ], card_action: { type: 1, url: https://example.com } } }这个结构看起来复杂但我们可以将其分解为几个功能模块来理解卡片头区source,main_title、内容主体区emphasis_content,horizontal_content_list,quote_area、交互操作区jump_list,card_action。并非所有字段都必须填充我们可以根据业务需要灵活组合。2.2 ABAP中的数据结构映射内表与字符串拼接的艺术在ABAP中我们没有原生的JSON对象构建这样的嵌套结构通常有两种主流方式使用/ui2/cl_json等类库进行序列化这是更现代、更安全的方式。我们需要先定义对应的ABAP结构或使用内表组合填充数据后调用类方法将其转换为JSON字符串。这种方式结构清晰易于维护特别是对于复杂的嵌套数据。手动拼接JSON字符串这是最直接、依赖最少的方式通过CONCATENATE或字符串模板|...|来拼接。虽然灵活但极易因引号、逗号、括号缺失或错位导致JSON解析失败且代码可读性差。对于企业微信机器人这种固定格式的接口强烈推荐使用第一种方式。我们可以定义一个或多个结构来对应JSON的各个层级。例如可以定义如下结构这里用简化版示意TYPES: BEGIN OF ty_horizontal_content, type TYPE i, keyname TYPE string, value TYPE string, url TYPE string, END OF ty_horizontal_content. TYPES: ty_horizontal_content_list TYPE TABLE OF ty_horizontal_content WITH EMPTY KEY. TYPES: BEGIN OF ty_template_card, card_type TYPE string, source TYPE REF TO data, “ 使用REF TO data来灵活处理可选对象 main_title TYPE REF TO data, horizontal_content_list TYPE ty_horizontal_content_list, card_action TYPE REF TO data, END OF ty_template_card.在实际填充时我们需要动态创建source、main_title等子对象的数据。核心技巧在于对于可能为空的嵌套对象在最终组装JSON前进行判断如果其所有字段都为空则不应在JSON中生成该键值对。企业微信接口对可选字段的处理比较严格发送一个空的“source”: {}对象可能导致调用失败。3. 实战构建一个文本通知型模版卡片text_noticetext_notice是最常用的一种卡片类型适合用于系统通知、审批提醒、数据简报等场景。它的特点是拥有一个醒目的主标题区域并可以下方以“键-值”对的形式横向排列多个信息字段。3.1 场景定义与字段规划假设我们需要从SAP系统中推送一张“采购订单创建成功”的通知卡片。我们需要展示的信息包括主标题采购订单创建通知主标题描述系统已成功生成新的采购订单关键数据订单总金额需要突出显示关键字段订单编号、供应商名称、创建人、创建日期操作提供一个按钮点击后跳转到SAP GUI或Fiori Launchpad中该订单的显示界面。对应到template_card的字段card_type:“text_notice”main_title:{“title”: “采购订单创建通知”, “desc”: “系统已成功生成新的采购订单”}emphasis_content:{“title”: “订单总额”, “desc”: “¥52,300.00”}(这里title和desc的角色可根据需要互换通常desc放高亮数据)horizontal_content_list: 这是一个数组包含订单号、供应商等字段。card_action:{“type”: 1, “url”: “sap://订单显示链接”}或一个HTTP链接。3.2 ABAP代码实现从数据准备到JSON生成下面我们演示使用/ui2/cl_json来生成JSON的完整步骤。首先假设我们已经从SAP数据表如EKKO、EKPO中获取了相关数据。DATA: lo_json TYPE REF TO /ui2/cl_json. DATA: lv_json_string TYPE string. DATA: ls_card TYPE ty_template_card. “ 假设ty_template_card是我们根据3.2节定义的结构 DATA: lt_content TYPE ty_horizontal_content_list. DATA: ls_content LIKE LINE OF lt_content. DATA: ls_source TYPE REF TO data. DATA: ls_main_title TYPE REF TO data. DATA: ls_emphasis TYPE REF TO data. DATA: ls_action TYPE REF TO data. FIELD-SYMBOLS: fs_source TYPE any, fs_main_title TYPE any, fs_emphasis TYPE any, fs_action TYPE any. “ 1. 准备数据构建 horizontal_content_list ls_content-type 0. “ 0-普通文本 ls_content-keyname ‘采购订单号’. ls_content-value lv_ebeln. “ 从SAP获取的订单号 APPEND ls_content TO lt_content. ls_content-keyname ‘供应商’. ls_content-value lv_name1. “ 供应商名称 APPEND ls_content TO lt_content. ls_content-keyname ‘创建人’. ls_content-value lv_ernam. “ 创建者 APPEND ls_content TO lt_content. ls_content-keyname ‘创建日期’. ls_content-value lv_aedat. “ 创建日期 APPEND ls_content TO lt_content. “ 2. 构建可选的嵌套对象source (这里我们选择不添加source跳过) “ 3. 构建 main_title 对象 CREATE DATA ls_main_title TYPE (“ 定义一个临时结构对应main_title BEGIN OF ts_main_title, title TYPE string, desc TYPE string, END OF ts_main_title ). ASSIGN ls_main_title-* TO fs_main_title. fs_main_title-title ‘采购订单创建通知’. fs_main_title-desc ‘系统已成功生成新的采购订单’. “ 4. 构建 emphasis_content 对象 CREATE DATA ls_emphasis TYPE ( BEGIN OF ts_emphasis, title TYPE string, desc TYPE string, END OF ts_emphasis ). ASSIGN ls_emphasis-* TO fs_emphasis. fs_emphasis-title ‘订单总额’. fs_emphasis-desc ‘¥’ lv_netwr. “ 订单净值 “ 5. 构建 card_action 对象 CREATE DATA ls_action TYPE ( BEGIN OF ts_action, type TYPE i, url TYPE string, END OF ts_action ). ASSIGN ls_action-* TO fs_action. fs_action-type 1. “ 1-跳转URL fs_action-url ‘https://your-sap-fiori.com/sap/bc/ui5_ui5/sap/po_display?sap-client100po-number’ lv_ebeln. “ 6. 组装顶层卡片对象 ls_card-card_type ‘text_notice’. ls_card-main_title ls_main_title. ls_card-emphasis_content ls_emphasis. ls_card-horizontal_content_list lt_content. ls_card-card_action ls_action. “ 7. 序列化为JSON字符串 lo_json /ui2/cl_jsoncreate( ). lv_json_string lo_json-serialize( data ls_card compress abap_true “ 压缩JSON移除不必要的空格 name_mapping /ui2/cl_jsoncreate_name_mapping( it_mapping VALUE #( ( abap ‘CARD_TYPE’ json ‘card_type’ ) ( abap ‘MAIN_TITLE’ json ‘main_title’ ) ( abap ‘EMPHASIS_CONTENT’ json ‘emphasis_content’ ) ( abap ‘HORIZONTAL_CONTENT_LIST’ json ‘horizontal_content_list’ ) ( abap ‘CARD_ACTION’ json ‘card_action’ ) ) ) ). “ 8. 将lv_json_string包裹成完整的请求体 DATA(lv_request_body) |{“msgtype”: “template_card”, “template_card”: { lv_json_string } }|.注意上述代码中为了清晰展示省略了ty_template_card结构完整定义、字段类型转换如金额格式化为字符串、以及错误处理。实际开发中/ui2/cl_json对字段名默认使用大写转换因此通过name_mapping来确保生成小写的JSON键名至关重要否则企业微信接口可能无法识别。3.3 接口调用与结果验证生成lv_request_body后接下来的调用过程与发送普通文本消息无异都是通过ABAP的HTTP客户端CL_HTTP_CLIENT向Webhook URL发起POST请求。这里有一个关键细节必须确保HTTP请求头Content-Type设置为application/json。调用成功后在企业微信群聊中你将收到一张格式清晰的卡片消息。主标题醒目关键金额被高亮显示订单详细信息以整齐的列表形式呈现底部还有一个可点击的“查看详情”按钮由card_action定义。这比纯文本“订单号XXXX金额XXXX已创建”的体验提升了好几个档次。4. 进阶图文展示型模版卡片news_notice与交互按钮当通知内容包含图片或者希望以更丰富的版式展示时news_notice类型更为合适。它允许设置一个头图card_image并且其内容区域vertical_content_list是纵向排列的适合展示一段较长的描述或多段落信息。4.1news_notice卡片结构特点与text_notice相比主要区别在于card_type: 设置为“news_notice”。card_image: 这是一个对象包含url图片链接、aspect_ratio宽高比如2.25:1。vertical_content_list: 替代了horizontal_content_list这是一个对象数组每个对象包含title和desc用于纵向展示多段内容。jump_list: 通常在这里放置多个跳转链接例如“操作指南”、“相关制度”、“申请页面”等。这种卡片非常适合用于发布公告、新闻、产品上线通知等。例如推送一个“SAP系统月度维护通知”可以使用头图展示维护时间线在vertical_content_list中详细列出影响范围、回退方案、联系人等信息。4.2 在ABAP中处理图片与复杂结构对于card_image最大的挑战在于图片URL。它必须是公网可访问的URL。在企业内网环境中通常有几种解决方案将图片上传到公司的公共文件服务器或对象存储获取其公网访问链接。使用企业微信的“上传临时素材”接口但该接口需要独立的应用凭证corpid, secret与机器人Webhook不同集成复杂度更高。更常见的做法是采用第一种。在ABAP中构建vertical_content_list的逻辑与构建horizontal_content_list类似只是字段名和结构发生了变化。我们需要定义新的类型。TYPES: BEGIN OF ty_vertical_content, title TYPE string, desc TYPE string, END OF ty_vertical_content. TYPES: ty_vertical_content_list TYPE TABLE OF ty_vertical_content WITH EMPTY KEY.填充数据时就像填充一个内表一样简单。这种纵向布局在阅读长文本时比横向的键值对更友好。4.3 实现交互按钮button_selection与button_list模版卡片最强大的功能之一是支持交互按钮。这允许接收者直接在消息中进行选择或确认而无需跳转到其他应用。主要涉及两个字段button_selection: 这是一个选择题组件包含question_key问题标识和option_list选项列表。用户选择后机器人会收到一条包含选择结果的事件回调。注意要接收回调机器人必须配置在具备相应权限的应用中仅Webhook无法实现。这对于需要收集简单反馈如“是否同意”“请选择优先级高/中/低”的场景非常有用。button_list: 这是一个按钮列表每个按钮可以定义text按钮文字、style样式1-灰色2-蓝色、key按钮标识用于回调。用户点击后同样会触发回调事件。在ABAP端我们的主要工作是构造出包含这些按钮信息的JSON消息并发送。而处理用户点击后的回调则需要一个独立的、能够接收企业微信POST请求的HTTP服务这在ABAP中可以通过创建ICF服务或使用NetWeaver的Web服务实现这超出了单纯“推送”的范畴属于双向交互集成复杂度更高。对于大多数仅需“发送通知并引导跳转”的场景使用card_action或jump_list来定义链接按钮就足够了。如果你的场景确实需要即时交互那么必须提前规划好回调事件的接收与处理逻辑。5. 避坑指南ABAP开发中的常见问题与调试技巧即便理解了数据结构在实际编码中依然会遇到各种问题。以下是我在多次集成中总结出的关键坑点。5.1 JSON格式错误引号、逗号与空值处理这是新手最容易出错的地方。当手动拼接字符串时稍有不慎就会导致JSON语法错误。字符串值必须用双引号ABAP字符串用单引号但JSON标准要求字符串值用双引号。拼接时务必注意转换。使用/ui2/cl_json可以自动处理。尾随逗号JSON中对象或数组的最后一个元素后面不能有逗号。手动拼接时循环内添加字段很容易在最后一项后多出一个逗号。空值处理对于可选字段如source,quote_area如果决定不提供最安全的做法是不要在最终的JSON对象中包含这个键。而不是发送一个“source”: null或“source”: {}。使用/ui2/cl_json时如果某个引用类型变量REF TO data是初始值IS INITIAL它默认不会序列化该字段这正是我们需要的。调试建议在调用HTTP接口前先将拼接好的lv_request_body输出到ALV或直接WRITE出来。复制其内容使用在线的JSON格式化验证工具如 jsonformatter.org进行检查能快速定位语法错误。5.2 字段类型与长度限制企业微信接口对每个字段都有明确的类型和长度限制。card_type必须是特定的字符串如“text_notice”、“news_notice”拼写错误会导致卡片类型不被识别消息可能发送失败或回退为文本。title/desc长度主标题、描述等文本字段有长度限制通常为几十到上百个UTF-8字符。在从SAP的长文本字段如MAKTX取值时需要使用STRING函数或自定义逻辑进行截断并考虑添加“...”后缀。url有效性card_action、jump_list、card_image中的URL必须是有效的HTTP/HTTPS链接且企业微信客户端能够访问。内网地址需要确保接收者网络可达或者通过企业微信的“可信域名”进行配置。5.3 HTTP调用与错误响应处理ABAP的HTTP调用CL_HTTP_CLIENT需要妥善处理异常和响应。设置超时时间通过CL_HTTP_CLIENT的REQUEST方法设置TIMEOUT属性避免因网络问题导致程序长时间等待。检查HTTP状态码发送后务必检查RESPONSE的GET_STATUS方法。返回200并不完全代表成功还需要解析响应体。解析错误码企业微信接口的响应体是JSON格式例如{“errcode”:0,“errmsg”:“ok”}表示成功。任何非0的errcode都意味着失败errmsg会给出具体原因如“invalid json”JSON无效、“invalid agentid”应用ID无效等。你的ABAP程序需要能够解析这个JSON响应并根据错误码进行相应处理如记录日志、发送警报、重试等。一个健壮的调用模块应该包含完整的异常处理CX_ROOT、HTTP状态码判断和业务错误码解析。5.4 性能与批量发送考量如果需要向大量用户或群聊发送模版卡片直接循环调用单个Webhook接口可能触发频率限制企业微信机器人有调用频率限制。此时应考虑消息缓冲队列将待发送的消息先写入一个Z表由一个后台作业定时读取并发送。合并发送如果内容相似可以考虑使用企业微信的“群发”接口但这通常需要应用凭证而非机器人Webhook。异步处理使用ABAP的BACKGROUND TASK或RFC将发送逻辑异步化避免影响主业务程序的响应时间。6. 场景扩展将模版卡片融入SAP业务流程掌握了基础发送能力后我们可以将其灵活嵌入到各种SAP业务流程中实现主动式、可视化的业务通知。6.1 采购审批流提醒在采购订单、合同审批过程中当单据到达某个审批节点时通过BAdI如ME_PROCESS_PO_CUST或工作流事件SWE_EVENT_CREATE触发ABAP程序构造一张模版卡片。卡片上清晰显示单据类型与编号、申请金额、供应商、当前审批人。并可通过card_action设置一个深度链接审批人点击后直接跳转到SAP Fiori的“我的待办”应用或GUI的审批事务码如ME28界面实现“通知-处理”的无缝衔接。6.2 生产异常报警在MES或生产执行接口中当设备停机、质量检验不合格、物料短缺时触发报警。此时发送的模版卡片可以设置为news_notice类型头图使用醒目的警告图标vertical_content_list中详细列出设备/工位、异常代码、描述、发生时间、建议措施。jump_list中可以链接到该设备的实时监控页面或维修工单创建界面。这样生产主管和维修工程师能在第一时间获取结构化、可视化的报警信息。6.3 每日/每周业务数据简报利用ABAP后台作业SM36在每天早晨自动运行一个报表程序从各业务模块SD、MM、FI抽取关键KPI数据如昨日销售额、待处理订单数、库存预警物料数、即将到期付款项。将这些数据用text_notice卡片进行汇总emphasis_content高亮显示最重要的指标如销售额horizontal_content_list列出其他指标。部门经理在上班路上就能通过企业微信掌握业务概貌。6.4 与“提及”功能结合模版卡片消息同样支持mentioned_list成员和mentioned_mobile_list手机号参数。在构造消息体时可以将这两个参数与template_card并列。例如在生产异常报警中除了发送到群还可以直接维修班组长的企业微信账号确保关键责任人能立刻被提醒。需要注意的是被的成员必须在机器人所在的群聊中。7. 个人实践心得与优化建议经过多个项目的实践我发现要让模版卡片推送真正好用、稳定有几个细节值得特别关注。首先卡片内容的“信息密度”和“可读性”需要精心设计。不要试图把所有的字段都塞进horizontal_content_list。优先展示最核心的3-5个字段例如订单号、金额、状态、时间。过多的信息会让卡片显得臃肿失去重点。对于次要信息可以考虑放在quote_area作为引用或者通过card_action链接到详情页面去查看。其次关于图片资源的管理。如果使用card_image务必确保图片服务器的稳定性和访问速度。图片加载失败会严重影响卡片效果。建议使用公司内部的CDN或稳定的文件服务并对图片进行适当的压缩避免因图片过大导致消息加载缓慢。对于动态生成的图表如KPI趋势图可以在ABAP端调用一些图表生成库如GRAPH_MATRIX生成图片并自动上传到指定存储位置再将URL填入卡片实现完全自动化的图文报表推送。再者错误处理必须闭环。发送消息的ABAP程序不能“一发了之”。务必捕获并记录所有可能的异常网络异常、接口返回错误、JSON解析错误等。可以将发送日志记录到一张自定义表中包含发送时间、接收群/人、消息内容、发送状态、错误信息等。这对于后续排查问题、审计消息发送情况至关重要。甚至可以设置一个监控机制当连续发送失败达到一定次数时触发另一条报警消息通知系统管理员。最后保持接口调用的轻量化和异步化。发送HTTP请求是相对耗时的I/O操作。在BAdI或用户出口中直接同步调用可能会阻塞主业务流程影响用户体验。我的做法是在需要发送消息时只将必要的参数如单据号、类型、接收者写入一个“消息队列”表。然后由一个独立的、低优先度的后台作业每隔几分钟扫描这个队列表批量取出数据进行消息组装和发送。这样既实现了实时性几分钟的延迟对于大多数业务通知是可接受的又避免了对在线交易性能的冲击。模版卡片消息的集成技术本身并不复杂难在如何将其与复杂的SAP业务流程优雅、可靠地结合。它不仅仅是一个消息发送功能更是改善用户体验、提升业务流程触达效率的利器。从简单的文本升级到结构化的卡片这种改变带来的体验提升是显而易见的也值得我们在设计通知时多花一份心思。