Compare commits

...

25 Commits

Author SHA1 Message Date
李东云
76387a45b4 chore(release): 0.1.10 2025-09-26 17:30:11 +08:00
李东云
0009619d9c ci: 添加发布到注册表的CI工作流
- 创建了基于标签推送触发的发布工作流
- 实现了仓库代码检出和文件列表展示
- 添加了使用阿里云镜像源的依赖安装和打包步骤
- 集成了将构建产物上传到私有注册表的功能
- 配置了完整的CI运行状态输出和调试信息

Signed-off-by: 李东云 <dongyu.li@luxcreo.ai>
2025-09-26 17:29:56 +08:00
ch4o5
c060e84476 chore(release): 0.1.9 2024-09-12 11:06:35 +00:00
李东云
bebac5992a feat(email): 增加了单独定制发件人的方法
Signed-off-by: 李东云 <dongyu.li@luxcreo.ai>
2024-09-12 19:06:23 +08:00
ch4o5
7d15d85515 chore(release): 0.1.8 2024-09-12 10:31:25 +00:00
李东云
04719839ad build(composer): 更新依赖
Signed-off-by: 李东云 <dongyu.li@luxcreo.ai>
2024-09-12 18:31:00 +08:00
李东云
1c057722bc feat(email): 增加了密送参数
Signed-off-by: 李东云 <dongyu.li@luxcreo.ai>
2024-09-12 18:28:29 +08:00
ch4o5
52f4867c72 chore(release): 0.1.7 2023-07-05 08:49:21 +00:00
李东云
b51699b0d9 feat(session): 增加了定制的 session 中间件
只有已登录的 Session 才保存

Signed-off-by: 李东云 <dongyu.li@luxcreo.ai>
2023-07-05 16:49:04 +08:00
李东云
a570e1964e build(composer): 更新依赖
限定了 hyperf/crontab 的版本,避开有问题的版本
2023-03-30 18:00:00 +08:00
ch4o5
def9f0469d chore(release): 0.1.6 2023-03-21 03:02:49 +00:00
李东云
5ed5070f32 build(composer): 更新依赖 2023-03-21 11:02:17 +08:00
李东云
8dcf586baf test(email): 更新了邮件发送的测试账号 2023-02-27 11:05:13 +08:00
李东云
a7a8087509 fix(middleware.i18n): 尝试修复了多语言无法正确获取的问题 2023-02-18 15:50:46 +08:00
ch4o5
e50a2e08df chore(release): 0.1.5 2023-02-03 02:28:18 +00:00
李东云
fca8f9d013 fix(commonHandler): 修复了一处兼容问题 2023-02-03 10:28:00 +08:00
ch4o5
113e7fca57 chore(release): 0.1.4 2023-02-02 08:03:53 +00:00
李东云
24161f0841 style(cs-fixer): prettier 2023-02-02 16:03:44 +08:00
ch4o5
fe3181c7d1 chore(release): 0.1.3 2023-02-02 07:33:26 +00:00
李东云
6c27804856 fix(extend): 修复了解析不到为空不为数组的问题 2023-02-02 15:32:11 +08:00
李东云
c3eb95d300 fix(extend): 修复了解析不到为null不为数组的问题 2023-02-01 21:11:46 +08:00
李东云
a7a61bd674 fix(extend): 修复了解析不到为null不为数组的问题 2023-02-01 21:05:51 +08:00
李东云
7fd7d23051 feat(email/sms): 添加了发送邮件和短信的事件 2023-01-29 16:41:30 +08:00
李东云
0b4fb23779 build(composer): 修复 php 版本限制 2023-01-28 18:43:57 +08:00
李东云
eb20bc9fdb build(composer): 修复 php 版本限制 2023-01-28 18:42:49 +08:00
24 changed files with 2577 additions and 912 deletions

View File

