Skip to content

Commit de9a1e3

Browse files
authored
feat 增加企微智能机器人回调支持 (#2936)
* feat 增加企微智能机器人回调支持 * feat 文档增加企微智能机器人相关说明 * perf 增加异常数据提示
1 parent 12f0175 commit de9a1e3

File tree

7 files changed

+202
-17
lines changed

7 files changed

+202
-17
lines changed

docs/src/6.x/work/server.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,30 @@ $server->handleUserTagUpdated(function($message, \Closure $next) {
9393
});
9494
```
9595

96+
### 智能机器人事件
97+
因智能机器人消息变更为JSON格式,需要在获取 `server` 对象时指定消息格式为json:
98+
```php
99+
// 指定消息格式 JSON
100+
$server = $app->getServer(messageType: 'json');
101+
102+
// 获取解密后的机器人消息
103+
$message = $server->getDecryptedMessage();
104+
105+
// 回复消息
106+
$server->with(function($message, \Closure $next) {
107+
return [
108+
'msgtype' => 'stream',
109+
'stream' => [
110+
'id' => 'id00001',
111+
'finish' => true,
112+
'content' => '信息已收到',
113+
],
114+
];
115+
});
116+
```
117+
118+
回复消息具体格式请参考官方文档:[企业微信智能机器人文档](https://developer.work.weixin.qq.com/document/path/101039)
119+
96120
## 其它事件处理
97121

98122
以上便捷方法都只处理了特定事件,其它状态,可以通过自定义事件处理中间件的形式处理:

src/Kernel/Encryptor.php

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ class Encryptor
5656
public const ERROR_BASE64_DECODE = -40010; // Base64 decoding failed
5757

5858
public const ERROR_XML_BUILD = -40011; // XML build failed
59+
60+
public const ERROR_JSON_BUILD = -40012; // JOSN build failed
5961

6062
public const ILLEGAL_BUFFER = -41003; // Illegal buffer
6163

@@ -87,9 +89,11 @@ public function getToken(): string
8789
* @throws RuntimeException
8890
* @throws Exception
8991
*/
90-
public function encrypt(string $plaintext, ?string $nonce = null, int|string|null $timestamp = null): string
92+
public function encrypt(string $plaintext, ?string $nonce = null, int|string|null $timestamp = null, string $messageType = 'xml'): string
9193
{
92-
return $this->encryptAsXml($plaintext, $nonce, $timestamp);
94+
return $messageType === 'xml' ?
95+
$this->encryptAsXml($plaintext, $nonce, $timestamp) :
96+
$this->encryptAsJson($plaintext, $nonce, $timestamp);
9397
}
9498

9599
public function encryptAsXml(string $plaintext, ?string $nonce = null, int|string|null $timestamp = null): string
@@ -106,8 +110,28 @@ public function encryptAsXml(string $plaintext, ?string $nonce = null, int|strin
106110
return Xml::build($response);
107111
}
108112

113+
public function encryptAsJson(string $plaintext, ?string $nonce = null, int|string|null $timestamp = null): string
114+
{
115+
$encrypted = $this->encryptAsArray($plaintext, $nonce, $timestamp);
116+
117+
$response = [
118+
'encrypt' => $encrypted['ciphertext'],
119+
'msgsignature' => $encrypted['signature'],
120+
'timestamp' => $encrypted['timestamp'],
121+
'nonce' => $encrypted['nonce'],
122+
];
123+
124+
$jsonStr = json_encode($response, JSON_UNESCAPED_UNICODE);
125+
126+
if ($jsonStr === false) {
127+
throw new RuntimeException('Invalid json data.', self::ERROR_JSON_BUILD);
128+
}
129+
130+
return $jsonStr;
131+
}
132+
109133
/**
110-
* @throws RuntimeException
134+
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
111135
*/
112136
public function encryptAsArray(string $plaintext, ?string $nonce = null, int|string|null $timestamp = null): array
113137
{

src/Kernel/Message.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* @property string $FromUserName
1616
* @property string $ToUserName
1717
* @property string $Encrypt
18+
* @property string $encrypt
1819
*
1920
* @implements ArrayAccess<array-key, mixed>
2021
*/
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace EasyWeChat\Kernel\Traits;
4+
5+
use EasyWeChat\Kernel\Encryptor;
6+
use EasyWeChat\Kernel\Exceptions\BadRequestException;
7+
use EasyWeChat\Kernel\Message;
8+
use EasyWeChat\Kernel\Support\Xml;
9+
use Illuminate\Support\Facades\Log;
10+
11+
trait DecryptJsonMessage
12+
{
13+
public function decryptJsonMessage(
14+
Message $message,
15+
Encryptor $encryptor,
16+
string $signature,
17+
int|string $timestamp,
18+
string $nonce
19+
): Message {
20+
$ciphertext = $message->encrypt;
21+
22+
$this->validateSignature($encryptor->getToken(), $ciphertext, $signature, $timestamp, $nonce);
23+
24+
$message->merge(json_decode(
25+
$encryptor->decrypt(
26+
ciphertext: $ciphertext,
27+
msgSignature: $signature,
28+
nonce: $nonce,
29+
timestamp: $timestamp
30+
)
31+
, true) ?? []);
32+
33+
return $message;
34+
}
35+
36+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
namespace EasyWeChat\Kernel\Traits;
4+
5+
use EasyWeChat\Kernel\Encryptor;
6+
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
7+
use EasyWeChat\Kernel\Message;
8+
use EasyWeChat\Kernel\Support\Xml;
9+
use Illuminate\Support\Facades\Log;
10+
use Nyholm\Psr7\Response;
11+
use Psr\Http\Message\ResponseInterface;
12+
13+
trait RespondJsonMessage
14+
{
15+
/**
16+
* @param mixed $response
17+
* @param Message $message
18+
* @param Encryptor|null $encryptor
19+
* @return ResponseInterface
20+
* @throws InvalidArgumentException
21+
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
22+
*/
23+
public function transformJsonToReply(mixed $response, Message $message, ?Encryptor $encryptor = null): ResponseInterface
24+
{
25+
if (empty($response)) {
26+
return new Response(200, [], 'success');
27+
}
28+
29+
return $this->createJsonResponse(
30+
attributes: $this->normalizeJsonResponse($response),
31+
encryptor: $encryptor
32+
);
33+
}
34+
35+
/**
36+
* @param mixed $response
37+
* @return array
38+
* @throws InvalidArgumentException
39+
*/
40+
protected function normalizeJsonResponse(mixed $response): array
41+
{
42+
if (! is_string($response) && is_callable($response)) {
43+
$response = $response();
44+
}
45+
46+
if (is_array($response)) {
47+
if (! isset($response['msgtype'])) {
48+
throw new InvalidArgumentException('msgtype cannot be empty.');
49+
}
50+
51+
return $response;
52+
}
53+
54+
throw new InvalidArgumentException(
55+
sprintf('Invalid Response type "%s".', gettype($response))
56+
);
57+
}
58+
59+
/**
60+
* @param array $attributes
61+
* @param Encryptor|null $encryptor
62+
* @return ResponseInterface
63+
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
64+
*/
65+
protected function createJsonResponse(array $attributes, ?Encryptor $encryptor = null): ResponseInterface
66+
{
67+
$jsonStr = json_encode($attributes, JSON_UNESCAPED_UNICODE);
68+
69+
if (is_string($jsonStr)) {
70+
return new Response(200, ['Content-Type' => 'application/json'], $encryptor ? $encryptor->encrypt($jsonStr, messageType: $this->messageType) : $jsonStr);
71+
}
72+
73+
throw new InvalidArgumentException(
74+
sprintf('Invalid Response content "%s".', implode(',', $attributes))
75+
);
76+
}
77+
}

src/Work/Application.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,13 @@ public function setEncryptor(Encryptor $encryptor): static
8181
return $this;
8282
}
8383

84-
public function getServer(): Server|ServerInterface
84+
public function getServer(string $messageType = 'xml'): Server|ServerInterface
8585
{
8686
if (! $this->server) {
8787
$this->server = new Server(
8888
encryptor: $this->getEncryptor(),
89-
request: $this->getRequest()
89+
request: $this->getRequest(),
90+
messageType: $messageType,
9091
);
9192
}
9293

src/Work/Server.php

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
use EasyWeChat\Kernel\Contracts\Server as ServerInterface;
99
use EasyWeChat\Kernel\Encryptor;
1010
use EasyWeChat\Kernel\ServerResponse;
11+
use EasyWeChat\Kernel\Traits\DecryptJsonMessage;
1112
use EasyWeChat\Kernel\Traits\DecryptXmlMessage;
1213
use EasyWeChat\Kernel\Traits\InteractWithHandlers;
1314
use EasyWeChat\Kernel\Traits\InteractWithServerRequest;
15+
use EasyWeChat\Kernel\Traits\RespondJsonMessage;
1416
use EasyWeChat\Kernel\Traits\RespondXmlMessage;
1517
use Nyholm\Psr7\Response;
1618
use Psr\Http\Message\ResponseInterface;
@@ -19,13 +21,16 @@
1921
class Server implements ServerInterface
2022
{
2123
use DecryptXmlMessage;
24+
use DecryptJsonMessage;
2225
use InteractWithHandlers;
2326
use InteractWithServerRequest;
2427
use RespondXmlMessage;
28+
use RespondJsonMessage;
2529

2630
public function __construct(
2731
protected Encryptor $encryptor,
2832
?ServerRequestInterface $request = null,
33+
protected string $messageType = 'xml',
2934
) {
3035
$this->request = $request;
3136
}
@@ -52,7 +57,9 @@ public function serve(): ResponseInterface
5257
$response = $this->handle(new Response(200, [], 'SUCCESS'), $message);
5358

5459
if (! ($response instanceof ResponseInterface)) {
55-
$response = $this->transformToReply($response, $message, $this->encryptor);
60+
$response = $this->messageType === 'xml' ?
61+
$this->transformToReply($response, $message, $this->encryptor) :
62+
$this->transformJsonToReply($response, $message, $this->encryptor);
5663
}
5764

5865
return ServerResponse::make($response);
@@ -201,13 +208,16 @@ protected function decryptRequestMessage(): Closure
201208
{
202209
return function (Message $message, Closure $next): mixed {
203210
$query = $this->getRequest()->getQueryParams();
204-
$this->decryptMessage(
205-
$message,
206-
$this->encryptor,
211+
212+
$params = [
207213
$query['msg_signature'] ?? '',
208214
$query['timestamp'] ?? '',
209215
$query['nonce'] ?? ''
210-
);
216+
];
217+
218+
$this->messageType === 'xml'
219+
? $this->decryptMessage($message, $this->encryptor, ...$params)
220+
: $this->decryptJsonMessage($message, $this->encryptor, ...$params);
211221

212222
return $next($message);
213223
};
@@ -224,12 +234,24 @@ public function getDecryptedMessage(?ServerRequestInterface $request = null): \E
224234
$message = $this->getRequestMessage($request);
225235
$query = $request->getQueryParams();
226236

227-
return $this->decryptMessage(
228-
message: $message,
229-
encryptor: $this->encryptor,
230-
signature: $query['msg_signature'] ?? '',
231-
timestamp: $query['timestamp'] ?? '',
232-
nonce: $query['nonce'] ?? ''
233-
);
237+
$params = [
238+
$query['msg_signature'] ?? '',
239+
$query['timestamp'] ?? '',
240+
$query['nonce'] ?? ''
241+
];
242+
243+
if ($this->messageType === 'xml') {
244+
return $this->decryptMessage(
245+
$message,
246+
$this->encryptor,
247+
...$params
248+
);
249+
} else {
250+
return $this->decryptJsonMessage(
251+
$message,
252+
$this->encryptor,
253+
...$params
254+
);
255+
}
234256
}
235257
}

0 commit comments

Comments
 (0)