first commit

This commit is contained in:
2025-09-12 15:23:08 +08:00
commit a80c237bbb
117 changed files with 15628 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.*

22
.env.example Normal file
View File

@@ -0,0 +1,22 @@
APP_NAME=sfyy
APP_ENV=dev
APP_DEBUG=false
APP_URL=http://127.0.0.1:9501
JWT_SECRET=azOVxsOWt3r0ozZNz8Ss429ht0T8z6OpeIJAIwNp6X0xqrbEY2epfIWyxtC1qSNM8eD6/LQ/SahcQi2ByXa/2A==
DB_DRIVER=mysql
DB_HOST=s2.gnip.vip
DB_PORT=20191
DB_DATABASE=mineadmin
DB_USERNAME=hhl
DB_PASSWORD=hhltest
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
DB_PREFIX=
REDIS_HOST=s2.gnip.vip
REDIS_AUTH=hhltest
REDIS_PORT=4379
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));
}

63
BLADE.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.

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.

87
README.md Normal file
View File

@@ -0,0 +1,87 @@
## 仓库
- [sfyy_server](https://codeup.aliyun.com/67039465d8d1ada68263f984/hhl/rewrite/hyperf_service.git) - git远程仓库地址
## 特性
- **最新技术栈**:使用 PHP8.3/hyperf3.1/swoole5.1.4/phpredis 6.0.2 等后端前沿技术开发
## 文档
[文档地址 Github](https://hyperf.wiki/3.1/)
## 前序准备
- [php8.3](https://www.php.net/) 和 [git](https://git-scm.com/) - 项目开发环境
- [swoole](https://www.swoole.com/) - 熟悉 swoole 特性
- [php8.3](https://www.php.net/) - 熟悉 php 基础语法
- [hyperf](https://hyperf.wiki/3.1/) - 熟悉 `hyperf` 基本语法
## 安装和使用
- 安装 swoole 和 phpredis 扩展
```
自行搜索安装教程
```
- 获取代码
```bash
git clone https://codeup.aliyun.com/67039465d8d1ada68263f984/hhl/rewrite/hyperf_service.git
mkdir uploads
```
- vendor
```bash
composer install
```
- 运行
```bash
cp .env.example .env
vim .env
php bin/hyperf.php start
```
- command 函数
```bash
#框架自有
php bin/hyperf.php gen:controller LoginController
php bin/hyperf.php gen:model UserModel
php bin/hyperf.php gen:request LoginRequest
php bin/hyperf.php gen:command TestCommand
php bin/hyperf.php gen:job TestJob
php bin/hyperf.php gen:listener TestListener
php bin/hyperf.php gen:middleware AuthMiddleware
php bin/hyperf.php gen:amqp-consumer DemoConsumer
php bin/hyperf.php gen:amqp-producer DemoProducer
php bin/hyperf.php gen:constant ErrorCode --type enum
#新增命令
php bin/hyperf.php gen:service LoginService
php bin/hyperf.php gen:cron OssTask
php bin/hyperf.php gen:event TestEvent
```
## Git 贡献提交规范
- `feat` 新功能
- `fix` 修补 bug
- `docs` 文档
- `style` 格式、样式(不影响代码运行的变动)
- `refactor` 重构(即不是新增功能,也不是修改 BUG 的代码)
- `perf` 优化相关,比如提升性能、体验
- `test` 添加测试
- `build` 编译相关的修改,对项目构建或者依赖的改动
- `ci` 持续集成修改
- `chore` 构建过程或辅助工具的变动
- `revert` 回滚到上一个版本
- `workflow` 工作流改进
- `mod` 不确定分类的修改
- `wip` 开发中
- `types` 类型

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Annotation;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class ResponseFormat
{
/**
* @param string $format
*/
public function __construct(public string $format) {}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace App\Aspect;
use App\Annotation\ResponseFormat;
use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Aop\AbstractAspect;
use Hyperf\Di\Exception\Exception;
use Hyperf\HttpServer\Request;
use Psr\Container\ContainerInterface;
use Hyperf\Di\Aop\ProceedingJoinPoint;
#[Aspect]
class ResponseFormatAspect extends AbstractAspect
{
/**
* @var array|class-string[]
*/
public array $annotations = [
ResponseFormat::class,
];
/**
* @param ContainerInterface $container
*/
public function __construct(protected ContainerInterface $container) {}
/**
* @param ProceedingJoinPoint $proceedingJoinPoint
* @return mixed
* @throws Exception
*/
public function process(ProceedingJoinPoint $proceedingJoinPoint): mixed
{
// 获取注解定义的格式
$annotation = $proceedingJoinPoint->getAnnotationMetadata()->class[ResponseFormat::class]
?? $proceedingJoinPoint->getAnnotationMetadata()->method[ResponseFormat::class] ?? null;
if ($annotation) {
// 将注解格式存入请求属性(覆盖中间件的默认值)
$request = $proceedingJoinPoint->arguments['request'] ?? null;
if ($request instanceof Request) {
$request->withAttribute('response_format', $annotation->format);
}
}
return $proceedingJoinPoint->process();
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace App\Command\GenClass;
use Hyperf\Command\Command as HyperfCommand;
use Hyperf\Command\Annotation\Command;
use Hyperf\Devtool\Generator\GeneratorCommand;
use Psr\Container\ContainerInterface;
#[Command]
class RepositoryGenCommand extends GeneratorCommand
{
/**
* @param ContainerInterface $container
*/
public function __construct(protected ContainerInterface $container)
{
parent::__construct('gen:repository');
}
/**
* @return void
*/
public function configure(): void
{
parent::configure();
$this->setDescription('Create a new repository class');
$this->setHelp('php bin/hyperf.php gen:repository fileRepository');
}
/**
* 获取 stubs
* @return string
*/
protected function getStub(): string
{
return __DIR__ . '/stubs/repository.stub';
}
/**
* 获取默认命名空间
* @return string
*/
protected function getDefaultNamespace(): string
{
return 'App\\Repository';
}
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace App\Command\GenClass;
use Hyperf\Command\Annotation\Command;
use Hyperf\Devtool\Generator\GeneratorCommand;
use Psr\Container\ContainerInterface;
#[Command]
class ServiceGenCommand extends GeneratorCommand
{
/**
* @param ContainerInterface $container
*/
public function __construct(protected ContainerInterface $container)
{
parent::__construct('gen:service');
}
/**
* @return void
*/
public function configure(): void
{
parent::configure();
$this->setDescription('Create a new service class');
$this->setHelp('php bin/hyperf.php gen:service /folder/fileService');
}
/**
* 获取 stubs
* @return string
*/
protected function getStub(): string
{
return __DIR__ . '/stubs/service.stub';
}
/**
* 获取默认命名空间
* @return string
*/
protected function getDefaultNamespace(): string
{
return 'App\\Service';
}
}

View File

@@ -0,0 +1,16 @@
<?php
/**
* This service file is part of item.
*
* @author ctexthuang
* @contact ctexthuang@qq.com
*/
declare(strict_types=1);
namespace %NAMESPACE%;
final class %CLASS% extends BaseRepository
{
public function __construct(protected readonly xxx $model) {}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* This service file is part of item.
*
* @author ctexthuang
* @contact ctexthuang@qq.com
*/
declare(strict_types=1);
namespace %NAMESPACE%;
class %CLASS% extends
{
public function handle()
{
//todo Write logic
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Common\Macros;
use Hyperf\Database\Schema\Blueprint;
class BlueprintMacros
{
public static function register(): void
{
Blueprint::macro('authorFields', function () {
/** @var Blueprint $this */
$this->unsignedBigInteger('created_by')->nullable()->comment('创建人ID');
$this->unsignedBigInteger('updated_by')->nullable()->comment('更新人ID');
return $this;
});
}
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Constants;
use Hyperf\Constants\Annotation\Constants;
use Hyperf\Constants\Annotation\Message;
use Hyperf\Constants\EnumConstantsTrait;
#[Constants]
final class AdminCode extends ResultCode
{
#[Message("登录失败")]
public const int LOGIN_ERROR = 10001;
#[Message("验证已过期")]
public const int LOGIN_TOKEN_ERROR = 10002;
}

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,32 @@
<?php
declare(strict_types=1);
namespace App\Constants\Model\AdminUser;
use Hyperf\Constants\Annotation\Constants;
use Hyperf\Constants\Annotation\Message;
use Hyperf\Constants\EnumConstantsTrait;
#[Constants]
enum AdminMenuStatusCode: int
{
use EnumConstantsTrait;
#[Message('正常')]
case Normal = 1;
#[Message('停用')]
case DISABLE = 2;
public function isNormal(): bool
{
return $this === self::Normal;
}
public function isDisable(): bool
{
return $this === self::DISABLE;
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Constants\Model\AdminUser;
use Hyperf\Constants\Annotation\Constants;
use Hyperf\Constants\Annotation\Message;
use Hyperf\Constants\EnumConstantsTrait;
#[Constants]
enum AdminRoleStatusCode: int
{
use EnumConstantsTrait;
#[Message('正常')]
case Normal = 1;
#[Message('停用')]
case DISABLE = 2;
public function isNormal(): bool
{
return $this === self::Normal;
}
public function isDisable(): bool
{
return $this === self::DISABLE;
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\Constants\Model\AdminUser;
use Hyperf\Constants\Annotation\Constants;
use Hyperf\Constants\Annotation\Message;
use Hyperf\Constants\EnumConstantsTrait;
#[Constants]
enum AdminUserStatusCode: int
{
use EnumConstantsTrait;
#[Message('正常')]
case Normal = 1;
#[Message('停用')]
case DISABLE = 2;
public function isNormal(): bool
{
return $this === self::Normal;
}
public function isDisable(): bool
{
return $this === self::DISABLE;
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace App\Constants\Model\AdminUser;
use Hyperf\Constants\Annotation\Constants;
use Hyperf\Constants\Annotation\Message;
use Hyperf\Constants\EnumConstantsTrait;
#[Constants]
enum AdminUserTypeCode: int
{
use EnumConstantsTrait;
#[Message('系统用户')]
case SYSTEM = 100;
#[Message('普通用户')]
case USER = 200;
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace App\Constants;
use Hyperf\Constants\AbstractConstants;
use Hyperf\Constants\Annotation\Constants;
use Hyperf\Constants\Annotation\Message;
use Hyperf\Constants\EnumConstantsTrait;
#[Constants]
class ResultCode extends AbstractConstants
{
use EnumConstantsTrait;
#[Message("success")]
final public const int SUCCESS = 0;
#[Message("failed")]
final public const int ERROR = 1;
}

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,25 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Annotation\ResponseFormat;
use App\Controller\AbstractController;
use App\Request\Admin\LoginRequest;
use App\Service\Admin\Login\LoginService;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\RequestMapping;
use Hyperf\Validation\Annotation\Scene;
#[Controller(prefix: "admin/login")]
#[ResponseFormat('admin')]
final class LoginController extends AbstractController
{
#[RequestMapping(path: "login", methods: "POST")]
#[Scene(scene: "login")]
public function login(LoginRequest $request)
{
return (new LoginService)->handle();
}
}

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,14 @@
<?php
namespace App\Exception;
use App\Constants\ResultCode;
use Hyperf\Server\Exception\ServerException;
class ErrException extends ServerException
{
/**
* @var int
*/
protected $code = ResultCode::ERROR;
}

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,71 @@
<?php
namespace App\Exception\Handler;
use App\Lib\Return\AdminReturn;
use App\Lib\Return\ApiReturn;
use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\HttpMessage\Stream\SwooleStream;
use Hyperf\HttpServer\Request;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\ResponseInterface;
use Throwable;
abstract class BaseErrExceptionHandler extends ExceptionHandler
{
public function __construct(
private readonly Request $request,
private readonly ContainerInterface $container,
) {}
/**
* @param Throwable $e
* @param ResponseInterface $response
* @return MessageInterface|ResponseInterface
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
protected function handlerResponse(
Throwable $e,
ResponseInterface $response
): MessageInterface|ResponseInterface
{
// 从注解获取响应格式(优先于路径解析)
$format = $this->request->getAttribute('response_format') ?? $this->repairResponseFormatByPath();
// 动态选择策略
$returnClass = match ($format) {
'admin', 'common' => AdminReturn::class,
'api' => ApiReturn::class,
default => null,
};
if (!$returnClass) return $response;
/**
* @var AdminReturn|ApiReturn $returnObj
*/
$returnObj = $this->container->get($returnClass);
$result = $returnObj->error($e->getMessage(), $e->getCode());
$this->stopPropagation();
return $response->withHeader("Content-Type", "application/json")
->withStatus(200)
->withBody(new SwooleStream(json_encode($result, JSON_UNESCAPED_UNICODE)));
}
/**
* @return string
*/
private function repairResponseFormatByPath(): string
{
// 兜底逻辑:根据路径前缀推断
return match (explode('/', $this->request->path())[0] ?? '') {
'admin', 'common' => 'admin',
'api' => 'api',
default => 'default',
};
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Exception\Handler;
use App\Exception\ErrException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class ErrExceptionHandler extends BaseErrExceptionHandler
{
/**
* @param Throwable $throwable
* @param ResponseInterface $response
* @return ResponseInterface
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function handle(Throwable $throwable, ResponseInterface $response): ResponseInterface
{
return $throwable instanceof ErrException
? $this->handlerResponse($throwable, $response)
: $response;
}
/**
* @param Throwable $throwable
* @return bool
*/
public function isValid(Throwable $throwable): bool
{
return $throwable instanceof ErrException;
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Exception\Handler;
use Hyperf\Validation\ValidationException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class ValidationExceptionHandler extends BaseErrExceptionHandler
{
/**
* @param Throwable $throwable
* @param ResponseInterface $response
* @return ResponseInterface
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function handle(Throwable $throwable, ResponseInterface $response): ResponseInterface
{
return $throwable instanceof ValidationException
? $this->handlerResponse($throwable, $response)
: $response;
}
/**
* @param Throwable $throwable
* @return bool
*/
public function isValid(Throwable $throwable): bool
{
return $throwable instanceof ValidationException;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Interface;
use Lcobucci\JWT\UnencryptedToken;
interface JwtInterface
{
public function builderAccessToken(string $sub, ?\Closure $callable = null): UnencryptedToken;
public function builderRefreshToken(string $sub, ?\Closure $callable = null): UnencryptedToken;
public function parserAccessToken(string $accessToken): UnencryptedToken;
public function parserRefreshToken(string $refreshToken): UnencryptedToken;
public function addBlackList(UnencryptedToken $token): bool;
public function hasBlackList(UnencryptedToken $token): bool;
public function removeBlackList(UnencryptedToken $token): bool;
public function getConfig(string $key, mixed $default = null): mixed;
}

233
app/Lib/Jwt/AbstractJwt.php Normal file
View File

@@ -0,0 +1,233 @@
<?php
namespace App\Lib\Jwt;
use App\Interface\JwtInterface;
use Carbon\Carbon;
use Hyperf\Cache\CacheManager;
use Hyperf\Cache\Driver\DriverInterface;
use Hyperf\Collection\Arr;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\JwtFacade;
use Lcobucci\JWT\Signer;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\UnencryptedToken;
use Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
use Lcobucci\JWT\Validation\Constraint\StrictValidAt;
use Psr\SimpleCache\InvalidArgumentException;
abstract class AbstractJwt implements JwtInterface
{
/**
* @param array $config
* @param CacheManager $cacheManager
* @param Clock $clock
* @param AccessTokenConstraint $accessTokenConstraint
* @param RefreshTokenConstraint $refreshTokenConstraint
*/
public function __construct(
private readonly array $config,
private readonly CacheManager $cacheManager,
private readonly Clock $clock,
private readonly AccessTokenConstraint $accessTokenConstraint,
private readonly RefreshTokenConstraint $refreshTokenConstraint
) {}
/**
* @param string $sub
* @param \Closure|null $callable
* @return UnencryptedToken
*/
public function builderAccessToken(string $sub, ?\Closure $callable = null): UnencryptedToken
{
return $this->getJwtFacade()->issue(
$this->getSigner(),
$this->getSigningKey(),
function (Builder $builder, \DateTimeImmutable $immutable) use ($sub, $callable) {
$builder = $builder->identifiedBy($sub);
if ($callable !== null) {
$builder = $callable($builder);
}
return $builder->expiresAt($this->getExpireAt($immutable));
}
);
}
/**
* @param string $sub
* @param \Closure|null $callable
* @return UnencryptedToken
*/
public function builderRefreshToken(string $sub, ?\Closure $callable = null): UnencryptedToken
{
return $this->getJwtFacade()->issue(
$this->getSigner(),
$this->getSigningKey(),
function (Builder $builder, \DateTimeImmutable $immutable) use ($sub, $callable) {
$builder = $builder->identifiedBy($sub);
$builder = $builder->expiresAt($this->getRefreshExpireAt($immutable));
if ($callable !== null) {
$builder = $callable($builder);
}
return $builder->relatedTo('refresh');
}
);
}
/**
* @param string $accessToken
* @return UnencryptedToken
*/
public function parserAccessToken(string $accessToken): UnencryptedToken
{
return $this->getJwtFacade()
->parse(
$accessToken,
new SignedWith(
$this->getSigner(),
$this->getSigningKey()
),
new StrictValidAt(
$this->clock,
$this->clock->now()->diff($this->getExpireAt($this->clock->now()))
),
$this->getBlackListConstraint(),
$this->refreshTokenConstraint
);
}
/**
* @param string $refreshToken
* @return UnencryptedToken
*/
public function parserRefreshToken(string $refreshToken): UnencryptedToken
{
return $this->getJwtFacade()
->parse(
$refreshToken,
new SignedWith(
$this->getSigner(),
$this->getSigningKey()
),
new StrictValidAt(
$this->clock,
$this->clock->now()->diff($this->getRefreshExpireAt($this->clock->now()))
),
$this->getBlackListConstraint(),
$this->accessTokenConstraint
);
}
/**
* @param UnencryptedToken $token
* @return bool
* @throws InvalidArgumentException
*/
public function addBlackList(UnencryptedToken $token): bool
{
return $this->getCacheDriver()->set($token->toString(), 1, $this->getBlackConfig('ttl', 600));
}
/**
* @param UnencryptedToken $token
* @return bool
* @throws InvalidArgumentException
*/
public function hasBlackList(UnencryptedToken $token): bool
{
return $this->getCacheDriver()->has($token->toString());
}
/**
* @param UnencryptedToken $token
* @return bool
* @throws InvalidArgumentException
*/
public function removeBlackList(UnencryptedToken $token): bool
{
return $this->getCacheDriver()->delete($token->toString());
}
/**
* @param string $key
* @param mixed|null $default
* @return mixed
*/
public function getConfig(string $key, mixed $default = null): mixed
{
return Arr::get($this->config, $key, $default);
}
/**
* @return JwtFacade
*/
private function getJwtFacade(): JwtFacade
{
return new JwtFacade(clock: $this->clock);
}
/**
* @return Signer
*/
private function getSigner(): Signer
{
return Arr::get($this->config, 'alg');
}
/**
* @return Key
*/
private function getSigningKey(): Key
{
return Arr::get($this->config, 'key');
}
/**
* @return DriverInterface
*/
private function getCacheDriver(): DriverInterface
{
return $this->cacheManager->getDriver($this->getBlackConfig('connection'));
}
/**
* @param string $name
* @param mixed|null $default
* @return mixed
*/
private function getBlackConfig(string $name, mixed $default = null): mixed
{
return Arr::get($this->config, 'blacklist.' . $name, $default);
}
/**
* @return Constraint
*/
private function getBlackListConstraint(): Constraint
{
return new BlackListConstraint((bool) $this->getBlackConfig('enable', false), $this->getCacheDriver());
}
/**
* @param \DateTimeImmutable $immutable
* @return \DateTimeImmutable
*/
private function getExpireAt(\DateTimeImmutable $immutable): \DateTimeImmutable
{
return Carbon::create($immutable)
->addSeconds(Arr::get($this->config, 'ttl', 3600))
->toDateTimeImmutable();
}
/**
* @param \DateTimeImmutable $immutable
* @return \DateTimeImmutable
*/
private function getRefreshExpireAt(\DateTimeImmutable $immutable): \DateTimeImmutable
{
return Carbon::create($immutable)
->addSeconds(Arr::get($this->config, 'refresh_ttl', 7200))
->toDateTimeImmutable();
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Lib\Jwt;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Validation\ConstraintViolation;
class AccessTokenConstraint implements Constraint
{
public function assert(Token $token): void
{
if (! $token->isRelatedTo('refresh')) throw ConstraintViolation::error('Token is not a refresh token', $this);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Lib\Jwt;
use Hyperf\Cache\Driver\DriverInterface;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Validation\ConstraintViolation;
use Psr\SimpleCache\InvalidArgumentException;
class BlackListConstraint implements Constraint
{
/**
* @param bool $enable
* @param DriverInterface $cache
*/
public function __construct(
private readonly bool $enable,
private readonly DriverInterface $cache
) {}
/**
* @param Token $token
* @return void
* @throws InvalidArgumentException
*/
public function assert(Token $token): void
{
if ($this->enable !== true) return;
if ($this->cache->has($token->toString())) throw ConstraintViolation::error('Token is in blacklist', $this);
}
}

15
app/Lib/Jwt/Clock.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
namespace App\Lib\Jwt;
use Carbon\Carbon;
use DateTimeImmutable;
use Psr\Clock\ClockInterface;
class Clock implements ClockInterface
{
public function now(): DateTimeImmutable
{
return Carbon::now()->toDateTimeImmutable();
}
}

7
app/Lib/Jwt/Jwt.php Normal file
View File

@@ -0,0 +1,7 @@
<?php
namespace App\Lib\Jwt;
use App\Interface\JwtInterface;
final class Jwt extends AbstractJwt implements JwtInterface {}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Lib\Jwt;
use App\Interface\JwtInterface;
use Hyperf\Collection\Arr;
use Hyperf\Contract\ConfigInterface;
use function Hyperf\Support\make;
final class JwtFactory
{
public function __construct(
private readonly ConfigInterface $config,
) {}
/**
* @param string $name
* @return JwtInterface
*/
public function get(string $name = 'default'): JwtInterface
{
return make(Jwt::class, [
'config' => $this->getConfig($name),
]);
}
/**
* 获取场景配置
* @param string $scene
* @return array
*/
public function getConfig(string $scene): array
{
if ($scene === 'default') {
return $this->config->get($this->getConfigKey());
}
return Arr::merge(
$this->config->get($this->getConfigKey()),
$this->config->get($this->getConfigKey($scene), [])
);
}
/**
* @param string $name
* @return string
*/
private function getConfigKey(string $name = 'default'): string
{
return 'jwt.' . $name;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Lib\Jwt;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Validation\ConstraintViolation;
class RefreshTokenConstraint implements Constraint
{
public function assert(Token $token): void
{
if ($token->isRelatedTo('refresh')) throw ConstraintViolation::error('Token is a refresh token', $this);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Lib\Return;
class AdminReturn extends CommonReturn
{
/**
* 通用返回
* @param array $res
* @return array
*/
final protected function afterSuccess(array $res): array
{
return $res;
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Lib\Return;
class ApiReturn extends CommonReturn
{
/**
* 通用返回
* @param array $res
* @return array
*/
final protected function afterSuccess(array $res): array
{
return $res;
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Lib\Return;
use App\Constants\ResultCode;
abstract class CommonReturn
{
/**
* 通用 success 返回
* @param string $msg
* @param array $data
* @param int|ResultCode $code
* @param array $debug
* @return array
*/
final public function success(string $msg = 'success', array $data = [], ResultCode|int $code = ResultCode::SUCCESS, array $debug = []): array
{
$res = [
'code' => $code,
'message' => $msg,
'data' => $data
];
return $this->afterSuccess(array_merge($res, $debug));
}
/**
* 通用 fail 返回
* @param string $msg
* @param array $data
* @param int|ResultCode $code
* @param array $debug
* @return array
*/
final public function error(string $msg = 'failed', ResultCode|int $code = ResultCode::ERROR, array $data = [], array $debug = []): array
{
$res = [
'code' => $code,
'message' => $msg,
'data' => $data
];
return $this->afterSuccess(array_merge($res, $debug));
}
/**
* 通用类调子类返回方便切面类识别
* @param array $res
* @return array
*/
abstract protected function afterSuccess(array $res): array;
}

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();
}
}

113
app/Model/AdminMenu.php Normal file
View File

@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace App\Model;
use App\Constants\Model\AdminUser\AdminMenuStatusCode;
use App\Model\Meta\AdminUserMeta;
use App\Model\Meta\MetaCast;
use Carbon\Carbon;
use Hyperf\Database\Model\Events\Deleting;
use Hyperf\Database\Model\Relations\BelongsToMany;
use Hyperf\Database\Model\Relations\HasMany;
use Hyperf\Database\Model\Collection;
/**
* @property int $id
* @property int $parent_id
* @property string $name
* @property AdminUserMeta $meta
* @property string $path
* @property string $component
* @property string $redirect
* @property int $status
* @property int $sort
* @property int $created_by
* @property int $updated_by
* @property Carbon $created_at
* @property Carbon $updated_at
* @property string $remark
* @property Collection|AdminRole[] $roles
* @property Collection|AdminMenu[] $children
*/
class AdminMenu extends Model
{
/**
* The table associated with the model.
*/
protected ?string $table = 'admin_menu';
/**
* The attributes that are mass assignable.
*/
protected array $fillable = [
'id',
'parent_id',
'name',
'component',
'redirect',
'status',
'sort',
'created_by',
'updated_by',
'created_at',
'updated_at',
'remark',
'meta',
'path',
];
/**
* The attributes that should be cast to native types.
*/
protected array $casts = [
'id' => 'integer',
'parent_id' => 'integer',
'status' => AdminMenuStatusCode::class,
'sort' => 'integer',
'created_by' => 'integer',
'updated_by' => 'integer',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'meta' => MetaCast::class,
'path' => 'string',
];
/**
* 通过中间表获取角色.
*/
public function roles(): BelongsToMany
{
return $this->belongsToMany(
AdminRole::class,
'admin_role_belongs_menu',
'menu_id',
'role_id'
);
}
/**
* @return HasMany
*/
public function children(): HasMany
{
return $this
->hasMany(self::class, 'parent_id', 'id')
->where('status', AdminMenuStatusCode::Normal)
->orderBy('sort')
->with('children');
}
/**
* @param Deleting $event
* @return void
*/
public function deleting(Deleting $event): void
{
$this->roles()->detach();
}
}

96
app/Model/AdminRole.php Normal file
View File

@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace App\Model;
use Hyperf\Database\Model\Events\Deleting;
use Hyperf\Database\Model\Relations\BelongsToMany;
/**
* @property int $id
* @property string $name
* @property string $code
* @property int $status
* @property int $sort
* @property int $created_by
* @property int $updated_by
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property string $remark
*/
class AdminRole extends Model
{
/**
* The table associated with the model.
*/
protected ?string $table = 'admin_role';
/**
* The attributes that are mass assignable.
*/
protected array $fillable = [
'id',
'name',
'code',
'status',
'sort',
'created_by',
'updated_by',
'created_at',
'updated_at',
'remark'
];
/**
* The attributes that should be cast to native types.
*/
protected array $casts = [
'id' => 'integer',
'status' => 'integer',
'sort' => 'integer',
'created_by' => 'integer',
'updated_by' => 'integer',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* @return BelongsToMany
*/
public function adminMenus(): BelongsToMany
{
return $this->belongsToMany(
AdminMenu::class,
'role_belongs_menu',
'role_id',
'menu_id'
);
}
/**
* @return BelongsToMany
*/
public function adminUsers(): BelongsToMany
{
return $this->belongsToMany(
AdminUser::class,
'admin_user_belongs_role',
'role_id',
'user_id'
);
}
/**
* @param Deleting $event
* @return void
*/
public function deleting(Deleting $event): void
{
$this->adminUsers()->detach();
$this->adminMenus()->detach();
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Model;
/**
* @property int $id
* @property int $admin_role_id
* @property int $admin_menu_id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
*/
class AdminRoleBelongsMenu extends Model
{
/**
* The table associated with the model.
*/
protected ?string $table = 'admin_role_belongs_menu';
/**
* The attributes that are mass assignable.
*/
protected array $fillable = [];
/**
* The attributes that should be cast to native types.
*/
protected array $casts = ['id' => 'integer', 'admin_role_id' => 'integer', 'admin_menu_id' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime'];
}

189
app/Model/AdminUser.php Normal file
View File

@@ -0,0 +1,189 @@
<?php
declare(strict_types=1);
namespace App\Model;
use App\Constants\Model\AdminUser\AdminUserStatusCode;
use App\Constants\Model\AdminUser\AdminUserTypeCode;
use Carbon\Carbon;
use Hyperf\Collection\Enumerable;
use Hyperf\Database\Model\Collection;
use Hyperf\Database\Model\Events\Creating;
use Hyperf\Database\Model\Events\Deleted;
use Hyperf\Database\Model\Relations\BelongsToMany;
/**
* @property int $id
* @property string $username
* @property string $password
* @property string $user_type
* @property string $nickname
* @property string $phone
* @property string $email
* @property string $avatar
* @property string $signed
* @property AdminUserStatusCode $status
* @property string $login_ip
* @property string $login_time
* @property string $backend_setting
* @property int $created_by
* @property int $updated_by
* @property Carbon $created_at
* @property Carbon $updated_at
* @property string $remark
*/
class AdminUser extends Model
{
/**
* The table associated with the model.
*/
protected ?string $table = 'admin_user';
/**
* The attributes that are mass assignable.
*/
protected array $fillable = [
'id',
'username',
'password',
'user_type',
'nickname',
'phone',
'email',
'avatar',
'signed',
'status',
'login_ip',
'login_time',
'backend_setting',
'created_by',
'updated_by',
'created_at',
'updated_at',
'remark'
];
/**
* The attributes that should be cast to native types.
*/
protected array $casts = [
'id' => 'integer',
'status' => AdminUserStatusCode::class,
'user_type' => AdminUserTypeCode::class,
'created_by' => 'integer',
'updated_by' => 'integer',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'backend_setting' => 'json',
];
/**
* 隐藏的字段列表.
* @var string[]
*/
protected array $hidden = ['password'];
/**
* @return BelongsToMany
*/
public function roles(): BelongsToMany
{
return $this->belongsToMany(
AdminRole::class,
'admin_user_belongs_role',
);
}
/**
* @param Creating $event
* @return void
*/
public function creating(Creating $event): void
{
if (!$this->isDirty('password')) $this->resetPassword();
}
/**
* @param Deleted $event
* @return void
*/
public function deleted(Deleted $event): void
{
$this->roles()->detach();
}
/**
* @return void
*/
public function resetPassword(): void
{
$this->password = 'admin';
}
/**
* @param string $password
* @return void
*/
public function setPasswordAttribute(string $password): void
{
$this->attributes['password'] = password_hash($password, PASSWORD_DEFAULT);
}
/**
* @param string $password
* @return bool
*/
public function verifyPassword(string $password): bool
{
return password_verify($password, $this->password);
}
/**
* @return bool
*/
public function isSuperAdmin(): bool
{
return $this->roles()->where('code','SuperAdmin')->exists();
}
/**
* @param array $fields
* @return Collection
*/
public function getRoles(array $fields): Collection
{
return $this->roles()
->where('status',AdminUserStatusCode::Normal)
->select($fields)
->get();
}
/**
* @return \Hyperf\Collection\Collection|Enumerable|Collection<int, AdminMenu>
*/
public function getPermissions(): Collection|Enumerable|\Hyperf\Collection\Collection
{
return $this->roles()
->with('adminMenus')
->orderBy('sort')
->get()
->pluck('adminMenus')
->flatten();
}
/**
* @param string $permission
* @return bool
*/
public function hasPermission(string $permission): bool
{
return $this->roles()
->whereRelation('adminMenus','name',$permission)
->exists();
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace App\Model;
/**
*/
class AdminUserBelongsMenu extends Model
{
/**
* The table associated with the model.
*/
protected ?string $table = 'admin_user_belongs_menu';
/**
* The attributes that are mass assignable.
*/
protected array $fillable = [];
/**
* The attributes that should be cast to native types.
*/
protected array $casts = [];
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Model;
/**
* @property int $id
* @property int $admin_user_id
* @property int $admin_role_id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
*/
class AdminUserBelongsRole extends Model
{
/**
* The table associated with the model.
*/
protected ?string $table = 'admin_user_belongs_role';
/**
* The attributes that are mass assignable.
*/
protected array $fillable = [];
/**
* The attributes that should be cast to native types.
*/
protected array $casts = ['id' => 'integer', 'admin_user_id' => 'integer', 'admin_role_id' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime'];
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Model;
/**
* @property int $id
* @property int $admin_user_id
* @property string $username
* @property string $ip
* @property string $os
* @property string $browser
* @property int $status
* @property string $message
* @property string $login_time
* @property string $remark
*/
class AdminUserLoginLog extends Model
{
/**
* The table associated with the model.
*/
protected ?string $table = 'admin_user_login_log';
/**
* The attributes that are mass assignable.
*/
protected array $fillable = [];
/**
* The attributes that should be cast to native types.
*/
protected array $casts = ['id' => 'integer', 'admin_user_id' => 'integer', 'status' => 'integer'];
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Model;
/**
* @property int $id
* @property int $admin_user_id
* @property string $username
* @property string $method
* @property string $router
* @property string $service_name
* @property string $ip
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property string $remark
*/
class AdminUserOperationLog extends Model
{
/**
* The table associated with the model.
*/
protected ?string $table = 'admin_user_operation_log';
/**
* The attributes that are mass assignable.
*/
protected array $fillable = [];
/**
* The attributes that should be cast to native types.
*/
protected array $casts = ['id' => 'integer', 'admin_user_id' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime'];
}

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Model\Meta;
use App\Model\Model;
/**
* @property string $title 标题
* @property string $i18n 国际化
* @property string $badge 徽章
* @property string $icon 图标
* @property bool $affix 是否固定
* @property bool $hidden 是否隐藏
* @property string $type 类型
* @property bool $cache 是否缓存
* @property bool $copyright 是否显示版权
* @property string $link 链接
* @property string $componentPath 视图文件类型
* @property string $componentSuffix 视图前缀路径
* @property string $breadcrumbEnable 是否显示面包屑
* @property string $activeName 激活高亮的菜单标识
* @property string $auth 前端权限判断,允许访问的权限码
* @property string $role 前端权限判断,允许访问的角色码
* @property string $user 前端权限判断,允许访问的用户名
*/
final class AdminUserMeta extends Model
{
/**
* @var bool
*/
public bool $incrementing = false;
/**
* @var array|string[]
*/
protected array $fillable = [
'title',
'i18n',
'badge',
'icon',
'affix',
'hidden',
'type',
'cache',
'copyright',
'breadcrumbEnable',
'componentPath',
'componentSuffix',
'link',
'activeName',
'auth',
'role',
'user',
];
/**
* @var array|string[]
*/
protected array $casts = [
'affix' => 'boolean',
'hidden' => 'boolean',
'cache' => 'boolean',
'copyright' => 'boolean',
'breadcrumbEnable' => 'boolean',
'title' => 'string',
'componentPath' => 'string',
'componentSuffix' => 'string',
'i18n' => 'string',
'badge' => 'string',
'icon' => 'string',
'type' => 'string',
'link' => 'string',
'activeName' => 'string',
'auth' => 'array',
'role' => 'array',
'user' => 'array',
];
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Model\Meta;
use Hyperf\Codec\Json;
use Hyperf\Contract\CastsAttributes;
class MetaCast implements CastsAttributes
{
/**
* @param $model
* @param string $key
* @param $value
* @param array $attributes
* @return AdminUserMeta
*/
public function get($model, string $key, $value, array $attributes): AdminUserMeta
{
return new AdminUserMeta(empty($value) ? [] : Json::decode($value));
}
/**
* @param $model
* @param string $key
* @param $value
* @param array $attributes
* @return array|string
*/
public function set($model, string $key, $value, array $attributes): array|string
{
return Json::encode($value);
}
}

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,34 @@
<?php
/**
* This service file is part of item.
*
* @author ctexthuang
* @contact ctexthuang@qq.com
*/
declare(strict_types=1);
namespace App\Repository;
use App\Model\AdminUser;
/**
* Class AdminUserRepository
* @extends BaseRepository<AdminUser>
*/
final class AdminUserRepository extends BaseRepository
{
public function __construct(protected readonly AdminUser $model) {}
/**
* @param string $username
* @return AdminUser|null
*/
public function findByUserName(string $username): AdminUser|null
{
// @phpstan-ignore-next-line
return $this->model->newQuery()
->where('username', $username)
->first();
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace App\Repository;
use App\Repository\Traits\BootTrait;
use App\Repository\Traits\RepositoryOrderByTrait;
use Hyperf\Collection\Collection;
use Hyperf\Contract\LengthAwarePaginatorInterface;
use Hyperf\Database\Model\Builder;
use Hyperf\Database\Model\Model;
use Hyperf\DbConnection\Traits\HasContainer;
use Hyperf\Paginator\AbstractPaginator;
/**
* @template T of Model
* @property T $model
*/
abstract class BaseRepository
{
use BootTrait;
use HasContainer;
use RepositoryOrderByTrait;
public const string PER_PAGE_PARAM_NAME = 'per_page';
public function handleSearch(Builder $query, array $params): Builder
{
return $query;
}
public function handleItems(Collection $items): Collection
{
return $items;
}
public function handlePage(LengthAwarePaginatorInterface $paginator): array
{
if ($paginator instanceof AbstractPaginator) {
$items = $paginator->getCollection();
} else {
$items = Collection::make($paginator->items());
}
$items = $this->handleItems($items);
return [
'list' => $items->toArray(),
'total' => $paginator->total(),
];
}
public function list(array $params = []): Collection
{
return $this->handleItems($this->perQuery($this->getQuery(), $params)->get());
}
public function count(array $params = []): int
{
return $this->perQuery($this->getQuery(), $params)->count();
}
public function page(array $params = [], ?int $page = null, ?int $pageSize = null): array
{
$result = $this->perQuery($this->getQuery(), $params)->paginate(
perPage: $pageSize,
pageName: static::PER_PAGE_PARAM_NAME,
page: $page,
);
return $this->handlePage($result);
}
/**
* @return T
*/
public function create(array $data): mixed
{
// @phpstan-ignore-next-line
return $this->getQuery()->create($data);
}
public function updateById(mixed $id, array $data): bool
{
return (bool) $this->getQuery()->whereKey($id)->first()?->update($data);
}
/**
* @return null|T
*/
public function saveById(mixed $id, array $data): mixed
{
$model = $this->getQuery()->whereKey($id)->first();
if ($model) {
$model->fill($data)->save();
return $model;
}
return null;
}
public function deleteById(mixed $id): int
{
// @phpstan-ignore-next-line
return $this->model::destroy($id);
}
public function forceDeleteById(mixed $id): bool
{
return (bool) $this->getQuery()->whereKey($id)->forceDelete();
}
/**
* @return null|T
*/
public function findById(mixed $id): mixed
{
return $this->getQuery()->whereKey($id)->first();
}
public function findByField(mixed $id, string $field): mixed
{
return $this->getQuery()->whereKey($id)->value($field);
}
/**
* @return null|T
*/
public function findByFilter(array $params): mixed
{
return $this->perQuery($this->getQuery(), $params)->first();
}
public function perQuery(Builder $query, array $params): Builder
{
$this->startBoot($query, $params);
return $this->handleSearch($query, $params);
}
public function getQuery(): Builder
{
return $this->model->newQuery();
}
public function existsById(mixed $id): bool
{
return (bool) $this->getQuery()->whereKey($id)->exists();
}
/**
* @return T
*/
public function getModel(): Model
{
return $this->model;
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Repository\Traits;
use function Hyperf\Support\class_basename;
use function Hyperf\Support\class_uses_recursive;
trait BootTrait
{
protected function startBoot(...$params): void
{
$traits = class_uses_recursive(static::class);
foreach ($traits as $trait) {
$method = 'boot' . class_basename($trait);
if (method_exists($this, $method)) {
$this->{$method}(...$params);
}
}
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Repository\Traits;
use Hyperf\Database\Model\Builder;
trait RepositoryOrderByTrait
{
public function handleOrderBy(Builder $query, $params): Builder
{
if ($this->enablePageOrderBy()) {
$orderByField = $params[$this->getOrderByParamName()] ?? $query->getModel()->getKeyName();
$orderByDirection = $params[$this->getOrderByDirectionParamName()] ?? 'desc';
$query->orderBy($orderByField, $orderByDirection);
}
return $query;
}
protected function bootRepositoryOrderByTrait(Builder $query, array $params): void
{
$this->handleOrderBy($query, $params);
}
protected function getOrderByParamName(): string
{
return 'order_by';
}
protected function getOrderByDirectionParamName(): string
{
return 'order_by_direction';
}
protected function enablePageOrderBy(): bool
{
return true;
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace App\Request\Admin;
use Hyperf\Validation\Request\FormRequest;
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
];
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* This service file is part of item.
*
* @author ctexthuang
* @contact ctexthuang@qq.com
*/
declare(strict_types=1);
namespace App\Service\Admin;
use App\Lib\Return\AdminReturn;
use Hyperf\Context\Context;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Contract\RequestInterface;
abstract class BaseAdminService
{
/**
* 请求对象注入
* @var RequestInterface
*/
#[Inject]
protected RequestInterface $request;
/**
* 返回对象注入
* @var AdminReturn
*/
#[Inject]
protected AdminReturn $adminReturn;
/**
* 管理员 id
* @var int
*/
protected int $adminId = 0;
/**
* 主构造函数
*/
public function __construct()
{
$this->adminId = Context::get('admin_id',0);
}
/**
* 主函数抽象类
*/
abstract public function handle();
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* This service file is part of item.
*
* @author ctexthuang
* @contact ctexthuang@qq.com
*/
declare(strict_types=1);
namespace App\Service\Admin\Login;
use App\Constants\Model\AdminUser\AdminUserStatusCode;
use App\Exception\ErrException;
use App\Interface\JwtInterface;
use App\Lib\Jwt\JwtFactory;
use App\Repository\AdminUserRepository;
use App\Service\Admin\BaseAdminService;
use Hyperf\Di\Annotation\Inject;
class LoginService extends BaseAdminService
{
/**
* @var string jwt场景
*/
private string $jwt = 'admin';
/**
* @var AdminUserRepository
*/
#[Inject]
protected AdminUserRepository $userRepository;
/**
* @var JwtFactory
*/
#[Inject]
protected JwtFactory $jwtFactory;
/**
* @return array
*/
public function handle(): array
{
$adminInfo = $this->userRepository->findByUserName((string)$this->request->input('username'));
if (!$adminInfo) throw new ErrException('后台管理员不存在');
if (! $adminInfo->verifyPassword((string) $this->request->input('password'))) {
// $this->dispatcher->dispatch(new UserLoginEvent($user, $ip, $os, $browser, false));
throw new ErrException('密码错误');
}
if ($adminInfo->status == AdminUserStatusCode::DISABLE) throw new ErrException('用户已禁用');
$jwtHandle = $this->getJwt();
return [
'access_token' => $jwtHandle->builderAccessToken((string) $adminInfo->id)->toString(),
'refresh_token' => $jwtHandle->builderRefreshToken((string) $adminInfo->id)->toString(),
'expire_at' => (int) $jwtHandle->getConfig('ttl', 0),
];
}
/**
* @return JwtInterface
*/
private function getJwt(): JwtInterface
{
return $this->jwtFactory->get($this->jwt);
}
}

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();
})();

85
composer.json Normal file
View File

@@ -0,0 +1,85 @@
{
"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.1",
"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/logger": "~3.1.0",
"hyperf/memory": "~3.1.0",
"hyperf/model-cache": "~3.1.0",
"hyperf/paginator": "^3.1",
"hyperf/process": "~3.1.0",
"hyperf/redis": "~3.1.0",
"hyperf/validation": "^3.1",
"lcobucci/jwt": "^5.5"
},
"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"
]
}
}

10324
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,14 @@
<?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\Database\Schema\Blueprint::class => App\Common\Macros\BlueprintMacros::class,
];

View File

@@ -0,0 +1,53 @@
<?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 [
'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',
],
],
/**
* Supported IDEs: "sublime", "textmate", "cursor", "emacs", "macvim", "phpstorm", "idea",
* "vscode", "vscode-insiders", "vscode-remote", "vscode-insiders-remote",
* "atom", "nova", "netbeans", "xdebug"
*/
'ide' => env('DEVTOOL_IDE', ''),
];

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 [
'handler' => [
'http' => [
Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler::class,
App\Exception\Handler\ErrExceptionHandler::class,
App\Exception\Handler\ValidationExceptionHandler::class,
App\Exception\Handler\AppExceptionHandler::class,
],
],
];

68
config/autoload/jwt.php Normal file
View File

@@ -0,0 +1,68 @@
<?php
use App\Lib\Jwt\Jwt;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Token\RegisteredClaims;
use function Hyperf\Support\env;
return [
'default' => [
// jwt 配置 https://lcobucci-jwt.readthedocs.io/en/latest/
'driver' => Jwt::class,
// jwt 签名key
'key' => InMemory::base64Encoded(env('JWT_SECRET')),
// jwt 签名算法 可选 https://lcobucci-jwt.readthedocs.io/en/latest/supported-algorithms/
'alg' => new Sha256(),
// token过期时间单位为秒
'ttl' => (int) env('JWT_TTL', 3600),
// 刷新token过期时间单位为秒
'refresh_ttl' => (int) env('JWT_REFRESH_TTL', 7200),
// 黑名单模式
'blacklist' => [
// 是否开启黑名单
'enable' => true,
// 黑名单缓存前缀
'prefix' => 'jwt_blacklist',
// 黑名单缓存驱动
'connection' => 'default',
// 黑名单缓存时间 该时间一定要设置比token过期时间要大一点最好设置跟过期时间一样
'ttl' => (int) env('JWT_BLACKLIST_TTL', 7201),
],
'claims' => [
// 默认的jwt claims
RegisteredClaims::ISSUER => (string) env('APP_NAME'),
],
],
'admin' => [
// jwt 配置 https://lcobucci-jwt.readthedocs.io/en/latest/
'driver' => Jwt::class,
// jwt 签名key
'key' => InMemory::base64Encoded(env('JWT_SECRET')),
// jwt 签名算法 可选 https://lcobucci-jwt.readthedocs.io/en/latest/supported-algorithms/
'alg' => new Sha256(),
// token过期时间单位为秒
'ttl' => (int) env('ADMIN_JWT_TTL', 3600),
// 刷新token过期时间单位为秒
'refresh_ttl' => (int) env('ADMIN_JWT_REFRESH_TTL', 7200),
// 黑名单模式
'blacklist' => [
// 是否开启黑名单
'enable' => true,
// 黑名单缓存前缀
'prefix' => 'jwt_blacklist',
// 黑名单缓存驱动
'connection' => 'default',
// 黑名单缓存时间 该时间一定要设置比token过期时间要大一点最好设置跟过期时间一样
'ttl' => (int) env('ADMIN_JWT_BLACKLIST_TTL', 7201),
],
'claims' => [
// 默认的jwt claims
RegisteredClaims::ISSUER => 'ADMIN'. env('APP_NAME'),
],
],
// 在你想要使用不同的场景时,可以在这里添加配置.可以填一个。其他会使用默认配置
'application' => [
// jwt 配置 https://lcobucci-jwt.readthedocs.io/en/latest/
],
];

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,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
*/
return [
'default' => [
'handler' => [
'class' => Monolog\Handler\StreamHandler::class,
'constructor' => [
'stream' => BASE_PATH . '/runtime/logs/hyperf.log',
'level' => Monolog\Logger::DEBUG,
],
],
'formatter' => [
'class' => Monolog\Formatter\LineFormatter::class,
'constructor' => [
'format' => null,
'dateFormat' => 'Y-m-d H:i:s',
'allowInlineLineBreaks' => true,
],
],
],
];

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
*/
use Hyperf\Validation\Middleware\ValidationMiddleware;
return [
'http' => [
ValidationMiddleware::class,
],
];

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,50 @@
<?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\Server\Event;
use Hyperf\Server\Server;
use Swoole\Constant;
return [
'mode' => SWOOLE_PROCESS,
'servers' => [
[
'name' => 'http',
'type' => Server::SERVER_HTTP,
'host' => '0.0.0.0',
'port' => 9501,
'sock_type' => SWOOLE_SOCK_TCP,
'callbacks' => [
Event::ON_REQUEST => [Hyperf\HttpServer\Server::class, 'onRequest'],
],
'options' => [
// Whether to enable request lifecycle event
'enable_request_lifecycle' => false,
],
],
],
'settings' => [
Constant::OPTION_ENABLE_COROUTINE => true,
Constant::OPTION_WORKER_NUM => swoole_cpu_num(),
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,
],
'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,16 @@
<?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 [
'locale' => 'zh_CN',
'fallback_locale' => 'en',
'path' => BASE_PATH . '/storage/languages',
];

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,43 @@
<?php
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('admin_user', function (Blueprint $table) {
$table->comment('管理员信息表');
$table->bigIncrements('id')->comment('用户ID,主键');
$table->string('username', 20)->unique()->comment('用户名')->unique();
$table->string('password', 100)->comment('密码');
$table->string('user_type', 3)->default('100')->comment('用户类型:100=系统用户');
$table->string('nickname', 30)->default('')->comment('用户昵称');
$table->string('phone', 11)->default('')->comment('手机');
$table->string('email', 50)->default('')->comment('用户邮箱');
$table->string('avatar', 255)->default('')->comment('用户头像');
$table->string('signed', 255)->default('')->comment('个人签名');
$table->tinyInteger('status')->default(1)->comment('状态:1=正常,2=停用');
$table->ipAddress('login_ip')->default('127.0.0.1')->comment('最后登陆IP');
$table->timestamp('login_time')->useCurrent()->comment('最后登陆时间');
$table->json('backend_setting')->nullable()->comment('后台设置数据');
$table->unsignedBigInteger('created_by')->default(0)->comment('创建人ID');
$table->unsignedBigInteger('updated_by')->default(0)->comment('更新人ID');
$table->datetimes();
$table->string('remark', 255)->default('')->comment('备注');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('admin_user');
}
};

View File

@@ -0,0 +1,39 @@
<?php
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('admin_menu', function (Blueprint $table) {
$table->comment('后台菜单信息表');
$table->bigIncrements('id')->comment('主键');
$table->bigInteger('parent_id')->unsigned()->comment('父ID');
$table->string('name', 50)->default('')->comment('菜单名称')->unique();
$table->json('meta')->comment('附加属性')->nullable();
$table->string('path', 60)->default('')->comment('路径');
$table->string('component', 150)->default('')->comment('组件路径');
$table->string('redirect', 100)->comment('重定向地址')->default('');
$table->tinyInteger('status')->comment('状态:1=正常,2=停用')->default(1);
$table->smallInteger('sort')->comment('排序')->default(0);
$table->unsignedBigInteger('created_by')->default(0)->comment('创建人ID');
$table->unsignedBigInteger('updated_by')->default(0)->comment('更新人ID');
$table->datetimes();
$table->string('remark', 60)->comment('备注')->default('');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('admin_menu');
}
};

View File

@@ -0,0 +1,35 @@
<?php
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('admin_role', function (Blueprint $table) {
$table->comment('后台角色信息表');
$table->bigIncrements('id')->comment('主键');
$table->string('name', 30)->comment('角色名称');
$table->string('code', 100)->comment('角色代码')->unique();
$table->tinyInteger('status')->comment('状态:1=正常,2=停用')->default(1);
$table->smallInteger('sort')->comment('排序')->default(0);
$table->unsignedBigInteger('created_by')->default(0)->comment('创建人ID');
$table->unsignedBigInteger('updated_by')->default(0)->comment('更新人ID');
$table->datetimes();
$table->string('remark')->comment('备注')->default('');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('admin_role');
}
};

Some files were not shown because too many files have changed in this diff Show More