@@ -0,0 +1,30 @@
name: Release development version to registry
on:
push:
tags:
- '**.**'
jobs:
Publish on Tagged:
runs-on: ubuntu-latest
steps:
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
- name: Zip files in the repository
run: |
sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list
sed -i 's|security.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list
apt-get update
apt-get install zip
zip -r dist.zip *
- name: Publish to registry
run: |
curl --user ch4o5:4fd300672472e666014314c1c94c604c634165a9 \
--upload-file ./dist.zip \
https://nest.doylee.cn/api/packages/HDK/composer?version=${{ gitea.ref_name }}
- run: echo "🍏 This job's status is ${{ job.status }}."

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
vendor/
.phpunit.result.cache
.php-cs-fixer.cache
.php-cs-fixer.cache
runtime/

2
.idea/HDK-Core.iml generated
View File

@@ -2,7 +2,6 @@
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="Singularity\HDK\Core\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="Singularity\HDK\Test\Core\" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/recursion-context" />
@@ -92,7 +91,6 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php80" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-ctype" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php81" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php72" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher" />

24
.idea/php-docker-settings.xml generated Normal file
View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpDockerContainerSettings">
<list>
<map>
<entry key="9fb85f67-19fd-423f-9358-1b155a12eb7d">
<value>
<DockerContainerSettings>
<option name="version" value="1" />
<option name="volumeBindings">
<list>
<DockerVolumeBindingImpl>
<option name="containerPath" value="/opt/project" />
<option name="hostPath" value="$PROJECT_DIR$" />
</DockerVolumeBindingImpl>
</list>
</option>
</DockerContainerSettings>
</value>
</entry>
</map>
</list>
</component>
</project>

14
.idea/php.xml generated
View File

@@ -107,7 +107,6 @@
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php80" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php81" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php72" />
<path value="$PROJECT_DIR$/vendor/symfony/translation" />
<path value="$PROJECT_DIR$/vendor/symfony/translation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher" />
@@ -154,6 +153,19 @@
<path value="$PROJECT_DIR$/vendor/symfony/stopwatch" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpstan" />
<path value="$PROJECT_DIR$/vendor/doctrine/deprecations" />
<path value="$PROJECT_DIR$/vendor/psr/clock" />
<path value="$PROJECT_DIR$/vendor/hyperf/crontab" />
<path value="$PROJECT_DIR$/vendor/fidry/cpu-core-counter" />
<path value="$PROJECT_DIR$/vendor/carbonphp/carbon-doctrine-types" />
<path value="$PROJECT_DIR$/vendor/cooper/hyperf-pest" />
<path value="$PROJECT_DIR$/vendor/clue/ndjson-react" />
<path value="$PROJECT_DIR$/vendor/evenement/evenement" />
<path value="$PROJECT_DIR$/vendor/react/child-process" />
<path value="$PROJECT_DIR$/vendor/react/cache" />
<path value="$PROJECT_DIR$/vendor/react/dns" />
<path value="$PROJECT_DIR$/vendor/react/stream" />
<path value="$PROJECT_DIR$/vendor/react/event-loop" />
<path value="$PROJECT_DIR$/vendor/react/socket" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="7.4" />

View File

