Skip to content

Commit cea4c52

Browse files
committed
完善相关接口
1 parent 1d28174 commit cea4c52

29 files changed

+1017
-156
lines changed

composer.json

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
{
22
"name": "calchen/laravel-dingtalk-robot-notification",
3-
"description": "钉钉群机器人 Laravel/Lumen 消息通知扩展包(Dingtalk robot message notifications for Laravel/Lumen)",
4-
"keywords": ["laravel", "lumen", "dingtalk", "laravel notification"],
3+
"description": "钉钉智能群助手 Laravel/Lumen 消息通知扩展包(Dingtalk robot message notifications for Laravel/Lumen)",
4+
"keywords": [
5+
"laravel",
6+
"lumen",
7+
"dingtalk",
8+
"laravel notification",
9+
"钉钉",
10+
"钉钉群机器人",
11+
"群机器人",
12+
"钉钉智能群助手",
13+
"智能群助手"
14+
],
515
"license": "MIT",
616
"authors": [
717
{
@@ -11,6 +21,7 @@
1121
],
1222
"require": {
1323
"php": "^7.0",
24+
"ext-json": "*",
1425
"guzzlehttp/guzzle": "^6.0",
1526
"illuminate/notifications": "^5.5|^6.0"
1627
},
@@ -29,7 +40,7 @@
2940
},
3041
"autoload-dev": {
3142
"psr-4": {
32-
"Calchen\\LaravelOcr\\Test\\": "tests"
43+
"Calchen\\LaravelDingtalkRobot\\Test\\": "tests"
3344
}
3445
},
3546
"extra": {

config/dingtalk_robot.php

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<?php
22

33
return [
4+
// 这里注入到 Laravel 容器中的 HTTP 客户端的名称,如果不填写则默认创建
5+
// 提供这个功能是为了方便用户替换自己使用的 HTTP 客户端,
6+
'http_client_name' => null,
47
// 默认发送的机器人
58
'default' => [
69
// 机器人的 access_token

phpunit.xml.dist

+6-5
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@
2020
</filter>
2121

2222
<php>
23-
<env name="TENCENT_CLOUD_SECRET_ID" value=""/>
24-
<env name="TENCENT_CLOUD_SECRET_KEY" value=""/>
25-
<env name="TENCENT_CLOUD_OCR_ENDPOINT" value="ocr.tencentcloudapi.com"/>
26-
<env name="TENCENT_CLOUD_OCR_REGION" value=""/>
27-
<env name="TENCENT_CLOUD_APP_ID" value=""/>
23+
<env name="AT_PERSON_PHONE" value=""/>
24+
<env name="DINGTALK_ROBOT_DEFAULT_ACCESS_TOKEN" value=""/>
25+
<env name="DINGTALK_ROBOT_KEYWORDS_ACCESS_TOKEN" value=""/>
26+
<env name="DINGTALK_ROBOT_SIGNATURE_ACCESS_TOKEN" value=""/>
27+
<env name="DINGTALK_ROBOT_SIGNATURE_SECURITY_VALUES" value=""/>
28+
<env name="DINGTALK_ROBOT_ACCESS_TOKEN" value=""/>
2829
</php>
2930

3031
<logging>

src/DingtalkRobot.php

+92-31
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
namespace Calchen\LaravelDingtalkRobot;
44

5-
use Calchen\LaravelDingtalkRobot\Exception\Exception;
6-
use Calchen\LaravelDingtalkRobot\Exception\HttpException;
7-
use Calchen\LaravelDingtalkRobot\Exception\InvalidConfigurationException;
85
use Calchen\LaravelDingtalkRobot\Exceptions\ErrorCodes;
6+
use Calchen\LaravelDingtalkRobot\Exceptions\Exception;
7+
use Calchen\LaravelDingtalkRobot\Exceptions\HttpException;
8+
use Calchen\LaravelDingtalkRobot\Exceptions\InvalidConfigurationException;
99
use Calchen\LaravelDingtalkRobot\Message\Message;
10-
use GuzzleHttp\Client;
10+
use GuzzleHttp\Client as GuzzleClient;
11+
use GuzzleHttp\ClientInterface;
12+
use Illuminate\Support\Facades\App;
13+
use Psr\Http\Message\ResponseInterface;
1114

1215
/**
1316
* 钉钉群消息机器 API.
@@ -16,6 +19,11 @@
1619
*/
1720
class DingtalkRobot
1821
{
22+
/**
23+
* @var GuzzleClient
24+
*/
25+
protected static $httpClient;
26+
1927
/**
2028
* 允许的安全设置
2129
*/
@@ -26,6 +34,9 @@ class DingtalkRobot
2634
'ip', // IP地址(段)
2735
];
2836

37+
/**
38+
* @var array
39+
*/
2940
protected $config;
3041
/**
3142
* @var string
@@ -40,11 +51,11 @@ class DingtalkRobot
4051
protected $message = null;
4152

4253
/**
43-
* Dingtalk constructor.
54+
* DingtalkRobot constructor.
4455
*/
4556
public function __construct()
4657
{
47-
$this->robot();
58+
//
4859
}
4960

5061
/**
@@ -53,12 +64,17 @@ public function __construct()
5364
* @param string $name
5465
*
5566
* @return $this
56-
* @throws \Exception
67+
* @throws Exception
5768
*/
5869
public function robot($name = 'default'): self
5970
{
6071
$configs = config('dingtalk_robot');
6172

73+
// http_client_name 只能是 string
74+
if (isset($configs['http_client_name']) && !is_string($configs['http_client_name'])) {
75+
throw new InvalidConfigurationException(null, ErrorCodes::HTTP_CLIENT_NAME_INVALID);
76+
}
77+
6278
// name 必须存在
6379
if (!isset($configs[$name])) {
6480
$message = __(ErrorCodes::MESSAGES[ErrorCodes::INVALID_ROBOT_NAME], [
@@ -110,7 +126,7 @@ public function robot($name = 'default'): self
110126
* @param Message $message
111127
*
112128
* @return $this
113-
* @throws \Exception
129+
* @throws Exception
114130
*/
115131
public function setMessage(Message $message): self
116132
{
@@ -130,50 +146,95 @@ public function getMessage(): array
130146
return $this->message->getMessage();
131147
}
132148

149+
/**
150+
* 获取 http 客户端
151+
* 如果容器已经注入了可记录日志的 guzzle 优先使用
152+
*
153+
* @return ClientInterface
154+
*/
155+
private function getHttpClient(): ClientInterface
156+
{
157+
if (!(self::$httpClient instanceof ClientInterface)) {
158+
$configs = config('dingtalk_robot');
159+
if (isset($configs['http_client_name']) && class_exists($configs['http_client_name'])) {
160+
self::$httpClient = App::make($configs['http_client_name']);
161+
}
162+
163+
if (!(self::$httpClient instanceof ClientInterface)) {
164+
self::$httpClient = new GuzzleClient([
165+
'timeout' => $this->config['timeout'] ?? 2.0,
166+
]);
167+
}
168+
}
169+
170+
return self::$httpClient;
171+
}
172+
133173
/**
134174
* 发起请求,返回的内容与直接调用钉钉接口返回的内容一致.
135175
*
136-
* @return bool|string
176+
* @return array
137177
* @throws Exception
138178
*/
139-
public function send(): string
179+
public function send(): array
140180
{
141181
if (is_null($this->message)) {
142182
throw new InvalidConfigurationException('Please set message object');
143183
}
144184

145-
$client = new Client([
146-
'timeout' => $this->config['timeout'] ?? 2.0,
147-
]);
148-
149185
$query = [
150-
'access_token' => $this->config['access_token']
186+
'access_token' => $this->config['access_token'],
151187
];
152188

153189
// 在请求接口前根据安全设置进行处理
154190
$securityType = $this->config['security_type'];
155191
$securityValues = $this->config['security_values'];
192+
// 签名的安全设置
156193
if ($securityType == self::SECURITY_TYPES[2]) {
157-
$query['timestamp'] = time();
194+
// 这里出文档要求:当前时间戳,单位是毫秒,与请求调用时间误差不能超过1小时
195+
// 故此简单乘以1000,没有用 microtime
196+
$query['timestamp'] = time() * 1000;
158197
$strToSign = $query['timestamp']."\n".$securityValues;
159198
$query['sign'] = base64_encode(hash_hmac('sha256', $strToSign, $securityValues, true));
160199
}
161200

162-
try {
163-
$response = $client->post(
164-
$this->robotUrl,
165-
[
166-
'json' => $this->message->getMessage(),
167-
'headers' => [
168-
'Content-Type' => 'application/json',
169-
],
170-
'query' => $query,
171-
]
172-
);
173-
174-
return $response->getBody()->getContents();
175-
} catch (Exception $e) {
176-
throw new HttpException($e->getMessage(), $e->getCode(), $e);
201+
/** @var ResponseInterface $response */
202+
$response = $this->getHttpClient()->post(
203+
$this->robotUrl,
204+
[
205+
'json' => $this->message->getMessage(),
206+
'headers' => [
207+
'Content-Type' => 'application/json',
208+
],
209+
'query' => $query,
210+
]
211+
);
212+
213+
$result = $response->getBody()->getContents();
214+
if ($response->getStatusCode() !== 200) {
215+
throw new HttpException($result, ErrorCodes::RESPONSE_FAILED);
216+
}
217+
$result = json_decode($result, true);
218+
if (is_null($result)) {
219+
throw new Exception($result, ErrorCodes::RESPONSE_BODY_ERROR);
220+
}
221+
222+
if (isset($result['errcode']) && $result['errcode'] != 0) {
223+
// 单独处理安全设置失败
224+
if ($result['errcode'] === 310000) {
225+
$message = __(ErrorCodes::MESSAGES[ErrorCodes::SECURITY_VERIFICATION_FAILED], [
226+
'message' => $result['errmsg'],
227+
]);
228+
throw new Exception($message, ErrorCodes::SECURITY_VERIFICATION_FAILED);
229+
} else {
230+
$message = __(ErrorCodes::MESSAGES[ErrorCodes::RESPONSE_RESULT_UNKNOWN_ERROR], [
231+
'code' => $result['errcode'],
232+
'message' => $result['errmsg'],
233+
]);
234+
throw new Exception($message, ErrorCodes::RESPONSE_RESULT_UNKNOWN_ERROR);
235+
}
177236
}
237+
238+
return $result;
178239
}
179240
}

src/Exceptions/ErrorCodes.php

+27-11
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,39 @@
99
class ErrorCodes
1010
{
1111
// 1-999 用于通用基本错误
12-
//
12+
const RESPONSE_FAILED = 1;
13+
const RESPONSE_BODY_ERROR = 2;
1314

1415
// 1001-9999 用于基础业务错误
15-
const INVALID_ROBOT_NAME = 1001;
16-
const ACCESS_TOKEN_IS_NECESSARY = 1002;
17-
const INVALID_SECURITY_TYPE = 1003;
18-
const SECURITY_VALUES_IS_NECESSARY = 1004;
19-
const INVALID_SECURITY_VALUES_KEYWORDS = 1005;
16+
const INVALID_ROBOT_NAME = 1001;
17+
const ACCESS_TOKEN_IS_NECESSARY = 1002;
18+
const INVALID_SECURITY_TYPE = 1003;
19+
const SECURITY_VALUES_IS_NECESSARY = 1004;
20+
const INVALID_SECURITY_VALUES_KEYWORDS = 1005;
2021
const INVALID_SECURITY_VALUES_SIGNATURE = 1006;
22+
const HTTP_CLIENT_NAME_INVALID = 1007;
23+
const MOBILES_INVALID = 1008;
24+
const HIDE_AVATAR_INVALID = 1009;
25+
const BTN_ORIENTATION_INVALID = 1010;
26+
const SECURITY_VERIFICATION_FAILED = 1011;
27+
const RESPONSE_RESULT_UNKNOWN_ERROR = 1012;
2128

2229
const MESSAGES = [
23-
self::INVALID_ROBOT_NAME => 'Robot name: :name not exist. Please check your config in file dingtalk_robot.php',
24-
self::ACCESS_TOKEN_IS_NECESSARY => 'access_token is necessary in config',
25-
self::INVALID_SECURITY_TYPE => 'security_type in config is invalid, only value in '. DingtalkRobot::class .'::SECURITY_TYPES is acceptable',
26-
self::SECURITY_VALUES_IS_NECESSARY => 'security_values is necessary when security_type is keywords or signature',
27-
self::INVALID_SECURITY_VALUES_KEYWORDS => 'security_values is invalid',
30+
self::RESPONSE_FAILED => 'http request failed',
31+
self::RESPONSE_BODY_ERROR => 'response body decode failed',
32+
33+
self::INVALID_ROBOT_NAME => 'robot name: :name not exist. Please check your config in file dingtalk_robot.php',
34+
self::ACCESS_TOKEN_IS_NECESSARY => 'access_token is necessary in config',
35+
self::INVALID_SECURITY_TYPE => 'security_type in config is invalid, only value in '.DingtalkRobot::class.'::SECURITY_TYPES is acceptable',
36+
self::SECURITY_VALUES_IS_NECESSARY => 'security_values is necessary when security_type is keywords or signature',
37+
self::INVALID_SECURITY_VALUES_KEYWORDS => 'security_values is invalid',
2838
self::INVALID_SECURITY_VALUES_SIGNATURE => 'security_values 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',
2945

3046
];
3147
}

src/Exceptions/Exception.php

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
<?php
22

3-
namespace Calchen\LaravelDingtalkRobot\Exception;
3+
namespace Calchen\LaravelDingtalkRobot\Exceptions;
44

5-
use Calchen\LaravelDingtalkRobot\Exceptions\ErrorCodes;
65
use Throwable;
76

87
/**

src/Exceptions/HttpException.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace Calchen\LaravelDingtalkRobot\Exception;
3+
namespace Calchen\LaravelDingtalkRobot\Exceptions;
44

55
/**
66
* Http 请求异常.

src/Exceptions/InvalidArgumentException.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace Calchen\LaravelDingtalkRobot\Exception;
3+
namespace Calchen\LaravelDingtalkRobot\Exceptions;
44

55
/**
66
* 入参异常.

src/Exceptions/InvalidConfigurationException.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace Calchen\LaravelDingtalkRobot\Exception;
3+
namespace Calchen\LaravelDingtalkRobot\Exceptions;
44

55
/**
66
* 配置文件异常.

src/Message/ActionCardMessage.php

+7-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
namespace Calchen\LaravelDingtalkRobot\Message;
44

5-
use Calchen\LaravelDingtalkRobot\Exception\InvalidConfigurationException;
5+
use Calchen\LaravelDingtalkRobot\Exceptions\InvalidConfigurationException;
6+
use Calchen\LaravelDingtalkRobot\Exceptions\ErrorCodes;
67

78
/**
89
* ActionCard类型,包含整体跳转和独立跳转.
@@ -58,22 +59,22 @@ public function __construct(string $title = null, string $text = null, $hideAvat
5859
public function setMessage(string $title, string $text, $hideAvatar = null, $btnOrientation = null): self
5960
{
6061
$this->message = [
61-
'msgtype' => 'actionCard',
62+
'msgtype' => 'actionCard',
6263
'actionCard' => [
6364
'title' => $title,
64-
'text' => $text,
65+
'text' => $text,
6566
],
6667
];
6768

6869
if (!is_null($hideAvatar)) {
6970
if (!in_array($hideAvatar, self::HIDE_AVATAR_VALUES)) {
70-
throw new InvalidConfigurationException('hideAvatar value can only be 0 or 1');
71+
throw new InvalidConfigurationException(null, ErrorCodes::HIDE_AVATAR_INVALID);
7172
}
7273
$this->message['actionCard']['hideAvatar'] = $hideAvatar;
7374
}
7475
if (!is_null($btnOrientation)) {
7576
if (!in_array($btnOrientation, self::BTN_ORIENTATION_VALUES)) {
76-
throw new InvalidConfigurationException('hideAvatar value can only be 0 or 1');
77+
throw new InvalidConfigurationException(null, ErrorCodes::BTN_ORIENTATION_INVALID);
7778
}
7879
$this->message['actionCard']['btnOrientation'] = $btnOrientation;
7980
}
@@ -109,7 +110,7 @@ public function setSingle(string $singleTitle, string $singleUrl): self
109110
public function addButton(string $title, string $actionUrl): self
110111
{
111112
$this->message['actionCard']['btns'][] = [
112-
'title' => $title,
113+
'title' => $title,
113114
'actionURL' => $actionUrl,
114115
];
115116
unset($this->message['actionCard']['singleTitle']);

0 commit comments

Comments
 (0)