├── LICENSE
├── README.md
├── README_en.md
├── composer.json
├── config
└── dingtalk_robot.php
└── src
├── DingtalkRobot.php
├── DingtalkRobotChannel.php
├── DingtalkRobotNoticeServiceProvider.php
├── Exceptions
├── ErrorCodes.php
├── Exception.php
├── HttpException.php
├── InvalidArgumentException.php
└── InvalidConfigurationException.php
├── Facade.php
├── Message
├── ActionCardMessage.php
├── AtTrait.php
├── FeedCardMessage.php
├── LinkMessage.php
├── MarkdownMessage.php
├── Message.php
└── TextMessage.php
├── Robot.php
└── helpers.php
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 陈恺垣
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
laravel-dingtalk-robot-notification
2 | 钉钉群机器人 Laravel/Lumen 扩展包
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | > [English](https://github.com/calchen/laravel-dingtalk-robot-notification/blob/master/README_en.md)
25 |
26 | 这是一个[钉钉群机器人](https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq)的 Laravel/Lumen 消息通知(Notification)扩展包
27 |
28 | ## 安装
29 |
30 | 推荐使用 composer 进行安装:
31 |
32 | ```shell
33 | $ composer require calchen/laravel-dingtalk-robot-notification:^2.0
34 | ```
35 |
36 | ### Laravel
37 |
38 | Laravel 5.5+ 已经实现了扩展包发现机制,您不需要进行额外的加载操作,但是依然需要将配置文件发布出来:
39 |
40 | ```shell
41 | php artisan vendor:publish --provider="Calchen\LaravelDingtalkRobot\DingtalkRobotNoticeServiceProvider"
42 | ```
43 |
44 | ### Lumen
45 |
46 | Lumen 并未移植扩展包自动发现机制,所以需要手动加载扩展包并复制配置文件。
47 |
48 | 打开配置文件 `bootstrap/app.php` 并在大约 81 行左右添加如下内容:
49 | ```php
50 | $app->register(Calchen\LaravelDingtalkRobot\DingtalkRobotNoticeServiceProvider::class);
51 | ```
52 |
53 | 将文件系统配置文件从 `vendor/calchen/laravel-dingtalk-robot-notification/config/dingtalk_robot.php` 复制到 `config/dingtalk_robot.php`
54 |
55 | ### 非 Laravel/Lumen 框架
56 |
57 | 无需考虑加载问题,请使用全局函数 `\dingtalk_robot()` 或直接创建 \Calchen\LaravelDingtalkRobot\DingtalkRobot 实例,以发送消息。
58 |
59 | ## 配置
60 |
61 | 打开配置文件 `config/dingtalk_robot.php` 并按照如下格式添加或修改配置:
62 |
63 | ```php
64 | 'robotName' => [
65 | 'access_token' => 'xxxx',
66 | 'timeout' => 2.0,
67 | 'security_types' => [
68 | 'signature',
69 | ],
70 | 'security_signature' => 'SECxxxx',
71 | ],
72 | ```
73 | 请注意,如果需要配置多个机器人,请重复以上操作,并为不同机器人给予不同的 robotName
74 |
75 | ### 配置说明
76 | | 配置项 | 必须 | 数据类型 | 说明 | 备注 |
77 | |-------------------- |------ |------------- |----------------------------------------------- |------------------------------------------------------------------------------------------------------------ |
78 | | http_client_name | 否 | string/null | Guzzle 实例的名称 | 默认值:null,注入在 Laravel 中的 Guzzle 实例的名称,以便替换 HTTP 客户端 |
79 | | robotName | 是 | string | 机器人名称 | 这个名称为了区别不同的机器人 |
80 | | access_token | 是 | string | 创建机器人后 Webhook URL 中 access_token 的值 | |
81 | | timeout | 否 | int/float | 超时时间 | 默认值:2.0秒,具体见 [Guzzle 文档](http://docs.guzzlephp.org/en/stable/request-options.html#timeout) |
82 | | security_types | 是 | array | 安全设置 | 旧机器人是不存在该项配置的传 null 或不设置该配置项;新机器人可以组合选择:自定义关键字、加签、IP地址(段) |
83 | | security_types.* | 是 | string/null | 设置项 | 枚举值:null、keywords、signature、ip |
84 | | security_signature | 否 | string | 安全模式包含加签时需要的密钥字符串 | 应当以 SEC 开头 | |
85 |
86 | ### 获取 access_token 并设置安全设置
87 |
88 | 首先在钉钉群选择添加一个群机器人(智能群助手),如果您不知道如何设置请查看 [钉钉文档](https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq),请注意这里需要设置的是"自定义"类型的机器人。
89 |
90 | 根据您的需要设置机器人名字,并选择安全设置。在完成后您将获得一个 webhook 地址,该地址中 access_token 的值即配置中使用到的 access_token 的值,
91 | 请妥善保存该 access_token。选择的安全设置就是配置中 security_types 的值,如果您选择了“加签”的安全设置,您还需要妥善保存密钥,该密钥即配置中 security_signature 的值
92 |
93 | ### HTTP 客户端注入
94 |
95 | 为了方便在某些情况下需要统一管理扩展包使用的 HTTP 客户端,提供了 `http_client_name` 配置项,以实现从 Laravel 容器中获取已经注入的 Guzzle 实例。如果您不需要手动管理 HTTP 客户端,您可以不设置该配置项或将该配置项值设置成 null。
96 |
97 | ## 使用方法
98 |
99 | Tips:为了方便快速调试功能,内置了一个使用了 Notifiable Trait 的类:Calchen\LaravelDingtalkRobot\Robot ,以下均以此对象为例,实际开发中请务必根据您项目情况进行对应处理。
100 |
101 | 首先需要先创建一个 TestDingtalkNotification ,如果是 Laravel 可通过 artisan 命令创建
102 |
103 | ```shell
104 | php artisan make:notification TestDingtalkNotification
105 | ```
106 |
107 | 如果是 Lumen 那么可能需要您手动创建 app/Notifications 文件夹并创建 TestDingtalkNotification.php 文件
108 |
109 | ```php
110 | setRobot($notifiable->getName());
156 |
157 | return $message;
158 | }
159 | }
160 | ```
161 |
162 | 根据 [Laravel](https://laravel.com/docs/6.x/notifications) 文档发送通知可以使用 Notifiable Trait
163 | ```php
164 | use Calchen\LaravelDingtalkRobot\Robot;
165 |
166 | (new Robot)->notify(new TestDingtalkNotification());
167 | ```
168 | 也可以使用 Notification Facade
169 |
170 | ```php
171 | \Notification::send(new Robot, new TestDingtalkNotification());
172 | ```
173 |
174 | **重点:**
175 |
176 | TestDingtalkNotification 中的 toDingTalkRobot 方法中可使用下面的五种消息类型。
177 |
178 | ### 文本类型消息
179 |
180 | ```php
181 | use Calchen\LaravelDingtalkRobot\Message\TextMessage;
182 |
183 | public function toDingTalkRobot($notifiable)
184 | {
185 | $message = new TextMessage('我就是我, @1825718XXXX 是不一样的烟火');
186 |
187 | // 可@某人或某些人,被@的人的手机号应出现在上面的消息体中
188 | $message->at('1825718XXXX');
189 | // $message->at(['1825718XXXX', '1825718XXXY']);
190 |
191 | // 可@全部人,被@的人的手机号不需要出现在消息体中
192 | // $message->atAll();
193 |
194 | // 这里可以指定机器人,如果不需要指定则默认使用名称为 default 的机器人
195 | $message->setRobot($notifiable->getName());
196 |
197 | return $message;
198 | }
199 | ```
200 |
201 | ### link 类型消息
202 |
203 | ```php
204 | use Calchen\LaravelDingtalkRobot\Message\LinkMessage;
205 |
206 | public function toDingTalkRobot($notifiable)
207 | {
208 | $message = new LinkMessage(
209 | '自定义机器人协议',
210 | '群机器人是钉钉群的高级扩展功能。群机器人可以将第三方服务的信息聚合到群聊中,实现自动化的信息同步。例如:通过聚合GitHub,GitLab等源码管理服务,实现源码更新同步;通过聚合Trello,JIRA等项目协调服务,实现项目信息同步。不仅如此,群机器人支持Webhook协议的自定义接入,支持更多可能性,例如:你可将运维报警提醒通过自定义机器人聚合到钉钉群。',
211 | 'https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.Rqyvqo&treeId=257&articleId=105735&docType=1'
212 | );
213 |
214 | // 也可以这样写
215 | // $message = new LinkMessage();
216 | // $message->setMessage(
217 | // '自定义机器人协议',
218 | // '群机器人是钉钉群的高级扩展功能。群机器人可以将第三方服务的信息聚合到群聊中,实现自动化的信息同步。例如:通过聚合GitHub,GitLab等源码管理服务,实现源码更新同步;通过聚合Trello,JIRA等项目协调服务,实现项目信息同步。不仅如此,群机器人支持Webhook协议的自定义接入,支持更多可能性,例如:你可将运维报警提醒通过自定义机器人聚合到钉钉群。',
219 | // 'https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.Rqyvqo&treeId=257&articleId=105735&docType=1'
220 | // );
221 |
222 | // 如果想让链接在 PC 端用系统默认浏览器打开可以这样
223 | // $message = new LinkMessage(
224 | // '自定义机器人协议',
225 | // '群机器人是钉钉群的高级扩展功能。群机器人可以将第三方服务的信息聚合到群聊中,实现自动化的信息同步。例如:通过聚合GitHub,GitLab等源码管理服务,实现源码更新同步;通过聚合Trello,JIRA等项目协调服务,实现项目信息同步。不仅如此,群机器人支持Webhook协议的自定义接入,支持更多可能性,例如:你可将运维报警提醒通过自定义机器人聚合到钉钉群。',
226 | // 'https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.Rqyvqo&treeId=257&articleId=105735&docType=1',
227 | // false
228 | // );
229 | //
230 | // $message = new LinkMessage();
231 | // $message->setMessage(
232 | // '自定义机器人协议',
233 | // '群机器人是钉钉群的高级扩展功能。群机器人可以将第三方服务的信息聚合到群聊中,实现自动化的信息同步。例如:通过聚合GitHub,GitLab等源码管理服务,实现源码更新同步;通过聚合Trello,JIRA等项目协调服务,实现项目信息同步。不仅如此,群机器人支持Webhook协议的自定义接入,支持更多可能性,例如:你可将运维报警提醒通过自定义机器人聚合到钉钉群。',
234 | // 'https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.Rqyvqo&treeId=257&articleId=105735&docType=1',
235 | // false
236 | // );
237 |
238 | // 这里可以指定机器人,如果不需要指定则默认使用名称为 default 的机器人
239 | $message->setRobot($notifiable->getName());
240 |
241 | return $message;
242 | }
243 | ```
244 |
245 | ### markdown 类型消息
246 |
247 | ```php
248 | use Calchen\LaravelDingtalkRobot\Message\MarkdownMessage;
249 |
250 | public function toDingTalkRobot($notifiable)
251 | {
252 | $message = new MarkdownMessage(
253 | '杭州天气',
254 | "#### 杭州天气 \n > 9度,@1825718XXXX 西北风1级,空气良89,相对温度73%\n\n > \n > ###### 10点20分发布 [天气](http://www.thinkpage.cn/) "
255 | );
256 |
257 | // 可@某人或某些人,被@的人的手机号应出现在上面的消息体中
258 | $message->at('1825718XXXX');
259 | // $message->at(['1825718XXXX', '1825718XXXY']);
260 |
261 | // 可@全部人,被@的人的手机号不需要出现在消息体中
262 | // $message->atAll();
263 |
264 | // 这里可以指定机器人,如果不需要指定则默认使用名称为 default 的机器人
265 | $message->setRobot($notifiable->getName());
266 |
267 | return $message;
268 | }
269 | ```
270 |
271 | ### ActionCard 类型消息
272 |
273 | #### 整体跳转类型
274 |
275 | ```php
276 | use Calchen\LaravelDingtalkRobot\Message\ActionCardMessage;
277 |
278 | public function toDingTalkRobot($notifiable)
279 | {
280 | $message = new ActionCardMessage(
281 | '乔布斯 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身',
282 | " \n #### 乔布斯 20 年前想打造的苹果咖啡厅 \n\n Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划"
283 | );
284 | $message->setSingle('阅读全文', 'https://www.dingtalk.com/');
285 |
286 | // 如果想让链接在 PC 端用系统默认浏览器打开可以这样
287 | // $message->setSingle('阅读全文', 'https://www.dingtalk.com/', false);
288 |
289 | // 这里可以指定机器人,如果不需要指定则默认使用名称为 default 的机器人
290 | $message->setRobot($notifiable->getName());
291 |
292 | return $message;
293 | }
294 | ```
295 |
296 | #### 独立跳转类型
297 |
298 | ```php
299 | use Calchen\LaravelDingtalkRobot\Message\ActionCardMessage;
300 |
301 | public function toDingTalkRobot($notifiable)
302 | {
303 | $message = new ActionCardMessage(
304 | '乔布斯 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身',
305 | " \n #### 乔布斯 20 年前想打造的苹果咖啡厅 \n\n Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划"
306 | );
307 |
308 | // 添加一个或多个按钮
309 | $message->addButton('内容不错', 'https://www.dingtalk.com/');
310 | $message->addButton('不感兴趣', 'https://www.dingtalk.com/');
311 |
312 | // 如果想让链接在 PC 端用系统默认浏览器打开可以这样
313 | // $message->addButton('不感兴趣', 'https://www.dingtalk.com/', false);
314 |
315 | // 这里可以指定机器人,如果不需要指定则默认使用名称为 default 的机器人
316 | $message->setRobot($notifiable->getName());
317 |
318 | return $message;
319 | }
320 | ```
321 |
322 | ### FeedCard 类型消息
323 |
324 | ```php
325 | use Calchen\LaravelDingtalkRobot\Message\FeedCardMessage;
326 |
327 | public function toDingTalkRobot($notifiable)
328 | {
329 | $message = new FeedCardMessage();
330 |
331 | // 添加一个或多个链接
332 | $message->addLink(
333 | '时代的火车向前开',
334 | 'https://mp.weixin.qq.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI',
335 | 'https://www.dingtalk.com/'
336 | );
337 | $message->addLink(
338 | '时代的火车向前开2',
339 | 'https://mp.weixin.qq.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI',
340 | 'https://www.dingtalk.com/'
341 | );
342 |
343 | // 如果想让链接在 PC 端用系统默认浏览器打开可以这样
344 | // $message->addLink(
345 | // '时代的火车向前开2',
346 | // 'https://mp.weixin.qq.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI',
347 | // 'https://www.dingtalk.com/',
348 | // false
349 | // );
350 |
351 | // 这里可以指定机器人,如果不需要指定则默认使用名称为 default 的机器人
352 | $message->setRobot($notifiable->getName());
353 |
354 | return $message;
355 | }
356 | ```
357 |
358 | ## 使用消息通知(Notification)给多个机器人发同一条消息
359 |
360 | ```php
361 | use Calchen\LaravelDingtalkRobot\Robot;
362 |
363 | $notification = new TestDingtalkNotification();
364 |
365 | (new Robot('robot_1'))->notify($notification);
366 | (new Robot('robot_2'))->notify($notification);
367 |
368 | // TestDingtalkNotification 文件
369 | use Calchen\LaravelDingtalkRobot\Message\TextMessage;
370 |
371 | public function toDingTalkRobot($notifiable)
372 | {
373 | $message = new TextMessage('我就是我, 是不一样的烟火');
374 |
375 | // 这里可以指定机器人,如果不需要指定则默认使用名称为 default 的机器人
376 | // 这里需要 $notifiable 内保存机器人的名称,并提供获取的方法,方可实现给不同机器人发同一条消息
377 | $message->setRobot($notifiable->getName());
378 |
379 | return $message;
380 | }
381 | ```
382 |
383 | ## 不使用消息通知(Notification)
384 |
385 | 如果在非 Laravel/Lumen 框架中使用或者您不想使用 Notification ,而是直接调用机器人接口发送信息,那么有多种方式可以实现:
386 |
387 | ### 容器解析
388 |
389 | ```php
390 | use Calchen\LaravelDingtalkRobot\DingtalkRobot;
391 | use Calchen\LaravelDingtalkRobot\Message\TextMessage;
392 |
393 | $message = new TextMessage('我就是我, 是不一样的烟火');
394 | $message->setRobot('机器人名字');
395 |
396 | app(DingtalkRobot::class)->setMessage($message)->send();
397 | ```
398 |
399 | ### 辅助函数 dingtalk_robot
400 |
401 | ```php
402 | use Calchen\LaravelDingtalkRobot\Message\TextMessage;
403 |
404 | $message = new TextMessage('我就是我, 是不一样的烟火');
405 | $message->setRobot('机器人名字');
406 |
407 | dingtalk_robot()->setMessage($message)->send();
408 | ```
409 |
410 | ### 直接创建并调用接口
411 |
412 | ```php
413 | use Calchen\LaravelDingtalkRobot\DingtalkRobot;
414 | use Calchen\LaravelDingtalkRobot\Message\TextMessage;
415 |
416 | $message = new TextMessage('我就是我, 是不一样的烟火');
417 | $message->setRobot('机器人名字');
418 |
419 | (new DingtalkRobot)->setMessage($message)->send();
420 | ```
421 |
422 | ## 从1.x升级到2.x
423 |
424 | 首先感谢您使用 1.x 版本的扩展包,因为钉钉机器人更新了安全设置,在升级过程中为了使得代码更整洁因此出现了无法兼容的情况,故此将新版升级至2.x版本。这里将明确给出两个版本的差异,以便您以最少的成本进行升级。
425 |
426 | ### 配置项的差异
427 |
428 | #### 新增
429 |
430 | ##### http_client_name
431 |
432 | 为了方便在某些情况下需要统一管理扩展包使用的 HTTP 客户端,提供了 `http_client_name` 配置项,以实现从 Laravel 容器中获取已经注入的 Guzzle 实例。如果您不需要手动管理 HTTP 客户端,您可以不设置该配置项或将该配置项值设置成 null。
433 |
434 | ##### 机器人名称.security_types
435 |
436 | 数据结构为数组。如果您使用的是旧机器人,配置项中无“安全设置”相关内容,那么您可以不设置该字段,或将其设置成:
437 |
438 | ```php
439 | 'security_types' => [
440 | null,
441 | ],
442 | ```
443 |
444 | 如果您使用的是新机器人,请按照机器人的实际情况配置该项,可以组合选择:自定义关键字、加签、IP地址(段),如:
445 |
446 | ```php
447 | 'security_types' => [
448 | 'keywords', // 自定义关键字
449 | 'signature', // 加签
450 | 'ip', // IP地址(段)
451 | ],
452 | ```
453 |
454 | ##### 机器人名称.security_signature
455 |
456 | 密钥字符串。当安全模式包含加签时该字段是必须的。请注意,该字符串前三位应该是 SEC。
457 |
458 | ## 鸣谢
459 |
460 | 感谢 [王举](https://github.com/wowiwj),他的 [wangju/ding-notice](https://github.com/wowiwj/ding-notice) 项目给予了我很多启发。本项目中的部分代码原形来自于该项目。
461 |
462 | 感谢 [aolinver](https://github.com/aolinver),他为本项目实现了部分可以设置链接的消息的链接在 PC 端用系统默认浏览器打开的功能。
463 |
464 |
465 | ## 开源协议
466 |
467 | [MIT](http://opensource.org/licenses/MIT)
468 |
--------------------------------------------------------------------------------
/README_en.md:
--------------------------------------------------------------------------------
1 | laravel-dingtalk-robot-notification
2 | Dingtalk Robot for Laravel/Lumen
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | > [中文](https://github.com/calchen/laravel-dingtalk-robot-notification/blob/master/README.md)
25 |
26 | This is Laravel/Lumen custom notification channel for [DingTalk group assistant](https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq).
27 |
28 | ## Installing
29 |
30 | Composer is recommended for installation:
31 |
32 | ```shell
33 | $ composer require calchen/laravel-dingtalk-robot-notification:^2.0
34 | ```
35 |
36 | ### Laravel
37 |
38 | For Laravel 5.5+ package auto discovery feature will help you loading everything you need. But you still need to publish the configuration file:
39 |
40 | ```shell
41 | php artisan vendor:publish --provider="Calchen\LaravelDingtalkRobot\DingtalkRobotNoticeServiceProvider"
42 | ```
43 |
44 | ### Lumen
45 |
46 | Open your `bootstrap/app.php` and add this line:
47 |
48 | ```php
49 | $app->register(Calchen\LaravelDingtalkRobot\DingtalkRobotNoticeServiceProvider::class);
50 | ```
51 |
52 | Copy configuration file from `vendor/calchen/laravel-dingtalk-robot-notification/config/dingtalk_robot.php` to `config/dingtalk_robot.php`
53 |
54 | ### Other frameworks
55 |
56 | Without considering the loading problem, please use the global function `\dingtalk_robot()` or directly create the \Calchen\LaravelDingtalkRobot\DingtalkRobot instance to send the message.
57 |
58 | ## Configuration
59 |
60 | Open your `config/dingtalk_robot.php` and add these lines:
61 |
62 | ```php
63 | 'robotName' => [
64 | 'access_token' => 'xxxx',
65 | 'timeout' => 2.0,
66 | 'security_types' => [
67 | 'signature',
68 | ],
69 | 'security_signature' => 'SECxxxx',
70 | ],
71 | ```
72 |
73 | If you need to configure more than one robot, repeat the above and give different `robotName` to different robots
74 |
75 | ### Details
76 |
77 | | key | required | type | remarks | 备注 |
78 | |-------------------- |------ |------------- |----------------------------------------------- |------------------------------------------------------------------------------------------------------------ |
79 | | http_client_name | N | string/null | Guzzle 实例的名称 | 默认值:null,注入在 Laravel 中的 Guzzle 实例的名称,以便替换 HTTP 客户端 |
80 | | robotName | Y | string | 机器人名称 | 这个名称为了区别不同的机器人 |
81 | | access_token | Y | string | 创建机器人后 Webhook URL 中 access_token 的值 | |
82 | | timeout | N | int/float | 超时时间 | 默认值:2.0秒,具体见 [Guzzle 文档](http://docs.guzzlephp.org/en/stable/request-options.html#timeout) |
83 | | security_types | Y | array | 安全设置 | 旧机器人是不存在该项配置的传 null 或不设置该配置项;新机器人可以组合选择:自定义关键字、加签、IP地址(段) |
84 | | security_types.* | Y | string/null | 设置项 | 枚举值:null、keywords、signature、ip |
85 | | security_signature | N | string | 安全模式包含加签时需要的密钥字符串 | 应当以 SEC 开头 | |
86 |
87 | ### 获取 access_token 并设置安全设置
88 |
89 | 首先在钉钉群选择添加一个群机器人(智能群助手),如果您不知道如何设置请查看 [钉钉文档](https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq),请注意这里需要设置的是"自定义"类型的机器人。
90 |
91 | 根据您的需要设置机器人名字,并选择安全设置。在完成后您将获得一个 webhook 地址,该地址中 access_token 的值即配置中使用到的 access_token 的值,
92 | 请妥善保存该 access_token。选择的安全设置就是配置中 security_types 的值,如果您选择了“加签”的安全设置,您还需要妥善保存密钥,该密钥即配置中 security_signature 的值
93 |
94 | ### HTTP 客户端注入
95 |
96 | 为了方便在某些情况下需要统一管理扩展包使用的 HTTP 客户端,提供了 `http_client_name` 配置项,以实现从 Laravel 容器中获取已经注入的 Guzzle 实例。如果您不需要手动管理 HTTP 客户端,您可以不设置该配置项或将该配置项值设置成 null。
97 |
98 | ## 使用方法
99 |
100 | Tips:为了方便快速调试功能,内置了一个使用了 Notifiable Trait 的类:Calchen\LaravelDingtalkRobot\Robot ,以下均以此对象为例,实际开发中请务必根据您项目情况进行对应处理。
101 |
102 | 首先需要先创建一个 TestDingtalkNotification ,如果是 Laravel 可通过 artisan 命令创建
103 |
104 | ```shell
105 | php artisan make:notification TestDingtalkNotification
106 | ```
107 |
108 | 如果是 Lumen 那么可能需要您手动创建 app/Notifications 文件夹并创建 TestDingtalkNotification.php 文件
109 |
110 | ```php
111 | setRobot($notifiable->getName());
157 |
158 | return $message;
159 | }
160 | }
161 | ```
162 |
163 | 根据 [Laravel](https://laravel.com/docs/6.x/notifications) 文档发送通知可以使用 Notifiable Trait
164 |
165 | ```php
166 | use Calchen\LaravelDingtalkRobot\Robot;
167 |
168 | (new Robot)->notify(new TestDingtalkNotification());
169 | ```
170 |
171 | 也可以使用 Notification Facade
172 |
173 | ```php
174 | \Notification::send(new Robot, new TestDingtalkNotification());
175 | ```
176 |
177 | **重点:**
178 |
179 | TestDingtalkNotification 中的 toDingTalkRobot 方法中可使用下面的五种消息类型。
180 |
181 | ### 文本类型消息
182 |
183 | ```php
184 | use Calchen\LaravelDingtalkRobot\Message\TextMessage;
185 |
186 | public function toDingTalkRobot($notifiable)
187 | {
188 | $message = new TextMessage('我就是我, @1825718XXXX 是不一样的烟火');
189 |
190 | // 可@某人或某些人,被@的人的手机号应出现在上面的消息体中
191 | $message->at('1825718XXXX');
192 | // $message->at(['1825718XXXX', '1825718XXXY']);
193 |
194 | // 可@全部人,被@的人的手机号不需要出现在消息体中
195 | // $message->atAll();
196 |
197 | // 这里可以指定机器人,如果不需要指定则默认使用名称为 default 的机器人
198 | $message->setRobot($notifiable->getName());
199 |
200 | return $message;
201 | }
202 | ```
203 |
204 | ### link 类型消息
205 |
206 | ```php
207 | use Calchen\LaravelDingtalkRobot\Message\LinkMessage;
208 |
209 | public function toDingTalkRobot($notifiable)
210 | {
211 | $message = new LinkMessage(
212 | '自定义机器人协议',
213 | '群机器人是钉钉群的高级扩展功能。群机器人可以将第三方服务的信息聚合到群聊中,实现自动化的信息同步。例如:通过聚合GitHub,GitLab等源码管理服务,实现源码更新同步;通过聚合Trello,JIRA等项目协调服务,实现项目信息同步。不仅如此,群机器人支持Webhook协议的自定义接入,支持更多可能性,例如:你可将运维报警提醒通过自定义机器人聚合到钉钉群。',
214 | 'https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.Rqyvqo&treeId=257&articleId=105735&docType=1'
215 | );
216 |
217 | // 也可以这样写
218 | // $message = new LinkMessage();
219 | // $message->setMessage(
220 | // '自定义机器人协议',
221 | // '群机器人是钉钉群的高级扩展功能。群机器人可以将第三方服务的信息聚合到群聊中,实现自动化的信息同步。例如:通过聚合GitHub,GitLab等源码管理服务,实现源码更新同步;通过聚合Trello,JIRA等项目协调服务,实现项目信息同步。不仅如此,群机器人支持Webhook协议的自定义接入,支持更多可能性,例如:你可将运维报警提醒通过自定义机器人聚合到钉钉群。',
222 | // 'https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.Rqyvqo&treeId=257&articleId=105735&docType=1'
223 | // );
224 |
225 | // 如果想让链接在 PC 端用系统默认浏览器打开可以这样
226 | // $message = new LinkMessage(
227 | // '自定义机器人协议',
228 | // '群机器人是钉钉群的高级扩展功能。群机器人可以将第三方服务的信息聚合到群聊中,实现自动化的信息同步。例如:通过聚合GitHub,GitLab等源码管理服务,实现源码更新同步;通过聚合Trello,JIRA等项目协调服务,实现项目信息同步。不仅如此,群机器人支持Webhook协议的自定义接入,支持更多可能性,例如:你可将运维报警提醒通过自定义机器人聚合到钉钉群。',
229 | // 'https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.Rqyvqo&treeId=257&articleId=105735&docType=1',
230 | // false
231 | // );
232 | //
233 | // $message = new LinkMessage();
234 | // $message->setMessage(
235 | // '自定义机器人协议',
236 | // '群机器人是钉钉群的高级扩展功能。群机器人可以将第三方服务的信息聚合到群聊中,实现自动化的信息同步。例如:通过聚合GitHub,GitLab等源码管理服务,实现源码更新同步;通过聚合Trello,JIRA等项目协调服务,实现项目信息同步。不仅如此,群机器人支持Webhook协议的自定义接入,支持更多可能性,例如:你可将运维报警提醒通过自定义机器人聚合到钉钉群。',
237 | // 'https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.Rqyvqo&treeId=257&articleId=105735&docType=1',
238 | // false
239 | // );
240 |
241 | // 这里可以指定机器人,如果不需要指定则默认使用名称为 default 的机器人
242 | $message->setRobot($notifiable->getName());
243 |
244 | return $message;
245 | }
246 | ```
247 |
248 | ### markdown 类型消息
249 |
250 | ```php
251 | use Calchen\LaravelDingtalkRobot\Message\MarkdownMessage;
252 |
253 | public function toDingTalkRobot($notifiable)
254 | {
255 | $message = new MarkdownMessage(
256 | '杭州天气',
257 | "#### 杭州天气 \n > 9度,@1825718XXXX 西北风1级,空气良89,相对温度73%\n\n > \n > ###### 10点20分发布 [天气](http://www.thinkpage.cn/) "
258 | );
259 |
260 | // 可@某人或某些人,被@的人的手机号应出现在上面的消息体中
261 | $message->at('1825718XXXX');
262 | // $message->at(['1825718XXXX', '1825718XXXY']);
263 |
264 | // 可@全部人,被@的人的手机号不需要出现在消息体中
265 | // $message->atAll();
266 |
267 | // 这里可以指定机器人,如果不需要指定则默认使用名称为 default 的机器人
268 | $message->setRobot($notifiable->getName());
269 |
270 | return $message;
271 | }
272 | ```
273 |
274 | ### ActionCard 类型消息
275 |
276 | #### 整体跳转类型
277 |
278 | ```php
279 | use Calchen\LaravelDingtalkRobot\Message\ActionCardMessage;
280 |
281 | public function toDingTalkRobot($notifiable)
282 | {
283 | $message = new ActionCardMessage(
284 | '乔布斯 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身',
285 | " \n #### 乔布斯 20 年前想打造的苹果咖啡厅 \n\n Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划"
286 | );
287 | $message->setSingle('阅读全文', 'https://www.dingtalk.com/');
288 |
289 | // 如果想让链接在 PC 端用系统默认浏览器打开可以这样
290 | // $message->setSingle('阅读全文', 'https://www.dingtalk.com/', false);
291 |
292 | // 这里可以指定机器人,如果不需要指定则默认使用名称为 default 的机器人
293 | $message->setRobot($notifiable->getName());
294 |
295 | return $message;
296 | }
297 | ```
298 |
299 | #### 独立跳转类型
300 |
301 | ```php
302 | use Calchen\LaravelDingtalkRobot\Message\ActionCardMessage;
303 |
304 | public function toDingTalkRobot($notifiable)
305 | {
306 | $message = new ActionCardMessage(
307 | '乔布斯 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身',
308 | " \n #### 乔布斯 20 年前想打造的苹果咖啡厅 \n\n Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划"
309 | );
310 |
311 | // 添加一个或多个按钮
312 | $message->addButton('内容不错', 'https://www.dingtalk.com/');
313 | $message->addButton('不感兴趣', 'https://www.dingtalk.com/');
314 |
315 | // 如果想让链接在 PC 端用系统默认浏览器打开可以这样
316 | // $message->addButton('不感兴趣', 'https://www.dingtalk.com/', false);
317 |
318 | // 这里可以指定机器人,如果不需要指定则默认使用名称为 default 的机器人
319 | $message->setRobot($notifiable->getName());
320 |
321 | return $message;
322 | }
323 | ```
324 |
325 | ### FeedCard 类型消息
326 |
327 | ```php
328 | use Calchen\LaravelDingtalkRobot\Message\FeedCardMessage;
329 |
330 | public function toDingTalkRobot($notifiable)
331 | {
332 | $message = new FeedCardMessage();
333 |
334 | // 添加一个或多个链接
335 | $message->addLink(
336 | '时代的火车向前开',
337 | 'https://mp.weixin.qq.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI',
338 | 'https://www.dingtalk.com/'
339 | );
340 | $message->addLink(
341 | '时代的火车向前开2',
342 | 'https://mp.weixin.qq.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI',
343 | 'https://www.dingtalk.com/'
344 | );
345 |
346 | // 如果想让链接在 PC 端用系统默认浏览器打开可以这样
347 | // $message->addLink(
348 | // '时代的火车向前开2',
349 | // 'https://mp.weixin.qq.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI',
350 | // 'https://www.dingtalk.com/',
351 | // false
352 | // );
353 |
354 | // 这里可以指定机器人,如果不需要指定则默认使用名称为 default 的机器人
355 | $message->setRobot($notifiable->getName());
356 |
357 | return $message;
358 | }
359 | ```
360 |
361 | ## 使用消息通知(Notification)给多个机器人发同一条消息
362 |
363 | ```php
364 | use Calchen\LaravelDingtalkRobot\Robot;
365 |
366 | $notification = new TestDingtalkNotification();
367 |
368 | (new Robot('robot_1'))->notify($notification);
369 | (new Robot('robot_2'))->notify($notification);
370 |
371 | // TestDingtalkNotification 文件
372 | use Calchen\LaravelDingtalkRobot\Message\TextMessage;
373 |
374 | public function toDingTalkRobot($notifiable)
375 | {
376 | $message = new TextMessage('我就是我, 是不一样的烟火');
377 |
378 | // 这里可以指定机器人,如果不需要指定则默认使用名称为 default 的机器人
379 | // 这里需要 $notifiable 内保存机器人的名称,并提供获取的方法,方可实现给不同机器人发同一条消息
380 | $message->setRobot($notifiable->getName());
381 |
382 | return $message;
383 | }
384 | ```
385 |
386 | ## 不使用消息通知(Notification)
387 |
388 | 如果在非 Laravel/Lumen 框架中使用或者您不想使用 Notification ,而是直接调用机器人接口发送信息,那么有多种方式可以实现:
389 |
390 | ### 容器解析
391 |
392 | ```php
393 | use Calchen\LaravelDingtalkRobot\DingtalkRobot;
394 | use Calchen\LaravelDingtalkRobot\Message\TextMessage;
395 |
396 | $message = new TextMessage('我就是我, 是不一样的烟火');
397 | $message->setRobot('机器人名字');
398 |
399 | app(DingtalkRobot::class)->setMessage($message)->send();
400 | ```
401 |
402 | ### 辅助函数 dingtalk_robot
403 |
404 | ```php
405 | use Calchen\LaravelDingtalkRobot\Message\TextMessage;
406 |
407 | $message = new TextMessage('我就是我, 是不一样的烟火');
408 | $message->setRobot('机器人名字');
409 |
410 | dingtalk_robot()->setMessage($message)->send();
411 | ```
412 |
413 | ### 直接创建并调用接口
414 |
415 | ```php
416 | use Calchen\LaravelDingtalkRobot\DingtalkRobot;
417 | use Calchen\LaravelDingtalkRobot\Message\TextMessage;
418 |
419 | $message = new TextMessage('我就是我, 是不一样的烟火');
420 | $message->setRobot('机器人名字');
421 |
422 | (new DingtalkRobot)->setMessage($message)->send();
423 | ```
424 |
425 | ## 从1.x升级到2.x
426 |
427 | 首先感谢您使用 1.x 版本的扩展包,因为钉钉机器人更新了安全设置,在升级过程中为了使得代码更整洁因此出现了无法兼容的情况,故此将新版升级至2.x版本。这里将明确给出两个版本的差异,以便您以最少的成本进行升级。
428 |
429 | ### 配置项的差异
430 |
431 | #### 新增
432 |
433 | ##### http_client_name
434 |
435 | 为了方便在某些情况下需要统一管理扩展包使用的 HTTP 客户端,提供了 `http_client_name` 配置项,以实现从 Laravel 容器中获取已经注入的 Guzzle 实例。如果您不需要手动管理 HTTP 客户端,您可以不设置该配置项或将该配置项值设置成 null。
436 |
437 | ##### 机器人名称.security_types
438 |
439 | 数据结构为数组。如果您使用的是旧机器人,配置项中无“安全设置”相关内容,那么您可以不设置该字段,或将其设置成:
440 |
441 | ```php
442 | 'security_types' => [
443 | null,
444 | ],
445 | ```
446 |
447 | 如果您使用的是新机器人,请按照机器人的实际情况配置该项,可以组合选择:自定义关键字、加签、IP地址(段),如:
448 |
449 | ```php
450 | 'security_types' => [
451 | 'keywords', // 自定义关键字
452 | 'signature', // 加签
453 | 'ip', // IP地址(段)
454 | ],
455 | ```
456 |
457 | ##### 机器人名称.security_signature
458 |
459 | 密钥字符串。当安全模式包含加签时该字段是必须的。请注意,该字符串前三位应该是 SEC。
460 |
461 | ## 鸣谢
462 |
463 | 感谢 [王举](https://github.com/wowiwj),他的 [wangju/ding-notice](https://github.com/wowiwj/ding-notice) 项目给予了我很多启发。本项目中的部分代码原形来自于该项目。
464 |
465 | 感谢 [aolinver](https://github.com/aolinver),他为本项目实现了部分可以设置链接的消息的链接在 PC 端用系统默认浏览器打开的功能。
466 |
467 |
468 | ## 开源协议
469 |
470 | [MIT](http://opensource.org/licenses/MIT)
471 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "calchen/laravel-dingtalk-robot-notification",
3 | "description": "钉钉智能群助手 Laravel/Lumen 消息通知扩展包(Dingtalk robot message notifications for Laravel/Lumen)",
4 | "keywords": [
5 | "laravel",
6 | "lumen",
7 | "notification",
8 | "laravel notification",
9 | "lumen notification",
10 | "钉钉",
11 | "钉钉群机器人",
12 | "群机器人",
13 | "钉钉智能群助手",
14 | "智能群助手",
15 | "dingtalk",
16 | "group assistant",
17 | "robot",
18 | "group robot",
19 | "custom channel",
20 | "丁丁",
21 | "dingding",
22 | "ding"
23 | ],
24 | "license": "MIT",
25 | "authors": [
26 | {
27 | "name": "陈恺垣",
28 | "email": "contact@chenky.com"
29 | }
30 | ],
31 | "require": {
32 | "php": "^7.0",
33 | "ext-json": "*",
34 | "guzzlehttp/guzzle": "^6.3.1|^7.0.1",
35 | "illuminate/notifications": "^5.5|^6.0|^7.0|^8.0",
36 | "illuminate/support": "^5.5|^6.0|^7.0|^8.0"
37 | },
38 | "require-dev": {
39 | "php-coveralls/php-coveralls": "^2.1",
40 | "orchestra/testbench": "^3.5|^4.0|^5.0|^6.0"
41 | },
42 | "autoload": {
43 | "psr-4": {
44 | "Calchen\\LaravelDingtalkRobot\\": "src"
45 | },
46 | "files": [
47 | "src/helpers.php"
48 | ]
49 | },
50 | "autoload-dev": {
51 | "psr-4": {
52 | "Calchen\\LaravelDingtalkRobot\\Test\\": "tests"
53 | }
54 | },
55 | "extra": {
56 | "laravel": {
57 | "providers": [
58 | "Calchen\\LaravelDingtalkRobot\\DingtalkRobotNoticeServiceProvider"
59 | ],
60 | "aliases": {
61 | "DingtalkRobot": "Calchen\\LaravelDingtalkRobot\\Facade"
62 | }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/config/dingtalk_robot.php:
--------------------------------------------------------------------------------
1 | null,
7 | // 默认发送的机器人
8 | 'default' => [
9 | // 机器人的 access_token
10 | 'access_token' => env('DINGTALK_ROBOT_ACCESS_TOKEN'),
11 |
12 | // 请求的超时时间
13 | 'timeout' => env('DINGTALK_ROBOT_TIMEOUT', 2.0),
14 |
15 | // 安全设置
16 | // 多个配置项以数组形式设置(见:\Calchen\LaravelDingtalkRobot\DingtalkRobot::SECURITY_TYPES)
17 | 'security_types' => [
18 | 'keywords',
19 | 'signature',
20 | ],
21 |
22 | // types 中包含 signature 时,该字段是必须的,为密钥字符串
23 | 'security_signature' => env('DINGTALK_ROBOT_SECURITY_SIGNATURE'),
24 | ],
25 | ];
26 |
--------------------------------------------------------------------------------
/src/DingtalkRobot.php:
--------------------------------------------------------------------------------
1 | $name,
86 | ]);
87 | throw new InvalidConfigurationException($message, ErrorCodes::INVALID_ROBOT_NAME);
88 | }
89 |
90 | $config = $configs[$name];
91 |
92 | // access_token 必须有
93 | if (! isset($config['access_token'])) {
94 | throw new InvalidConfigurationException(null, ErrorCodes::ACCESS_TOKEN_IS_NECESSARY);
95 | }
96 |
97 | // security_types 只能是允许的值,默认认为是旧机器人
98 | $securityTypes = Arr::get($config, 'security_types', [null]);
99 | if (! is_array($securityTypes) || count($securityTypes) == 0 ||
100 | count(array_diff($securityTypes, self::SECURITY_TYPES)) != 0
101 | ) {
102 | throw new InvalidConfigurationException(null, ErrorCodes::INVALID_SECURITY_TYPES);
103 | }
104 | // security_types 中有 null 表示旧机器人,旧机器人没有任何安全设置,故不应该有任何其他设置
105 | if (in_array(null, $securityTypes) && count($securityTypes) > 1) {
106 | throw new InvalidConfigurationException(null, ErrorCodes::OLD_ROBOT_SECURITY_TYPE_INVALID);
107 | }
108 |
109 | // 签名类型的需要有 security_signature,签名以 SEC 开头
110 | $securitySignature = Arr::get($config, 'security_signature');
111 | if (in_array(self::SECURITY_TYPES[2], $securityTypes)) {
112 | if (is_null($securitySignature)) {
113 | throw new InvalidConfigurationException(null, ErrorCodes::SECURITY_SIGNATURE_IS_NECESSARY);
114 | }
115 |
116 | if (! is_string($securitySignature) || strpos($securitySignature, 'SEC') !== 0) {
117 | throw new InvalidConfigurationException(null, ErrorCodes::INVALID_SECURITY_SIGNATURE);
118 | }
119 | }
120 |
121 | $this->config = $config;
122 |
123 | return $this;
124 | }
125 |
126 | /**
127 | * 将创建好的 message 对象保存到当前对象中以便后续发送
128 | *
129 | * @param Message $message
130 | * @return $this
131 | *
132 | * @throws Exception
133 | */
134 | public function setMessage(Message $message): self
135 | {
136 | $this->message = $message;
137 | $this->robot($message->getRobot());
138 |
139 | return $this;
140 | }
141 |
142 | /**
143 | * 获取 message 对象的内容.
144 | *
145 | * @return array
146 | */
147 | public function getMessage(): array
148 | {
149 | return $this->message->getMessage();
150 | }
151 |
152 | /**
153 | * 获取 http 客户端
154 | * 如果容器已经注入了可记录日志的 guzzle 优先使用.
155 | *
156 | * @return ClientInterface
157 | */
158 | private function getHttpClient(): ClientInterface
159 | {
160 | $configs = config('dingtalk_robot');
161 | if (isset($configs['http_client_name']) && app()->has($configs['http_client_name'])) {
162 | $client = app($configs['http_client_name']);
163 | if ($client instanceof ClientInterface) {
164 | return $client;
165 | }
166 | }
167 |
168 | if (! self::$httpClient instanceof ClientInterface) {
169 | self::$httpClient = new GuzzleClient([
170 | 'timeout' => $this->config['timeout'] ?? 2.0,
171 | ]);
172 | }
173 |
174 | return self::$httpClient;
175 | }
176 |
177 | /**
178 | * 发起请求,返回的内容与直接调用钉钉接口返回的内容一致.
179 | *
180 | * @return array
181 | *
182 | * @throws Exception
183 | */
184 | public function send(): array
185 | {
186 | if (is_null($this->message)) {
187 | throw new InvalidConfigurationException(null, ErrorCodes::MESSAGE_REQUIRED);
188 | }
189 |
190 | $query = [
191 | 'access_token' => $this->config['access_token'],
192 | ];
193 |
194 | // 在请求接口前根据安全设置进行处理
195 | $securityTypes = $this->config['security_types'];
196 | // 签名的安全设置
197 | if (in_array(self::SECURITY_TYPES[2], $securityTypes)) {
198 | $securitySignature = $this->config['security_signature'];
199 | // 这里出文档要求:当前时间戳,单位是毫秒,与请求调用时间误差不能超过1小时
200 | // 故此简单乘以1000,没有用 microtime
201 | $query['timestamp'] = time() * 1000;
202 | $strToSign = $query['timestamp']."\n".$securitySignature;
203 | $query['sign'] = base64_encode(hash_hmac('sha256', $strToSign, $securitySignature, true));
204 | }
205 |
206 | /** @var ResponseInterface $response */
207 | $response = $this->getHttpClient()->post(
208 | $this->robotUrl,
209 | [
210 | 'json' => $this->message->getMessage(),
211 | 'headers' => [
212 | 'Content-Type' => 'application/json',
213 | ],
214 | 'query' => $query,
215 | ]
216 | );
217 |
218 | $result = $response->getBody()->getContents();
219 | if ($response->getStatusCode() !== 200) {
220 | throw new HttpException($result, ErrorCodes::RESPONSE_FAILED);
221 | }
222 | $result = json_decode($result, true);
223 | if (is_null($result)) {
224 | throw new Exception($result, ErrorCodes::RESPONSE_BODY_ERROR);
225 | }
226 |
227 | if (isset($result['errcode']) && $result['errcode'] != 0) {
228 | // 单独处理安全设置失败
229 | if ($result['errcode'] === 310000) {
230 | $message = __(ErrorCodes::MESSAGES[ErrorCodes::SECURITY_VERIFICATION_FAILED], [
231 | 'message' => $result['errmsg'],
232 | ]);
233 | throw new Exception($message, ErrorCodes::SECURITY_VERIFICATION_FAILED);
234 | } else {
235 | $message = __(ErrorCodes::MESSAGES[ErrorCodes::RESPONSE_RESULT_UNKNOWN_ERROR], [
236 | 'code' => $result['errcode'],
237 | 'message' => $result['errmsg'],
238 | ]);
239 | throw new Exception($message, ErrorCodes::RESPONSE_RESULT_UNKNOWN_ERROR);
240 | }
241 | }
242 |
243 | return $result;
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/src/DingtalkRobotChannel.php:
--------------------------------------------------------------------------------
1 | toDingTalkRobot($notifiable);
29 | if (! $message instanceof Message) {
30 | throw new Exception(null, ErrorCodes::SHOULD_BE_INSTANCEOF_MESSAGE);
31 | }
32 |
33 | $ding = new DingtalkRobot();
34 | $ding->setMessage($message)->send();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/DingtalkRobotNoticeServiceProvider.php:
--------------------------------------------------------------------------------
1 | setupConfig();
19 | }
20 |
21 | /**
22 | * 处理配置项.
23 | *
24 | * @return void
25 | */
26 | protected function setupConfig()
27 | {
28 | $source = realpath($raw = __DIR__.'/../config/dingtalk_robot.php') ?: $raw;
29 |
30 | if ($this->app instanceof LaravelApplication && $this->app->runningInConsole()) {
31 | $this->publishes([
32 | $source => config_path('dingtalk_robot.php'),
33 | ]);
34 | } elseif ($this->app instanceof LumenApplication) {
35 | $this->app->configure('dingtalk_robot');
36 | }
37 |
38 | $this->mergeConfigFrom($source, 'dingtalk_robot');
39 | }
40 |
41 | /**
42 | * 注册服务
43 | *
44 | * @return void
45 | */
46 | public function register()
47 | {
48 | $this->app->singleton(DingtalkRobot::class, function ($app) {
49 | return new DingtalkRobot();
50 | });
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Exceptions/ErrorCodes.php:
--------------------------------------------------------------------------------
1 | 'http request failed',
33 | self::RESPONSE_BODY_ERROR => 'response body decode failed',
34 |
35 | self::INVALID_ROBOT_NAME => 'robot name: :name not exist. Please check your config in file dingtalk_robot.php',
36 | self::ACCESS_TOKEN_IS_NECESSARY => 'access_token is necessary in config',
37 | self::INVALID_SECURITY_TYPES => 'security type in config is invalid, only value in '.DingtalkRobot::class.'::SECURITY_TYPES is acceptable',
38 | self::INVALID_SECURITY_SIGNATURE => 'security signature is invalid',
39 | self::HTTP_CLIENT_NAME_INVALID => 'http_client_name is invalid',
40 | self::MOBILES_INVALID => 'mobiles should be string or array',
41 | self::HIDE_AVATAR_INVALID => 'hideAvatar value can only be 0 or 1',
42 | self::BTN_ORIENTATION_INVALID => 'btnOrientation value can only be 0 or 1',
43 | self::SECURITY_VERIFICATION_FAILED => 'security verification failed, reason is: :message',
44 | self::RESPONSE_RESULT_UNKNOWN_ERROR => 'response result is unknown error, code is: :code, message is: :message',
45 | self::SHOULD_BE_INSTANCEOF_MESSAGE => 'function toDingTalkRobot in the $notification should return instanceof '.Message::class,
46 | self::MESSAGE_REQUIRED => 'Please set message object',
47 | self::DINGTALK_ROBOT_CONFIG_IS_EMPTY => 'Please set dingtalk_robot config',
48 | self::OLD_ROBOT_SECURITY_TYPE_INVALID => 'old robot security types could only has null',
49 | self::SECURITY_SIGNATURE_IS_NECESSARY => 'security signature is necessary when security types has signature',
50 | ];
51 | }
52 |
--------------------------------------------------------------------------------
/src/Exceptions/Exception.php:
--------------------------------------------------------------------------------
1 | setMessage($title, $text, $hideAvatar, $btnOrientation);
45 | }
46 | }
47 |
48 | /**
49 | * ActionCard 的整体跳转和独立跳转两种类型中 title text hideAvatar btnOrientation 都是共同拥有的.
50 | *
51 | * @param string $title 首屏会话透出的展示内容
52 | * @param string $text markdown 格式的消息
53 | * @param int|null $hideAvatar 0-按钮竖直排列,1-按钮横向排列
54 | * @param int|null $btnOrientation 0-正常发消息者头像,1-隐藏发消息者头像
55 | * @return ActionCardMessage
56 | *
57 | * @throws InvalidConfigurationException
58 | */
59 | public function setMessage(string $title, string $text, $hideAvatar = null, $btnOrientation = null): self
60 | {
61 | $this->message = [
62 | 'msgtype' => 'actionCard',
63 | 'actionCard' => [
64 | 'title' => $title,
65 | 'text' => $text,
66 | ],
67 | ];
68 |
69 | if (! is_null($hideAvatar)) {
70 | if (! in_array($hideAvatar, self::HIDE_AVATAR_VALUES)) {
71 | throw new InvalidConfigurationException(null, ErrorCodes::HIDE_AVATAR_INVALID);
72 | }
73 | $this->message['actionCard']['hideAvatar'] = $hideAvatar;
74 | }
75 | if (! is_null($btnOrientation)) {
76 | if (! in_array($btnOrientation, self::BTN_ORIENTATION_VALUES)) {
77 | throw new InvalidConfigurationException(null, ErrorCodes::BTN_ORIENTATION_INVALID);
78 | }
79 | $this->message['actionCard']['btnOrientation'] = $btnOrientation;
80 | }
81 |
82 | return $this;
83 | }
84 |
85 | /**
86 | * 设置整体跳转类型参数,与独立跳转互斥.
87 | *
88 | * @param string $singleTitle 单个按钮的方案。(设置此项和singleURL后btns无效。)
89 | * @param string $singleUrl 点击singleTitle按钮触发的URL
90 | * @param bool $pcSlide 链接是否在 PC 端侧栏打开,true 在 PC 端侧栏打开;false 在系统默认浏览器
91 | * @return $this
92 | */
93 | public function setSingle(string $singleTitle, string $singleUrl, bool $pcSlide = true): self
94 | {
95 | $this->message['actionCard']['singleTitle'] = $singleTitle;
96 | $this->message['actionCard']['singleURL'] = $this->getFinalUrl($singleUrl, $pcSlide);
97 | unset($this->message['actionCard']['btns']);
98 |
99 | return $this;
100 | }
101 |
102 | /**
103 | * 设置独立跳转类型按钮参数,可设置多个,与整体跳转互斥.
104 | *
105 | * @param string $title 按钮方案
106 | * @param string $actionUrl 点击按钮触发的URL
107 | * @param bool $pcSlide 链接是否在 PC 端侧栏打开,true 在 PC 端侧栏打开;false 在系统默认浏览器
108 | * @return ActionCardMessage
109 | */
110 | public function addButton(string $title, string $actionUrl, bool $pcSlide = true): self
111 | {
112 | $this->message['actionCard']['btns'][] = [
113 | 'title' => $title,
114 | 'actionURL' => $this->getFinalUrl($actionUrl, $pcSlide),
115 | ];
116 | unset($this->message['actionCard']['singleTitle']);
117 | unset($this->message['actionCard']['singleURL']);
118 |
119 | return $this;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/Message/AtTrait.php:
--------------------------------------------------------------------------------
1 | at['atMobiles'] = $mobiles;
32 |
33 | return $this;
34 | }
35 |
36 | /**
37 | * @所有人
38 | *
39 | * @return Message
40 | */
41 | public function atAll(): self
42 | {
43 | $this->at['isAtAll'] = true;
44 |
45 | return $this;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Message/FeedCardMessage.php:
--------------------------------------------------------------------------------
1 | setMessage();
18 | }
19 |
20 | public function setMessage()
21 | {
22 | $this->message = [
23 | 'msgtype' => 'feedCard',
24 | 'feedCard' => [
25 | 'links' => [],
26 | ],
27 | ];
28 | }
29 |
30 | /**
31 | * 增加链接.
32 | *
33 | * @param string $title 单条信息文本
34 | * @param string $messageUrl 点击单条信息到跳转链接
35 | * @param string $picUrl 单条信息后面图片的URL
36 | * @param bool $pcSlide 链接是否在 PC 端侧栏打开,true 在 PC 端侧栏打开;false 在系统默认浏览器
37 | * @return FeedCardMessage
38 | */
39 | public function addLink(string $title, string $messageUrl, string $picUrl, bool $pcSlide = true): self
40 | {
41 | $this->message['feedCard']['links'][] = [
42 | 'title' => $title,
43 | 'messageURL' => $this->getFinalUrl($messageUrl, $pcSlide),
44 | 'picURL' => $picUrl,
45 | ];
46 |
47 | return $this;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Message/LinkMessage.php:
--------------------------------------------------------------------------------
1 | setMessage($title, $text, $messageUrl, $picUrl, $pcSlide);
30 | }
31 | }
32 |
33 | /**
34 | * @param string $title 消息标题
35 | * @param string $text 消息内容。如果太长只会部分展示
36 | * @param string $messageUrl 点击消息跳转的 URL
37 | * @param string $picUrl 图片 URL
38 | * @param bool $pcSlide 链接是否在 PC 端侧栏打开,true 在 PC 端侧栏打开;false 在系统默认浏览器
39 | * @return LinkMessage
40 | */
41 | public function setMessage(
42 | string $title,
43 | string $text,
44 | string $messageUrl,
45 | string $picUrl = '',
46 | bool $pcSlide = true
47 | ): self {
48 | $this->message = [
49 | 'msgtype' => 'link',
50 | 'link' => [
51 | 'title' => $title,
52 | 'text' => $text,
53 | 'picUrl' => $picUrl,
54 | 'messageUrl' => $this->getFinalUrl($messageUrl, $pcSlide),
55 | ],
56 | ];
57 |
58 | return $this;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Message/MarkdownMessage.php:
--------------------------------------------------------------------------------
1 | setMessage($title, $text);
24 | }
25 | }
26 |
27 | /**
28 | * @param string $title 首屏会话透出的展示内容
29 | * @param string $text markdown 格式的消息
30 | * @return MarkdownMessage
31 | */
32 | public function setMessage(string $title, string $text): self
33 | {
34 | $this->message = [
35 | 'msgtype' => 'markdown',
36 | 'markdown' => [
37 | 'title' => $title,
38 | 'text' => $text,
39 | ],
40 | ];
41 |
42 | return $this;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Message/Message.php:
--------------------------------------------------------------------------------
1 | message + ['at' => $this->at];
29 | }
30 |
31 | /**
32 | * 设置接受消息的机器人名称.
33 | *
34 | * @param string $robot
35 | * @return Message
36 | */
37 | public function setRobot(string $robot): self
38 | {
39 | $this->robot = $robot;
40 |
41 | return $this;
42 | }
43 |
44 | /**
45 | * 获取机器人名称.
46 | *
47 | * @return string
48 | */
49 | public function getRobot(): string
50 | {
51 | return $this->robot;
52 | }
53 |
54 | /**
55 | * 将 URL 转换成指定的 schema 形式,以便控制其 PC 端打开方式为钉钉侧边栏或系统默认浏览器.
56 | *
57 | * @link https://ding-doc.dingtalk.com/doc#/serverapi2/iat9q8/e300ae98
58 | *
59 | * @param string $url
60 | * @param bool $pcSlide
61 | * @return string
62 | */
63 | public function getFinalUrl(string $url, bool $pcSlide = true): string
64 | {
65 | return sprintf(
66 | 'dingtalk://dingtalkclient/page/link?url=%s&pc_slide=%s', urlencode($url), $pcSlide ? 'true' : 'false'
67 | );
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Message/TextMessage.php:
--------------------------------------------------------------------------------
1 | setMessage($content);
23 | }
24 | }
25 |
26 | /**
27 | * @param string $content 消息内容
28 | * @return TextMessage
29 | */
30 | public function setMessage(string $content): self
31 | {
32 | $this->message = [
33 | 'msgtype' => 'text',
34 | 'text' => [
35 | 'content' => $content,
36 | ],
37 | ];
38 |
39 | return $this;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Robot.php:
--------------------------------------------------------------------------------
1 | setName($name);
23 | }
24 |
25 | /**
26 | * @return string
27 | */
28 | public function getName(): string
29 | {
30 | return $this->name;
31 | }
32 |
33 | /**
34 | * @param string $name
35 | */
36 | public function setName(string $name)
37 | {
38 | $this->name = $name;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/helpers.php:
--------------------------------------------------------------------------------
1 |