Compare commits

...

11 Commits

Author SHA1 Message Date
李东云
68451820b0 chore(release): 1.0.0-beta.12 2025-08-08 14:33:07 +08:00
李东云
55da02080e feat(exception): 新增业务错误处理和 API 迁移支持
- 新增业务错误类型和相关错误码
- 实现 API 迁移异常处理
- 增加未实现功能异常处理
- 更新错误处理逻辑,支持新错误类型

Signed-off-by: 李东云 <dongyu.li@luxcreo.ai>
2025-08-02 18:35:31 +08:00
李东云
0f7b9464c9 chore(release): 1.0.0-beta.11 2025-07-11 16:36:06 +08:00
李东云
bf40c6e681 feat: 新增SP相关配置的枚举
- 添加 EnvConfiguration 接口,定义开发和生产环境常量
- 添加 ServiceProviderConfiguration 接口,继承环境和站点配置接口
- 添加 SiteConfiguration接口,定义中国和北美站点常量

Signed-off-by: 李东云 <dongyu.li@luxcreo.ai>
2025-07-11 16:34:10 +08:00
李东云
9a8575b382 chore(release): 1.0.0-beta.10 2025-07-09 14:36:33 +08:00
李东云
c6ab9db698 feat(core): 添加 X-SP-ID 解析中间件
- 新增 SpParseMiddleware 类,用于解析请求中的 X-SP-ID 头- 将解析后的 SP ID存入 Hyperf 上下文中,供后续使用
- 此中间件适用于需要根据 SP ID 进行权限控制或日志记录的场景

Signed-off-by: 李东云 <dongyu.li@luxcreo.ai>
2025-07-09 14:36:11 +08:00
李东云
2e0e4feaa5 chore(release): 1.0.0-beta.9 2025-07-07 13:28:26 +08:00
李东云
c8b454540d build(deps): 更新依赖并添加 PHPSpec 支持
- 添加 Symfony polyfill-php84依赖
- 更新 rector/rector 至最新版本
- 添加 PHPSpec 相关配置和依赖
- 更新 composer.json 配置
- 调整项目结构,添加 spec 目录作为测试源

Signed-off-by: 李东云 <dongyu.li@luxcreo.ai>
2025-07-07 13:27:25 +08:00
李东云
bb11a1d8f4 chore(release): 1.0.0-beta.8 2025-03-04 02:34:32 +00:00
李东云
b4dcd01e8d build(composer): Update Dependencies 2025-03-04 02:33:58 +00:00
李东云
3b3502f763 feat(email): 添加邮件附件功能
- 在 EmailWillSent 事件中添加 attachmentPaths 和 attachments 属性
- 在 EmailService 中添加处理附件的逻辑
- 更新 EmailWillSentListener 以支持附件发送
2025-03-04 02:33:24 +00:00
21 changed files with 1860 additions and 1035 deletions

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

@@ -4,6 +4,7 @@
<content url="file://$MODULE_DIR$">
<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\" />
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/runtime" />
<excludeFolder url="file://$MODULE_DIR$/vendor/adbario/php-dot-notation" />
<excludeFolder url="file://$MODULE_DIR$/vendor/alibabacloud/credentials" />
@@ -184,6 +185,11 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/react/event-loop" />
<excludeFolder url="file://$MODULE_DIR$/vendor/react/socket" />
<excludeFolder url="file://$MODULE_DIR$/vendor/react/stream" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpspec/php-diff" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpspec/phpspec" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpspec/prophecy" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php84" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/yaml" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

25
.idea/jsonSchemas.xml generated Normal file
View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JsonSchemaMappingsProjectConfiguration">
<state>
<map>
<entry key="composer.json">
<value>
<SchemaInfo>
<option name="name" value="composer.json" />
<option name="relativePathToSchema" value="https://getcomposer.org/schema.json" />
<option name="applicationDefined" value="true" />
<option name="patterns">
<list>
<Item>
<option name="path" value="composer.json" />
</Item>
</list>
</option>
</SchemaInfo>
</value>
</entry>
</map>
</state>
</component>
</project>

View File

