消息组件初始版本
This commit is contained in:
commit
62cfeb1772
|
@ -0,0 +1,10 @@
|
||||||
|
!.gitignore
|
||||||
|
!.gitattributes
|
||||||
|
*.DS_Store
|
||||||
|
*.idea
|
||||||
|
*.svn
|
||||||
|
*.git
|
||||||
|
composer.lock
|
||||||
|
*.cache
|
||||||
|
vendor
|
||||||
|
config
|
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$header = '';
|
||||||
|
|
||||||
|
return (new \PhpCsFixer\Config())
|
||||||
|
->setRiskyAllowed(true)
|
||||||
|
->setRules([
|
||||||
|
'@PSR2' => true,
|
||||||
|
'@Symfony' => true,
|
||||||
|
'@DoctrineAnnotation' => true,
|
||||||
|
'@PhpCsFixer' => true,
|
||||||
|
'header_comment' => [
|
||||||
|
'comment_type' => 'PHPDoc',
|
||||||
|
'header' => $header,
|
||||||
|
'separate' => 'none',
|
||||||
|
'location' => 'after_declare_strict',
|
||||||
|
],
|
||||||
|
'array_syntax' => [
|
||||||
|
'syntax' => 'short',
|
||||||
|
],
|
||||||
|
'list_syntax' => [
|
||||||
|
'syntax' => 'short',
|
||||||
|
],
|
||||||
|
'concat_space' => [
|
||||||
|
'spacing' => 'one',
|
||||||
|
],
|
||||||
|
'blank_line_before_statement' => [
|
||||||
|
'statements' => [
|
||||||
|
'declare',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'general_phpdoc_annotation_remove' => [
|
||||||
|
'annotations' => [
|
||||||
|
'author',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'ordered_imports' => [
|
||||||
|
'imports_order' => [
|
||||||
|
'class', 'function', 'const',
|
||||||
|
],
|
||||||
|
'sort_algorithm' => 'alpha',
|
||||||
|
],
|
||||||
|
'single_line_comment_style' => [
|
||||||
|
'comment_types' => [
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'yoda_style' => [
|
||||||
|
'always_move_variable' => false,
|
||||||
|
'equal' => false,
|
||||||
|
'identical' => false,
|
||||||
|
],
|
||||||
|
'phpdoc_align' => [
|
||||||
|
'align' => 'left',
|
||||||
|
],
|
||||||
|
'multiline_whitespace_before_semicolons' => [
|
||||||
|
'strategy' => 'no_multi_line',
|
||||||
|
],
|
||||||
|
'constant_case' => [
|
||||||
|
'case' => 'lower',
|
||||||
|
],
|
||||||
|
'class_attributes_separation' => true,
|
||||||
|
'combine_consecutive_unsets' => true,
|
||||||
|
'declare_strict_types' => true,
|
||||||
|
'linebreak_after_opening_tag' => true,
|
||||||
|
'lowercase_static_reference' => true,
|
||||||
|
'no_useless_else' => true,
|
||||||
|
'no_unused_imports' => true,
|
||||||
|
'not_operator_with_successor_space' => true,
|
||||||
|
'not_operator_with_space' => false,
|
||||||
|
'ordered_class_elements' => true,
|
||||||
|
'php_unit_strict' => false,
|
||||||
|
'phpdoc_separation' => false,
|
||||||
|
'single_quote' => true,
|
||||||
|
'standardize_not_equals' => true,
|
||||||
|
'multiline_comment_opening_closing' => true,
|
||||||
|
])
|
||||||
|
->setFinder(
|
||||||
|
\PhpCsFixer\Finder::create()
|
||||||
|
->exclude('public')
|
||||||
|
->exclude('runtime')
|
||||||
|
->exclude('vendor')
|
||||||
|
->in(__DIR__)
|
||||||
|
)
|
||||||
|
->setUsingCache(false)
|
||||||
|
;
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) Vinchan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,54 @@
|
||||||
|
|
||||||
|
## 消息通知组件
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
* 监控发送应用异常
|
||||||
|
* 支持多种通道(钉钉群机器人、飞书群机器人、邮件、QQ 频道机器人、企业微信群机器人)
|
||||||
|
* 支持扩展自定义通道
|
||||||
|
|
||||||
|
## 环境要求
|
||||||
|
|
||||||
|
* hyperf >= 2.0
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require vinchan/message-notify -vvv
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置文件
|
||||||
|
|
||||||
|
发布配置文件`config/message.php`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hyperf vendor:publish vinchan/message-notify
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
```php
|
||||||
|
Notify::make()->setChannel(DingTalkChannel::class)
|
||||||
|
->setTemplate(Text::class)
|
||||||
|
->setTitle('标题')->setText('内容')->setAt(['all'])->setPipeline('info')
|
||||||
|
->send();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 通道
|
||||||
|
|
||||||
|
| 通道名称 | 命名空间 | 支持格式 |
|
||||||
|
|-------|----------------------------------------|---------------|
|
||||||
|
| 钉钉群 | \MessageNotify\Channel\DingTalkChannel | Text、Markdown |
|
||||||
|
| 飞书群 | \MessageNotify\Channel\FeiShuChannel | Text、Markdown |
|
||||||
|
| 企业微信群 | \MessageNotify\Channel\WechatChannel | Text、Markdown |
|
||||||
|
|
||||||
|
## 格式
|
||||||
|
|
||||||
|
| 格式名称 | 命名空间 |
|
||||||
|
|----------|----------------------------------|
|
||||||
|
| Text | \MessageNotify\Template\Text |
|
||||||
|
| Markdown | \MessageNotify\Template\Markdown |
|
||||||
|
|
||||||
|
## 协议
|
||||||
|
|
||||||
|
MIT 许可证(MIT)。有关更多信息,请参见[协议文件](LICENSE)。
|
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"name": "ykxiao/easy-message",
|
||||||
|
"description": "Modify the extended fork version",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "ykxiao",
|
||||||
|
"email": "yk_9001@icloud.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"EasyMessage\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"EasyMessageTest\\": "test/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.4",
|
||||||
|
"ext-json": "*",
|
||||||
|
"hyperf/guzzle": "^1.1|^2.1|^3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.0",
|
||||||
|
"phpunit/phpunit": "^9.4",
|
||||||
|
"hyperf/di": "^2.2",
|
||||||
|
"hyperf/utils": "^2.0",
|
||||||
|
"hyperf/config": "*",
|
||||||
|
"hyperf/ide-helper": "v2.2.*"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "phpunit -c phpunit.xml --colors=always",
|
||||||
|
"cs-fix": "./vendor/bin/php-cs-fixer fix"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"sort-packages": true
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"hyperf": {
|
||||||
|
"config": "EasyMessage\\ConfigProvider"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit backupGlobals="false"
|
||||||
|
backupStaticAttributes="false"
|
||||||
|
bootstrap="./test/bootstrap.php"
|
||||||
|
colors="true"
|
||||||
|
convertErrorsToExceptions="true"
|
||||||
|
convertNoticesToExceptions="true"
|
||||||
|
convertWarningsToExceptions="true"
|
||||||
|
processIsolation="false"
|
||||||
|
stopOnFailure="false">
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Tests">
|
||||||
|
<directory suffix="Test.php">./test</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
<coverage processUncoveredFiles="true">
|
||||||
|
<include>
|
||||||
|
<directory suffix=".php">./src</directory>
|
||||||
|
</include>
|
||||||
|
</coverage>
|
||||||
|
</phpunit>
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use MessageNotify\Channel\DingTalkChannel;
|
||||||
|
use MessageNotify\Channel\FeiShuChannel;
|
||||||
|
use MessageNotify\Channel\MailChannel;
|
||||||
|
use MessageNotify\Channel\WechatChannel;
|
||||||
|
use MessageNotify\Contracts\MessageNotifyInterface;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'default' => env('NOTIFY_DEFAULT_CHANNEL', 'mail'),
|
||||||
|
'channels' => [
|
||||||
|
// 钉钉群机器人
|
||||||
|
DingTalkChannel::class => [
|
||||||
|
'default' => MessageNotifyInterface::INFO,
|
||||||
|
'pipeline' => [
|
||||||
|
// 业务信息告警群
|
||||||
|
MessageNotifyInterface::INFO => [
|
||||||
|
'token' => env('NOTIFY_DINGTALK_TOKEN', ''),
|
||||||
|
'secret' => env('NOTIFY_DINGTALK_SECRET', ''),
|
||||||
|
'keyword' => env('NOTIFY_DINGTALK_KEYWORD', []),
|
||||||
|
],
|
||||||
|
// 错误信息告警群
|
||||||
|
MessageNotifyInterface::ERROR => [
|
||||||
|
'token' => env('NOTIFY_DINGTALK_TOKEN', ''),
|
||||||
|
'secret' => env('NOTIFY_DINGTALK_SECRET', ''),
|
||||||
|
'keyword' => env('NOTIFY_DINGTALK_KEYWORD', []),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// 飞书群机器人
|
||||||
|
FeiShuChannel::class => [
|
||||||
|
'default' => MessageNotifyInterface::INFO,
|
||||||
|
'pipeline' => [
|
||||||
|
'info' => [
|
||||||
|
'token' => env('NOTIFY_FEISHU_TOKEN', ''),
|
||||||
|
'secret' => env('NOTIFY_FEISHU_SECRET', ''),
|
||||||
|
'keyword' => env('NOTIFY_FEISHU_KEYWORD'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// 邮件
|
||||||
|
MailChannel::class => [
|
||||||
|
'default' => MessageNotifyInterface::INFO,
|
||||||
|
'pipeline' => [
|
||||||
|
'info' => [
|
||||||
|
'dsn' => env('NOTIFY_MAIL_DSN'),
|
||||||
|
'from' => env('NOTIFY_MAIL_FROM'),
|
||||||
|
'to' => env('NOTIFY_MAIL_TO'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// 企业微信群机器人
|
||||||
|
WechatChannel::class => [
|
||||||
|
'default' => MessageNotifyInterface::INFO,
|
||||||
|
'pipeline' => [
|
||||||
|
'info' => [
|
||||||
|
'token' => env('NOTIFY_WECHAT_TOKEN'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MessageNotify\Channel;
|
||||||
|
|
||||||
|
use Hyperf\Contract\ConfigInterface;
|
||||||
|
use MessageNotify\Exceptions\MessageNotificationException;
|
||||||
|
use MessageNotify\Template\AbstractTemplate;
|
||||||
|
|
||||||
|
abstract class AbstractChannel
|
||||||
|
{
|
||||||
|
public function getConfig()
|
||||||
|
{
|
||||||
|
if (class_exists(\Hyperf\Utils\ApplicationContext::class)) {
|
||||||
|
$configContext = make(ConfigInterface::class);
|
||||||
|
|
||||||
|
return $configContext->get('message.channels.' . get_class($this));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MessageNotificationException('ApplicationContext is not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public function send(AbstractTemplate $template);
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MessageNotify\Channel;
|
||||||
|
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
|
use GuzzleHttp\RequestOptions;
|
||||||
|
use MessageNotify\Exceptions\MessageNotificationException;
|
||||||
|
use MessageNotify\Template\AbstractTemplate;
|
||||||
|
|
||||||
|
class DingTalkChannel extends AbstractChannel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws GuzzleException
|
||||||
|
*/
|
||||||
|
public function send(AbstractTemplate $template): bool
|
||||||
|
{
|
||||||
|
$query = $this->getQuery($template->getPipeline());
|
||||||
|
|
||||||
|
$client = $this->getClient($query);
|
||||||
|
|
||||||
|
$option = [
|
||||||
|
RequestOptions::HEADERS => [],
|
||||||
|
RequestOptions::JSON => $template->dingTalkBody(),
|
||||||
|
];
|
||||||
|
$request = $client->post('', $option);
|
||||||
|
$result = json_decode($request->getBody()->getContents(), true);
|
||||||
|
|
||||||
|
if ($result['errcode'] !== 0) {
|
||||||
|
throw new MessageNotificationException($result['errmsg']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClient(string $query)
|
||||||
|
{
|
||||||
|
$config['base_uri'] = 'https://oapi.dingtalk.com/robot/send' . $query;
|
||||||
|
|
||||||
|
if (class_exists(\Hyperf\Utils\ApplicationContext::class)) {
|
||||||
|
return make(Client::class, [$config]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Client($config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getQuery(string $pipeline): string
|
||||||
|
{
|
||||||
|
$timestamp = time() * 1000;
|
||||||
|
|
||||||
|
$config = $this->getConfig();
|
||||||
|
$config = $config['pipeline'][$pipeline] ?? $config['pipeline'][$config['default']];
|
||||||
|
|
||||||
|
$secret = hash_hmac('sha256', $timestamp . "\n" . $config['secret'], $config['secret'], true);
|
||||||
|
$sign = urlencode(base64_encode($secret));
|
||||||
|
return "?access_token={$config['token']}×tamp={$timestamp}&sign={$sign}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MessageNotify\Channel;
|
||||||
|
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
|
use GuzzleHttp\RequestOptions;
|
||||||
|
use MessageNotify\Exceptions\MessageNotificationException;
|
||||||
|
use MessageNotify\Template\AbstractTemplate;
|
||||||
|
|
||||||
|
class FeiShuChannel extends AbstractChannel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws GuzzleException
|
||||||
|
*/
|
||||||
|
public function send(AbstractTemplate $template): bool
|
||||||
|
{
|
||||||
|
$client = $this->getClient($template->getPipeline());
|
||||||
|
|
||||||
|
$timestamp = time();
|
||||||
|
$config = [
|
||||||
|
'timestamp' => $timestamp,
|
||||||
|
'sign' => $this->getSign($timestamp, $template->getPipeline()),
|
||||||
|
];
|
||||||
|
|
||||||
|
$option = [
|
||||||
|
RequestOptions::HEADERS => [],
|
||||||
|
RequestOptions::JSON => array_merge($config, $template->feiShuBody()),
|
||||||
|
];
|
||||||
|
|
||||||
|
$request = $client->post('', $option);
|
||||||
|
$result = json_decode($request->getBody()->getContents(), true);
|
||||||
|
|
||||||
|
if (! isset($result['StatusCode']) || $result['StatusCode'] !== 0) {
|
||||||
|
throw new MessageNotificationException($result['msg']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClient(string $pipeline)
|
||||||
|
{
|
||||||
|
$config = $this->config($pipeline);
|
||||||
|
|
||||||
|
$uri['base_uri'] = 'https://open.feishu.cn/open-apis/bot/v2/hook/' . $config['token'];
|
||||||
|
|
||||||
|
if (class_exists(\Hyperf\Utils\ApplicationContext::class)) {
|
||||||
|
return make(Client::class, [$uri]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Client($config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getSign(int $timestamp, string $pipeline): string
|
||||||
|
{
|
||||||
|
$config = $this->config($pipeline);
|
||||||
|
$secret = hash_hmac('sha256', '', $timestamp . "\n" . $config['secret'], true);
|
||||||
|
return base64_encode($secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function config(string $pipeline)
|
||||||
|
{
|
||||||
|
$config = $this->getConfig();
|
||||||
|
return $config['pipeline'][$pipeline] ?? $config['pipeline'][$config['default']];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MessageNotify\Channel;
|
||||||
|
|
||||||
|
use MessageNotify\Template\AbstractTemplate;
|
||||||
|
|
||||||
|
class MailChannel extends AbstractChannel
|
||||||
|
{
|
||||||
|
public function send(AbstractTemplate $template)
|
||||||
|
{
|
||||||
|
// TODO: Implement send() method.
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MessageNotify\Channel;
|
||||||
|
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\RequestOptions;
|
||||||
|
use MessageNotify\Exceptions\MessageNotificationException;
|
||||||
|
use MessageNotify\Template\AbstractTemplate;
|
||||||
|
|
||||||
|
class WechatChannel extends AbstractChannel
|
||||||
|
{
|
||||||
|
public function send(AbstractTemplate $template): bool
|
||||||
|
{
|
||||||
|
$client = $this->getClient($template->getPipeline());
|
||||||
|
|
||||||
|
$option = [
|
||||||
|
RequestOptions::HEADERS => [],
|
||||||
|
RequestOptions::JSON => $template->wechatBody(),
|
||||||
|
];
|
||||||
|
$request = $client->post('', $option);
|
||||||
|
$result = json_decode($request->getBody()->getContents(), true);
|
||||||
|
|
||||||
|
if ($result['errcode'] !== 0) {
|
||||||
|
throw new MessageNotificationException($result['errmsg']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getClient(string $pipeline)
|
||||||
|
{
|
||||||
|
$config = $this->config($pipeline);
|
||||||
|
|
||||||
|
$uri['base_uri'] = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=' . $config['token'];
|
||||||
|
|
||||||
|
if (class_exists(\Hyperf\Utils\ApplicationContext::class)) {
|
||||||
|
return make(Client::class, [$uri]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Client($config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function config(string $pipeline)
|
||||||
|
{
|
||||||
|
$config = $this->getConfig();
|
||||||
|
return $config['pipeline'][$pipeline] ?? $config['pipeline'][$config['default']];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MessageNotify;
|
||||||
|
|
||||||
|
use MessageNotify\Channel\AbstractChannel;
|
||||||
|
use MessageNotify\Contracts\MessageNotifyInterface;
|
||||||
|
use MessageNotify\Exceptions\MessageNotificationException;
|
||||||
|
use MessageNotify\Template\AbstractTemplate;
|
||||||
|
use MessageNotify\Template\Text;
|
||||||
|
|
||||||
|
class Client
|
||||||
|
{
|
||||||
|
protected AbstractChannel $channel;
|
||||||
|
|
||||||
|
protected AbstractTemplate $template;
|
||||||
|
|
||||||
|
protected array $at = [];
|
||||||
|
|
||||||
|
protected string $pipeline = MessageNotifyInterface::INFO;
|
||||||
|
|
||||||
|
protected string $title = '';
|
||||||
|
|
||||||
|
protected string $text = '';
|
||||||
|
|
||||||
|
private string $errorMessage;
|
||||||
|
|
||||||
|
public function getChannel(): AbstractChannel
|
||||||
|
{
|
||||||
|
return $this->channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTemplate(): AbstractTemplate
|
||||||
|
{
|
||||||
|
return $this->template ?? new Text();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAt(): array
|
||||||
|
{
|
||||||
|
return $this->at;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getText(): string
|
||||||
|
{
|
||||||
|
return $this->text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setChannel($channel = null): Client
|
||||||
|
{
|
||||||
|
if (! $channel instanceof AbstractChannel) {
|
||||||
|
$channel = make($channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->channel = $channel;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTemplate($template = ''): Client
|
||||||
|
{
|
||||||
|
if (! $template instanceof AbstractChannel) {
|
||||||
|
$template = make($template);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->template = $template;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPipeline(): string
|
||||||
|
{
|
||||||
|
return $this->pipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPipeline(string $pipeline = ''): Client
|
||||||
|
{
|
||||||
|
$this->pipeline = $pipeline ?? MessageNotifyInterface::INFO;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAt(array $at = []): Client
|
||||||
|
{
|
||||||
|
$this->at = $at;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTitle(string $title = ''): Client
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setText(string $text = ''): Client
|
||||||
|
{
|
||||||
|
$this->text = $text;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$template = $this->getTemplate()->setAt($this->getAt())
|
||||||
|
->setTitle($this->getTitle())->setText($this->getText())
|
||||||
|
->setPipeline($this->getPipeline());
|
||||||
|
|
||||||
|
$this->getChannel()->send($template);
|
||||||
|
return true;
|
||||||
|
} catch (MessageNotificationException $exception) {
|
||||||
|
$this->errorMessage = $exception->getMessage();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getErrorMessage(): string
|
||||||
|
{
|
||||||
|
return $this->errorMessage;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MessageNotify;
|
||||||
|
|
||||||
|
use MessageNotify\Contracts\MessageNotifyInterface;
|
||||||
|
|
||||||
|
class ConfigProvider
|
||||||
|
{
|
||||||
|
public function __invoke(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'dependencies' => [
|
||||||
|
MessageNotifyInterface::class => Client::class,
|
||||||
|
],
|
||||||
|
'annotations' => [
|
||||||
|
'scan' => [
|
||||||
|
'paths' => [
|
||||||
|
__DIR__,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'publish' => [
|
||||||
|
[
|
||||||
|
'id' => 'config',
|
||||||
|
'description' => 'The config of message client.',
|
||||||
|
'source' => __DIR__ . '/../publish/message.php',
|
||||||
|
'destination' => BASE_PATH . '/config/autoload/message.php',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MessageNotify\Contracts;
|
||||||
|
|
||||||
|
interface MessageNotifyInterface
|
||||||
|
{
|
||||||
|
public const INFO = 'info';
|
||||||
|
|
||||||
|
public const ERROR = 'error';
|
||||||
|
|
||||||
|
public const EMERGENCY = 'emergency';
|
||||||
|
|
||||||
|
public const ALERT = 'alert';
|
||||||
|
|
||||||
|
public const CRITICAL = 'critical';
|
||||||
|
|
||||||
|
public const WARNING = 'warning';
|
||||||
|
|
||||||
|
public const NOTICE = 'notice';
|
||||||
|
|
||||||
|
public const DEBUG = 'debug';
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MessageNotify\Exceptions;
|
||||||
|
|
||||||
|
class MessageNotificationException extends \RuntimeException
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MessageNotify;
|
||||||
|
|
||||||
|
use Hyperf\Utils\ApplicationContext;
|
||||||
|
|
||||||
|
class Notify
|
||||||
|
{
|
||||||
|
public static function make(): Client
|
||||||
|
{
|
||||||
|
if (class_exists(ApplicationContext::class)) {
|
||||||
|
return make(Client::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Client();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MessageNotify\Template;
|
||||||
|
|
||||||
|
use MessageNotify\Contracts\MessageNotifyInterface;
|
||||||
|
|
||||||
|
abstract class AbstractTemplate
|
||||||
|
{
|
||||||
|
protected array $at = [];
|
||||||
|
|
||||||
|
protected string $pipeline = MessageNotifyInterface::INFO;
|
||||||
|
|
||||||
|
protected string $text = '';
|
||||||
|
|
||||||
|
protected string $title = '';
|
||||||
|
|
||||||
|
public function getText(): string
|
||||||
|
{
|
||||||
|
return $this->text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setText(string $text): AbstractTemplate
|
||||||
|
{
|
||||||
|
$this->text = $text;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTitle(string $title): AbstractTemplate
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPipeline(): string
|
||||||
|
{
|
||||||
|
return $this->pipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPipeline(string $pipeline): AbstractTemplate
|
||||||
|
{
|
||||||
|
$this->pipeline = $pipeline;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAt(array $at = []): AbstractTemplate
|
||||||
|
{
|
||||||
|
$this->at = $at;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAt(): array
|
||||||
|
{
|
||||||
|
return $this->at;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isAtAll(): bool
|
||||||
|
{
|
||||||
|
return in_array('all', $this->at) || in_array('ALL', $this->at);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public function getBody();
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MessageNotify\Template;
|
||||||
|
|
||||||
|
class Markdown extends AbstractTemplate
|
||||||
|
{
|
||||||
|
public function getBody(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dingTalkBody(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'msgtype' => 'markdown',
|
||||||
|
'markdown' => [
|
||||||
|
'title' => $this->getTitle(),
|
||||||
|
'text' => $this->getText(),
|
||||||
|
],
|
||||||
|
'at' => [
|
||||||
|
'isAtAll' => $this->isAtAll(),
|
||||||
|
'atMobiles' => $this->getAt(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function feiShuBody(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'msg_type' => 'post',
|
||||||
|
'content' => [
|
||||||
|
'post' => [
|
||||||
|
'zh_cn' => [
|
||||||
|
'title' => $this->getTitle(),
|
||||||
|
'content' => [$this->getFeiShuText()],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function wechatBody(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'msgtype' => 'markdown',
|
||||||
|
'markdown' => [
|
||||||
|
'content' => $this->getTitle() . $this->getText(),
|
||||||
|
'mentioned_list' => in_array('all', $this->getAt()) ? [] : [$this->getAt()],
|
||||||
|
'mentioned_mobile_list' => in_array('all', $this->getAt()) ? ['@all'] : [$this->getAt()],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFeiShuText(): array
|
||||||
|
{
|
||||||
|
$text = is_array($this->getText()) ? $this->getText() : json_decode($this->getText(), true) ?? [
|
||||||
|
[
|
||||||
|
'tag' => 'text',
|
||||||
|
'text' => $this->getText(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$at = $this->getFeiShuAt();
|
||||||
|
|
||||||
|
return array_merge($text, $at);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFeiShuAt(): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
if ($this->isAtAll()) {
|
||||||
|
$result[] = [
|
||||||
|
'tag' => 'at',
|
||||||
|
'user_id' => 'all',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$at = $this->getAt();
|
||||||
|
foreach ($at as $item) {
|
||||||
|
// TODO::需要加入邮箱与收集@人
|
||||||
|
if (strchr($item, '@') === false) {
|
||||||
|
$result[] = [
|
||||||
|
'tag' => 'at',
|
||||||
|
'email' => $item,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$result[] = [
|
||||||
|
'tag' => 'at',
|
||||||
|
'user_id' => $item,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MessageNotify\Template;
|
||||||
|
|
||||||
|
class Text extends AbstractTemplate
|
||||||
|
{
|
||||||
|
public function getBody(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dingTalkBody(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'msgtype' => 'text',
|
||||||
|
'text' => [
|
||||||
|
'content' => $this->getText(),
|
||||||
|
],
|
||||||
|
'at' => [
|
||||||
|
'isAtAll' => $this->isAtAll(),
|
||||||
|
'atMobiles' => $this->getAt(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function feiShuBody(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'msg_type' => 'text',
|
||||||
|
'content' => [
|
||||||
|
'text' => $this->getText() . $this->getFeiShuAt(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function wechatBody(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'msgtype' => 'text',
|
||||||
|
'text' => [
|
||||||
|
'content' => $this->getText(),
|
||||||
|
'mentioned_list' => in_array('all', $this->getAt()) ? [] : [$this->getAt()],
|
||||||
|
'mentioned_mobile_list' => in_array('all', $this->getAt()) ? ['@all'] : [$this->getAt()],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFeiShuAt(): string
|
||||||
|
{
|
||||||
|
if ($this->isAtAll()) {
|
||||||
|
return '<at user_id="all">所有人</at>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$at = $this->getAt();
|
||||||
|
$result = '';
|
||||||
|
foreach ($at as $item) {
|
||||||
|
if (strchr($item, '@') === false) {
|
||||||
|
$result .= '<at phone="' . $item . '">' . $item . '</at>';
|
||||||
|
} else {
|
||||||
|
$result .= '<at email="' . $item . '">' . $item . '</at>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MessageNotifyTest;
|
||||||
|
|
||||||
|
use MessageNotify\Channel\DingTalkChannel;
|
||||||
|
use MessageNotify\Channel\FeiShuChannel;
|
||||||
|
use MessageNotify\Channel\WechatChannel;
|
||||||
|
use MessageNotify\Contracts\MessageNotifyInterface;
|
||||||
|
use MessageNotify\Notify;
|
||||||
|
use MessageNotify\Template\Markdown;
|
||||||
|
use MessageNotify\Template\Text;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class NotifyTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testCase()
|
||||||
|
{
|
||||||
|
$dingTalkChannel = new DingTalkChannel();
|
||||||
|
$feiShuChannel = new FeiShuChannel();
|
||||||
|
$wechatChannel = new WechatChannel();
|
||||||
|
|
||||||
|
$markdown = new Markdown();
|
||||||
|
$text = new Text();
|
||||||
|
|
||||||
|
$notify = Notify::make()->setChannel(DingTalkChannel::class)
|
||||||
|
->setAt(['all'])
|
||||||
|
->setTitle('标题')
|
||||||
|
->setText('测试')
|
||||||
|
->setPipeline(MessageNotifyInterface::INFO)
|
||||||
|
->setTemplate(Markdown::class)
|
||||||
|
->send();
|
||||||
|
|
||||||
|
$this->assertEquals($notify, true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
use Hyperf\Di\Container;
|
||||||
|
use Hyperf\Di\Definition\DefinitionSourceFactory;
|
||||||
|
use Hyperf\Utils\ApplicationContext;
|
||||||
|
|
||||||
|
! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
|
||||||
|
|
||||||
|
$container = new Container((new DefinitionSourceFactory(true))());
|
||||||
|
|
||||||
|
ApplicationContext::setContainer($container);
|
Loading…
Reference in New Issue