消息组件初始版本
This commit is contained in:
25
src/Channel/AbstractChannel.php
Normal file
25
src/Channel/AbstractChannel.php
Normal file
@ -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);
|
||||
}
|
60
src/Channel/DingTalkChannel.php
Normal file
60
src/Channel/DingTalkChannel.php
Normal file
@ -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}";
|
||||
}
|
||||
}
|
68
src/Channel/FeiShuChannel.php
Normal file
68
src/Channel/FeiShuChannel.php
Normal file
@ -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']];
|
||||
}
|
||||
}
|
15
src/Channel/MailChannel.php
Normal file
15
src/Channel/MailChannel.php
Normal file
@ -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.
|
||||
}
|
||||
}
|
50
src/Channel/WechatChannel.php
Normal file
50
src/Channel/WechatChannel.php
Normal file
@ -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']];
|
||||
}
|
||||
}
|
122
src/Client.php
Normal file
122
src/Client.php
Normal file
@ -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;
|
||||
}
|
||||
}
|
34
src/ConfigProvider.php
Normal file
34
src/ConfigProvider.php
Normal file
@ -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',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
24
src/Contracts/MessageNotifyInterface.php
Normal file
24
src/Contracts/MessageNotifyInterface.php
Normal file
@ -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';
|
||||
}
|
9
src/Exceptions/MessageNotificationException.php
Normal file
9
src/Exceptions/MessageNotificationException.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MessageNotify\Exceptions;
|
||||
|
||||
class MessageNotificationException extends \RuntimeException
|
||||
{
|
||||
}
|
19
src/Notify.php
Normal file
19
src/Notify.php
Normal file
@ -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();
|
||||
}
|
||||
}
|
69
src/Template/AbstractTemplate.php
Normal file
69
src/Template/AbstractTemplate.php
Normal file
@ -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();
|
||||
}
|
100
src/Template/Markdown.php
Normal file
100
src/Template/Markdown.php
Normal file
@ -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;
|
||||
}
|
||||
}
|
67
src/Template/Text.php
Normal file
67
src/Template/Text.php
Normal file
@ -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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user