@@ -2,6 +2,13 @@
<project version="4">
<component name="PhpTestFrameworkSettings">
<test_tools>
<tool tool_name="PHPSpec">
<settings>
<configurations>
<local_configuration executable_path="$PROJECT_DIR$/vendor/phpspec/phpspec/bin/phpspec" />
</configurations>
</settings>
</tool>
<tool tool_name="Pest">
<settings>
<configurations>

9
.idea/php.xml generated
View File

@@ -15,9 +15,6 @@
<PhpCSFixerConfiguration standards="PSR1;PSR2;Symfony;DoctrineAnnotation;PHP70Migration;PHP71Migration" tool_path="$PROJECT_DIR$/vendor/friendsofphp/php-cs-fixer/php-cs-fixer" />
</phpcsfixer_settings>
</component>
<component name="PhpExternalFormatter">
<option name="externalFormatter" value="PHP_CBF" />
</component>
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/hyperf/pool" />
@@ -200,6 +197,12 @@
<path value="$PROJECT_DIR$/vendor/react/stream" />
<path value="$PROJECT_DIR$/vendor/react/event-loop" />
<path value="$PROJECT_DIR$/vendor/react/socket" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php72" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php84" />
<path value="$PROJECT_DIR$/vendor/symfony/yaml" />
<path value="$PROJECT_DIR$/vendor/phpspec/phpspec" />
<path value="$PROJECT_DIR$/vendor/phpspec/php-diff" />
<path value="$PROJECT_DIR$/vendor/phpspec/prophecy" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.1" />

View File

@@ -1,4 +1,44 @@
# 版本更新日志
## [1.0.0-beta.12](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/compare/v1.0.0-beta.11...v1.0.0-beta.12) (2025-08-08)
### ✨ Features | 新功能
* **exception:** 新增业务错误处理和 API 迁移支持 ([55da020](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/55da02080efa5bd6978b7d0a0e3bbcb1cde41027))
## [1.0.0-beta.11](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/compare/v1.0.0-beta.10...v1.0.0-beta.11) (2025-07-11)
### ✨ Features | 新功能
* 新增SP相关配置的枚举 ([bf40c6e](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/bf40c6e681cb1ff37a8fc331a3cd0cdb495e6ff1))
## [1.0.0-beta.10](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/compare/v1.0.0-beta.9...v1.0.0-beta.10) (2025-07-09)
### ✨ Features | 新功能
* **core:** 添加 X-SP-ID 解析中间件 ([c6ab9db](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/c6ab9db69810bcd395c52cc304dffa54978d6e84))
## [1.0.0-beta.9](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/compare/v1.0.0-beta.8...v1.0.0-beta.9) (2025-07-07)
### 📦‍ Build System | 打包构建
* **deps:** 更新依赖并添加 PHPSpec 支持 ([c8b4545](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/c8b454540d2c4371c72f6cb79b5f65b97e7b92f2))
## [1.0.0-beta.8](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/compare/v1.0.0-beta.7...v1.0.0-beta.8) (2025-03-04)
### ✨ Features | 新功能
* **email:** 添加邮件附件功能 ([3b3502f](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/3b3502f7634b48c31c3e7689cdcfe1f4b504b968))
### 📦‍ Build System | 打包构建
* **composer:** Update Dependencies ([b4dcd01](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/commit/b4dcd01e8d1fcd2c1fa211ef2a54512593500780))
## [1.0.0-beta.7](http://124.126.16.154:8888/singularity/HyperfDevelopmentKitCore/compare/v1.0.0-beta.6...v1.0.0-beta.7) (2025-03-03)

View File

@@ -1 +1 @@
1.0.0-beta.7
1.0.0-beta.12

View File

@@ -43,6 +43,7 @@
"symfony/polyfill-php81": "^1.28",
"symfony/polyfill-php82": "^1.28",
"symfony/polyfill-php83": "^1.28",
"symfony/polyfill-php84": "^1.28",
"teapot/status-code": "^1.1.2"
},
"require-dev": {
@@ -59,8 +60,9 @@
"hyperf/testing": "^3.1",
"hyperf/validation": "3.1.*",
"pestphp/pest": "^2.23",
"phpspec/phpspec": "^7.5",
"phpstan/phpstan": "^1.10.49",
"rector/rector": "^0.15.25",
"rector/rector": "*",
"roave/security-advisories": "dev-latest",
"spatie/pest-plugin-test-time": "^2.0",
"swoole/ide-helper": "*",
@@ -107,7 +109,7 @@
"phpunit.xml"
]
},
"minimum-stability": "dev",
"minimum-stability": "beta",
"prefer-stable": true,
"scripts": {
"post-root-package-install": [],
@@ -125,10 +127,14 @@
]
},
"repositories": {
"nest": {
"type": "composer",
"url": "https://nest.doylee.cn/api/packages/HDK/composer"
},
"packagist": {
"type": "composer",
"url": "https://mirrors.aliyun.com/composer/"
}
},
"version": "1.0.0-beta.7"
"version": "1.0.0-beta.12"
}