@@ -1,4 +1,93 @@
# 版本更新日志
### [0.1.10](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/compare/v0.1.9...v0.1.10) (2025-09-26)
### 👷 Continuous Integration | CI 配置
* 添加发布到注册表的CI工作流 ([0009619](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/0009619d9c74472dd2477d29896fa8e1e64fee36))
### [0.1.9](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/compare/v0.1.8...v0.1.9) (2024-09-12)
### ✨ Features | 新功能
* **email:** 增加了单独定制发件人的方法 ([bebac59](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/bebac5992a89ebe41eb20ea926938f4b3fde2cc1))
### [0.1.8](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/compare/v0.1.7...v0.1.8) (2024-09-12)
### ✨ Features | 新功能
* **email:** 增加了密送参数 ([1c05772](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/1c057722bcf407ab1faf6b5feb285e1e472fc627))
### 📦‍ Build System | 打包构建
* **composer:** 更新依赖 ([0471983](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/04719839ad518ef3c00ffb67b8b563561de36858))
### [0.1.7](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/compare/v0.1.6...v0.1.7) (2023-07-05)
### 📦‍ Build System | 打包构建
* **composer:** 更新依赖 ([a570e19](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/a570e1964e01b9e29de05f4a7607a4ed567beffa))
### ✨ Features | 新功能
* **session:** 增加了定制的 session 中间件 ([b51699b](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/b51699b0d98322df33fb3a0bccebe5f4034e2a71))
### [0.1.6](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/compare/v0.1.5...v0.1.6) (2023-03-21)
### 🐛 Bug Fixes | Bug 修复
* **middleware.i18n:** 尝试修复了多语言无法正确获取的问题 ([a7a8087](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/a7a80875092653e56d8dc9351743a08933d59282))
### ✅ Tests | 测试
* **email:** 更新了邮件发送的测试账号 ([8dcf586](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/8dcf586baf1960f906d63c15f3d659b16b456048))
### 📦‍ Build System | 打包构建
* **composer:** 更新依赖 ([5ed5070](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/5ed5070f323d0ea34b8c99b5ac826dc6c24431df))
### [0.1.5](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/compare/v0.1.4...v0.1.5) (2023-02-03)
### 🐛 Bug Fixes | Bug 修复
* **commonHandler:** 修复了一处兼容问题 ([fca8f9d](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/fca8f9d013e1d121bf8bc997710d77ff038fdae0))
### [0.1.4](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/compare/v0.1.3...v0.1.4) (2023-02-02)
### 💄 Styles | 风格
* **cs-fixer:** prettier ([24161f0](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/24161f084127602a32d9a7ca3af122eb26169fec))
### [0.1.3](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/compare/v0.1.2...v0.1.3) (2023-02-02)
### 📦‍ Build System | 打包构建
* **composer:** 修复 php 版本限制 ([0b4fb23](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/0b4fb2377985dc70a2cfa5a603f73e2ced14dc8c))
* **composer:** 修复 php 版本限制 ([eb20bc9](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/eb20bc9fdbc887d0e478a3ceaa4396001fbbbdfd))
### ✨ Features | 新功能
* **email/sms:** 添加了发送邮件和短信的事件 ([7fd7d23](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/7fd7d2305120f60b70390b49c6256b01fbec6485))
### 🐛 Bug Fixes | Bug 修复
* **extend:** 修复了解析不到为null不为数组的问题 ([c3eb95d](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/c3eb95d300f312b14204edd6b97d80b76c273ff3))
* **extend:** 修复了解析不到为null不为数组的问题 ([a7a61bd](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/a7a61bd6748ca432e95c396738ebdd7dcfe96463))
* **extend:** 修复了解析不到为空不为数组的问题 ([6c27804](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/6c278048568c6b070192b290f54562f05c1803e6))
### [0.1.2](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/compare/v0.1.1...v0.1.2) (2023-01-28)

View File

@@ -15,7 +15,7 @@
}
],
"require": {
"php": "^7.4 || ^8.0",
"php": "^7.4|^8.0",
"ext-redis": "^5.3",
"ext-swoole": "*",
"composer/composer": ">=2.0",
@@ -42,6 +42,7 @@
"firebase/php-jwt": "^6.3",
"friendsofphp/php-cs-fixer": "^3.13",
"guzzlehttp/guzzle": "^7.5",
"hyperf/crontab": "<=3.0.9 || >3.0.13 <3.1",
"hyperf/session": "^2.2",
"hyperf/validation": "^2.2",
"pestphp/pest": "^1.22",
@@ -57,7 +58,11 @@
"hyperf/session": "Session 鉴权必需",
"symfony/mailer": "用于发送电子邮件",
"guzzlehttp/guzzle": "需要发起 http 请求时必需",
"alibabacloud/dysmsapi-20170525": "阿里云短信服务必需"
"alibabacloud/dysmsapi-20170525": "阿里云短信服务必需",
"laminas/laminas-text": "用于cli展示表格和大字",
"gevman/interactive-cli": "用于提供漂亮的交互式命令行",
"clue/stdio-react": "一个基于 reactphp 的交互式命令行",
"brainmaestro/composer-git-hooks": "用于 php 项目的 git-hooks"
},
"config": {
"optimize-autoloader": true,
@@ -109,5 +114,5 @@
"url": "https://repo.huaweicloud.com/repository/php/"
}
},
"version": "0.1.2"
"version": "0.1.10"
}

2548
composer.lock generated

File diff suppressed because it is too large Load Diff

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

