# Markdown 模块

你可以使用标准Markdown语法标签,快捷地构造消息卡片内容,Markdown模块可用于渲染文本格式、图片、分割线等元素。本文介绍如何使用Markdown模块。

可使用Markdown标签的内容模块包括:

  • 使用独立的Markdown模块,用一段Markdown标签构造包含格式化文本、图片、分割线的消息卡片内容。
  • 将text对象的tag字段声明为hi_md,使Markdown的格式化文本能嵌入多列文本布局、图片描述等使用场景

# 字段属性说明

参数 是否必须 类型 示例值 说明
tag String markdown 模块标签,固定值markdown。
content String **加粗** 使用已支持的Markdown语法构造Markdown内容。
textAlign
待开发
String left 设置文本内容的对齐方式,取值:
left:左对齐
center:居中对齐
right:右对齐
默认值:left
multiUrl Struct url对象 差异化跳转,仅在需要PC、移动端跳转不同链接使用

# 使用示例

  [
    ...
     {
      "tag": "markdown",
      "content": "普通文本\n标准emoji 😁😢🌞💼🏆❌✅\n*斜体*\n**粗体**\n~~删除线~~\n<u>下划线</u>\n[文字链接](https://www.feishu.cn)\n<font color=\"green\">这是一个绿色文本</font>\n<font color=\"red\">这是一个红色文本</font>\n<font color=\"grey\">这是一个灰色文本</font>\n<at open_id=\"ou_0f35****397e07de3f2\"></at>\n<at user_id=\"98643\"></at>\n<at email=\"zenglinhong@kanzhun.com\"></at>",
      "textAlign": "left"
    }
    ...
  ]
1
2
3
4
5
6
7
8
9

# 样式示例

# 支持的语法