2529
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -163,4 +163,11 @@ return [
'default' => 'DOC deleted',
],
],
// 业务错误
'business' => [
'default' => 'Business Error',
'old_version' => 'This feature is not available in the current version.',
'not_implemented' => 'This feature is not implemented yet.',
],
];

View File

@@ -172,4 +172,11 @@ return [
'default' => '文档已删除',
],
],
// 业务错误
'business' => [
'default' => '业务错误',
'old_version' => '当前版本过低,请升级到最新版本',
'not_implemented' => '该功能尚未实现',
],
];

View File

@@ -12,6 +12,7 @@ namespace Singularity\HDK\Core\Constants;
use Hyperf\Constants\AbstractConstants;
use Hyperf\Constants\Annotation\Constants;
use Hyperf\Constants\Annotation\Message;
/**
* Singularity\HyperfDevelopmentKit\Utils\Constants\CommonErrorCode@hyperf-development-kit
@@ -319,4 +320,15 @@ class CommonErrorCode extends AbstractConstants
* @Message("common_error.model.document.default")
*/
public const DOCUMENT_NOT_EXISTS = 4020201;
// 500 业务错误
#[Message('common_error.business.defaul')]
public const BUSINESS_ERROR = 500001;
// 501 版本问题
#[Message('common_error.business.old_version')]
public const BUSINESS_FLAT_OLD_VERSION = 501001;
#[Message('common_error.business.not_implemented')]
public const BUSINESS_NOT_IMPLEMENTED = 501002;
}

View File

@@ -0,0 +1,16 @@
<?php
/**
* EnvConfiguration.php@Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/7/11
*/
namespace Singularity\HDK\Core\Enumerations\Sp;
interface EnvConfiguration
{
public const DEVELOPMENT = 'development';
public const PRODUCTION = 'production';
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* ServiceProviderConfiguration.php@Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/7/11
*/
namespace Singularity\HDK\Core\Enumerations\Sp;
interface ServiceProviderConfiguration extends EnvConfiguration, SiteConfiguration {}

View File

@@ -0,0 +1,16 @@
<?php
/**
* SiteConfigration.php@Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/7/11
*/
namespace Singularity\HDK\Core\Enumerations\Sp;
interface SiteConfiguration
{
public const CN = 'CN';
public const NA = 'NA';
}

View File

@@ -1,4 +1,5 @@
<?php
/**
* EmailWillSent.php@HDK-Core
*
@@ -10,6 +11,7 @@
namespace Singularity\HDK\Core\Events;
use Singularity\HDK\Core\Service\EmailService;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
/**
@@ -25,9 +27,9 @@ class EmailWillSent
{
public function __construct(
/**
* @var string|string[] $target
* @var Address|string|string[]|Address[] $target
*/
public string|array $target,
public Address|string|array $target,
/**
* @var non-empty-string $subject
@@ -68,6 +70,17 @@ class EmailWillSent
* @var EmailService|null
*/
public ?EmailService $sender = null,
) {
}
/**
* 附件路径集合、含附件路径、文件名、文件类型的数组集合
* @var string[]|array{'path': string, 'name': string, 'mimeType': string}[] $attachmentPaths
*/
public array $attachmentPaths = [],
/**
* 附件内容集合、含附件内容、文件名、文件类型的数组集合
* @var string[]|array{'content': string, 'name': string, 'mimeType': string}[] $attachments
*/
public array $attachments = [],
) {}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* DomainOrderNotSupportException.php@LuxDesign
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/1
*/
declare(strict_types=1);
namespace Singularity\HDK\Core\Exceptions;
use Hyperf\HttpMessage\Exception\HttpException;
use Singularity\HDK\Core\Constants\CommonErrorCode;
use Teapot\StatusCode\RFC\RFC7231;
use Throwable;
final class ApiMigrated extends HttpException
{
public function __construct(
public string $apiPath,
int $code = CommonErrorCode::BUSINESS_FLAT_OLD_VERSION,
?string $message = null,
Throwable $previous = null,
) {
parent::__construct(RFC7231::SEE_OTHER, $message, $code, $previous);
}
public function getNewPath(): string
{
return $this->apiPath;
}
}

