feat(email/sms): 添加了发送邮件和短信的事件

This commit is contained in:
李东云
2023-01-29 16:41:30 +08:00
parent 0b4fb23779
commit 7fd7d23051
9 changed files with 484 additions and 2 deletions

76
docs/Email.md Normal file
View File

@@ -0,0 +1,76 @@
# Email | 邮件
提供一套通用 Service 用于定制需求,和一套现成的事件/监听器用于异步发送。
## 服务类
[\Singularity\HDK\Core\Service\EmailService](../src/Service/EmailService.php)
* 发送纯文本邮件的 `\Singularity\HDK\Core\Service\EmailService::sendText()`
* 发送 Html 富文本邮件的 `\Singularity\HDK\Core\Service\EmailService::sendHtml()`
## 事件机制
> 这种方式无法监听邮件发送的结果!
>
> 如果你需要根据邮件发送的结果进行处理,
> 请使用服务类进行自定义
更好的方法可能是在事件发生后触发异步队列,
但如果将异步队列封装在本包中,
相当于强制引用本库的项目使用异步队列机制,
这是我们不愿意看到的。
所以这里的监听器只是集成了同步发送的操作。
如果你想要定义更好的设计,
你可以直接使用提供的 Service 类进行定制。
> 或许这里还有优化空间😄
### 事件
[\Singularity\HDK\Core\Events\EmailWillSent](../src/Events/EmailWillSent.php)
### 监听器
[\Singularity\HDK\Core\Listener\EmailWillSentListener](../src/Listener/EmailWillSentListener.php)
### 使用方法
监听器已经通过 [ConfigProvider](../src/ConfigProvider.php) 机制注册,
所以你只需要在要发送邮件的地方调度这个事件即可。
> 这也就意味着,你无法调用这个事件,但不触发这个监听器。
> 如果你有这样做的必要,请创建一个自己的事件。
> 这也是为了保证功能的完整性和原子性。
具体使用方法,如下代码所示:
```php
<?php
namespace App\Service;
use Hyperf\Di\Annotation\Inject;
use Psr\EventDispatcher\EventDispatcherInterface;
use App\Event\UserRegistered;use Singularity\HDK\Core\Events\EmailWillSent;
class UserService
{
/**
* @var EventDispatcherInterface
*/
#[Inject]
private $eventDispatcher;
public function register()
{
// 我们假设存在 User 这个实体
$user = new User();
$result = $user->save();
// ↓
// 这里 dispatch(object $event) 会逐个运行监听该事件的监听器
$this->eventDispatcher->dispatch(new EmailWillSent(
target: $user->mobile,
subject: '注册成功通知',
type: 'html', // 默认 text 可以不传
content: <<<HTML
<h1>恭喜你,注册成功!</h1>
HTML,
));
// ↑
return $result;
}
}
```

75
docs/Sms.md Normal file
View File

@@ -0,0 +1,75 @@
# SMS | 短信
提供一套通用 Service 用于定制需求,和一套现成的事件/监听器用于异步发送。
## 服务类
[\Singularity\HDK\Core\Service\SmsService](../src/Service/SmsService.php)
* 发送国内短信的 `\Singularity\HDK\Core\Service\SmsService::sendSmsCountryside()`
## 事件机制
> 这种方式无法监听短信发送的结果!
>
> 如果你需要根据短信发送的结果进行处理,
> 请使用服务类进行自定义
更好的方法可能是在事件发生后触发异步队列,
但如果将异步队列封装在本包中,
相当于强制引用本库的项目使用异步队列机制,
这是我们不愿意看到的。
所以这里的监听器只是集成了同步发送的操作。
如果你想要定义更好的设计,
你可以直接使用提供的 Service 类进行定制。
> 或许这里还有优化空间😄
### 事件
[\Singularity\HDK\Core\Events\SmsWillSent](../src/Events/SmsWillSent.php)
### 监听器
[\Singularity\HDK\Core\Listener\SmsWillSentListener](../src/Listener/SmsWillSentListener.php)
### 使用方法
监听器已经通过 [ConfigProvider](../src/ConfigProvider.php) 机制注册,
所以你只需要在要发送短信的地方调度这个事件即可。
> 这也就意味着,你无法调用这个事件,但不触发这个监听器。
> 如果你有这样做的必要,请创建一个自己的事件。
> 这也是为了保证功能的完整性和原子性。
具体使用方法,如下代码所示:
```php
<?php
namespace App\Service;
use Hyperf\Di\Annotation\Inject;
use Psr\EventDispatcher\EventDispatcherInterface;
use App\Event\UserRegistered;use Singularity\HDK\Core\Events\EmailWillSent;
class UserService
{
/**
* @var EventDispatcherInterface
*/
#[Inject]
private $eventDispatcher;
public function register()
{
// 我们假设存在 User 这个实体
$user = new User();
$result = $user->save();
// ↓
// 这里 dispatch(object $event) 会逐个运行监听该事件的监听器
$this->eventDispatcher->dispatch(new EmailWillSent(
target: $user->mobile,
subject: '注册成功通知',
type: 'html', // 默认 text 可以不传
content: <<<HTML
<h1>恭喜你,注册成功!</h1>
HTML,
));
// ↑
return $result;
}
}
```

