消息组件初始版本
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