@@ -6,4 +6,6 @@ parameters:
- tests
ignoreErrors:
- '#Constant BASE_PATH not found#'
- '#Property [a-zA-Z0-9\\_]+::\$[a-zA-Z0-9]+ is never written, only read.#'
- '#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,85 @@
<?php
/**
* EmailWillSent.php@HDK-Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2023/1/16
*/
namespace Singularity\HDK\Core\Events;
use Singularity\HDK\Core\Service\EmailService;
/**
* 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[] $bcc
*/
public array $bcc = [];
/**
* @var string 'text'|'html' $type
*/
public string $type = 'text';
/**
* @var EmailService|null
*/
public ?EmailService $sender;
public function __construct(
/**
* @phpstan-param string|string[] $target
*/
$target,
string $subject,
string $content,
/**
* @var non-empty-string[] $cc
*/
array $cc = [],
string $type = 'text',
array $bcc = [],
?EmailService $sender = null
) {
$this->type = $type;
$this->cc = $cc;
$this->content = $content;
$this->subject = $subject;
$this->target = $target;
$this->bcc = $bcc;
$this->sender = $sender;
}
}

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

@@ -80,7 +80,7 @@ class CommonHandler extends ExceptionHandler
$this->request->url();
$is_debug = $this->request->hasHeader('Postman-Token')
|| str_starts_with($this->request->header('User-Agent'), 'apifox');
|| str_starts_with($this->request->header('User-Agent', ''), 'apifox');
$request_data = Json::encode($this->request->getParsedBody());
$request_headers = Json::encode($this->request->getHeaders());

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,91 @@
<?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\StdoutLoggerInterface;
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 object $event
*
* @return void
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function process(object $event): void
{
$stdoutLogger = $this->container->get(StdoutLoggerInterface::class);
/** @var $event EmailWillSent */
$emailService = $event->sender ?? EmailService::make();
try {
$event->type === 'html'
? $emailService->sendHtml(
$event->target,
$event->subject,
$event->content,
$event->cc,
$event->bcc,
)
: $emailService->sendText(
$event->target,
$event->subject,
$event->content,
$event->cc,
$event->bcc,
);
$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));
}
}
}

View File