View File

@@ -7,3 +7,5 @@ parameters:
ignoreErrors:
- '#Constant BASE_PATH not found#'
- '#Property [a-zA-Z0-9\\_]+::\$[a-zA-Z0-9]+ is never written, only read.#'
- '#Method [a-zA-Z0-9\\_]+::[a-zA-Z0-9_]+\(\) has parameter \$[a-zA-Z0-9_]+ with no value type specified in iterable type array.#'
- '#Method [a-zA-Z0-9\\_]+::[a-zA-Z0-9_]+\(\) has parameter \$[a-zA-Z0-9_]+ with no type specified.#'

View File

@@ -14,6 +14,8 @@ namespace Singularity\HDK\Core;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Framework\Logger\StdoutLogger;
use Singularity\HDK\Core\Listener\EmailWillSentListener;
use Singularity\HDK\Core\Listener\SmsWillSentListener;
class ConfigProvider
{
@@ -38,7 +40,10 @@ class ConfigProvider
'commands' => [
],
// 与 commands 类似
'listeners' => [],
'listeners' => [
EmailWillSentListener::class,
SmsWillSentListener::class
],
// 组件默认配置文件,即执行命令后会把 source 的对应的文件复制为 destination 对应的的文件
'publish' => [
[

View File

@@ -0,0 +1,69 @@
<?php
/**
* EmailWillSent.php@HDK-Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2023/1/16
*/
namespace Singularity\HDK\Core\Events;
/**
* Singularity\HDK\Core\Events\EmailWillSent@HDK-Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2023/1/16
*
* @link ../../docs/Email.md
*/
class EmailWillSent
{
/**
* @var string|string[] $target
*/
public $target;
/**
* @var non-empty-string $subject
*/
public string $subject;
/**
* @var non-empty-string $content
*/
public string $content;
/**
* @var string[] $cc
*/
public array $cc = [];
/**
* @var string 'text'|'html' $type
*/
public string $type = 'text';
public function __construct(
/**
* @phpstan-param string|string[] $target
*/
$target,
string $subject,
string $content,
/**
* @var non-empty-string[] $cc
*/
array $cc = [],
string $type = 'text'
) {
$this->type = $type;
$this->cc = $cc;
$this->content = $content;
$this->subject = $subject;
$this->target = $target;
}
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* SmsWillSent.php@HDK-Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2023/1/16
*/
namespace Singularity\HDK\Core\Events;
/**
* Singularity\HDK\Core\Events\SmsWillSent@HDK-Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2023/1/16
*
* @link ../../docs/SMS.md
*/
class SmsWillSent
{
/**
* @var non-empty-string $phone 目标手机号
* @example +8613700000001
* @example 8613700000001
* @example 13700000001
*/
public string $phone;
/**
* @var array<literal-string, literal-string|int>|null $templateParam 模板变量
*/
public ?array $templateParam = null;
/**
* @var literal-string|null $templateCode 模板代码
*/
public ?string $templateCode = null;
/**
* @var literal-string|null $signName 模板签名
*/
public ?string $signName = null;
public function __construct(
/**
* @var non-empty-string $phone 目标手机号
* @example +8613700000001
* @example 8613700000001
* @example 13700000001
*/
string $phone,
/**
* @var array<literal-string, literal-string|int>|null $templateParam 模板变量
*/
?array $templateParam = null,
/**
* @var literal-string|null $templateCode 模板代码
*/
?string $templateCode = null,
/**
* @var literal-string|null $signName 模板签名
*/
?string $signName = null
) {
$this->signName = $signName;
$this->templateCode = $templateCode;
$this->templateParam = $templateParam;
$this->phone = $phone;
}
}

View File

@@ -0,0 +1,30 @@
<?php
/**
* AbstractListener.php@HDK-Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2023/1/16
*/
namespace Singularity\HDK\Core\Listener;
use Hyperf\Contract\ContainerInterface;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Event\Contract\ListenerInterface;
abstract class AbstractListener implements ListenerInterface
{
/**
* @var ContainerInterface
* @Inject()
*/
protected ContainerInterface $container;
/**
* @var StdoutLoggerInterface
* @Inject()
*/
protected StdoutLoggerInterface $stdoutLogger;
}

View File

@@ -0,0 +1,90 @@
<?php
/**
* EmailWillSentListener.php@HDK-Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2023/1/16
*/
namespace Singularity\HDK\Core\Listener;
use Hyperf\Contract\ContainerInterface;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Event\Contract\ListenerInterface;
use JetBrains\PhpStorm\NoReturn;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Singularity\HDK\Core\Constants\CommonErrorCode;
use Singularity\HDK\Core\Events\EmailWillSent;
use Singularity\HDK\Core\Service\EmailService;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
/**
* Singularity\HDK\Core\Listener\EmailWillSentListener@HDK-Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2023/1/16
*
* @link ../../docs/Email.md
*/
class EmailWillSentListener extends AbstractListener
{
/**
* @inheritDoc
*/
public function listen(): array
{
return [
EmailWillSent::class,
];
}
/**
* @param EmailWillSent $event
* @return void
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function process(object $event): void
{
$stdoutLogger = $this->container->get(StdoutLoggerInterface::class);
$emailService = $this->container->get(EmailService::class);
try {
$event->type === 'html'
? $emailService->sendHtml(
$event->target,
$event->subject,
$event->content,
$event->cc
)
: $emailService->sendText(
$event->target,
$event->subject,
$event->content,
$event->cc
);
$stdoutLogger->info('邮件发送成功!');
$stdoutLogger->info("To: $event->target");
$stdoutLogger->info("Subject: $event->subject");
$stdoutLogger->debug('Content: ');
$stdoutLogger->debug($event->content);
} catch (TransportExceptionInterface $e) {
$code = CommonErrorCode::SERVER_MESSAGE_EMAIL_ERROR;
$msg = $e->getMessage();
if (strpos($msg, '500 Error: bad syntax')) {
$code = CommonErrorCode::SERVER_MESSAGE_EMAIL_NOT_FOUND;
$msg = CommonErrorCode::getMessage($code);
}
$stdoutLogger->alert('邮件发送失败!');
$stdoutLogger->error(
<<<ERROR_LOG
[$code] $msg
ERROR_LOG
);
}
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* SmsWillSentListener.php@HDK-Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2023/1/16
*/
namespace Singularity\HDK\Core\Listener;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Singularity\HDK\Core\Events\SmsWillSent;
use Singularity\HDK\Core\Service\SmsService;
use Throwable;
/**
* Singularity\HDK\Core\Listener\SmsWillSentListener@HDK-Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2023/1/16
*/
class SmsWillSentListener extends AbstractListener
{
/**
* @inheritDoc
*/
public function listen(): array
{
return [
SmsWillSent::class,
];
}
/**
* @param SmsWillSent $event
* @return void
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function process(object $event): void
{
$smsService = $this->container->get(SmsService::class);
try {
$smsService->sendSmsCountryside(
$event->phone,
$event->templateCode,
$event->templateParam,
$event->signName
);
} catch (Throwable $throwable) {
$code = $throwable->getCode();
$message = $throwable->getMessage();
$this->stdoutLogger->alert('短信发送失败!');
$this->stdoutLogger->error(sprintf("[%u] %s", $code, $message));
}
}
}