View File

@@ -32,6 +32,8 @@ use Lmc\HttpConstants\Header;
use Psr\Http\Message\ResponseInterface;
use RedisException;
use Singularity\HDK\Core\Constants\CommonErrorCode;
use Singularity\HDK\Core\Exceptions\ApiMigrated;
use Singularity\HDK\Core\Exceptions\NotImplementedException;
use Singularity\HDK\Core\Exceptions\ValidateException;
use Singularity\HDK\Core\Service\ApiStyleService;
use Symfony\Component\Mailer\Exception\TransportException;
@@ -133,7 +135,7 @@ class CommonHandler extends ExceptionHandler
$data = [
$code_name => CommonErrorCode::REQUEST_PARAMS_ERROR,
$message_name => $is_testing ? $throwable->getMessage() : CommonErrorCode::getMessage(
CommonErrorCode::SERVER_ERROR
CommonErrorCode::SERVER_ERROR,
),
];
$status_code = RFC4918::UNPROCESSABLE_ENTITY;
@@ -158,7 +160,7 @@ class CommonHandler extends ExceptionHandler
$code,
[
'param' => $throwable->getFieldName(),
]
],
)
: $message,
];
@@ -178,7 +180,7 @@ class CommonHandler extends ExceptionHandler
$code = empty($throwable->getCode()) ? CommonErrorCode::MODEL_NOT_FOUND : $throwable->getCode();
$message = empty($throwable->getCode()) ? CommonErrorCode::getMessage(
$code,
['resource' => '资源']
['resource' => '资源'],
) : $throwable->getMessage();
$data = [$code_name => $code, $message_name => $message];
$status_code = RFC7231::NOT_FOUND;
@@ -193,7 +195,7 @@ class CommonHandler extends ExceptionHandler
$data = [
$code_name => $code,
$message_name => CommonErrorCode::getMessage(
$is_testing ? $code : CommonErrorCode::SERVER_CACHE_REDIS_ERROR
$is_testing ? $code : CommonErrorCode::SERVER_CACHE_REDIS_ERROR,
),
];
}
@@ -207,17 +209,39 @@ class CommonHandler extends ExceptionHandler
$data = [
$code_name => $code,
$message_name => CommonErrorCode::getMessage(
$is_testing ? $code : CommonErrorCode::SERVER_MESSAGE_EMAIL_ERROR
$is_testing ? $code : CommonErrorCode::SERVER_MESSAGE_EMAIL_ERROR,
),
];
}
// 500 业务错误
if ($throwable instanceof ApiMigrated) {
$code = $throwable->getCode();
$data = [
$code_name => $code,
$message_name => $throwable->getMessage(),
];
$headers = [
'Location' => $throwable->getNewPath(),
];
}
if ($throwable instanceof NotImplementedException || $throwable instanceof \Hyperf\Framework\Exception\NotImplementedException) {
$code = $throwable->getCode();
if ($code === 0) {
$code = CommonErrorCode::BUSINESS_NOT_IMPLEMENTED;
}
$message = $throwable->getMessage() ?? CommonErrorCode::getMessage($code);
$data = [
$code_name => $code,
$message_name => $message,
];
}
if (empty($data)) {
// 其他情况
$data = [
$code_name => $is_testing ? $throwable->getCode(
) == 0 ? CommonErrorCode::SERVER_ERROR : $throwable->getCode() : CommonErrorCode::SERVER_ERROR,
$message_name => $is_testing ? $throwable->getMessage() : __(
CommonErrorCode::getMessage(CommonErrorCode::SERVER_ERROR)
CommonErrorCode::getMessage(CommonErrorCode::SERVER_ERROR),
),
];
// 其他错误
@@ -263,15 +287,17 @@ REQUEST_DATA:
TRACE:
{$throwable->getTraceAsString()}
===============================
ERROR_LOG
ERROR_LOG,
);
$data = Json::encode($data);
if ($restful === ApiStyleService::RESTFUL) {
$response = $response->withStatus(
$status_code ?? $throwable->status ?? $throwable->statusCode ?? RFC7231::INTERNAL_SERVER_ERROR
$status_code ?? $throwable->status ?? $throwable->statusCode ?? RFC7231::INTERNAL_SERVER_ERROR,
);
}
foreach ($headers ?? [] as $header => $value) {
$response->withHeader($header, $value);
}
return $response->withBody(new SwooleStream($data));
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* NotImplementedException.php@Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/8/2
*/
declare(strict_types=1);
namespace Singularity\HDK\Core\Exceptions;
use Hyperf\HttpMessage\Exception\HttpException;
use Singularity\HDK\Core\Constants\CommonErrorCode;
use Teapot\StatusCode\RFC\RFC7231;
use Throwable;
final class NotImplementedException extends HttpException
{
public function __construct(
int $code = CommonErrorCode::BUSINESS_NOT_IMPLEMENTED,
?string $message = null,
Throwable $previous = null,
) {
$message ??= CommonErrorCode::getMessage($code);
parent::__construct(RFC7231::NOT_IMPLEMENTED, $message, $code, $previous);
}
}