@@ -36,10 +36,10 @@ class InternationalizationMiddleware implements MiddlewareInterface
$language = $request->getHeaderLine('Accept-Language');
if (!empty($language)) {
switch (true) {
case in_array($language, ['en', 'en_us', 'en-us', 'en-uk', 'en_uk']):
case in_array(strtolower($language), ['en', 'en_us', 'en-us', 'en-uk', 'en_uk']):
$language = 'en';
break;
case in_array($language, ['zh', 'zh_cn', 'zh-cn']):
case in_array(strtolower($language), ['zh', 'zh_cn', 'zh-cn']):
$language = 'zh_CN';
break;
default:

View File

@@ -0,0 +1,165 @@
<?php
/**
* SessionMiddleware.php@HDK-Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2023/3/8
*/
namespace Singularity\HDK\Core\Middleware;
use Carbon\Carbon;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\SessionInterface;
use Hyperf\HttpMessage\Cookie\Cookie;
use Hyperf\HttpMessage\Server\Response;
use Hyperf\Session\SessionManager;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
/**
* Singularity\HDK\Auth\Middleware\SessionMiddleware@HDK-Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2023/3/8
*/
class SessionMiddleware implements MiddlewareInterface
{
private SessionManager $sessionManager;
private ConfigInterface $config;
public function __construct(SessionManager $sessionManager, ConfigInterface $config)
{
$this->config = $config;
$this->sessionManager = $sessionManager;
}
/**
* Process an incoming server request.
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if (!$this->isSessionAvailable()) {
return $handler->handle($request);
}
$session = $this->sessionManager->start($request);
try {
$response = $handler->handle($request);
} finally {
$this->storeCurrentUrl($request, $session);
$session = $this->sessionManager->getSession();
/*
* 现在的机制,但凡写入 Redis 的数据,
* 过期时间就是固定的 gc_maxlifetime
*
* 而 RedisHandler 只会被依赖注入时加载一次,
* 后续无法临时修改 gc_maxlifetime
*
* 又因为现在没有其他情况用到 session
* 只有登录之后才会记下用户信息
*
* 所以一个妥协的方案,就是只有登录了再写入 Redis
*
* (除非重写整套 RedisHandler/RedisHandlerFactory/...
*/
if (!$this->auth($session)) {
$this->sessionManager->end($session);
}
}
return $this->addCookieToResponse($request, $response, $session);
}
private function isSessionAvailable(): bool
{
return $this->config->has('session.handler');
}
/**
* Store the current URL for the request if necessary.
*/
private function storeCurrentUrl(RequestInterface $request, SessionInterface $session)
{
if ($request->getMethod() === 'GET') {
$session->setPreviousUrl($this->fullUrl($request));
}
}
private function auth(SessionInterface $session): bool
{
return $session->has('userInfo');
}
/**
* Add the session cookie to the response·.
*/
private function addCookieToResponse(
ServerRequestInterface $request,
ResponseInterface $response,
SessionInterface $session
): ResponseInterface {
$cookie = new Cookie(
$session->getName(),
$session->getId(),
$this->getCookieExpirationDate($session),
$this->config->get('session.options.path', '/'),
$this->config->get('session.options.domain', $request->getUri()->getHost()),
$this->config->get(
'session.options.secure',
strtolower($request->getUri()->getScheme()) === 'https'
),
true,
$this->config->get('session.options.samesite', Cookie::SAMESITE_LAX)
);
if (!method_exists($response, 'withCookie')) {
return $response->withHeader('Set-Cookie', (string)$cookie);
}
/* @var Response $response */
return $response->withCookie($cookie);
}
/**
* Get the full URL for the request.
*/
private function fullUrl(RequestInterface $request): string
{
$uri = $request->getUri();
$query = $uri->getQuery();
$question = $uri->getHost() . $uri->getPath() == '/' ? '/?' : '?';
return $query ? $this->url($request) . $question . $query : $this->url($request);
}
/**
* Get the session lifetime in seconds.
*/
private function getCookieExpirationDate(SessionInterface $session): int
{
// if ($this->config->get('session.options.expire_on_close')) {
if (!$this->auth($session)) {
$expirationDate = 0;
} else {
$expireSeconds = $this->config->get('session.options.cookie_lifetime', 5 * 60 * 60);
$expirationDate = Carbon::now()->addSeconds($expireSeconds)->getTimestamp();
}
return $expirationDate;
}
/**
* Get the URL (no query string) for the request.
*/
private function url(RequestInterface $request): string
{
return rtrim(preg_replace('/\?.*/', '', (string)$request->getUri()));
}
}

View File