名称 语法 示例 支持的版本 可用范围 备注
换行 \n 文本
换行
3.11 Markdown模块text对象的hi_md模式
斜体 *斜体* 斜体 3.11 Markdown模块text对象的hi_md模式
加粗 **粗体** 粗体 3.11 Markdown模块text对象的hi_md模式
删除线 ~~删除线~~ 删除线 3.11 Markdown模块text对象的hi_md模式
下划线 <u>下划线</u> 下划线 3.11 Markdown模块text对象的hi_md模式
超链接 [标题](链接)
[直书](https://zhishu.zhipin.com)
直书 (opens new window) 3.11 Markdown模块text对象的hi_md模式
彩色文本 <font color="green">这是一个绿色文本</font>
<font color="red">这是一个红色文本</font>
<font color="grey">这是一个灰色文本</font>
这是一个绿色文本
这是一个红色文本
这是一个灰色文本
3.14 Markdown模块text对象的hi_md模式 color属性为枚举值:green, red,grey
@指定人 <at open_id="ou_0f3515*****97e07de3f2"></at>
<at user_id="98**3"></at>
<at email="zenglinhong@kanzhun.com"></at>
@人名 3.14 Markdown模块text对象的hi_md模式 属性只能够配置:open_id, user_id, email

WARNING

彩色文本和@指定人语法出现在低版本时,会进行降级展示!!!

# 特殊字符转义

如果你要展示的字符命中以下字符,需要对字符进行转义才能保证正常展示。转义符号对照表如下:

  1. 使用斜线转义的特殊字符
    特殊字符 转义符 描述
    \ \\ 斜线
    ` \` 反单引号
    * \* 星号
    _ \_ 下划线
    { \{ 大括号左
    } \} 大括号右
    [ \[ 中括号左
    ] \] 中括号右
    ( \( 小括号左
    ) \) 小括号右
    # \# 井号
    + \+ 加号
    - \- 减号
    . \.
    ! \! 感叹号
    ~ \~ 波浪线
    > \> 大于号
    < \< 小于号
  2. 使用HTML编码转义的特殊字符
    特殊符号 转义符 描述
    ` ` &nbsp; 半角空格,charCode = 160
    ` ` &ensp; 半角空格,charCode = 8194
    ` ` &emsp; 全角空格,charCode = 8195
    & &amp; 连接符
    ' &#39; 单引号
    " &quot; 双引号

# SDK简化处理(java)

为简化您的使用,我们提供了服务端发送消息卡片markdown模块内容的文本转换工具类,示例如下

引入开放平台整体sdk依赖

<dependency>
    <groupId>com.zhipin.cockatiel.common</groupId>
    <artifactId>zhipin-cockatiel-common-openapi</artifactId>
    <version>RELEASE</version>
</dependency>
1
2
3
4
5

java处理方式一

参照其中markdown模块的工具类方法MarkdownTextEscapeUtils.escapeString()

//全类型卡片拼接构造示例
@Test
public void testSendFullCard() throws Exception {
    CompressCardContentBody contentBody = CompressCardContentBody.builder()
            // header
            .header(Header.builder()
                    .text(CardText.builder()
                            .content("普通文本")
                            .tag(TextEnum.PLAIN_TEXT.getTag())
                            .build())
                    .color(CardColorEnum.BLUE.getValue())
                    .build())
            // cardLink
            .cardLink(CardLink.builder()
                    .url("替换为您实际的内容")
                    .pcUrl("替换为您实际的内容")
                    .androidUrl("")
                    .iosUrl("替换为您实际的内容")
                    .build())
            // globalConfig
            .globalConfig(GlobalConfig.builder()
                    .forward(Boolean.FALSE)
                    .shareCard(Boolean.TRUE)
                    .build())
            // modules
            .modules(Lists.newArrayList(
                    // img
                    ImageModule.builder()
                            .tag(ModuleTagEnum.IMAGE.getTag())
                            .title(CardText.builder()
                                    .tag(TextEnum.PLAIN_TEXT.getTag())
                                    .content("您的第一个图片模块")
                                    .build())
                            .imgKey("替换为您实际的内容")
                            .alt(CardText.builder()
                                    .tag(TextEnum.PLAIN_TEXT.getTag())
                                    .content("图片模块文本")
                                    .build())
                            .preview(Boolean.TRUE)
                            .build(),
                    // split_line
                    BaseModule.builder()
                            .tag(ModuleTagEnum.SPLIT_LINE.getTag())
                            .build(),
                    // div text
                    DivModule.builder()
                            .tag(ModuleTagEnum.CONTENT.getTag())
                            .text(CardText.builder()
                                    .tag(TextEnum.PLAIN_TEXT.getTag())
                                    .content("内容模块——单文本")
                                    .build())
                            .build(),
                    // markdown
                    MarkdownModule.builder()
                            .tag(ModuleTagEnum.MARKDOWN.getTag())
                            .content(MarkdownTextEscapeUtils.escapeString("markdown内容含有需展示的特殊符号-~[]{}~"))
                            .textAlign(TextAlignEnum.LEFT.getValue())
                            .multiUrl(CardLink.builder()
                                    .url("替换为您实际的内容")
                                    .pcUrl("替换为您实际的内容")
                                    .androidUrl("")
                                    .iosUrl("替换为您实际的内容")
                                    .build())
                            .build(),
                    // div hi_md
                    DivModule.builder()
                            .tag(ModuleTagEnum.CONTENT.getTag())
                            .text(CardText.builder()
                                    .tag(TextEnum.HI_MD.getTag())
                                    .content(MarkdownTextEscapeUtils.escapeString("markdown内容含有需展示的特殊符号-~[]{}~"))
                                    .build())
                            .build(),

                    // div 多文本
                    ImageDivModule.builder()
                            .tag(ModuleTagEnum.CONTENT.getTag())
                            .arrange(ArrangeEnum.BISECTED.getValue())
                            .text(CardText.builder()
                                    .tag(TextEnum.PLAIN_TEXT.getTag())
                                    .content("内容模块——单文本")
                                    .build())
                            .fields(Lists.newArrayList(
                                    CardField.builder()
                                            .text(CardText.builder()
                                                    .tag(TextEnum.PLAIN_TEXT.getTag())
                                                    .content("内容模块——多文本1")
                                                    .build())
                                            .needLayout(Boolean.TRUE)
                                            .build(),
                                    CardField.builder()
                                            .text(CardText.builder()
                                                    .tag(TextEnum.PLAIN_TEXT.getTag())
                                                    .content("内容模块——多文本2")
                                                    .build())
                                            .needLayout(Boolean.TRUE)
                                            .build(),
                                    CardField.builder()
                                            .text(CardText.builder()
                                                    .tag(TextEnum.PLAIN_TEXT.getTag())
                                                    .content("内容模块——多文本3")
                                                    .build())
                                            .needLayout(Boolean.TRUE)
                                            .build()

                            ))
                            .extra(CardImage.builder()
                                    .tag(ModuleTagEnum.IMAGE.getTag())
                                    .imgKey("替换为您实际的内容")
                                    .alt(CardText.builder()
                                            .tag(TextEnum.PLAIN_TEXT.getTag())
                                            .content("图片模块文本")
                                            .build())
                                    .preview(Boolean.TRUE)
                                    .build())
                            .build(),
                    // action
                    ActionModule.builder()
                            .tag(ModuleTagEnum.ACTIONS.getTag())
                            .arrange(ArrangeEnum.BISECTED.getValue())
                            .actions(Lists.newArrayList(
                                    ButtonAction.builder()
                                            .type(ButtonTypeEnum.PRIMARY.getValue())
                                            .tag(ActionEnum.BUTTON.getTag())
                                            .needLayout(Boolean.TRUE)
                                            .text(CardText.builder()
                                                    .tag(TextEnum.PLAIN_TEXT.getTag())
                                                    .content("图片模块文本")
                                                    .build())
                                            .multiUrl(CardLink.builder()
                                                    .url("替换为您实际的内容")
                                                    .pcUrl("替换为您实际的内容")
                                                    .androidUrl("")
                                                    .iosUrl("替换为您实际的内容")
                                                    .build())
                                            .callBacks(Lists.newArrayList(
                                                    CardCallBack.builder()
                                                            .key("click1")
                                                            .value("1")
                                                            .build(),
                                                    CardCallBack.builder()
                                                            .key("click2")
                                                            .value("2")
                                                            .build()
                                            ))
                                            .confirm(CardConfirm.builder()
                                                    .title(CardText.builder()
                                                            .tag(TextEnum.PLAIN_TEXT.getTag())
                                                            .content("弹窗标题")
                                                            .build())
                                                    .text(CardText.builder()
                                                            .tag(TextEnum.PLAIN_TEXT.getTag())
                                                            .content("弹窗内容")
                                                            .build())
                                                    .build())
                                            .build(),
                                    ListSelectorAction.builder()
                                            .tag(ActionEnum.LIST_SELECTOR.getTag())
                                            .needLayout(Boolean.TRUE)
                                            .placeholder(CardText.builder()
                                                    .tag(TextEnum.PLAIN_TEXT.getTag())
                                                    .content("下拉列表 默认文本")
                                                    .build())
                                            .initialOption("joy")
                                            .options(Lists.newArrayList(
                                                    CardOption.builder()
                                                            .text(CardText.builder()
                                                                    .tag(TextEnum.PLAIN_TEXT.getTag())
                                                                    .content("下拉选项文本1")
                                                                    .build())
                                                            .value("joy")
                                                            .build(),
                                                    CardOption.builder()
                                                            .text(CardText.builder()
                                                                    .tag(TextEnum.PLAIN_TEXT.getTag())
                                                                    .content("下拉选项文本2")
                                                                    .build())
                                                            .value("tom")
                                                            .build()
                                            ))
                                            .callBacks(Lists.newArrayList(
                                                    CardCallBack.builder()
                                                            .key("option1")
                                                            .value("1")
                                                            .build(),
                                                    CardCallBack.builder()
                                                            .key("option2")
                                                            .value("2")
                                                            .build()
                                            ))
                                            .build()
                            ))
                            .build(),
                    // note
                    NoteModule.builder()
                            .tag(ModuleTagEnum.NOTE.getTag())
                            .elements(Lists.newArrayList(
                                    CardImage.builder()
                                            .tag(ModuleTagEnum.IMAGE.getTag())
                                            .imgKey("替换为您实际的内容")
                                            .alt(CardText.builder()
                                                    .tag(TextEnum.PLAIN_TEXT.getTag())
                                                    .content("备注模块文本")
                                                    .build())
                                            .preview(Boolean.TRUE)
                                            .build(),
                                    CardText.builder()
                                            .tag(TextEnum.PLAIN_TEXT.getTag())
                                            .content("备注文本1")
                                            .build(),
                                    CardText.builder()
                                            .tag(TextEnum.PLAIN_TEXT.getTag())
                                            .content("备注文本2")
                                            .build()
                            ))
                            .build()
            ))
            .build();
    String content = JSONObject.toJSONString(contentBody);
    CreateMessageResp resp = openApiClient.im().message().create(CreateMessageReq.newBuilder()
            .createMessageReqBody(CreateMessageReqBody.newBuilder()
                    .content(content)
                    .msgType(MsgTypeEnum.MSG_TYPE_COMPRESSIVE_CARD)
                    .receiveId("替换为您实际的内容")
                    .uuid(UUID.randomUUID().toString())
                    .build())
            .receiveIdType(CreateMessageReceiveIdTypeEnum.CHAT_ID)
            .userIdType(UserIdTypeEnum.USER_ID)
            .build());

    String msgId = resp.getData().getMessageId();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231

java处理方式二

此方式为自行进行字符串拼接,content的内容需要经过markdown模块的工具类方法MarkdownTextEscapeUtils.escapeStringReEscape()处理后才能正常发送

@Test
public void testSendCard() throws Exception {
    String content = "{\n" +
            "  \"modules\": [\n" +
            "    {\n" +
            "      \"tag\": \"div\",\n" +
            "      \"text\": {\n" +
            "        \"content\": \"" + MarkdownTextEscapeUtils.escapeStringReEscape("markdown内容含有需展示的特殊符号-~[]{}~") + "\" ," +
            "        \"tag\": \"hi_md\"\n" +
            "      }\n" +
            "    }\n" +
            "  ],\n" +
            "  \"globalConfig\": {\n" +
            "    \"forward\": true\n" +
            "  }\n" +
            "}";
    CreateMessageResp resp = openApiClient.im().message().create(CreateMessageReq.newBuilder()
            .userIdType(UserIdTypeEnum.USER_ID.getValue())
            .receiveIdType(CreateMessageReceiveIdTypeEnum.USER_ID)
            .createMessageReqBody(CreateMessageReqBody.newBuilder()
                    .content(content)
                    .msgType(MsgTypeEnum.MSG_TYPE_COMPRESSIVE_CARD.getValue())
                    .receiveId("替换为您的实际内容")
                    .build())
            .build());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

java处理方式三

此方式为通过JSONObject类来进行拼接处理,在设置content时对内容执行MarkdownTextEscapeUtils.escapeString()

@Test
public void testSend5() throws Exception {
        JSONObject card = new JSONObject();
        JSONArray modules = new JSONArray();
        card.put("modules", modules);
        JSONObject m1 = new JSONObject();
        modules.add(m1);
        m1.put("tag", "div");
        JSONObject m1Text = new JSONObject();
        m1.put("text", m1Text);
        m1Text.put("content", MarkdownTextEscapeUtils.escapeString("markdown内容含有需展示的特殊符号-~[]{}~"));
        m1Text.put("tag", "hi_md");
        JSONObject globalConfig = new JSONObject();
        card.put("globalConfig", globalConfig);
        globalConfig.put("forward", true);
        CreateMessageResp resp = openApiClient.im().message().create(CreateMessageReq.newBuilder()
            .userIdType(UserIdTypeEnum.USER_ID.getValue())
            .receiveIdType(CreateMessageReceiveIdTypeEnum.USER_ID)
            .createMessageReqBody(CreateMessageReqBody.newBuilder()
                .content(card.toJSONString())
                .msgType(MsgTypeEnum.MSG_TYPE_COMPRESSIVE_CARD.getValue())
                .receiveId("替换为您的实际内容")
                .build())
            .build());
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# Python语言简化处理支持

如您是使用python语言,因为目前我们平台暂未提供python语言的sdk,您在使用消息卡片markdown模块时可以参照上述java方法方式三,通过引入如下方法来进行内容文本的处理

markdown_chars = ["\\", "!", "#", "(", ")", "<", ">", "*", "+", "-", ".", "[", "]", "_", "`", "{", "}", "~"]

def escape_string(text, specified_chars=None):
    escaped = filter_escapes(text)
    if specified_chars is None:
        specified_chars = markdown_chars
    for markdown_char in specified_chars:
        escaped = escaped.replace(markdown_char, "\\" + markdown_char)
    return escaped

def filter_escapes(url):
    return (url.replace("&", "&amp;")
        .replace(" ", "&nbsp;")
        .replace("\u00A0", "&nbsp;")
        .replace("\u2002", "&ensp;")
        .replace("\u2003", "&emsp;")
        .replace("'", "&#39;")
        .replace("\"", "&quot;"))

##测试方法
##test_text = "~\u00A0&"
##print(escape_string(test_text))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
最后更新于 : 8/8/2024, 9:46:34 AM