View File

@@ -1,4 +1,5 @@
<?php
/**
* EmailWillSentListener.php@HDK-Core
*
@@ -64,7 +65,9 @@ class EmailWillSentListener implements ListenerInterface
subject: $event->subject,
html: $event->content,
cc: $event->cc,
bcc: $event->bcc,
bcc: $event->bcc,
attachmentPaths: $event->attachmentPaths,
attachments: $event->attachments,
priority: $event->priority
)
: $emailService->sendText(
@@ -72,7 +75,9 @@ class EmailWillSentListener implements ListenerInterface
subject: $event->subject,
text: $event->content,
cc: $event->cc,
bcc: $event->bcc,
bcc: $event->bcc,
attachmentPaths: $event->attachmentPaths,
attachments: $event->attachments,
priority: $event->priority
);

View File

@@ -0,0 +1,32 @@
<?php
/**
* SpParseMiddleware.php@Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2025/7/9
*/
declare(strict_types=1);
namespace Singularity\HDK\Core\Middleware;
use Hyperf\Context\Context;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
final class SpParseMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$sp = $request->getHeaderLine('X-SP-ID');
if (!empty($sp)) {
Context::set('sp', $sp);
}
return $handler->handle($request);
}
}

View File

@@ -79,7 +79,7 @@ class EmailService
}
/**
* @param string|array<string> $target
* @param Address|string|array<string|Address> $target
* @param string $subject
* @param string $text
* @param array<string> $cc
@@ -90,11 +90,13 @@ class EmailService
* @throws TransportExceptionInterface
*/
public function sendText(
string|array $target,
Address|string|array $target,
string $subject,
string $text,
array $cc = [],
array $bcc = [],
array $attachmentPaths = [],
array $attachments = [],
int $priority = Email::PRIORITY_NORMAL
): bool {
$email = (new Email())
@@ -106,6 +108,22 @@ class EmailService
->subject($subject)
->text($text);
foreach ($attachmentPaths as $attachmentPath) {
if (is_string($attachmentPath)) {
$email = $email->attachFromPath($attachmentPath);
} else {
$email = $email->attachFromPath($attachmentPath['path'], $attachmentPath['name'] ?? null, $attachmentPath['mimeType'] ?? null);
}
}
foreach ($attachments as $attachment) {
if (is_string($attachment)) {
$email = $email->attach($attachment);
} else {
$email = $email->attach($attachment['content'], $attachment['name'] ?? null, $attachment['mimeType'] ?? null);
}
}
$this->mailer->send($email);
return true;
@@ -114,7 +132,7 @@ class EmailService
/**
* 以 HTML 格式发送邮件
*
* @param string|array<string> $target
* @param Address|string|array<string|Address> $target
* @param string $subject
* @param string $html
* @param array<string> $cc
@@ -125,7 +143,7 @@ class EmailService
* @throws TransportExceptionInterface
*/
public function sendHtml(
string|array $target,
Address|string|array $target,
string $subject,
string $html,
array $cc = [],