@@ -42,6 +42,18 @@ class EmailService
);
}
public static function make(
?string $dsn = null,
?string $mailSenderName = null,
?string $mailSender = null
): EmailService {
return new static(
$dsn,
$mailSenderName,
$mailSender,
);
}
/**
* 发送邮件
*
@@ -66,9 +78,10 @@ class EmailService
/**
* @param string|array<string> $target
* @param string $subject
* @param string $text
* @param string $subject
* @param string $text
* @param array<string> $cc
* @param array $bcc
*
* @return bool
* @throws TransportExceptionInterface
@@ -77,12 +90,14 @@ class EmailService
$target,
string $subject,
string $text,
array $cc = []
array $cc = [],
array $bcc = []
): bool {
$email = (new Email())
->from(Address::create($this->from))
->to(...(is_array($target) ? $target : [$target]))
->cc(...$cc)
->bcc(...$bcc)
->subject($subject)
->text($text);
@@ -95,9 +110,10 @@ class EmailService
* 以 HTML 格式发送邮件
*
* @param string|array<string> $target
* @param string $subject
* @param string $html
* @param string $subject
* @param string $html
* @param array<string> $cc
* @param array $bcc
*
* @return bool
* @throws TransportExceptionInterface
@@ -106,12 +122,14 @@ class EmailService
$target,
string $subject,
string $html,
array $cc = []
array $cc = [],
array $bcc = []
): bool {
$email = (new Email())
->from(Address::create($this->from))
->to(...(is_array($target) ? $target : [$target]))
->cc(...$cc)
->bcc(...$bcc)
->subject($subject)
->html($html);

View File

@@ -11,7 +11,7 @@ use Psr\Http\Message\ServerRequestInterface;
class ExtendService
{
/** @var UtilsService $utils */
private $utils;
private UtilsService $utils;
public function __construct(UtilsService $utils)
{
@@ -20,23 +20,21 @@ class ExtendService
/**
* @param ServerRequestInterface|null $request
* @param array<string, string>|null $params
* @param array<string, string>|string|null $params
* @return string[]
*/
public function parse(
?ServerRequestInterface $request,
?array $params = null
$params = null
): array {
$params = $params ?? $request->getQueryParams();
$extends = $params['extends'] ?? null;
$params = $params ?? (isset($request) ? $request->getQueryParams() : null);
$extends = empty($params['extends']) ? [] : $params['extends'];
if (!empty($extends)) {
$extends = explode(',', $extends);
$extends = array_map('trim', $extends);
Context::set(self::class, $extends);
return $extends;
}
return [];
Context::set(self::class, $extends);
return $extends;
}
/**
@@ -44,7 +42,7 @@ class ExtendService
*/
public function getExtends(): array
{
return Context::get(self::class);
return Context::get(self::class) ?? [];
}
public function hasExtends(): bool

View File

@@ -12,25 +12,27 @@ namespace Singularity\HDK\Test\Core\Service;
use Singularity\HDK\Core\Service\EmailService;
use Symfony\Component\Mailer\Exception\TransportException;
$dsn = 'smtp://account@luxcreo.ai:Qfsd8866@smtp.qiye.aliyun.com:465';
$dsn = 'smtp://account@luxcreo.com:Qfsd8866@smtp-mail.outlook.com:587';
$mail_sender_name = 'LuxCreo';
$mail_sender = 'account@luxcreo.ai';
$mail_sender = 'account@luxcreo.com';
$email = new EmailService($dsn, $mail_sender_name, $mail_sender);
it('assertions that send HTML is available', function () use ($email) {
$result = $email->sendHtml(
'dongyun.li@luxcreo.ai',
'HDK Unit Test HTML',
<<<HTML
$target = 'dongyun.li@luxcreo.ai';
it('assertions that send HTML is available', function () use ($email, $target) {
$result = $email->sendHtml(
$target,
'HDK Unit Test HTML',
<<<HTML
<h1>Hello, World!</h1>
HTML
);
expect($result)->toBeTrue();
);
expect($result)->toBeTrue();
});
it('assertions that send Text is available', function () use ($email) {
it('assertions that send Text is available', function () use ($email, $target) {
$result = $email->sendText(
'dongyun.li@luxcreo.ai',
$target,
'HDK Unit Test Text',
<<<Text
<h1>Hello, World!</h1>
@@ -41,24 +43,26 @@ Text
it('assertions Error Receiver can be catch', function () use ($email) {
try {
$email->sendHtml(
$result = $email->sendHtml(
'unknown@luxcreo.ai',
'HDK Unit Test',
<<<HTML
<h1>Hello, World!</h1>
HTML
);
expect($result)->toBeTrue();
} catch (TransportException $t) {
expect($t->getCode())->toBe(554);
}
try {
$email->sendText(
$result = $email->sendText(
'unknown@luxcreo.ai',
'HDK Unit Test',
<<<Text
<h1>Hello, World!</h1>
Text
);
expect($result)->toBeTrue();
} catch (TransportException $t) {
expect($t->getCode())->toBe(554);
}

View File

@@ -9,12 +9,26 @@
namespace Singularity\HDK\Test\Core\Unit;
use Hyperf\Utils\ApplicationContext;
use Singularity\HDK\Core\Service\ExtendService;
use Singularity\HDK\Core\Service\UtilsService;
/** @var ExtendService $service */
$service = make(ExtendService::class, ['utils' => new UtilsService()]);
it('asserts no parameters can be parsed.', function (ExtendService $service, $params) {
$service->parse(
null,
$params
);
$result = $service->getExtends();
expect($result)->toBeArray()->toHaveCount(0)->toBe([]);
})->with([
[$service, null],
[$service, ''],
[$service, []],
[$service, ['extends' => '']],
[$service, ['extends' => null]],
]);
it('asserts query parameters can be parsed.', function () use ($service) {
$result = $service->parse(
null,