仓库金融单据服务
Some checks failed
Build Docker / build (push) Has been cancelled

This commit is contained in:
2025-07-08 15:10:36 +08:00
commit 7423491d9c
69 changed files with 12995 additions and 0 deletions

52
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1,52 @@
# Dev Container Dockerfile
#
# @link https://www.hyperf.io
# @document https://hyperf.wiki
# @contact group@hyperf.io
# @license https://github.com/hyperf/hyperf/blob/master/LICENSE
FROM hyperf/hyperf:8.3-alpine-v3.19-swoole
LABEL maintainer="Hyperf Developers <group@hyperf.io>" version="1.0" license="MIT" app.name="Hyperf"
##
# ---------- env settings ----------
##
# --build-arg timezone=Asia/Shanghai
ARG timezone
ENV TIMEZONE=${timezone:-"Asia/Shanghai"} \
APP_ENV=dev \
SCAN_CACHEABLE=(false)
# update
RUN set -ex \
# show php version and extensions
&& php -v \
&& php -m \
&& php --ri swoole \
# ---------- some config ----------
&& cd /etc/php* \
# - config PHP
&& { \
echo "upload_max_filesize=128M"; \
echo "post_max_size=128M"; \
echo "memory_limit=1G"; \
echo "date.timezone=${TIMEZONE}"; \
} | tee conf.d/99_overrides.ini \
# - config timezone
&& ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \
&& echo "${TIMEZONE}" > /etc/timezone \
# ---------- clear works ----------
&& rm -rf /var/cache/apk/* /tmp/* /usr/share/man \
&& echo -e "\033[42;37m Build Completed :).\033[0m\n"
WORKDIR /opt/www
# Composer Cache
# COPY ./composer.* /opt/www/
# RUN composer install --no-dev --no-scripts
COPY . /opt/www
RUN composer install && php bin/hyperf.php
EXPOSE 9501

View File

@ -0,0 +1,7 @@
{
"build": {
"context": "..",
"dockerfile": "./Dockerfile"
},
"forwardPorts": [9501]
}

5
.dockerignore Normal file
View File

@ -0,0 +1,5 @@
**
!app/
!bin/
!config/
!composer.*

17
.env.example Normal file
View File

@ -0,0 +1,17 @@
APP_NAME=skeleton
APP_ENV=dev
DB_DRIVER=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=hyperf
DB_USERNAME=root
DB_PASSWORD=
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
DB_PREFIX=
REDIS_HOST=localhost
REDIS_AUTH=(null)
REDIS_PORT=6379
REDIS_DB=0

54
.github/workflows/Dockerfile vendored Normal file
View File

@ -0,0 +1,54 @@
# Default Dockerfile
#
# @link https://www.hyperf.io
# @document https://hyperf.wiki
# @contact group@hyperf.io
# @license https://github.com/hyperf/hyperf/blob/master/LICENSE
FROM hyperf/hyperf:8.3-alpine-v3.19-swoole
LABEL maintainer="Hyperf Developers <group@hyperf.io>" version="1.0" license="MIT" app.name="Hyperf"
##
# ---------- env settings ----------
##
# --build-arg timezone=Asia/Shanghai
ARG timezone
ENV TIMEZONE=${timezone:-"Asia/Shanghai"} \
APP_ENV=prod \
SCAN_CACHEABLE=(true)
# update
RUN set -ex \
# show php version and extensions
&& php -v \
&& php -m \
&& php --ri swoole \
# ---------- some config ----------
&& cd /etc/php* \
# - config PHP
&& { \
echo "upload_max_filesize=128M"; \
echo "post_max_size=128M"; \
echo "memory_limit=1G"; \
echo "date.timezone=${TIMEZONE}"; \
} | tee conf.d/99_overrides.ini \
# - config timezone
&& ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \
&& echo "${TIMEZONE}" > /etc/timezone \
# ---------- clear works ----------
&& rm -rf /var/cache/apk/* /tmp/* /usr/share/man \
&& echo -e "\033[42;37m Build Completed :).\033[0m\n"
WORKDIR /opt/www
# Composer Cache
# COPY ./composer.* /opt/www/
# RUN composer install --no-dev --no-scripts
COPY . /opt/www
RUN print "\n" | composer install -o && php bin/hyperf.php
EXPOSE 9501
ENTRYPOINT ["php", "/opt/www/bin/hyperf.php", "start"]

12
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,12 @@
name: Build Docker
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Build
run: cp -rf .github/workflows/Dockerfile . && docker build -t hyperf .

25
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,25 @@
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
name: Release
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
.buildpath
.settings/
.project
*.patch
.idea/
.git/
runtime/
vendor/
.phpintel/
.env
.DS_Store
.phpunit*
*.cache
.vscode/
/phpstan.neon
/phpunit.xml

57
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,57 @@
# usermod -aG docker gitlab-runner
stages:
- build
- deploy
variables:
PROJECT_NAME: hyperf
REGISTRY_URL: registry-docker.org
build_test_docker:
stage: build
before_script:
# - git submodule sync --recursive
# - git submodule update --init --recursive
script:
- docker build . -t $PROJECT_NAME
- docker tag $PROJECT_NAME $REGISTRY_URL/$PROJECT_NAME:test
- docker push $REGISTRY_URL/$PROJECT_NAME:test
only:
- test
tags:
- builder
deploy_test_docker:
stage: deploy
script:
- docker stack deploy -c deploy.test.yml --with-registry-auth $PROJECT_NAME
only:
- test
tags:
- test
build_docker:
stage: build
before_script:
# - git submodule sync --recursive
# - git submodule update --init --recursive
script:
- docker build . -t $PROJECT_NAME
- docker tag $PROJECT_NAME $REGISTRY_URL/$PROJECT_NAME:$CI_COMMIT_REF_NAME
- docker tag $PROJECT_NAME $REGISTRY_URL/$PROJECT_NAME:latest
- docker push $REGISTRY_URL/$PROJECT_NAME:$CI_COMMIT_REF_NAME
- docker push $REGISTRY_URL/$PROJECT_NAME:latest
only:
- tags
tags:
- builder
deploy_docker:
stage: deploy
script:
- echo SUCCESS
only:
- tags
tags:
- builder

106
.php-cs-fixer.php Normal file
View File

@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
$header = <<<'EOF'
This file is part of Hyperf.
@link https://www.hyperf.io
@document https://hyperf.wiki
@contact group@hyperf.io
@license https://github.com/hyperf/hyperf/blob/master/LICENSE
EOF;
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',
],
'global_namespace_import' => [
'import_classes' => true,
'import_constants' => true,
'import_functions' => null,
],
'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,
'single_line_empty_body' => false,
])
->setFinder(
PhpCsFixer\Finder::create()
->exclude('public')
->exclude('runtime')
->exclude('vendor')
->in(__DIR__)
)
->setUsingCache(false);

12
.phpstorm.meta.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace PHPSTORM_META {
// Reflect
override(\Psr\Container\ContainerInterface::get(0), map(['' => '@']));
override(\Hyperf\Context\Context::get(0), map(['' => '@']));
override(\make(0), map(['' => '@']));
override(\di(0), map(['' => '@']));
override(\Hyperf\Support\make(0), map(['' => '@']));
override(\Hyperf\Support\optional(0), type(0));
override(\Hyperf\Tappable\tap(0), type(0));
}

54
Dockerfile Normal file
View File

@ -0,0 +1,54 @@
# Default Dockerfile
#
# @link https://www.hyperf.io
# @document https://hyperf.wiki
# @contact group@hyperf.io
# @license https://github.com/hyperf/hyperf/blob/master/LICENSE
FROM hyperf/hyperf:8.3-alpine-v3.19-swoole
LABEL maintainer="Hyperf Developers <group@hyperf.io>" version="1.0" license="MIT" app.name="Hyperf"
##
# ---------- env settings ----------
##
# --build-arg timezone=Asia/Shanghai
ARG timezone
ENV TIMEZONE=${timezone:-"Asia/Shanghai"} \
APP_ENV=prod \
SCAN_CACHEABLE=(true)
# update
RUN set -ex \
# show php version and extensions
&& php -v \
&& php -m \
&& php --ri swoole \
# ---------- some config ----------
&& cd /etc/php* \
# - config PHP
&& { \
echo "upload_max_filesize=128M"; \
echo "post_max_size=128M"; \
echo "memory_limit=1G"; \
echo "date.timezone=${TIMEZONE}"; \
} | tee conf.d/99_overrides.ini \
# - config timezone
&& ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \
&& echo "${TIMEZONE}" > /etc/timezone \
# ---------- clear works ----------
&& rm -rf /var/cache/apk/* /tmp/* /usr/share/man \
&& echo -e "\033[42;37m Build Completed :).\033[0m\n"
WORKDIR /opt/www
# Composer Cache
# COPY ./composer.* /opt/www/
# RUN composer install --no-dev --no-scripts
COPY . /opt/www
RUN composer install --no-dev -o && php bin/hyperf.php
EXPOSE 9501
ENTRYPOINT ["php", "/opt/www/bin/hyperf.php", "start"]

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Hyperf
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.

63
README.md Normal file
View File

@ -0,0 +1,63 @@
# Introduction
This is a skeleton application using the Hyperf framework. This application is meant to be used as a starting place for those looking to get their feet wet with Hyperf Framework.
# Requirements
Hyperf has some requirements for the system environment, it can only run under Linux and Mac environment, but due to the development of Docker virtualization technology, Docker for Windows can also be used as the running environment under Windows.
The various versions of Dockerfile have been prepared for you in the [hyperf/hyperf-docker](https://github.com/hyperf/hyperf-docker) project, or directly based on the already built [hyperf/hyperf](https://hub.docker.com/r/hyperf/hyperf) Image to run.
When you don't want to use Docker as the basis for your running environment, you need to make sure that your operating environment meets the following requirements:
- PHP >= 8.1
- Any of the following network engines
- Swoole PHP extension >= 5.0with `swoole.use_shortname` set to `Off` in your `php.ini`
- Swow PHP extension >= 1.3
- JSON PHP extension
- Pcntl PHP extension
- OpenSSL PHP extension If you need to use the HTTPS
- PDO PHP extension If you need to use the MySQL Client
- Redis PHP extension If you need to use the Redis Client
- Protobuf PHP extension If you need to use the gRPC Server or Client
# Installation using Composer
The easiest way to create a new Hyperf project is to use [Composer](https://getcomposer.org/). If you don't have it already installed, then please install as per [the documentation](https://getcomposer.org/download/).
To create your new Hyperf project:
```bash
composer create-project hyperf/hyperf-skeleton path/to/install
```
If your development environment is based on Docker you can use the official Composer image to create a new Hyperf project:
```bash
docker run --rm -it -v $(pwd):/app composer create-project --ignore-platform-reqs hyperf/hyperf-skeleton path/to/install
```
# Getting started
Once installed, you can run the server immediately using the command below.
```bash
cd path/to/install
php bin/hyperf.php start
```
Or if in a Docker based environment you can use the `docker-compose.yml` provided by the template:
```bash
cd path/to/install
docker-compose up
```
This will start the cli-server on port `9501`, and bind it to all network interfaces. You can then visit the site at `http://localhost:9501/` which will bring up Hyperf default home page.
## Hints
- A nice tip is to rename `hyperf-skeleton` of files like `composer.json` and `docker-compose.yml` to your actual project name.
- Take a look at `config/routes.php` and `app/Controller/IndexController.php` to see an example of a HTTP entrypoint.
**Remember:** you can always replace the contents of this README.md file to something that fits your project description.

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Amqp\Consumer;
use App\Service\AliLogsSignService;
use Hyperf\Amqp\Result;
use Hyperf\Amqp\Annotation\Consumer;
use Hyperf\Di\Annotation\Inject;
#[Consumer(exchange: 'wh_service_ali_sls', routingKey: 'wh_service_ali_sls_key', queue: 'wh_service_ali_sls_queue', name: "AliSlsConsumer", nums: 5)]
class AliSlsConsumer extends BaseConsumer
{
#[Inject]
protected AliLogsSignService $aliLogsSignService;
public function handle($data): Result
{
$this->aliLogsSignService->putWebTracking($data);
return Result::ACK;
}
}

View File

@ -0,0 +1,74 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午6:37
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\Amqp\Consumer;
use Hyperf\Amqp\Message\ConsumerMessage;
use Hyperf\Amqp\Result;
use PhpAmqpLib\Exception\AMQPChannelClosedException;
use PhpAmqpLib\Message\AMQPMessage;
use App\Log\Log;
use Exception;
use PhpAmqpLib\Exception\AMQPConnectionClosedException;
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午7:18
* Description: 消费者抽象基类.
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
abstract class BaseConsumer extends ConsumerMessage
{
/**
* 消费者处理逻辑
* @param mixed $data
* @param AMQPMessage $message
* @return Result
* @throws Exception
*/
public function consumeMessage($data, AMQPMessage $message): Result
{
$consumerClass = get_class($this);
try {
return $this->handle($data);
} catch (AMQPChannelClosedException | AMQPConnectionClosedException $e) {
Log::get('queue', 'queue')->error("AMQP通道关闭异常 ($consumerClass): " . $e->getMessage(), [
'data' => $data,
'exception' => $e,
]);
// 可选:重连逻辑 or 丢弃
return Result::ACK; // 或 NACK
} catch (Exception $e) {
Log::get('queue', 'queue')->error("AMQP消费者异常 ($consumerClass): " . $e->getMessage(), [
'data' => $data,
'exception' => $e,
]);
return Result::ACK;
}
}
/**
* 子类实现的核心处理逻辑
* @param mixed $data
* @return Result
*/
abstract protected function handle(mixed $data): Result;
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace App\Amqp\Producer;
use Hyperf\Amqp\Annotation\Producer;
use Hyperf\Amqp\Message\ProducerMessage;
#[Producer(exchange: 'wh_service_ali_sls', routingKey: 'wh_service_ali_sls_key')]
class AliSlsProducer extends ProducerMessage
{
public function __construct($data)
{
$this->payload = $data;
$this->properties['delivery_mode'] = 2; // 消息持久化
}
}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Constants;
use Hyperf\Constants\AbstractConstants;
use Hyperf\Constants\Annotation\Constants;
#[Constants]
class ErrorCode extends AbstractConstants
{
/**
* @Message("Server Error")
*/
public const SERVER_ERROR = 500;
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Controller;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Psr\Container\ContainerInterface;
abstract class AbstractController
{
#[Inject]
protected ContainerInterface $container;
#[Inject]
protected RequestInterface $request;
#[Inject]
protected ResponseInterface $response;
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Controller;
class IndexController extends AbstractController
{
public function index()
{
$user = $this->request->input('user', 'Hyperf');
$method = $this->request->getMethod();
return [
'method' => $method,
'message' => "Hello {$user}.",
];
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Exception;
use App\Constants\ErrorCode;
use Hyperf\Server\Exception\ServerException;
use Throwable;
class BusinessException extends ServerException
{
public function __construct(int $code = 0, string $message = null, Throwable $previous = null)
{
if (is_null($message)) {
$message = ErrorCode::getMessage($code);
}
parent::__construct($message, $code, $previous);
}
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Exception\Handler;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\HttpMessage\Stream\SwooleStream;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class AppExceptionHandler extends ExceptionHandler
{
public function __construct(protected StdoutLoggerInterface $logger)
{
}
public function handle(Throwable $throwable, ResponseInterface $response)
{
$this->logger->error(sprintf('%s[%s] in %s', $throwable->getMessage(), $throwable->getLine(), $throwable->getFile()));
$this->logger->error($throwable->getTraceAsString());
return $response->withHeader('Server', 'Hyperf')->withStatus(500)->withBody(new SwooleStream('Internal Server Error.'));
}
public function isValid(Throwable $throwable): bool
{
return true;
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/5
* * Time: 下午1:56
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\JsonRpc;
use App\Service\JsonRpcResponse;
use Hyperf\Di\Annotation\Inject;
/**
* Author: ykxiao
* Date: 2025/6/5
* Time: 下午1:56
* Description: 服务基类.
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
abstract class BaseService
{
#[Inject]
protected JsonRpcResponse $response;
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/5
* Time: 下午1:45
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\JsonRpc;
use Hyperf\RpcServer\Annotation\RpcService;
#[RpcService(name: 'InventoryService', server: 'jsonrpc-http', protocol: 'jsonrpc-http', publishTo: 'nacos')]
class InventoryService extends BaseService implements InventoryServiceInterface
{
public function addInventory(array $data): void
{
}
}

View File

@ -0,0 +1,26 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/5
* Time: 下午1:46
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\JsonRpc;
interface InventoryServiceInterface
{
/**
* 新增库存-手动单条.
* @param array $data
* @return void
*/
public function addInventory(array $data): void;
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Listener;
use Hyperf\Collection\Arr;
use Hyperf\Database\Events\QueryExecuted;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Logger\LoggerFactory;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
#[Listener]
class DbQueryExecutedListener implements ListenerInterface
{
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(ContainerInterface $container)
{
$this->logger = $container->get(LoggerFactory::class)->get('sql');
}
public function listen(): array
{
return [
QueryExecuted::class,
];
}
/**
* @param QueryExecuted $event
*/
public function process(object $event): void
{
if ($event instanceof QueryExecuted) {
$sql = $event->sql;
if (! Arr::isAssoc($event->bindings)) {
$position = 0;
foreach ($event->bindings as $value) {
$position = strpos($sql, '?', $position);
if ($position === false) {
break;
}
$value = "'{$value}'";
$sql = substr_replace($sql, $value, $position, 1);
$position += strlen($value);
}
}
$this->logger->info(sprintf('[%s] %s', $event->time, $sql));
}
}
}

View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Listener;
use Hyperf\AsyncQueue\AnnotationJob;
use Hyperf\AsyncQueue\Event\AfterHandle;
use Hyperf\AsyncQueue\Event\BeforeHandle;
use Hyperf\AsyncQueue\Event\Event;
use Hyperf\AsyncQueue\Event\FailedHandle;
use Hyperf\AsyncQueue\Event\RetryHandle;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Logger\LoggerFactory;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
#[Listener]
class QueueHandleListener implements ListenerInterface
{
protected LoggerInterface $logger;
public function __construct(ContainerInterface $container)
{
$this->logger = $container->get(LoggerFactory::class)->get('queue');
}
public function listen(): array
{
return [
AfterHandle::class,
BeforeHandle::class,
FailedHandle::class,
RetryHandle::class,
];
}
public function process(object $event): void
{
if ($event instanceof Event && $event->getMessage()->job()) {
$job = $event->getMessage()->job();
$jobClass = get_class($job);
if ($job instanceof AnnotationJob) {
$jobClass = sprintf('Job[%s@%s]', $job->class, $job->method);
}
$date = date('Y-m-d H:i:s');
switch (true) {
case $event instanceof BeforeHandle:
$this->logger->info(sprintf('[%s] Processing %s.', $date, $jobClass));
break;
case $event instanceof AfterHandle:
$this->logger->info(sprintf('[%s] Processed %s.', $date, $jobClass));
break;
case $event instanceof FailedHandle:
$this->logger->error(sprintf('[%s] Failed %s.', $date, $jobClass));
$this->logger->error((string) $event->getThrowable());
break;
case $event instanceof RetryHandle:
$this->logger->warning(sprintf('[%s] Retried %s.', $date, $jobClass));
break;
}
}
}
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Listener;
use Hyperf\Command\Event\AfterExecute;
use Hyperf\Coordinator\Constants;
use Hyperf\Coordinator\CoordinatorManager;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
#[Listener]
class ResumeExitCoordinatorListener implements ListenerInterface
{
public function listen(): array
{
return [
AfterExecute::class,
];
}
public function process(object $event): void
{
CoordinatorManager::until(Constants::WORKER_EXIT)->resume();
}
}

46
app/Log/AliSlsHandler.php Normal file
View File

@ -0,0 +1,46 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午6:33
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\Log;
use App\Amqp\Producer\AliSlsProducer;
use Hyperf\Amqp\Producer;
use Hyperf\Di\Annotation\Inject;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\LogRecord;
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午7:19
* Description: 阿里云日志服务处理器.
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
class AliSlsHandler extends AbstractProcessingHandler
{
#[Inject]
protected Producer $producer;
protected function write(LogRecord $record): void
{
$logs = ['channel' => $record['channel'] ?? '', 'formatted' => $record['formatted'] ?? ''];
$message = new AliSlsProducer($logs);
$this->producer->produce($message, true);
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午6:33
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\Log;
use Hyperf\Context\Context;
use Hyperf\Coroutine\Coroutine;
use Monolog\LogRecord;
use Monolog\Processor\ProcessorInterface;
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午7:19
* Description: 添加请求ID和协程ID
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
class AppendRequestIdProcessor implements ProcessorInterface
{
public const string REQUEST_ID = 'log.request.id';
public function __invoke(LogRecord $record): array|LogRecord
{
$record['extra']['request_id'] = Context::getOrSet(self::REQUEST_ID, uniqid('xw_cloud'));
$record['extra']['coroutine_id'] = Coroutine::id();
return $record;
}
}

63
app/Log/Log.php Normal file
View File

@ -0,0 +1,63 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午6:42
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\Log;
use Exception;
use Hyperf\Context\ApplicationContext;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Logger\LoggerFactory;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
use function Hyperf\Support\make;
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午7:19
* Description: 日志记录器类。
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
class Log
{
#[Inject]
protected LoggerInterface $logger;
/**
* 根据提供的名称和分组获取日志记录器实例。
*
* @param string $name 日志记录器的名称,默认为'app'。
* @param string $group 日志记录器的分组,默认为'job'。
* @return LoggerInterface 返回一个日志记录器实例。
* @throws Exception
*/
public static function get(string $name = 'app', string $group = 'job'): LoggerInterface
{
try {
// 尝试从应用上下文容器中获取LoggerFactory实例并进一步获取指定名称和分组的日志记录器
return ApplicationContext::getContainer()->get(LoggerFactory::class)->get($name, $group);
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
// 如果在获取过程中发生异常使用默认的日志记录器记录错误信息并抛出ApiException
$logs = make(LoggerFactory::class)->get('default');
$logs->error($e->getMessage());
throw new Exception($e->getMessage());
}
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午6:03
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\Log;
use Hyperf\Context\ApplicationContext;
use Hyperf\Logger\LoggerFactory;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午6:04
* Description: 创建一个日志记录器
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
class StdoutLoggerFactory
{
/**
* @param ContainerInterface $container
* @return LoggerInterface
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function __invoke(ContainerInterface $container): LoggerInterface
{
return ApplicationContext::getContainer()->get(LoggerFactory::class)->get();
}
}

22
app/Model/Model.php Normal file
View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Model;
use Hyperf\DbConnection\Model\Model as BaseModel;
use Hyperf\ModelCache\Cacheable;
use Hyperf\ModelCache\CacheableInterface;
abstract class Model extends BaseModel implements CacheableInterface
{
use Cacheable;
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Process;
use Hyperf\AsyncQueue\Process\ConsumerProcess;
use Hyperf\Process\Annotation\Process;
#[Process]
class AsyncQueueConsumer extends ConsumerProcess
{
}

View File

@ -0,0 +1,158 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午7:13
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\Service;
use Hyperf\Config\Annotation\Value;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Guzzle\ClientFactory;
use function Hyperf\Coroutine\co;
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午7:18
* Description: 阿里云日志服务.
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
class AliLogsSignService
{
protected const string LOG_SIGNATURE_METHOD = 'hmac-sha1';
protected const string LOG_API_VERSION = '0.6.0';
#[Value('alibaba.accessKeyId')]
protected string $accessKeyId;
#[Value('alibaba.accessKeySecret')]
protected string $accessKeySecret;
#[Value('alibaba.logs')]
protected array $logs;
#[Inject]
protected ClientFactory $clientFactory;
/**
* 签名及请求头拼接.
* @param mixed $method
* @param mixed $uri
* @param mixed $params
* @param mixed $body
* @param mixed $logProject
* @param mixed $logEndpoint
*/
public function buildHeaders(
string $method,
string $uri,
array $params,
string $body,
string|object $logProject,
string $logEndpoint
): array
{
$headers = [
'x-log-signaturemethod' => self::LOG_SIGNATURE_METHOD,
'x-log-apiversion' => self::LOG_API_VERSION,
'Host' => sprintf('%s.%s', $logProject, $logEndpoint),
'Content-Type' => 'application/json',
];
$contentLength = 0;
$contentMd5 = '';
if (!empty($body) && strlen($body) > 0) {
$contentLength = strlen($body);
$contentMd5 = strtoupper(md5($body));
$headers['Content-MD5'] = $contentMd5;
}
// date
setlocale(LC_TIME, 'en_US');
$date = gmdate('D, d M Y H:i:s \G\M\T', time());
$headers['Date'] = $date;
$headers['Content-Length'] = (string)$contentLength;
$contentType = $headers['Content-Type'];
$message = $method . "\n" . $contentMd5 . "\n" . $contentType . "\n" . $date . "\n";
// header
$filterHeaders = [];
foreach ($headers as $key => $val) {
if (str_starts_with($key, 'x-log-') || str_starts_with($key, 'x-acs-')) {
$filterHeaders[$key] = $val;
}
}
ksort($filterHeaders);
foreach ($filterHeaders as $key => $val) {
$message .= $key . ':' . $val . "\n";
}
// uri and params
$message .= $uri;
if (sizeof($params) > 0) {
$message .= '?';
}
ksort($params);
$sep = '';
foreach ($params as $key => $val) {
$message .= $sep . $key . '=' . $val;
$sep = '&';
}
// signature & authorization
$signature = $this->generateSignature($message);
$auth = 'LOG ' . $this->accessKeyId . ':' . $signature;
$headers['Authorization'] = $auth;
return $headers;
}
/**
* 实现调用PutWebTracking接口将多条日志合并进行采集.
*/
public function putWebTracking(array $record): void
{
$logEndpoint = $this->logs['log_endpoint'];
$logProject = $this->logs['log_project'];
$logStores = $this->logs['log_store'];
$params = [];
$body = [
'__topic__' => 'mes api logs',
'__source__' => 'admin api',
'__logs__' => [
['Logs' => $record['formatted']],
],
'__tags__' => [
'mes' => $record['channel'],
],
];
$body = json_encode($body);
$sign_url = sprintf('/logstores/%s/track', $logStores);
$headers = $this->buildHeaders('POST', $sign_url, $params, $body, $logProject, $logEndpoint);
$options = [
'headers' => $headers,
'body' => $body,
'query' => $params,
];
$url = sprintf('https://%s.%s/logstores/%s/track', $logProject, $logEndpoint, $logStores);
$client = $this->clientFactory->create();
co(function () use ($client, $url, $options) {
$client->request('POST', $url, $options);
});
}
protected function generateSignature(string $message): string
{
return base64_encode(hash_hmac('sha1', $message, $this->accessKeySecret, true));
}
}

View File

@ -0,0 +1,97 @@
<?php
/**
* Author: ykxiao
* Date: 2024/6/3
* Time: 22:45
* Description:
*/
declare(strict_types=1);
namespace App\Service;
use Hyperf\Logger\LoggerFactory;
use Psr\Log\LoggerInterface;
/**
* Author: ykxiao
* Date: 2025/1/3
* Time: 下午8:33
* Description: JsonRpc响应类.
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
class JsonRpcResponse
{
/**
* JsonRpc版本。
*/
private string $jsonRpcVersion = '2.0';
private mixed $id;
private mixed $result;
private mixed $error;
protected LoggerInterface $logger;
public function __construct(LoggerFactory $loggerFactory, $id = null, $result = null, $error = null)
{
$this->id = $id;
$this->result = $result;
$this->error = $error;
$this->logger = $loggerFactory->get('jsonrpc');
}
public function withId($id): static
{
$this->id = $id;
return $this;
}
public function withResult($result): static
{
$this->result = $result;
$this->logger->info(json_encode(['RPC result' => $result], JSON_UNESCAPED_UNICODE));
return $this;
}
public function withError($code, $message, $data = null): static
{
$this->error = [
'code' => $code,
'message' => $message,
'data' => $data,
];
$this->logger->error(json_encode(['RPC error' => $message], JSON_UNESCAPED_UNICODE));
return $this;
}
public function toArray(): array
{
$response = [
'jsonrpc' => $this->jsonRpcVersion,
'id' => $this->id,
];
if ($this->error) {
$response['error'] = $this->error;
} else {
$response['result'] = $this->result;
}
return $response;
}
public function toJson(): bool|string
{
return json_encode($this->toArray());
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/4
* Time: 下午1:57
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\Service;
use Hyperf\Database\Schema\Blueprint;
class MigrateService
{
/***
* 在创建的时候自动添加如下字段
* @param Blueprint $blueprint
* @return Blueprint
*/
public static function migrateCreateInfo(Blueprint $blueprint): Blueprint
{
$blueprint->integer('creator_id')->default(0)->unsigned()->comment('创建人ID');
$blueprint->string('creator_name', 45)->default('')->comment('创建人姓名');
$blueprint->integer('created_at')->default(0)->unsigned()->comment('创建时间');
$blueprint->integer('updated_at')->default(0)->unsigned()->comment('更新时间');
$blueprint->integer('deleted_at')->nullable()->unsigned()->comment('软删除时间');
return $blueprint;
}
public static function migrateTime(Blueprint $blueprint): Blueprint
{
$blueprint->integer('created_at')->default(0)->unsigned()->comment('创建时间');
$blueprint->integer('updated_at')->default(0)->unsigned()->comment('更新时间');
$blueprint->integer('deleted_at')->nullable()->unsigned()->comment('软删除时间');
return $blueprint;
}
}

25
bin/hyperf.php Normal file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env php
<?php
ini_set('display_errors', 'on');
ini_set('display_startup_errors', 'on');
ini_set('memory_limit', '1G');
error_reporting(E_ALL);
date_default_timezone_set('Asia/Shanghai');
! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
require BASE_PATH . '/vendor/autoload.php';
! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', Hyperf\Engine\DefaultOption::hookFlags());
// Self-called anonymous function that creates its own scope and keep the global namespace clean.
(function () {
Hyperf\Di\ClassLoader::init();
/** @var Psr\Container\ContainerInterface $container */
$container = require BASE_PATH . '/config/container.php';
$application = $container->get(Hyperf\Contract\ApplicationInterface::class);
$application->run();
})();

88
composer.json Normal file
View File

@ -0,0 +1,88 @@
{
"name": "hyperf/hyperf-skeleton",
"type": "project",
"keywords": [
"php",
"swoole",
"framework",
"hyperf",
"microservice",
"middleware"
],
"description": "A coroutine framework that focuses on hyperspeed and flexible, specifically use for build microservices and middlewares.",
"license": "Apache-2.0",
"require": {
"php": ">=8.3",
"hyperf/amqp": "~3.1.0",
"hyperf/async-queue": "~3.1.0",
"hyperf/cache": "~3.1.0",
"hyperf/command": "~3.1.0",
"hyperf/config": "~3.1.0",
"hyperf/constants": "~3.1.0",
"hyperf/database": "~3.1.0",
"hyperf/db-connection": "~3.1.0",
"hyperf/engine": "^2.10",
"hyperf/framework": "~3.1.0",
"hyperf/guzzle": "~3.1.0",
"hyperf/http-server": "~3.1.0",
"hyperf/json-rpc": "~3.1.0",
"hyperf/logger": "~3.1.0",
"hyperf/memory": "~3.1.0",
"hyperf/model-cache": "~3.1.0",
"hyperf/process": "~3.1.0",
"hyperf/redis": "~3.1.0",
"hyperf/rpc": "~3.1.0",
"hyperf/rpc-client": "~3.1.0",
"hyperf/rpc-server": "~3.1.0",
"hyperf/service-governance": "~3.1.0",
"hyperf/service-governance-nacos": "^3.1"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.0",
"hyperf/devtool": "~3.1.0",
"hyperf/testing": "~3.1.0",
"mockery/mockery": "^1.0",
"phpstan/phpstan": "^1.0",
"swoole/ide-helper": "^5.0"
},
"suggest": {
"ext-openssl": "Required to use HTTPS.",
"ext-json": "Required to use JSON.",
"ext-pdo": "Required to use MySQL Client.",
"ext-pdo_mysql": "Required to use MySQL Client.",
"ext-redis": "Required to use Redis Client."
},
"autoload": {
"psr-4": {
"App\\": "app/"
},
"files": []
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\": "./test/"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"optimize-autoloader": true,
"sort-packages": true
},
"extra": [],
"scripts": {
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-autoload-dump": [
"rm -rf runtime/container"
],
"test": "co-phpunit --prepend test/bootstrap.php --colors=always",
"cs-fix": "php-cs-fixer fix $1",
"analyse": "phpstan analyse --memory-limit 300M",
"start": [
"Composer\\Config::disableProcessTimeout",
"php ./bin/hyperf.php start"
]
}
}

10360
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

42
config/autoload/amqp.php Normal file
View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use function Hyperf\Support\env;
return [
'default' => [
'host' => env('AMQP_HOST', 'localhost'),
'port' => (int) env('AMQP_PORT', 5672),
'user' => env('AMQP_USER', 'guest'),
'password' => env('AMQP_PASSWORD', 'guest'),
'vhost' => env('AMQP_VHOST', '/'),
'concurrent' => [
'limit' => 1,
],
'pool' => [
'connections' => 2,
],
'params' => [
'insist' => false,
'login_method' => 'AMQPLAIN',
'login_response' => null,
'locale' => 'en_US',
'connection_timeout' => 3,
'read_write_timeout' => 6,
'context' => null,
'keepalive' => true,
'heartbeat' => 3,
'channel_rpc_timeout' => 0.0,
'close_on_destruct' => false,
'max_idle_channels' => 10,
],
],
];

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
return [
'scan' => [
'paths' => [
BASE_PATH . '/app',
],
'ignore_annotations' => [
'mixin',
],
],
];

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
return [
];

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
return [
'default' => [
'driver' => \Hyperf\AsyncQueue\Driver\RedisDriver::class,
'redis' => [
'pool' => 'default',
],
'channel' => '{queue}',
'timeout' => 2,
'retry_seconds' => 5,
'handle_timeout' => 10,
'processes' => 1,
'concurrent' => [
'limit' => 10,
],
'max_messages' => 0,
],
];

19
config/autoload/cache.php Normal file
View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
return [
'default' => [
'driver' => Hyperf\Cache\Driver\RedisDriver::class,
'packer' => Hyperf\Codec\Packer\PhpSerializerPacker::class,
'prefix' => 'c:',
'skip_cache_results' => [],
],
];

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
return [
];

View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use function Hyperf\Support\env;
return [
'default' => [
'driver' => env('DB_DRIVER', 'mysql'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', 3306),
'database' => env('DB_DATABASE', 'hyperf'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => env('DB_PREFIX', ''),
'pool' => [
'min_connections' => 1,
'max_connections' => 10,
'connect_timeout' => 10.0,
'wait_timeout' => 3.0,
'heartbeat' => -1,
'max_idle_time' => (float) env('DB_MAX_IDLE_TIME', 60),
],
'cache' => [
'handler' => Hyperf\ModelCache\Handler\RedisHandler::class,
'cache_key' => '{mc:%s:m:%s}:%s:%s',
'prefix' => 'default',
'ttl' => 3600 * 24,
'empty_model_ttl' => 600,
'load_script' => true,
],
'commands' => [
'gen:model' => [
'path' => 'app/Model',
'force_casts' => true,
'inheritance' => 'Model',
'uses' => '',
'table_mapping' => [],
],
],
],
];

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use App\Log\StdoutLoggerFactory;
use Hyperf\Contract\StdoutLoggerInterface;
return [
StdoutLoggerInterface::class => StdoutLoggerFactory::class,
];

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
return [
'generator' => [
'amqp' => [
'consumer' => [
'namespace' => 'App\\Amqp\\Consumer',
],
'producer' => [
'namespace' => 'App\\Amqp\\Producer',
],
],
'aspect' => [
'namespace' => 'App\\Aspect',
],
'command' => [
'namespace' => 'App\\Command',
],
'controller' => [
'namespace' => 'App\\Controller',
],
'job' => [
'namespace' => 'App\\Job',
],
'listener' => [
'namespace' => 'App\\Listener',
],
'middleware' => [
'namespace' => 'App\\Middleware',
],
'Process' => [
'namespace' => 'App\\Processes',
],
],
];

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
return [
'handler' => [
'http' => [
Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler::class,
App\Exception\Handler\AppExceptionHandler::class,
],
],
];

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
return [
Hyperf\ExceptionHandler\Listener\ErrorExceptionHandler::class,
Hyperf\Command\Listener\FailToHandleListener::class,
];

View File

@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use App\Log\AliSlsHandler;
use App\Log\AppendRequestIdProcessor;
use Monolog\Formatter\JsonFormatter;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\RotatingFileHandler;
use Psr\Log\LogLevel;
use function Hyperf\Support\env;
$env = env('APP_ENV', 'production');
$isProduction = $env === 'production';
$productionLogLevel = env('LOG_LEVEL', 'error');
$formatter = $isProduction
? [
'class' => JsonFormatter::class,
'constructor' => [
'format' => null,
'dateFormat' => 'Y-m-d H:i:s',
'allowInlineLineBreaks' => true,
],
]
: [
'class' => LineFormatter::class,
'constructor' => [
'format' => "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n",
'dateFormat' => 'Y-m-d H:i:s',
'allowInlineLineBreaks' => true,
],
];
$loggers = [
'default', 'sql', 'request', 'crontab', 'queue', 'timer', 'job', 'some', 'amqp'
];
$logLevel = $isProduction ? $productionLogLevel : LogLevel::INFO;
$config = [];
foreach ($loggers as $loggerName) {
$handler = $isProduction
? [
'class' => AliSlsHandler::class,
'constructor' => [
'filename' => BASE_PATH . sprintf('/runtime/logs/%s/%s.log', $loggerName, $loggerName),
'level' => $logLevel,
'maxFiles' => 30,
],
]
: [
'class' => RotatingFileHandler::class,
'constructor' => [
'filename' => BASE_PATH . sprintf('/runtime/logs/%s/%s.log', $loggerName, $loggerName),
'level' => $logLevel,
'maxFiles' => 30,
],
];
$config[$loggerName] = [
'handler' => $handler,
'formatter' => $formatter,
'processors' => [
[
'class' => AppendRequestIdProcessor::class,
],
],
];
}
return $config;

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
return [
'http' => [
],
];

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
return [
];

29
config/autoload/redis.php Normal file
View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use function Hyperf\Support\env;
return [
'default' => [
'host' => env('REDIS_HOST', 'localhost'),
'auth' => env('REDIS_AUTH', null),
'port' => (int) env('REDIS_PORT', 6379),
'db' => (int) env('REDIS_DB', 0),
'pool' => [
'min_connections' => 1,
'max_connections' => 10,
'connect_timeout' => 10.0,
'wait_timeout' => 3.0,
'heartbeat' => -1,
'max_idle_time' => (float) env('REDIS_MAX_IDLE_TIME', 60),
],
],
];

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use Hyperf\JsonRpc\HttpServer;
use Hyperf\Server\Event;
use Hyperf\Server\ServerInterface;
use Swoole\Constant;
use function Hyperf\Support\env;
return [
'mode' => SWOOLE_PROCESS,
'servers' => [
[
'name' => 'jsonrpc-http',
'type' => ServerInterface::SERVER_HTTP,
'host' => '0.0.0.0',
'port' => intval(env('HTTP_PORT', 9507)),
'sock_type' => SWOOLE_SOCK_TCP,
'callbacks' => [
Event::ON_REQUEST => [HttpServer::class, 'onRequest'],
],
],
],
'settings' => [
Constant::OPTION_ENABLE_COROUTINE => true,
Constant::OPTION_WORKER_NUM => min(swoole_cpu_num() * 4, 32),
Constant::OPTION_PID_FILE => BASE_PATH . '/runtime/hyperf.pid',
Constant::OPTION_OPEN_TCP_NODELAY => true,
Constant::OPTION_MAX_COROUTINE => 100000,
Constant::OPTION_OPEN_HTTP2_PROTOCOL => true,
Constant::OPTION_MAX_REQUEST => 100000,
Constant::OPTION_SOCKET_BUFFER_SIZE => 2 * 1024 * 1024,
Constant::OPTION_BUFFER_OUTPUT_SIZE => 2 * 1024 * 1024,
// 连接池配置
Constant::OPTION_REACTOR_NUM => swoole_cpu_num(),
],
'callbacks' => [
Event::ON_WORKER_START => [Hyperf\Framework\Bootstrap\WorkerStartCallback::class, 'onWorkerStart'],
Event::ON_PIPE_MESSAGE => [Hyperf\Framework\Bootstrap\PipeMessageCallback::class, 'onPipeMessage'],
Event::ON_WORKER_EXIT => [Hyperf\Framework\Bootstrap\WorkerExitCallback::class, 'onWorkerExit'],
],
];

View File

@ -0,0 +1,32 @@
<?php
/**
* Author: ykxiao
* Date: 2024/5/31
* Time: 08:40
* Description:
*/
declare(strict_types=1);
return [
'enable' => [
'discovery' => true,
'register' => true,
],
'consumers' => [],
'providers' => [],
'drivers' => [
'nacos' => [
'host' => '127.0.0.1',
'port' => 8848,
// The nacos account info
'username' => null,
'password' => null,
'guzzle' => [
'config' => null,
],
'heartbeat' => 5,
],
],
];

33
config/config.php Normal file
View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use Hyperf\Contract\StdoutLoggerInterface;
use Psr\Log\LogLevel;
use function Hyperf\Support\env;
return [
'app_name' => env('APP_NAME', 'skeleton'),
'app_env' => env('APP_ENV', 'dev'),
'scan_cacheable' => env('SCAN_CACHEABLE', false),
StdoutLoggerInterface::class => [
'log_level' => [
LogLevel::ALERT,
LogLevel::CRITICAL,
LogLevel::DEBUG,
LogLevel::EMERGENCY,
LogLevel::ERROR,
LogLevel::INFO,
LogLevel::NOTICE,
LogLevel::WARNING,
],
],
];

21
config/container.php Normal file
View File

@ -0,0 +1,21 @@
<?php
/**
* Initialize a dependency injection container that implemented PSR-11 and return the container.
*/
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use Hyperf\Context\ApplicationContext;
use Hyperf\Di\Container;
use Hyperf\Di\Definition\DefinitionSourceFactory;
$container = new Container((new DefinitionSourceFactory())());
return ApplicationContext::setContainer($container);

18
config/routes.php Normal file
View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use Hyperf\HttpServer\Router\Router;
Router::addRoute(['GET', 'POST', 'HEAD'], '/', 'App\Controller\IndexController@index');
Router::get('/favicon.ico', function () {
return '';
});

30
deploy.test.yml Normal file
View File

@ -0,0 +1,30 @@
version: '3.7'
services:
hyperf:
image: $REGISTRY_URL/$PROJECT_NAME:test
environment:
- "APP_PROJECT=hyperf"
- "APP_ENV=testing"
ports:
- "9501:9501"
deploy:
replicas: 1
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 5
update_config:
parallelism: 2
delay: 5s
order: start-first
networks:
- hyperf_net
configs:
- source: hyperf_v1.0
target: /opt/www/.env
configs:
hyperf_v1.0:
external: true
networks:
hyperf_net:
external: true

18
docker-compose.yml Normal file
View File

@ -0,0 +1,18 @@
version: '3'
services:
hyperf-skeleton:
container_name: hyperf-skeleton
image: hyperf-skeleton
build:
context: .
volumes:
- ./:/opt/www
ports:
- 9501:9501
environment:
- APP_ENV=dev
- SCAN_CACHEABLE=false
networks:
default:
name: hyperf-skeleton

View File

@ -0,0 +1,116 @@
<?php
/**
* Author: ykxiao
* Date: 2025/3/7
* Time: 下午3:27
* Description: 创建库存表 inventory 的迁移文件
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use App\Service\MigrateService;
use Hyperf\Database\Schema\Schema;
use Hyperf\Database\Schema\Blueprint;
use Hyperf\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('inventory', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('company_code', 50)->default('')->comment('公司代码');
$table->tinyInteger('status')->default(1)->comment('资源状态(-1:删除,0:禁用,1:在途,2:已入库,3:部分出库,4:全部出库,5:待分配库位)');
$table->integer('bale_id')->default(0)->comment('采购捆包id');
$table->tinyInteger('source')->default(1)->comment('数据来源(1:自建,2:仓储)');
$table->integer('stock_id')->default(0)->comment('库存汇总ID');
$table->string('bale_no', 45)->default('')->comment('捆包号');
$table->string('supplier', 50)->default('')->comment('供应商');
$table->integer('supplier_id')->default(0)->comment('供应商id');
$table->string('issuing', 50)->default('')->comment('开证公司');
$table->integer('issuing_id')->default(0)->comment('开证公司id');
$table->string('breed', 50)->default('')->comment('商品名称');
$table->integer('breed_id')->default(0)->comment('品名id');
$table->string('brand', 50)->default('')->comment('品牌');
$table->integer('brand_id')->default(0)->comment('品牌id');
$table->string('grade', 50)->default('')->comment('等级');
$table->integer('grade_id')->default(0)->comment('等级id');
$table->string('thickness', 50)->default('')->comment('厚度');
$table->string('width', 50)->default('')->comment('宽度');
$table->string('length', 50)->default('')->comment('长度');
$table->string('pieces', 50)->default('')->comment('片数');
$table->decimal('per_cube', 18, 9)->comment('单包立方');
$table->unsignedInteger('init_number')->default(0)->comment('入库初始件数');
$table->unsignedInteger('number')->default(0)->comment('库存件数');
$table->unsignedInteger('lock_number')->default(0)->comment('锁定件数');
$table->unsignedInteger('enable_number')->default(0)->comment('可用件数');
$table->unsignedInteger('out_number')->default(0)->comment('已出件数');
$table->decimal('foreign_currency_price', 18, 2)->default(0.00)->comment('外币单价');
$table->integer('foreign_currency_id')->default(0)->comment('外币名称id');
$table->string('foreign_currency_unit', 3)->comment('外币符号');
$table->decimal('purchase_amount', 18, 2)->default(0.00)->comment('采购金额');
$table->decimal('sales_price', 18, 2)->default(0.00)->comment('销售单价/挂牌价');
$table->decimal('pay_rate', 18, 4)->default(0.0000)->comment('实付汇率');
$table->decimal('purchase_price', 18, 2)->default(0.00)->comment('采购单价');
$table->decimal('cost_price', 18, 2)->comment('费用平摊单价');
$table->integer('storage_location_id')->default(0)->comment('库位ID');
$table->string('storage_location', 100)->default('')->comment('库位');
$table->string('contract_no', 45)->default('')->comment('合同号');
$table->string('bill_lading_no', 45)->default('')->comment('提单号');
$table->integer('port_id')->default(0)->comment('港口id');
$table->string('purpose_port', 255)->default('')->comment('目的港');
$table->integer('expected_storage_time')->default(0)->comment('预计入库时间');
$table->integer('expected_arrival_time')->default(0)->comment('预计到港时间');
$table->string('place_origin', 255)->default('')->comment('产地');
$table->tinyInteger('is_changed')->default(0)->comment('是否改动(0:未改动,1:已改动)');
$table->tinyInteger('qr_code_status')->default(0)->comment('扫码状态(1-未扫码2-已扫码)');
$table->tinyInteger('purchase_type')->default(0)->comment('采购类型 (1 合同采购, 2外调采购)');
$table->json('images')->comment('产品图片');
$table->string('thumbnail', 255)->default('')->comment('封面缩略图');
$table->integer('inbound_time')->default(0)->comment('入库时间');
$table->integer('stocker_id')->default(0)->comment('入库人id');
$table->string('stocker_name', 30)->default('')->comment('入库人名字');
$table->string('cargo_remark', 200)->default('')->comment('货物状态');
// 仓库字段
$table->string('purchase_sn', 45)->default('')->comment('采购单号');
$table->string('contract_sn', 45)->default('')->comment('采购合同号');
$table->string('container_no', 45)->default('')->comment('柜号/车牌号');
$table->tinyInteger('customer_type')->default(1)->comment('代理类型1-代理 2-非代理');
$table->tinyInteger('category')->default(1)->comment('货物类型1-原木 2-板材');
$table->tinyInteger('packaging_type')->default(1)->comment('包装类型1-整包、2-混包)');
$table->string('spec', 100)->default('')->comment('仓库规格');
$table->integer('sh_company_id')->default(0)->unsigned()->comment('收货公司id');
$table->string('sh_company_name', 45)->default('')->comment('收货公司');
$table->integer('aisle_id')->default(0)->unsigned()->comment('通道ID');
$table->string('aisle_name', 45)->default('')->comment('通道名');
$table->integer('warehouse_id')->default(0)->comment('仓库id');
$table->string('warehouse', 255)->default('')->comment('仓库');
$table->integer('location_id')->default(0)->unsigned()->comment('仓库库位ID');
$table->integer('attribute_id')->default(0)->unsigned()->comment('库位属性ID');
$table->string('custom', 45)->default('')->comment('自定义属性');
$table->string('location_no', 100)->default('')->comment('仓库库位拼接信息');
$table->string('remark', 300)->default('')->comment('备注');
MigrateService::migrateCreateInfo($table);
$table->comment('库存表');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('inventory');
}
};

View File

@ -0,0 +1,76 @@
<?php
/**
* Author: ykxiao
* Date: 2025/3/7
* Time: 下午3:27
* Description: 创建库存汇总表 stock 的迁移文件
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use App\Service\MigrateService;
use Hyperf\Database\Schema\Schema;
use Hyperf\Database\Schema\Blueprint;
use Hyperf\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('stock', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('company_code', 50)->default('')->comment('公司代码');
$table->integer('issuing_id')->default(0)->comment('开证公司id');
$table->integer('sh_company_id')->default(0)->unsigned()->comment('收货公司id');
$table->tinyInteger('source')->default(0)->comment('数据来源(1:自建,2:仓储)');
$table->tinyInteger('type')->default(1)->comment('汇总类型(1:在途,2现货)');
$table->integer('warehouse_id')->default(0)->comment('仓库ID');
$table->string('warehouse', 255)->default('')->comment('仓库');
$table->string('aisle_id', 50)->default('')->comment('通道号');
$table->string('aisle_name', 45)->default('')->comment('通道名');
$table->integer('breed_id')->default(0)->comment('品名ID');
$table->string('breed', 50)->default('')->comment('品名');
$table->integer('brand_id')->default(0)->comment('品牌ID');
$table->string('brand', 50)->default('')->comment('品牌');
$table->integer('grade_id')->default(0)->comment('等级ID');
$table->string('grade', 50)->default('')->comment('等级');
$table->string('thickness', 50)->default('')->comment('厚度');
$table->string('width', 50)->default('')->comment('宽度');
$table->string('length', 50)->default('')->comment('长度');
$table->decimal('min_price', 18)->default(0.00)->comment('最低价');
$table->decimal('sales_price', 18)->default(0.00)->comment('挂牌价');
$table->integer('number')->default(0)->comment('库存件数');
$table->integer('enable_number')->default(0)->comment('可用件数');
$table->integer('lock_number')->default(0)->comment('锁定件数');
$table->unsignedDecimal('cube', 18, 9)->default(0.000000000)->comment('库存方数');
$table->unsignedTinyInteger('sales_status')->default(1)->comment('销售状态(1:销售中,2:封盘中,3:隐藏中)');
$table->tinyInteger('status')->default(1)->comment('库存状态(0:禁用,1:正常)');
$table->integer('create_time')->default(0)->comment('创建时间');
$table->integer('update_time')->default(0)->comment('更新时间');
$table->integer('delete_time')->default(0)->comment('删除时间');
$table->unique(['company_code', 'source', 'type', 'warehouse_id', 'breed_id', 'brand_id', 'grade_id', 'thickness', 'width', 'length', 'aisle_id'], 'idx_unique');
$table->index(['company_code', 'breed_id', 'brand_id', 'grade_id', 'thickness', 'width', 'length'], 'idx_spec');
MigrateService::migrateTime($table);
$table->comment('库存汇总表');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('stock');
}
};

14
phpstan.neon.dist Normal file
View File

@ -0,0 +1,14 @@
# Magic behaviour with __get, __set, __call and __callStatic is not exactly static analyser-friendly :)
# Fortunately, You can ignore it by the following config.
#
# vendor/bin/phpstan analyse app --memory-limit 200M -l 0
#
parameters:
level: 0
paths:
- ./app
- ./config
reportUnmatchedIgnoredErrors: false
ignoreErrors:
- '#Static call to instance method Hyperf\\HttpServer\\Router\\Router::[a-zA-Z0-9\\_]+\(\)#'
- '#Static call to instance method Hyperf\\DbConnection\\Db::[a-zA-Z0-9\\_]+\(\)#'

16
phpunit.xml.dist Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="./test/bootstrap.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
<testsuites>
<testsuite name="Tests">
<directory suffix="Test.php">./test</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing" force="true"/>
</php>
<source>
<include>
<directory suffix=".php">./app</directory>
</include>
</source>
</phpunit>

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Cases;
use Hyperf\Testing\TestCase;
/**
* @internal
* @coversNothing
*/
class ExampleTest extends TestCase
{
public function testExample()
{
$this->get('/')->assertOk()->assertSee('Hyperf');
}
}

45
test/HttpTestCase.php Normal file
View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace HyperfTest;
use Hyperf\Testing\Client;
use PHPUnit\Framework\TestCase;
use function Hyperf\Support\make;
/**
* Class HttpTestCase.
* @method get($uri, $data = [], $headers = [])
* @method post($uri, $data = [], $headers = [])
* @method json($uri, $data = [], $headers = [])
* @method file($uri, $data = [], $headers = [])
* @method request($method, $path, $options = [])
*/
abstract class HttpTestCase extends TestCase
{
/**
* @var Client
*/
protected $client;
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
$this->client = make(Client::class);
}
public function __call($name, $arguments)
{
return $this->client->{$name}(...$arguments);
}
}

30
test/bootstrap.php Normal file
View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
ini_set('display_errors', 'on');
ini_set('display_startup_errors', 'on');
error_reporting(E_ALL);
date_default_timezone_set('Asia/Shanghai');
Swoole\Runtime::enableCoroutine(true);
! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
require BASE_PATH . '/vendor/autoload.php';
! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', Hyperf\Engine\DefaultOption::hookFlags());
Hyperf\Di\ClassLoader::init();
$container = require BASE_PATH . '/config/container.php';
$container->get(Hyperf\Contract\ApplicationInterface::class);