63 Commits
0.2.0 ... main

Author SHA1 Message Date
李东云
4484fb1eea chore(deps): 升级 hyperf 框架依赖至 3.0 版本
- 将所有 hyperf 组件版本从 2.2.x 升级到 3.0.x
- 更新 singularity/hdk-core 依赖从0.1.6 到 1.0.0
- 移除 minimum-stability 配置项
- 添加 runtime 目录到 .gitignore
- 更新 RedisArray 类型提示和返回值
- 为监听器方法添加 void 返回类型
- 更新 ColorLineFormatter 的 LogRecord 类型提示
- 添加 hyperf.php 入口文件
2025-09-28 09:41:31 +08:00
李东云
606672b1d6 refactor(admin): 迁移到 php8.2
- 将多个匿名函数改为箭头函数以提高可读性
- 修复部分变量类型不匹配的问题,增加类型转换
- 删除冗余的代码和注释,简化逻辑
- 更新数据库操作相关代码,提高安全性
- 优化数组处理和字符串操作方法
- 统一代码风格,提升整体代码质量
2025-09-26 18:10:31 +08:00
李东云
44105f8e47 chore(release): 0.5.1 2025-09-26 17:03:31 +08:00
李东云
db34c59acc chore(workflow): 更新发布工作流配置
- 替换 Debian 镜像源为阿里云镜像
- 优化 apt-get 更新和安装命令
- 修正工作流步骤名称大小写
- 保持工作流状态输出格式一致
2025-09-26 17:03:22 +08:00
李东云
43275f365c chore(release): 0.5.0 2025-09-26 17:01:00 +08:00
李东云
ff7e959daa chore(workflow): 修改发布任务名称
- 将发布任务名称从 "Publish to registry" 改为 "publish to registry"
- 保持其他工作流配置不变
2025-09-26 17:00:46 +08:00
李东云
55e2b0477a chore(release): 0.4.3 2025-09-26 16:52:03 +08:00
李东云
21ddc159aa chore(release): 0.4.2 2025-09-26 16:48:36 +08:00
李东云
98ba4a13dc feat(ci): 添加基于标签的自动发布工作流
- 创建了在打标签时触发的 Gitea 工作流
- 实现了代码检出和文件列表步骤
- 添加了将项目文件打包为 ZIP 的功能
- 配置了向私有注册表发布包的步骤
- 使用环境变量传递版本号和认证信息
- 添加了工作流状态输出便于调试追踪
2025-09-26 16:48:00 +08:00
李东云
bb438b5996 chore: 更新文件权限并添加发布脚本和CI配置
将多个文件的权限从644更改为755以允许执行
添加release.sh和docker-env.sh脚本以支持发布流程
添加Gitea工作流配置文件以实现自动化发布
添加.versionrc文件以规范版本更新日志格式
2025-04-19 09:23:26 +08:00
李东云
5f75dc1afb chore(release): 0.3.4 2023-04-19 03:13:06 +00:00
李东云
0d4e80fe15 docs(admin): 修改了登录的验证文案 2023-04-19 11:11:02 +08:00
李东云
5e327fbbc5 chore(release): 0.3.3 2023-04-18 03:04:22 +00:00
李东云
e60d9174e5 chore(release): 0.4.1 2023-04-18 02:11:07 +00:00
李东云
6330f5e390 fix(scaffold): 修复级联时菜单显示有误的问题 2023-04-18 10:10:16 +08:00
李东云
dfd814c817 chore(release): 0.4.0 2023-04-07 08:36:52 +00:00
李东云
27b5f54d57 build(semver): 初始化版本记录文件 2023-04-07 16:36:48 +08:00
李东云
dabfbdf861 build(hdk): 引入 hdk-core 2023-04-07 16:35:34 +08:00
李东云
e9e6084e97 build(composer): 限制 flysystem-oss 的版本 2023-04-07 16:23:23 +08:00
李东云
3c5b77dda6 build(composer): 限制 flysystem-oss 的版本 2023-04-07 16:15:33 +08:00
李东云
c2b56cf978 build: 工程化项目 2023-04-07 16:14:58 +08:00
李东云
254989e992 build(composer): 最低依赖版本改为 php7.4 2022-08-26 11:02:01 +08:00
李东云
8f5592ecc8 fix(menu): 修复添加菜单不选择权限时候的报错 2022-08-25 14:30:00 +08:00
李东云
710eefc4c6 build(composer): 更新包名 2022-08-25 14:23:00 +08:00
刀刀
c6ab3dbad4 Update README.md 2022-02-17 11:09:12 +08:00
刀刀
a780c3b3b8 Update README.md 2021-12-02 19:31:10 +08:00
刀刀
2b579e8c5f Merge pull request #57 from laoguoyandong/upd_admin_install_install_sql
修改install.sql文件
2021-11-18 17:24:41 +08:00
guoyandong
5084d4596d 修改install.sql文件 2021-11-18 17:09:23 +08:00
daodao97
4b9cea60ff perf: 更新分包中的 hyperf 版本指 2.2 2021-08-30 19:35:13 +08:00
刀刀
c881a36788 Merge pull request #55 from jyiL/patch-5
Update AbstractController.php
2021-08-14 16:09:59 +08:00
daodao97
a1c0372828 perf: upcode 2021-08-14 16:08:56 +08:00
jyiL
572c8a1065 Update AbstractController.php
设置filter的select默认值不生效问题
2021-08-07 10:51:13 +08:00
daodao97
c8a049faeb perf: upgrade to hyperf/* 2.2.0 2021-07-19 16:03:03 +08:00
daodao97
cb2b2b5428 Merge branch 'master' of github.com:hyperf-admin/hyperf-admin 2021-06-26 11:28:27 +08:00
daodao97
8b44c279c1 docs: website 2021-06-26 11:28:11 +08:00
刀刀
8f1df241a4 Merge pull request #53 from AKMCC-hub/master
feat: 脚手架增加group_by语法支持
2021-06-16 09:57:28 +08:00
刀刀
9abd51cc6b Update AbstractController.php 2021-06-16 09:56:41 +08:00
七戒
f4c429ea28 feat: 脚手架增加group_by语法支持 2021-06-15 15:52:14 +08:00
daodao97
839bd1e0b9 fix: tree child api 2021-05-18 17:49:55 +08:00
daodao97
4ccb7200eb Merge branch 'master' of github.com:hyperf-admin/hyperf-admin 2021-05-18 11:47:14 +08:00
daodao97
d79fdca30b fix: move_local_file_to_filesystem chmod 2021-05-18 11:46:30 +08:00
刀刀
11f249669e Update form.md 2021-04-26 09:46:10 +08:00
刀刀
a707dae623 Update README.md 2021-04-22 19:56:56 +08:00
刀刀
d5a7a584df Update install.md 2021-04-22 19:54:31 +08:00
刀刀
c0379e5137 Update README.md 2021-04-22 10:51:12 +08:00
刀刀
bde50ad7e2 Update README.md 2021-04-22 10:31:40 +08:00
daodao97
e4dd791a66 perf: tools 2021-04-21 16:54:21 +08:00
daodao97
5f1bdc834e fix: support custom sort cloumn 2021-04-21 16:25:24 +08:00
daodao97
0798685e90 feat: set readable by all user 2021-04-21 16:22:34 +08:00
三刀
1891c2053b docs: update images 2021-04-12 10:05:55 +08:00
刀刀
89505d63c3 Update AbstractController.php 2021-03-05 22:02:37 +08:00
刀刀
165bfe1a2a Update AbstractController.php 2021-03-05 22:00:29 +08:00
刀刀
5e2314292b Update install.md 2021-02-05 18:04:28 +08:00
daodoa97
c5ee5692f9 docs: add qa 2021-02-05 15:20:57 +08:00
daodoa97
de939c1c42 docs: add qa 2021-02-04 11:43:16 +08:00
daodoa97
5dedcff854 merge: branch 'master' of https://github.com/hyperf-admin/hyperf-admin 2021-02-04 11:23:59 +08:00
daodoa97
7727dd95cf fix: metric default disable 2021-02-04 11:22:47 +08:00
刀刀
1d9e2401fa Update install.md 2021-01-28 14:52:02 +08:00
刀刀
d6216f1830 Merge pull request #41 from wxfjamdc/patch-2
Update User.php
2021-01-23 18:46:30 +08:00
刀刀
78b06154bb Merge pull request #46 from wxfjamdc/patch-4
Update SystemController.php
2021-01-23 18:44:42 +08:00
WxAmd
20b2cb0a70 Update SystemController.php
返回有权限的 system_module
2021-01-23 16:19:08 +08:00
WxAmd
fceeda8203 Update User.php
保存用户信息时 realname 为空自动填充
2020-12-31 15:28:31 +08:00
daodao97
c4ba9db9a5 perf: support hyperf 2.1 2020-12-30 15:44:00 +08:00
141 changed files with 11399 additions and 926 deletions

View File

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

8
.gitignore vendored
View File

@@ -1,3 +1,5 @@
.idea
composer.lock
vendor
.idea/
vendor/
runtime/
.phpunit.result.cache
.php-cs-fixer.cache

23
.php-cs-fixer.dist.php Normal file
View File

@@ -0,0 +1,23 @@
<?php
/**
* .php-cs-fixer.dist.php@HDK-Core
*
* @author 李东云 <Dongyun.Li@LuxCreo.Ai>
* Powered by PhpStorm
* Created on 2023/1/9
*/
$finder = PhpCsFixer\Finder::create()->in([
__DIR__ . '/publish',
__DIR__ . '/src',
__DIR__ . '/tests',
]);
$config = new PhpCsFixer\Config();
return $config->setRules([
'@PSR12' => true,
'strict_param' => true,
'array_syntax' => ['syntax' => 'short'],
])
->setUsingCache(false)
->setFinder($finder);

68
.versionrc Normal file
View File

@@ -0,0 +1,68 @@
{
"header": "# 版本更新日志",
"preMajor": true,
"types": [
{
"type": "feat",
"section": "✨ Features | 新功能"
},
{
"type": "fix",
"section": "🐛 Bug Fixes | Bug 修复"
},
{
"type": "init",
"section": "🎉 Init | 初始化"
},
{
"type": "docs",
"section": "✏️ Documentation | 文档"
},
{
"type": "style",
"section": "💄 Styles | 风格"
},
{
"type": "refactor",
"section": "♻️ Code Refactoring | 代码重构"
},
{
"type": "perf",
"section": "⚡ Performance Improvements | 性能优化"
},
{
"type": "tests",
"section": "✅ Tests | 测试"
},
{
"type": "test",
"section": "✅ Tests | 测试"
},
{
"type": "revert",
"section": "⏪ Revert | 回退"
},
{
"type": "build",
"section": "📦‍ Build System | 打包构建"
},
{
"type": "chore",
"section": "🚀 Chore | 构建/工程依赖/工具"
},
{
"type": "ci",
"section": "👷 Continuous Integration | CI 配置"
}
],
"bumpFiles": [
{
"filename": "VERSION_TRACKER.txt",
"type": "plain-text"
},
{
"filename": "composer.json",
"type": "json"
}
]
}

85
CHANGELOG.md Normal file
View File

@@ -0,0 +1,85 @@
# 版本更新日志
### [0.5.1](http://124.126.16.154:8888/singularity/hyperf-admin/compare/v0.5.0...v0.5.1) (2025-09-26)
### 🚀 Chore | 构建/工程依赖/工具
* **workflow:** 更新发布工作流配置 ([db34c59](http://124.126.16.154:8888/singularity/hyperf-admin/commit/db34c59acc7825f6f10298b4a3ce34500639fe24))
## [0.5.0](http://124.126.16.154:8888/singularity/hyperf-admin/compare/v0.4.3...v0.5.0) (2025-09-26)
### 🚀 Chore | 构建/工程依赖/工具
* **workflow:** 修改发布任务名称 ([ff7e959](http://124.126.16.154:8888/singularity/hyperf-admin/commit/ff7e959daaec12a3f8fa5b52054f3ab2b5d13612))
### [0.4.3](http://124.126.16.154:8888/singularity/hyperf-admin/compare/v0.4.2...v0.4.3) (2025-09-26)
### [0.4.2](http://124.126.16.154:8888/singularity/hyperf-admin/compare/v0.3.4...v0.4.2) (2025-09-26)
### 🚀 Chore | 构建/工程依赖/工具
* 更新文件权限并添加发布脚本和CI配置 ([bb438b5](http://124.126.16.154:8888/singularity/hyperf-admin/commit/bb438b59963bb2a1906fc33311bbce85c60db5ce))
### ✨ Features | 新功能
* **ci:** 添加基于标签的自动发布工作流 ([98ba4a1](http://124.126.16.154:8888/singularity/hyperf-admin/commit/98ba4a13dc9d3317351c0c549a94d017ee48d6e0))
### [0.3.4](http://124.126.16.154:8888/singularity/hyperf-admin/compare/v0.3.3...v0.3.4) (2023-04-19)
### ✏️ Documentation | 文档
* **admin:** 修改了登录的验证文案 ([0d4e80f](http://124.126.16.154:8888/singularity/hyperf-admin/commit/0d4e80fe15e0e9a3de447012669ec2045323f1a5))
### [0.3.3](http://124.126.16.154:8888/singularity/hyperf-admin/compare/v0.3.2...v0.3.3) (2023-04-18)
### 📦‍ Build System | 打包构建
* **composer:** 最低依赖版本改为 php7.4 ([254989e](http://124.126.16.154:8888/singularity/hyperf-admin/commit/254989e9922bdb333e57e2cbd69e31464d4de1bf))
* **composer:** 更新包名 ([710eefc](http://124.126.16.154:8888/singularity/hyperf-admin/commit/710eefc4c6a3305c5653841c8f851e0b613f54e2))
* **composer:** 限制 flysystem-oss 的版本 ([e9e6084](http://124.126.16.154:8888/singularity/hyperf-admin/commit/e9e6084e97620dda06cc9bde7fbe5e5162bbf894))
* **composer:** 限制 flysystem-oss 的版本 ([3c5b77d](http://124.126.16.154:8888/singularity/hyperf-admin/commit/3c5b77dda68973e2fac9ce4c32a8cf8803129d33))
* **hdk:** 引入 hdk-core ([dabfbdf](http://124.126.16.154:8888/singularity/hyperf-admin/commit/dabfbdf8612188043b7a4354bbe5c6e518422af9))
* **semver:** 初始化版本记录文件 ([27b5f54](http://124.126.16.154:8888/singularity/hyperf-admin/commit/27b5f54d575ccc26747b00bf8c549c0935fb41d3))
* 工程化项目 ([c2b56cf](http://124.126.16.154:8888/singularity/hyperf-admin/commit/c2b56cf9783cca6f69067a5434d2a0fd19ec89d6))
### 🐛 Bug Fixes | Bug 修复
* **menu:** 修复添加菜单不选择权限时候的报错 ([8f5592e](http://124.126.16.154:8888/singularity/hyperf-admin/commit/8f5592ecc8a93e4749558ba2362ca357151bcf1c))
* **scaffold:** 修复级联时菜单显示有误的问题 ([6330f5e](http://124.126.16.154:8888/singularity/hyperf-admin/commit/6330f5e390f432e2541724219a3297cdcea6fb5a))
### 🚀 Chore | 构建/工程依赖/工具
* **release:** 0.4.0 ([dfd814c](http://124.126.16.154:8888/singularity/hyperf-admin/commit/dfd814c817d38521c764d83cec765ca0886e0467))
* **release:** 0.4.1 ([e60d917](http://124.126.16.154:8888/singularity/hyperf-admin/commit/e60d9174e571a5d71f5a1b5280b336f0b6d05abb))
### [0.4.1](http://124.126.16.154:8888/singularity/hyperf-admin/compare/v0.4.0...v0.4.1) (2023-04-18)
### 🐛 Bug Fixes | Bug 修复
* **scaffold:** 修复级联时菜单显示有误的问题 ([6330f5e](http://124.126.16.154:8888/singularity/hyperf-admin/commit/6330f5e390f432e2541724219a3297cdcea6fb5a))
## [0.4.0](http://124.126.16.154:8888/singularity/hyperf-admin/compare/v0.3.2...v0.4.0) (2023-04-07)
### 🐛 Bug Fixes | Bug 修复
* **menu:** 修复添加菜单不选择权限时候的报错 ([8f5592e](http://124.126.16.154:8888/singularity/hyperf-admin/commit/8f5592ecc8a93e4749558ba2362ca357151bcf1c))
### 📦‍ Build System | 打包构建
* **composer:** 最低依赖版本改为 php7.4 ([254989e](http://124.126.16.154:8888/singularity/hyperf-admin/commit/254989e9922bdb333e57e2cbd69e31464d4de1bf))
* **composer:** 更新包名 ([710eefc](http://124.126.16.154:8888/singularity/hyperf-admin/commit/710eefc4c6a3305c5653841c8f851e0b613f54e2))
* **composer:** 限制 flysystem-oss 的版本 ([e9e6084](http://124.126.16.154:8888/singularity/hyperf-admin/commit/e9e6084e97620dda06cc9bde7fbe5e5162bbf894))
* **composer:** 限制 flysystem-oss 的版本 ([3c5b77d](http://124.126.16.154:8888/singularity/hyperf-admin/commit/3c5b77dda68973e2fac9ce4c32a8cf8803129d33))
* **hdk:** 引入 hdk-core ([dabfbdf](http://124.126.16.154:8888/singularity/hyperf-admin/commit/dabfbdf8612188043b7a4354bbe5c6e518422af9))
* **semver:** 初始化版本记录文件 ([27b5f54](http://124.126.16.154:8888/singularity/hyperf-admin/commit/27b5f54d575ccc26747b00bf8c549c0935fb41d3))
* 工程化项目 ([c2b56cf](http://124.126.16.154:8888/singularity/hyperf-admin/commit/c2b56cf9783cca6f69067a5434d2a0fd19ec89d6))

View File

@@ -6,7 +6,7 @@
> 演示站点部署在`亚马逊免费主机`, 国内访问可能会慢
![HyperfAdmin架构](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/sJaJti.png)
![HyperfAdmin架构](https://gitee.com/daodao97/asset/raw/master/uPic/sJaJti.png)
前端为`vue multiple page`多页模式, 可以按模块打包, 默认包含两个模块`default` 默认模块, `system`系统管理模块, 绝大部分业务组件在`src/components`目录

1
VERSION_TRACKER.txt Normal file
View File

@@ -0,0 +1 @@
0.5.1

32
bin/hyperf.php Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
use Hyperf\Di\ClassLoader;
use Hyperf\Di\Container;
use Hyperf\Di\Definition\DefinitionSource;
use Hyperf\Utils\ApplicationContext;
use Psr\Container\ContainerInterface;
use Swoole\Runtime;
ini_set('display_errors', 'on');
ini_set('display_startup_errors', 'on');
error_reporting(E_ALL);
date_default_timezone_set('Asia/Shanghai');
!defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
!defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL);
Runtime::enableCoroutine();
require BASE_PATH . '/vendor/autoload.php';
ClassLoader::init();
$container = new Container(new DefinitionSource([]));
if (!$container instanceof ContainerInterface) {
throw new RuntimeException('The dependency injection container is invalid.');
}
$container = ApplicationContext::setContainer($container);

View File

@@ -1,30 +0,0 @@
#!/usr/bin/env bash
set -e
set -x
CURRENT_BRANCH="master"
BASEPATH=$(cd `dirname $0`; cd ../src/; pwd)
REPOS=$@
function split()
{
SHA1=`splitsh-lite --prefix=$1`
git push $2 "$SHA1:refs/heads/$CURRENT_BRANCH" -f
}
function remote()
{
git remote add $1 $2 || true
}
if [[ $# -eq 0 ]]; then
REPOS=$(ls $BASEPATH)
fi
for REPO in $REPOS ; do
remote $REPO git@github.com:hyperf-admin/$REPO.git
split "src/$REPO" $REPO
done
git pull origin $CURRENT_BRANCH

View File

@@ -1,104 +1,136 @@
{
"name": "hyperf-admin/hyperf-admin",
"description": "hyperf-admin",
"authors": [
{
"name": "daodao97",
"email": "daodao97@foxmail.com"
}
],
"require": {
"php": ">=7.3",
"ext-json": "*",
"ext-pdo": "*",
"ext-swoole": ">=4.4",
"aliyuncs/oss-sdk-php": "^2.3",
"box/spout": "^3.1",
"hyperf/amqp": "~2.1.0",
"hyperf/async-queue": "~2.1.0",
"hyperf/cache": "~2.1.0",
"hyperf/command": "~2.1.0",
"hyperf/config": "~2.1.0",
"hyperf/constants": "~2.1.0",
"hyperf/crontab": "~2.1.0",
"hyperf/database": "~2.1.0",
"hyperf/db-connection": "~2.1.0",
"hyperf/filesystem": "^2.0",
"hyperf/framework": "~2.1.0",
"hyperf/guzzle": "~2.1.0",
"hyperf/http-server": "~2.1.0",
"hyperf/logger": "~2.1.0",
"hyperf/memory": "~2.1.0",
"hyperf/metric": "~2.1.0",
"hyperf/nsq": "~2.1.0",
"hyperf/process": "~2.1.0",
"hyperf/redis": "~2.1.0",
"hyperf/snowflake": "~2.1.0",
"hyperf/validation": "~2.1.0",
"nette/php-generator": "^3.4",
"xxtime/flysystem-aliyun-oss": "^1.5",
"yadakhov/insert-on-duplicate-key": "^1.2",
"zoujingli/ip2region": "^1.0"
"name": "singularity/hyperf-admin",
"description": "hyperf-admin",
"authors": [
{
"name": "ch4o5",
"email": "dongyun.li@luxcreo.ai"
},
"replace": {
"hyperf-admin/base-utils": "self.version",
"hyperf-admin/admin": "self.version",
"hyperf-admin/alert-manager": "self.version",
"hyperf-admin/cron-center": "self.version",
"hyperf-admin/event-bus": "self.version",
"hyperf-admin/process-manager": "self.version",
"hyperf-admin/rule-engine": "self.version",
"hyperf-admin/validation": "self.version",
"hyperf-admin/workflow": "self.version",
"hyperf-admin/data-focus": "self.version",
"hyperf-admin/dev-tools": "self.version"
},
"autoload": {
"psr-4": {
"HyperfAdmin\\BaseUtils\\": "src/base-utils/src",
"HyperfAdmin\\Admin\\": "src/admin/src",
"HyperfAdmin\\AlertManager\\": "src/alert-manager/src",
"HyperfAdmin\\CronCenter\\": "src/cron-center/src",
"HyperfAdmin\\DataFocus\\": "src/data-focus/src",
"HyperfAdmin\\DevTools\\": "src/dev-tools/src",
"HyperfAdmin\\EventBus\\": "src/event-bus/src",
"HyperfAdmin\\ProcessManager\\": "src/process-manager/src",
"HyperfAdmin\\RuleEngine\\": "src/rule-engine/src",
"HyperfAdmin\\Validation\\": "src/validation/src",
"HyperfAdmin\\ConfigCenter\\": "src/config-center/src"
},
"files": [
"src/base-utils/src/Helper/array.php",
"src/base-utils/src/Helper/common.php",
"src/base-utils/src/Helper/constants.php",
"src/base-utils/src/Helper/system.php",
"src/data-focus/src/Util/func.php",
"src/event-bus/src/funcs.php",
"src/admin/src/funcs/common.php",
"src/data-focus/src/Util/SimpleHtmlDom.php"
],
"classmap": [
"src/base-utils/src/classmap"
]
},
"extra": {
"hyperf": {
"config": [
"HyperfAdmin\\Admin\\ConfigProvider",
"HyperfAdmin\\BaseUtils\\ConfigProvider@99",
"HyperfAdmin\\AlertManager\\ConfigProvider",
"HyperfAdmin\\CronCenter\\ConfigProvider",
"HyperfAdmin\\DataFocus\\ConfigProvider",
"HyperfAdmin\\DevTools\\ConfigProvider",
"HyperfAdmin\\EventBus\\ConfigProvider",
"HyperfAdmin\\ProcessManager\\ConfigProvider",
"HyperfAdmin\\ConfigCenter\\ConfigProvider"
]
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"sort-packages": true
{
"name": "daodao97",
"email": "daodao97@foxmail.com"
}
],
"require": {
"php": ">=8.2",
"ext-json": "*",
"ext-pdo": "*",
"ext-swoole": ">=4.4",
"aliyuncs/oss-sdk-php": "^2.3",
"box/spout": "^3.1",
"hyperf/amqp": "3.0.*",
"hyperf/async-queue": "3.0.*",
"hyperf/cache": "3.0.*",
"hyperf/code-generator": "^0.3.3",
"hyperf/command": "3.0.*",
"hyperf/config": "3.0.*",
"hyperf/constants": "3.0.*",
"hyperf/crontab": "3.0.*",
"hyperf/database": "3.0.*",
"hyperf/db-connection": "3.0.*",
"hyperf/filesystem": "3.0.*",
"hyperf/framework": "3.0.*",
"hyperf/guzzle": "3.0.*",
"hyperf/http-server": "3.0.*",
"hyperf/logger": "3.0.*",
"hyperf/memory": "3.0.*",
"hyperf/metric": "3.0.*",
"hyperf/nsq": "3.0.*",
"hyperf/process": "3.0.*",
"hyperf/redis": "3.0.*",
"hyperf/snowflake": "3.0.*",
"hyperf/validation": "3.0.*",
"nette/php-generator": "^3.4",
"singularity/hdk-core": "^1.0.0",
"xxtime/flysystem-aliyun-oss": "~1.5.0",
"yadakhov/insert-on-duplicate-key": "^1.2",
"zoujingli/ip2region": "^1.0"
},
"replace": {
"hyperf-admin/base-utils": "self.version",
"hyperf-admin/admin": "self.version",
"hyperf-admin/alert-manager": "self.version",
"hyperf-admin/cron-center": "self.version",
"hyperf-admin/event-bus": "self.version",
"hyperf-admin/process-manager": "self.version",
"hyperf-admin/rule-engine": "self.version",
"hyperf-admin/validation": "self.version",
"hyperf-admin/workflow": "self.version",
"hyperf-admin/data-focus": "self.version",
"hyperf-admin/dev-tools": "self.version"
},
"autoload": {
"psr-4": {
"HyperfAdmin\\BaseUtils\\": "src/base-utils/src",
"HyperfAdmin\\Admin\\": "src/admin/src",
"HyperfAdmin\\AlertManager\\": "src/alert-manager/src",
"HyperfAdmin\\CronCenter\\": "src/cron-center/src",
"HyperfAdmin\\DataFocus\\": "src/data-focus/src",
"HyperfAdmin\\DevTools\\": "src/dev-tools/src",
"HyperfAdmin\\EventBus\\": "src/event-bus/src",
"HyperfAdmin\\ProcessManager\\": "src/process-manager/src",
"HyperfAdmin\\RuleEngine\\": "src/rule-engine/src",
"HyperfAdmin\\Validation\\": "src/validation/src",
"HyperfAdmin\\ConfigCenter\\": "src/config-center/src"
},
"files": [
"src/base-utils/src/Helper/array.php",
"src/base-utils/src/Helper/common.php",
"src/base-utils/src/Helper/constants.php",
"src/base-utils/src/Helper/system.php",
"src/data-focus/src/Util/func.php",
"src/event-bus/src/funcs.php",
"src/admin/src/funcs/common.php",
"src/data-focus/src/Util/SimpleHtmlDom.php"
],
"classmap": [
"src/base-utils/src/classmap"
]
},
"extra": {
"hyperf": {
"config": [
"HyperfAdmin\\Admin\\ConfigProvider",
"HyperfAdmin\\BaseUtils\\ConfigProvider@99",
"HyperfAdmin\\AlertManager\\ConfigProvider",
"HyperfAdmin\\CronCenter\\ConfigProvider",
"HyperfAdmin\\DataFocus\\ConfigProvider",
"HyperfAdmin\\DevTools\\ConfigProvider",
"HyperfAdmin\\EventBus\\ConfigProvider",
"HyperfAdmin\\ProcessManager\\ConfigProvider",
"HyperfAdmin\\ConfigCenter\\ConfigProvider"
]
}
},
"prefer-stable": true,
"config": {
"sort-packages": true,
"secure-http": false
},
"scripts": {
"post-root-package-install": [],
"test": "vendor/bin/pest $1",
"cs-fix": "vendor/bin/php-cs-fixer fix $1 --rules=@PSR12 --allow-risky=yes",
"analyse": "vendor/bin/phpstan analyse $1",
"ci": [
"@analyse publish/ src/ tests/",
"@cs-fix",
"@test --ci",
"echo CI Success"
]
},
"repositories": {
"nest": {
"type": "composer",
"url": "https://nest.doylee.cn/api/packages/HDK/composer"
},
"packagist": {
"type": "composer",
"url": "https://mirrors.aliyun.com/composer/"
}
},
"version": "0.5.1",
"require-dev": {
"rector/rector": "^2.0"
}
}

10129
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
`hyperf-admin`是前后端分离的后台管理系统, 前端基于`vue``vue-admin-template`, 针对后台业务`列表`, `表单`等场景封装了大量业务组件, 后端基于`hyperf`实现, 整体思路是后端定义页面渲染规则, 前端页面渲染时首先拉取配置, 然后组件根据具体配置完成页面渲染, 方便开发者仅做少量的配置工作就能完成常见的`CRUD`工作, 同时支持自定义组件和自定义页面, 以开发更为复杂的页面.
![hyperf-admin架构](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/sJaJti.png)
![hyperf-admin架构](https://gitee.com/daodao97/asset/raw/master/uPic/sJaJti.png)
前端为`vue multiple page`多页模式, 可以按模块打包, 默认包含两个模块`default` 默认模块, `system`系统管理模块, 绝大部分业务组件在`src/components`目录, 前端文档详见 [这里](/frontend/form)
@@ -22,7 +22,7 @@
后端的详细文档见[这里](/backend/scaffold)
### UI预览
![样式预览](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/FW2cCN.png)
![样式预览](https://gitee.com/daodao97/asset/raw/master/uPic/FW2cCN.png)
## 依赖 & 参考

View File

@@ -4,6 +4,7 @@
* [安装](guide/install.md)
* [开发样例](guide/dev_example.md)
* [线上交流](guide/communication.md)
* [常见问题](guide/qa.md)
* 后端
* [脚手架](backend/scaffold.md)
* [表单详解](backend/form.md)

View File

@@ -1,14 +1,57 @@
### 为什么要做 CronCenter
#### 当前定时任务管理的解放方案及问题
1. 系统原生 crontab
- 需要有服务器权限, 有改动都要登录服务器手动操作
2. hyperf/crontab 提供的 基于 swoole/timer 的模拟 crontab
- 定时任务的编排以硬编码的方式放置在配置文件中
- 如果有变动需要 `编码->改配置->CodeReview->部署` 走一遍完整的上限流程
3. AirFlow 等工具
- 诚然也是一种很优秀的解决方案, 但对轻量级的定时任务需求来说有显得过重
### 如何规避上面的问题?
`CronCenter` 的思路是, 在 `hyperf/crontab` 的基础上, 将 `config/autoload/crontab.php` 中的定时任务配置, 迁移到数据库中, 提供后台, 以方便的管理所有定时任务的 `启动`, `停止`, `运行参数`, `单例/多例` 等等, 这样将极大的方便我们对定时任务的管理, 也相对轻量.
#### 具体实现
`hyperf-admin/cron-center` 重载了 `hyperf/crontab` 的关键类 `Hyperf/Crontab/Strategy/Executor`, 具体源码 [cron-center](https://github.com/hyperf-admin/hyperf-admin/blob/master/src/cron-center/src/ConfigProvider.php#L31)
#### 后台概览
![list](https://gitee.com/daodao97/asset/raw/master/imgs/8ynybw.png)
![lQJf50](https://gitee.com/daodao97/asset/raw/master/imgs/lQJf50.png)
#### 使用细节
脚本作业的管理中心, 可以在代码中实现`class`类型, `command`类型的脚本作业, 在后台添加相关任务, 即可在相应脚本机上执行
可以对任务的入口, 执行规划, 执行节点, 执行参数 等进行配置, 也可在列表主动触发任务
作业必须基于`App\Util\CronCenter\ClassJobAbstract`, 或`App/Util/CronCenter/CommandJobAbstract.php`抽象类进行实现, 才可进行执行状态的跟踪
作业必须基于`HyperfAdmin/CronCenter/ClassJobAbstract`, 或`HyperfAdmin/CronCenter/CommandJobAbstract.php`抽象类进行实现, 才可进行执行状态的跟踪
`CronCenter`的实现基于`hyperf-crontab`进行实现, 具体代码在`src/CronCenter`, 更多细节可查看[文档](https://hyperf.wiki/#/zh-cn/crontab)
### 相关配置
开启`cron-center` `config/autoload/crontab.php`
1. 首先要启用 `hyperf/crotnab`
`config/autoload/crontab.php`
```php
[
"enable" => true
// config/autoload/crontab.php
return [
"enable" => true,
...
]
```
2. 启用 `CronCenter`
```php
// config/config.php
return [
"cron_center" => [
"enable" => true,
],
...
]
```
### 此方案的问题
此种模拟式的 `crontab` 有一个很明显的弊端, 也就是任务的 `派遣`, `运行` 等都依赖于服务进程, 也就是服务一旦重启, 运行中的任务都会停止, 如果对任务的运行中断极度敏感, 则不建议使用此种方式.

View File

@@ -60,7 +60,7 @@ php bin/hyperf.php
可以查看到相关安装命令
![qIKtC8](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/qIKtC8.png)
![qIKtC8](https://gitee.com/daodao97/asset/raw/master/uPic/qIKtC8.png)
开发环境执行相应命令, 安装依赖的`db`结构即可, 在此之前请先确认`.env` 中已配置好相应连接信息.

View File

@@ -2,7 +2,7 @@
针对 数据库, `Model`, `Controller` 通用模型, 可以使用后台提供的`代码生成工具`来初始化大部分代码.
![9fnAUL(1)](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/9fnAUL%20%281%29.png)
![9fnAUL(1)](https://gitee.com/daodao97/asset/raw/master/uPic/9fnAUL%20%281%29.png)
连接池对应`config.autoload.databases` 中配置的可用链接, 选择好`连接池`, `数据库`, `表` 后下方表单会根据表结构字段渲染, 完成具体字段的配置, 点击提交.

View File

@@ -36,7 +36,7 @@ CREATE TABLE `config_center` (
#### 2. 创建 `Controller`, `Model`
![wlXctg](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/wlXctg.png)
![wlXctg](https://gitee.com/daodao97/asset/raw/master/uPic/wlXctg.png)
此时基础文件已经生成在`lib/` 下, 作为一个`composer`包模式开发, 我们将其转移至 `/opt/hyperf-admin/config-center`目录
@@ -73,7 +73,7 @@ CREATE TABLE `config_center` (
#### 3. 添加菜单 注册路由
![uAR5lj](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/uAR5lj.png)
![uAR5lj](https://gitee.com/daodao97/asset/raw/master/uPic/uAR5lj.png)
```php
<?php
@@ -87,7 +87,7 @@ register_route('/config_center', ConfigCenterController::class);
?> 若配置有不生效的情况, 执行 `rm vendor/hyperf-admin/config-center && composer require hyperf-admin/config-center` 重新安装即可
![EFvajy](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/EFvajy.png)
![EFvajy](https://gitee.com/daodao97/asset/raw/master/uPic/EFvajy.png)
至此已经完成了, 配置的`CRUD`.

View File

@@ -112,7 +112,7 @@ type表单项类型以下是支持的组件列表以下所有组件 pro
"field_name|字段名" => [
"type" => "textarea",
"props" => [
"row" => 6, // 行数, 默认6
"rows" => 6, // 行数, 默认6
]
]
]

View File

@@ -8,16 +8,16 @@
所以我们的服务一步步的变得膨胀, 变得不稳定, 是时候做出改变了
![Biv9dV](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/Biv9dV.png)
![Biv9dV](https://gitee.com/daodao97/asset/raw/master/uPic/Biv9dV.png)
我们将一些业务边界比较明显, 比较独立的功能, 以微服务的方式拆分出去, 然后注册到主项目上去, 这样我们就可以达到, 既使用一套ui, 又进行服务拆分的目的啦.
`remote_module`的注册也十分简单, `http://localhost:9528/system/#/cconf/cconf_website_config` 在站点管理中增加相应模块的配置即可
![1DIbj0](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/1DIbj0.png)
![1DIbj0](https://gitee.com/daodao97/asset/raw/master/uPic/1DIbj0.png)
然后在`菜单管理`中, 增加相应的模块菜单即可使用啦.
?> 注意 `remote_module` 必须基于`hyperf-admin/admin` 组件构建哦
?> 另外, 主的`HyperfAdmin`项目, 默认包含两个本地模块, `default`, `system`
?> 另外, 主的`HyperfAdmin`项目, 默认包含两个本地模块, `default`, `system`

View File

@@ -3,9 +3,9 @@
我们以 `用户管理` 这个页面功能为例, 讲解如何使用此功能.
先上效果图
![MXURtd](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/MXURtd.png)
![MXURtd](https://gitee.com/daodao97/asset/raw/master/uPic/MXURtd.png)
![rs0BhV](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/rs0BhV.png)
![rs0BhV](https://gitee.com/daodao97/asset/raw/master/uPic/rs0BhV.png)
此时我们只用配置好脚手架配置,即可为第三方服务提供后台页面的管理功能
```json

View File

@@ -1,4 +1,4 @@
我们使用微信群进行交流, 由于群二维码经常过期, 请添加下方个人微信, 拉你进群.
![WechatIMG7](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/WechatIMG7.jpeg ':size=50%')
![WechatIMG7](https://gitee.com/daodao97/asset/raw/master/uPic/WechatIMG7.jpeg ':size=50%')

View File

@@ -2,7 +2,7 @@
### 架构
![hyperf-admin架构](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/sJaJti.png)
![hyperf-admin架构](https://gitee.com/daodao97/asset/raw/master/uPic/sJaJti.png)
前端为`vue multiple page`多页模式, 可以按模块打包, 默认包含两个模块`default` 默认模块, `system`系统管理模块, 绝大部分业务组件在`src/components`目录, 前端文档详见 [这里](/frontend/form)
@@ -24,7 +24,7 @@
后端的详细文档见[这里](/backend/scaffold)
### UI预览
![样式预览](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/FW2cCN.png)
![样式预览](https://gitee.com/daodao97/asset/raw/master/uPic/FW2cCN.png)
### 依赖 & 参考

View File

@@ -48,13 +48,13 @@ CREATE TABLE `student_score` (
2. 通过`DevTools`开发者工具创建 `student_score` 相关的 `Model`, `Controller`
![YPdEli](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/YPdEli.png)
![YPdEli](https://gitee.com/daodao97/asset/raw/master/uPic/YPdEli.png)
选择好相应的表后, 点击提交, 此时工具已经帮我们创建好相应的`app/Controller/StudentScoreController.php`和`app/Model/Test/StudentScore.php`
3. 添加目录和菜单
![cs0SYX](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/cs0SYX.png)
![cs0SYX](https://gitee.com/daodao97/asset/raw/master/uPic/cs0SYX.png)
注册路由
@@ -65,7 +65,7 @@ CREATE TABLE `student_score` (
此时我们也已经完成了基础的`CRUD`开发
![MEoM4p](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/MEoM4p.png)
![MEoM4p](https://gitee.com/daodao97/asset/raw/master/uPic/MEoM4p.png)
哦对了, 还有各种筛选条件呢? 也很简单, 在 `scaffoldOptions` 中增加 `filter`配置即可
@@ -84,7 +84,7 @@ CREATE TABLE `student_score` (
}
```
![u68v1D](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/u68v1D.png)
![u68v1D](https://gitee.com/daodao97/asset/raw/master/uPic/u68v1D.png)
还有, 大家别忘了, 需求中还要去可以按页签显示, 改怎么办呢, 这个ui可有点复杂啊, 不过在`hyperf-admin`里也同样简单
@@ -112,7 +112,7 @@ CREATE TABLE `student_score` (
}
```
![Ax0WWD](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/Ax0WWD.png)
![Ax0WWD](https://gitee.com/daodao97/asset/raw/master/uPic/Ax0WWD.png)
至此我们已经完成了绝大部分的功能开发, 如果使用熟练, 我们应该能在十分钟内完成整个功能的前后端开发, 而且还支持复杂的前端效果.

View File

@@ -30,6 +30,8 @@ cd hyperf-admin
```
#### 2. 移除`hyperf-skeleton`中的日志配置, 因为 `admin` 底层已配置
底层的日志配置见 [base-utils/ConfigProvider](https://github.com/hyperf-admin/hyperf-admin/blob/master/src/base-utils/src/ConfigProvider.php#L22)
```shell
rm config/autoload/logger.php
```
@@ -65,6 +67,12 @@ LOCAL_DB_HOST=localhost
```shell
composer require hyperf-admin/hyperf-admin
```
如果存在依赖包的版本号问题, 注意, 请使用 composer2
```shell
composer require hyperf-admin/hyperf-admin -W
```
!> hyperf-admin 为分包模式, 实际应用中请根据情况安装
#### 6. 初始化`validation`的依赖文档
@@ -79,6 +87,7 @@ php bin/hyperf.php vendor:publish hyperf/validation
'password' => [
'salt' => env('HYPERF_ADMIN_PWD_SALT', 'c093d70f088499c3a837cae00c042f14'), // 用 md5(time()) 获取 salt
]
```
#### 8. 启动
@@ -87,7 +96,7 @@ php bin/hyperf.php vendor:publish hyperf/validation
composer watch
```
## nginx配置
## 生产环境nginx配置
```nginx
upstream backend {

22
docs/guide/qa.md Normal file
View File

@@ -0,0 +1,22 @@
#### 1. 导出服务的进度一直是0
导出任务没有默认启动,有两种方式可以启用他
1. 使用croncenter 的话,在 定时任务的管理后台 增加 export-task 任务即可, 参见[cron-center](http://daodao97.gitee.io/hyperf-admin/#/backend/components/business/cron-center)
2. 未使用croncenter 自己起个进程或定时任务 启动 `HyperfAdmin/Admin/Service/ExportService` 即可
#### 2. 前端页面点击菜单, 不能正常跳转
打开控制台可见如下错
![X6ijLg](https://gitee.com/daodao97/asset/raw/master/imgs/X6ijLg.png)
此时, 保证本地的 `node`, `npm` 为最新版本, 安装依赖时, 不要使用 `cnpm`
然后 `npm i``yarn` 重新安装并启动
#### 3. 点击下载中心的文件链接跳转地址提示需要登录
这里的逻辑是先跳转到 http://hyperf-admin.daodao.run/api/upload/ossprivateurl?key=oss/1/export_task/xxxxx.csv, 由接口 `api/upload/ossprivateurl` 生成临时的下载地址, 也就是我们需要此接口的权限, 如下操作即可
![KwtN0f](https://gitee.com/daodao97/asset/raw/master/imgs/KwtN0f.png)
勾选接口后, 点击提交, 并清理权限缓存
![EsMcs5](https://gitee.com/daodao97/asset/raw/master/imgs/EsMcs5.png)
同理, 其他接口的权限问题也可在此管理

128
guide.md Normal file
View File

@@ -0,0 +1,128 @@
# PHP Package boilerplate project explanation
PHP is a general-purpose server-side scripting language primarily used in web development. Originally created by Rasmus Lerdorf in 1994, it is now by The PHP Development Team.
PHP originally stood for "Personal Home Page", but now stands for "PHP: Hypertext Preprocessor".
## Further Material
- Homepage: [php.net](https://secure.php.net/)
- Documentation: [php.net/docs.php](https://secure.php.net/docs.php)
- PHP: The Right Way: [phptherightway.com](http://www.phptherightway.com/)
- Interactive PHP Tutorial: [learn-php.org](http://www.learn-php.org/)
## Topics, Tools and Terms
PHP packages were traditionally installed via PEAR (PHP Extension and Application Repository), but more recently the standard package and dependency management tool is Composer.
Composer lets us run install commands to add packages to our system, for example `composer require phpunit` would add the unit testing framework PHPUnit to our system.
For instructions on how to install Composer visit [getcomposer.org](https://getcomposer.org/download/).
### Dependency Management
Managing dependencies manually is time-consuming, fortunately Composer can automate this.
We can list our dependencies in a `composer.json` file and run `composer install` to bring these into our project.
An example `composer.json` file looks like this:
```json
{
"name": "example-project",
"require": {
"twig/twig": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^8.4"
}
}
```
The "require" block tells Composer that the Twig templating package is required for production use and can install Twig with a version of 3.x.x (ie. up to, but not including, version 4).
The "require-dev" block tells Composer that PHPUnit is required in development, but not in production.
Dependencies can be added to `composer.json` by
```bash
composer require author/package-name
```
Development dependencies can be added by
```bash
composer require author/package-name --dev
```
Dependencies can be updated to their latest maximum version by running
```bash
composer update
```
Composer will also generate a `composer.lock` file on each `composer update` and the initial `composer install`. This is not meant to be edited directly, it tells Composer to use specific versions of packages - particularly useful when hyhou want your development dependencies to match what you will push to production.
### Testing Tools
There are a number of testing tools available for PHP. The most popular one is [PHPUnit](https://phpunit.de/). PHPUnit follows the classic xUnit approach.
[Behat](http://behat.org/en/latest/) is the most popular behaviour-driven development (BDD) testing framework.
[Codeception](http://codeception.com/) is a framework combining BDD, unit testing, and integration testing, and is cross-compatible with PHPUnit.
In this guide we will be using PHPUnit as the testing framework.
## Directory Structure
A typical directory structure for a PHP project consists of a `src` directory that contains all source files and a `tests` directory that includes all tests. For web applications the publicly accessible files (eg. `index.php`) would reside in a `public` directory which would then be your webservers document root.
Another common convention is having a `bin` directory that may contain executable files to start your application.
- src/
- test/
- public/
- composer.json
- composer.lock
### Naming Conventions
Directory names are in lower case. Class and interface files should be in upper case and match the class or interface names.
Configuration, routes, and publicly accessible files should be in lower case.
For example the class `Example` should be contained in file `Example.php`, the publicly accessible route to the application should be `index.php`.
Tests match their production code file names with a `Test` suffix, e.g. tests for code in `src/Example.php` should be written in `test/ExampleTest.php`.
## Example Project
The main application consists of basically two files:
- `public/example.php` is the main executable that instantiates and runs:
- `src/Example/Greeting.php` contains the main application.
### Running the Tests
All tests can be run by executing
```bash
vendor/phpunit/phpunit/phpunit
```
`phpunit` will automatically find all tests inside the `test` directory and run them based on the configuration in the `phpunit.xml` file.
#### Testing Approach
The test for the class `Greeting` verifies that the return value of the `sayHello` method returns the string "Hello {name}", where {name} is the value passed through to the constructor.
### Running the Application
PHP has an in-built server for local development. To run this change into the directory `public` and run
```bash
php -S localhost:8000
```
Then open your browser at `http://localhost:8000/example.php`
You should see the text "Hello Ada Lovelace" being printed.

13
phpstan.dist.neon Normal file
View File

@@ -0,0 +1,13 @@
parameters:
level: 6
reportUnmatchedIgnoredErrors: false
checkGenericClassInNonGenericObjectType: false
paths:
- publish
- src
- tests
ignoreErrors:
- '#Constant BASE_PATH not found#'
- '#Property [a-zA-Z0-9\\_]+::\$[a-zA-Z0-9]+ is never written, only read\.#'
- '#Method [a-zA-Z0-9\\_]+::[a-zA-Z0-9]+\(\) is unused\.#'
- '#Method [a-zA-Z0-9\\_]+::[a-zA-Z0-9]+\(\) has parameter \$response with no value type specified in iterable type array\.#'

10
phpunit.xml Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php"
colors="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd">
<coverage/>
<testsuites>
<testsuite name="Unit">
<directory>./tests/Unit/</directory>
</testsuite>
</testsuites>
</phpunit>

30
rector.php Normal file
View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
// rector.php
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;
return static function (RectorConfig $rectorConfig): void {
// 1. 定义需要扫描的文件/目录
$rectorConfig->paths([
__DIR__ . '/src', // 替换为你项目代码的路径,例如:/app, /backend 等
// __DIR__ . '/tests', // 如果你想升级测试代码,也加入
]);
// 2. 一次性应用所有规则到 PHP 8.2 (推荐用于大型升级)
$rectorConfig->sets([
// 这将包含从 7.4 到 8.1 的所有升级规则,以及 8.2 的所有新规则
LevelSetList::UP_TO_PHP_82,
// 建议同时加入一些代码质量改进的规则
SetList::DEAD_CODE, // 移除无用代码
]);
// 可选:跳过某些文件或规则
// $rectorConfig->skip([
// // 例如:跳过特定文件
// // __DIR__ . '/src/Legacy/old_file.php',
// ]);
};

10
scripts/docker-env.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env sh
docker run \
--pull always \
-ti --rm --name "hdk-admin" \
-w "/srv/www" \
-v "$(pwd)":/srv/www \
-v ~/.ssh:/root/.ssh \
-v ~/.gitconfig:/root/.gitconfig \
harbor.luxcreo.cn/library/hyperf:8.2-swoole /bin/ash

5
scripts/release.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env sh
docker run --rm -it \
-v $(pwd):/app -e "GIT_AUTHOR_NAME=$(git config user.name)" -e "EMAIL=$(git config user.email)" \
detouched/standard-version:latest $1

View File

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

68
src/admin/.versionrc Normal file
View File

@@ -0,0 +1,68 @@
{
"header": "# 版本更新日志",
"preMajor": true,
"types": [
{
"type": "feat",
"section": "✨ Features | 新功能"
},
{
"type": "fix",
"section": "🐛 Bug Fixes | Bug 修复"
},
{
"type": "init",
"section": "🎉 Init | 初始化"
},
{
"type": "docs",
"section": "✏️ Documentation | 文档"
},
{
"type": "style",
"section": "💄 Styles | 风格"
},
{
"type": "refactor",
"section": "♻️ Code Refactoring | 代码重构"
},
{
"type": "perf",
"section": "⚡ Performance Improvements | 性能优化"
},
{
"type": "tests",
"section": "✅ Tests | 测试"
},
{
"type": "test",
"section": "✅ Tests | 测试"
},
{
"type": "revert",
"section": "⏪ Revert | 回退"
},
{
"type": "build",
"section": "📦‍ Build System | 打包构建"
},
{
"type": "chore",
"section": "🚀 Chore | 构建/工程依赖/工具"
},
{
"type": "ci",
"section": "👷 Continuous Integration | CI 配置"
}
],
"bumpFiles": [
{
"filename": "VERSION_TRACKER.txt",
"type": "plain-text"
},
{
"filename": "composer.json",
"type": "json"
}
]
}

0
src/admin/README.md Normal file → Executable file
View File

4
src/admin/composer.json Normal file → Executable file
View File

@@ -1,5 +1,5 @@
{
"name": "hyperf-admin/admin",
"name": "hdk/hdk-admin",
"type": "project",
"license": "MIT",
"authors": [
@@ -28,4 +28,4 @@
"config": "HyperfAdmin\\Admin\\ConfigProvider"
}
}
}
}

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env sh
docker run \
--pull always \
-ti --rm --name "hdk-admin" \
-w "/srv/www" \
-v "$(pwd)":/srv/www \
-v ~/.ssh:/root/.ssh \
harbor.luxcreo.cn/library/hyperf:7.4-swoole /bin/ash

5
src/admin/scripts/release.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env sh
docker run --rm -it \
-v $(pwd):/app -e "GIT_AUTHOR_NAME=ch4o5" -e "EMAIL=li_dongyun@outlook.com" \
detouched/standard-version:latest $1

0
src/admin/src/ConfigProvider.php Normal file → Executable file
View File

5
src/admin/src/Controller/AdminAbstractController.php Normal file → Executable file
View File

@@ -36,7 +36,7 @@ abstract class AdminAbstractController extends AbstractController
public function getRecordHistory()
{
$history_versions = $this->getEntity()->lastVersion($this->previous_version_number);
$history_versions = array_node_append($history_versions, 'user_id', 'username', function ($uids) {
return array_node_append($history_versions, 'user_id', 'username', function ($uids) {
$ret = User::query()->select(['id', 'username'])->whereIn('id', $uids)->get();
if (!$ret) {
return [];
@@ -49,14 +49,11 @@ abstract class AdminAbstractController extends AbstractController
}
return $ret;
});
return $history_versions;
}
/**
* 新版本检查
*
* @param int $id
* @param int $last_ver_id
*
* @return array
*/

0
src/admin/src/Controller/CommonConfigController.php Normal file → Executable file
View File

4
src/admin/src/Controller/LogController.php Normal file → Executable file
View File

@@ -94,7 +94,7 @@ class LogController extends AdminAbstractController
'width' => '480px',
'render' => function ($field, $row) {
if (!is_array($row['detail_json'])) {
$row['detail_json'] = json_decode($row['detail_json'], true);
$row['detail_json'] = json_decode((string) $row['detail_json'], true);
}
return $row['detail_json']['description'] ?? '';
@@ -112,7 +112,7 @@ class LogController extends AdminAbstractController
'width' => '200px',
'render' => function ($field, $row) {
if (!is_array($row['detail_json'])) {
$row['detail_json'] = json_decode($row['detail_json'], true);
$row['detail_json'] = json_decode((string) $row['detail_json'], true);
}
return $row['detail_json']['remark'] ?? '';

28
src/admin/src/Controller/MenuController.php Normal file → Executable file
View File

@@ -201,7 +201,7 @@ class MenuController extends AdminAbstractController
],
'permission|权限标识' => [
'type' => 'select',
'default' => [],
'default' => '',
'props' => [
'multiple' => true,
'selectApi' => '/system/routes?module={module}'
@@ -252,13 +252,11 @@ class MenuController extends AdminAbstractController
'tabs' => function() {
$conf = \HyperfAdmin\Admin\Service\CommonConfig::getValByName('website_config');
$system_module = $conf['system_module'] ?? [];
return array_map(function ($item) {
return [
'label' => $item['label'],
'value' => $item['name'],
'icon' => $item['icon']
];
}, $system_module);
return array_map(fn($item) => [
'label' => $item['label'],
'value' => $item['name'],
'icon' => $item['icon']
], $system_module);
},
'rowActions' => [
[
@@ -382,9 +380,9 @@ class MenuController extends AdminAbstractController
}
return $item;
}, array_filter(explode(',', $record['permission'])));
}, array_filter(explode(',', (string) $record['permission'])));
}
$scaffold_action = json_decode($record['scaffold_action'], true);
$scaffold_action = json_decode((string) $record['scaffold_action'], true);
$record['scaffold_action'] = $scaffold_action ? array_keys($scaffold_action) : [];
$record['pid'] = (new Menu())->getPathNodeIds($id);
}
@@ -395,7 +393,7 @@ class MenuController extends AdminAbstractController
if ($data['path'] == '#' || $data['path'] == '') {
$this->exception('菜单路由地址不能为空或"#"', ErrorCode::CODE_ERR_PARAM);
}
$paths = array_filter(explode('/', $data['path']));
$paths = array_filter(explode('/', (string) $data['path']));
if (count($paths) > 5) {
$this->exception('路由地址层级过深>5请设置精简一些', ErrorCode::CODE_ERR_PARAM);
}
@@ -423,14 +421,14 @@ class MenuController extends AdminAbstractController
protected function afterSave($pk_val, $data, $entity)
{
// 更新预置的脚手架权限
$scaffold_action = json_decode($entity->scaffold_action, true);
$scaffold_action = json_decode((string) $entity->scaffold_action, true);
$action_keys = $scaffold_action ? array_keys($scaffold_action) : [];
$need_del_ids = $scaffold_action ? array_values($scaffold_action) : [];
$router_ids = [];
if (!empty($data['scaffold_action'])) {
$need_del_ids = collect($scaffold_action)->except($data['scaffold_action'])->values()->toArray();
$scaffold_action = collect($scaffold_action)->only($data['scaffold_action'])->toArray();
$paths = array_filter(explode('/', $data['path']));
$paths = array_filter(explode('/', (string) $data['path']));
array_pop($paths);
$prefix = implode('/', $paths);
foreach ($data['scaffold_action'] as $k => $action) {
@@ -530,7 +528,7 @@ class MenuController extends AdminAbstractController
$route_list = $routes;
}
foreach ($route_list as $route => $v) {
$route = is_string($route) ? rtrim($route) : rtrim($v[0]->route);
$route = is_string($route) ? rtrim($route) : rtrim((string) $v[0]->route);
$route_key = "$http_method::{$route}";
if (in_array($route_key, $conf)) {
continue;
@@ -555,7 +553,7 @@ class MenuController extends AdminAbstractController
}
$right_options = [];
foreach ($conf as $route) {
[$http_method, $uri] = explode("::", $route, 2);
[$http_method, $uri] = explode("::", (string) $route, 2);
$dispatcher = container(DispatcherFactory::class)->getDispatcher('http');
$route_info = $dispatcher->dispatch($http_method, $uri);
if (!empty($route_info[1]->callback[0])) {

2
src/admin/src/Controller/RoleController.php Normal file → Executable file
View File

@@ -132,7 +132,7 @@ class RoleController extends AdminAbstractController
protected function afterSave($pk_val, $data)
{
$data['permissions'] = json_decode($data['permissions'], true);
$data['permissions'] = json_decode((string) $data['permissions'], true);
if (empty($data['permissions'])) {
return true;
}

8
src/admin/src/Controller/SystemController.php Normal file → Executable file
View File

@@ -29,9 +29,7 @@ class SystemController extends AdminAbstractController
if (isset($config['system_module']) && !$this->auth_service->isSupperAdmin()) {
$user_id = $this->auth_service->get('id');
$modules = $this->permission_service->getModules($user_id);
$config['system_module'] = array_filter($config['system_module'], function ($item) use ($modules) {
return !in_array($item['name'], $modules);
});
$config['system_module'] = array_filter($config['system_module'], fn($item) => in_array($item['name'], $modules));
}
return $this->success($config);
@@ -46,9 +44,7 @@ class SystemController extends AdminAbstractController
$kw = $this->request->input('kw', '');
$routes = $this->permission_service->getSystemRouteOptions();
$routes = array_filter($routes, function ($item) use ($kw) {
return Str::contains($item['value'], $kw);
});
$routes = array_filter($routes, fn($item) => Str::contains($item['value'], $kw));
return $this->success(array_values($routes));
}

0
src/admin/src/Controller/UploadController.php Normal file → Executable file
View File

20
src/admin/src/Controller/UserController.php Normal file → Executable file
View File

@@ -90,9 +90,7 @@ class UserController extends AdminAbstractController
'type' => 'date_range',
],
],
'hasOne' => function ($field, $row) {
return 'hyperf_admin.'.env('HYPERF_ADMIN_DB_NAME').'.user_role:user_id,role_id';
},
'hasOne' => fn($field, $row) => 'hyperf_admin.'.env('HYPERF_ADMIN_DB_NAME').'.user_role:user_id,role_id',
'table' => [
'columns' => [
'id',
@@ -100,9 +98,7 @@ class UserController extends AdminAbstractController
'username',
[
'field' => 'mobile',
'render' => function ($field, $row) {
return data_desensitization($field, 3, 4);
},
'render' => fn($field, $row) => data_desensitization($field, 3, 4),
],
['field' => 'avatar', 'render' => 'avatarRender'],
'email',
@@ -205,10 +201,10 @@ class UserController extends AdminAbstractController
}
$user = $this->getModel()->where('username', $username)->first();
if (!$user || $user['status'] !== YES) {
return $this->fail(ErrorCode::CODE_ERR_PARAM, '该用户不存在或已被禁用');
return $this->fail(ErrorCode::CODE_ERR_PARAM, 'Incorrect or invalid username');
}
if ($user->password !== $this->passwordHash($password)) {
return $this->fail(ErrorCode::CODE_ERR_PARAM);
return $this->fail(ErrorCode::CODE_ERR_PARAM, 'Incorrect password');
}
$data = [
'iat' => Carbon::now()->timestamp,
@@ -235,12 +231,12 @@ class UserController extends AdminAbstractController
public function passwordHash($password)
{
return sha1(md5($password) . md5(config('password.salt')));
return sha1(md5((string) $password) . md5((string) config('password.salt')));
}
public function logout()
{
$user = $this->auth_service->logout();
$this->auth_service->logout();
return $this->success();
}
@@ -259,9 +255,7 @@ class UserController extends AdminAbstractController
$task = new ExportTasks();
$task->name = $this->request->input('name');
$task->list_api = $url;
$task->filters = array_filter($this->request->input('filters'), function ($item) {
return $item !== '';
});
$task->filters = array_filter($this->request->input('filters'), fn($item) => $item !== '');
$task->operator_id = $this->userId() ?? 0;
if ((new ExportService())->getFirstSameTask($task->list_api, $task->filters, $task->operator_id)) { // 如果当天已经有相同过滤条件且还未成功生成文件的任务
return $this->success([], '已经有相同的任务,请勿重复导出');

0
src/admin/src/Crontab/ExportTask.php Normal file → Executable file
View File

2
src/admin/src/Install/InstallCommand.php Normal file → Executable file
View File

@@ -22,7 +22,7 @@ class InstallCommand extends HyperfCommand
$sql = file_get_contents(__DIR__ . '/install.sql');
$re = Db::connection('hyperf_admin')->getPdo()->exec($sql);
Db::connection('hyperf_admin')->getPdo()->exec($sql);
$this->output->success('hyperf-admin db install success');
}

2
src/admin/src/Install/UpdateCommand.php Normal file → Executable file
View File

@@ -36,7 +36,7 @@ class UpdateCommand extends HyperfCommand
$sql = file_get_contents($update_sql_file);
$re = Db::connection('hyperf_admin')->getPdo()->exec($sql);
Db::connection('hyperf_admin')->getPdo()->exec($sql);
$this->output->success('hyperf-admin db update success');

2
src/admin/src/Install/install.sql Normal file → Executable file
View File

@@ -56,7 +56,7 @@ CREATE TABLE `front_routes` (
`scaffold_action` varchar(255) NOT NULL DEFAULT '' COMMENT '脚手架预置权限',
`config` text COMMENT '配置化脚手架',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=90 DEFAULT CHARSET=utf8mb4 COMMENT='前端路由(菜单)';;
) ENGINE=InnoDB AUTO_INCREMENT=90 DEFAULT CHARSET=utf8mb4 COMMENT='前端路由(菜单)';
CREATE TABLE `global_config` (
`id` int(11) NOT NULL AUTO_INCREMENT,

0
src/admin/src/Middleware/AuthMiddleware.php Normal file → Executable file
View File

4
src/admin/src/Middleware/PermissionMiddleware.php Normal file → Executable file
View File

@@ -116,8 +116,6 @@ class PermissionMiddleware extends CoreMiddleware
}
/**
* @param int $code
* @param string|null $message
*
* @return \Psr\Http\Message\ResponseInterface
*/
@@ -152,7 +150,7 @@ class PermissionMiddleware extends CoreMiddleware
} else {
$body = '';
}
$ak = str_replace('ha ', '', explode(':', $client_token)[0] ?? '');
$ak = str_replace('ha ', '', explode(':', (string) $client_token)[0] ?? '');
$sk = config('client_user')[$ak] ?? '';
$auth = new AKSK($ak, $sk);
$token = $auth->token($method, $path, $host, $query, $content_type, $body);

0
src/admin/src/Model/CommonConfig.php Normal file → Executable file
View File

0
src/admin/src/Model/ExportTasks.php Normal file → Executable file
View File

0
src/admin/src/Model/FrontRoutes.php Normal file → Executable file
View File

6
src/admin/src/Model/GlobalConfig.php Normal file → Executable file
View File

@@ -57,7 +57,7 @@ class GlobalConfig extends BaseModel
if(empty($item)) {
return $default;
}
$config = json_decode($item[0]['value'], true);
$config = json_decode((string) $item[0]['value'], true);
if(is_null($config)) {
return $default;
}
@@ -76,8 +76,8 @@ class GlobalConfig extends BaseModel
public static function setConfig($name, $value, $ext = [], $raw = true)
{
$namespace = '';
if(($pos = strpos($name, '.')) !== false) {
$namespace = substr($name, 0, $pos);
if(($pos = strpos((string) $name, '.')) !== false) {
$namespace = substr((string) $name, 0, $pos);
}
if($raw) {
$value = json_encode($value);

0
src/admin/src/Model/OperatorLog.php Normal file → Executable file
View File

0
src/admin/src/Model/RequestLog.php Normal file → Executable file
View File

0
src/admin/src/Model/Role.php Normal file → Executable file
View File

0
src/admin/src/Model/RoleMenu.php Normal file → Executable file
View File

7
src/admin/src/Model/User.php Normal file → Executable file
View File

@@ -70,7 +70,7 @@ class User extends BaseModel
public function getRolesAttribute($value)
{
return explode(',', $value);
return explode(',', (string) $value);
}
public function setRolesAttribute($value)
@@ -82,4 +82,9 @@ class User extends BaseModel
{
return $value ?: $this->username;
}
public function setRealnameAttribute($value)
{
$this->attributes['realname'] = $value ?: $this->username;
}
}

0
src/admin/src/Model/UserRole.php Normal file → Executable file
View File

0
src/admin/src/Model/Version.php Normal file → Executable file
View File

9
src/admin/src/Model/Versionable.php Normal file → Executable file
View File

@@ -53,7 +53,7 @@ trait Versionable
public function getMorphClass()
{
if (strpos($this->getTable(), '.') !== false) {
if (str_contains($this->getTable(), '.')) {
return $this->getTable();
}
return $this->getConnectionName() . '.' . $this->getTable();
@@ -89,8 +89,6 @@ trait Versionable
/**
* 监听saved事件保存表更数据
*
* @param Saved $event
*/
public function saved(Saved $event)
{
@@ -119,8 +117,6 @@ trait Versionable
/**
* 记录数据版本前先记录请求
*
* @return RequestLog
*/
private function processRequest(): RequestLog
{
@@ -152,7 +148,7 @@ trait Versionable
{
$keep = $this->keep_version_count ?? 100;
$count = $this->versions()->count();
if ($keep > 0 && $count > $keep) {
if ($count > $keep) {
$this->versions()->limit($count - $keep)->delete();
}
}
@@ -170,7 +166,6 @@ trait Versionable
/**
* 前几个版本
*
* @param int $previous
*
* @return mixed
*/

0
src/admin/src/Service/AuthService.php Normal file → Executable file
View File

0
src/admin/src/Service/CommonConfig.php Normal file → Executable file
View File

9
src/admin/src/Service/ExportService.php Normal file → Executable file
View File

@@ -80,7 +80,7 @@ class ExportService
}
$task->fill(['status' => ExportTasks::STATUS_PROCESSING])->save();
$list_api = 'http://127.0.0.1:' . config('server.servers.0.port') . $task->list_api;
$query['_page'] = ($query['_page'] ?? 1);
$query['_page'] ??= 1;
$size = 100;
$query['_size'] = $size;
$query = array_merge($query, $task->filters);
@@ -95,9 +95,7 @@ class ExportService
$info_api = 'http://127.0.0.1:' . config('server.servers.0.port') . '/' . $subject . '/info';
}
$info = Guzzle::get($info_api, [], $headers);
$table_headers = array_filter($info['payload']['tableHeader'], function ($item) {
return $item['hidden'] ?? true;
});
$table_headers = array_filter($info['payload']['tableHeader'], fn($item) => $item['hidden'] ?? true);
$table_headers_str = [];
foreach($table_headers as $item) {
$table_headers_str[] = $item['title'];
@@ -166,9 +164,8 @@ class ExportService
}
$arr = array_map(function ($item) {
$item = csv_big_num($item);
$item = preg_replace('/\\n/', ' ', $item);
return $item;
return preg_replace('/\\n/', ' ', (string) $item);
}, $arr);
return implode(',', array_map(function ($item) {

4
src/admin/src/Service/GlobalConfig.php Normal file → Executable file
View File

@@ -9,8 +9,8 @@ class GlobalConfig
public static function setConfig($name, $value, $ext = [], $raw = true)
{
$namespace = '';
if(($pos = strpos($name, '.')) !== false) {
$namespace = substr($name, 0, $pos);
if(($pos = strpos((string) $name, '.')) !== false) {
$namespace = substr((string) $name, 0, $pos);
}
if($raw) {
$value = json_encode($value);

6
src/admin/src/Service/Menu.php Normal file → Executable file
View File

@@ -14,11 +14,7 @@ class Menu
{
$query = $this->query();
if (!empty($menu_ids)) {
$query->where(function ($query) use ($menu_ids) {
return $query->whereIn('id', $menu_ids)->orWhere(function ($query) use ($menu_ids) {
return $query->where('is_menu', 0)->whereIn('pid', $menu_ids);
});
});
$query->where(fn($query) => $query->whereIn('id', $menu_ids)->orWhere(fn($query) => $query->where('is_menu', 0)->whereIn('pid', $menu_ids)));
}
$query = $query->select([
'id',

0
src/admin/src/Service/ModuleProxy.php Normal file → Executable file
View File

4
src/admin/src/Service/OperatorLogService.php Normal file → Executable file
View File

@@ -31,11 +31,11 @@ class OperatorLogService
try {
// 页面url和名称
$page_url = request()->header('page-url');
$parse_url = parse_url($page_url);
$parse_url = parse_url((string) $page_url);
$fragment = $parse_url['fragment'] ?? '/'; // 抽出#后面的部分
$fragments = explode('?', $fragment); // 去掉querystring
$page_url = array_shift($fragments);
$page_name = urldecode(request()->header('page-name', '')); // 页面名称
$page_name = urldecode((string) request()->header('page-name', '')); // 页面名称
$relation_ids = json_encode($ids, JSON_UNESCAPED_UNICODE); // 如果没有版本启用则只记录操作的id
// 关联id-版本id记录
if(is_string($model) && $model) {

21
src/admin/src/Service/PermissionService.php Normal file → Executable file
View File

@@ -53,7 +53,7 @@ class PermissionService
if (!is_array($callback)) {
continue;
}
$route = is_string($route) ? rtrim($route) : rtrim($v[0]->route);
$route = is_string($route) ? rtrim($route) : rtrim((string) $v[0]->route);
$route_key = "$http_method::{$route}";
$options[] = [
'value' => $route_key,
@@ -127,7 +127,7 @@ class PermissionService
if (empty($role_ids)) {
return [];
}
$routes = RoleMenu::query()->distinct(true)->select(['router_id'])->whereIn('role_id', $role_ids)->get()->toArray();
$routes = RoleMenu::query()->distinct()->select(['router_id'])->whereIn('role_id', $role_ids)->get()->toArray();
return $routes ? array_column($routes, 'router_id') : [];
}
@@ -193,7 +193,7 @@ class PermissionService
$resources = [];
foreach ($list as $route) {
if (Str::contains($route['permission'], '::')) {
$permissions = array_filter(explode(',', $route['permission']));
$permissions = array_filter(explode(',', (string) $route['permission']));
foreach ($permissions as $permission) {
[
$http_method,
@@ -206,7 +206,7 @@ class PermissionService
}
} else {
// 这段代码为兼容老的数据
$paths = array_filter(explode('/', $route['path']));
$paths = array_filter(explode('/', (string) $route['path']));
$suffix = array_pop($paths);
$prefix = implode('/', $paths);
if ($suffix == 'list') {
@@ -245,7 +245,7 @@ class PermissionService
])->value('value')[$field] ?? [];
$data = [];
foreach ($open_apis as $route) {
[$http_method, $uri] = explode("::", $route, 2);
[$http_method, $uri] = explode("::", (string) $route, 2);
$data[] = compact('http_method', 'uri');
}
@@ -256,13 +256,12 @@ class PermissionService
{
$cache_key = $this->getPermissionCacheKey($user_id);
$options = [
'routeParser' => 'FastRoute\\RouteParser\\Std',
'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased',
'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased',
'routeCollector' => 'FastRoute\\RouteCollector',
'routeParser' => \FastRoute\RouteParser\Std::class,
'dataGenerator' => \FastRoute\DataGenerator\GroupCountBased::class,
'dispatcher' => \FastRoute\Dispatcher\GroupCountBased::class,
'routeCollector' => \FastRoute\RouteCollector::class,
];
if (!$dispatch_data = json_decode(Redis::get($cache_key), true)) {
/** @var RouteCollector $routeCollector */
$route_collector = new $options['routeCollector'](new $options['routeParser'], new $options['dataGenerator']);
$this->processUserResource($route_collector, $user_id, $auth_type);
$dispatch_data = $route_collector->getData();
@@ -359,7 +358,7 @@ class PermissionService
protected function prepareHandler($handler): array
{
if (is_string($handler)) {
if (strpos($handler, '@') !== false) {
if (str_contains($handler, '@')) {
return explode('@', $handler);
}

0
src/admin/src/Service/UserService.php Normal file → Executable file
View File

0
src/admin/src/config/config.php Normal file → Executable file
View File

0
src/admin/src/config/routes.php Normal file → Executable file
View File

0
src/admin/src/funcs/common.php Normal file → Executable file
View File

View File

@@ -9,8 +9,8 @@
}
],
"require": {
"hyperf/async-queue": "~2.1.0",
"hyperf/process": "~2.1.0",
"hyperf/async-queue": "~2.2.0",
"hyperf/process": "~2.2.0",
"hyperf-admin/rule-engine": "dev-master"
},
"autoload": {

View File

@@ -10,12 +10,8 @@ use HyperfAdmin\RuleEngine\Context\TimeContext;
class AlertJob extends Job
{
public $params;
public function __construct($params)
public function __construct(public $params)
{
// 这里最好是普通数据,不要使用携带 IO 的对象,比如 PDO 对象
$this->params = $params;
}
public function handle()

View File

@@ -22,8 +22,6 @@ class AlertService
*
* @param $params 数据
* @param int $delay 延时时间 单位秒
*
* @return bool
*/
public function push($params, int $delay = 0): bool
{

View File

@@ -9,38 +9,38 @@
}
],
"require": {
"php": ">=7.2",
"php": ">=7.3",
"ext-json": "*",
"ext-pdo": "*",
"ext-swoole": ">=4.4",
"ext-yaml": "*",
"aliyuncs/oss-sdk-php": "^2.3",
"box/spout": "^3.1",
"hyperf/amqp": "~2.1.0",
"hyperf/cache": "~2.1.0",
"hyperf/command": "~2.1.0",
"hyperf/config": "~2.1.0",
"hyperf/constants": "~2.1.0",
"hyperf/database": "~2.1.0",
"hyperf/db-connection": "~2.1.0",
"hyperf/filesystem": "~2.1.0",
"hyperf/framework": "~2.1.0",
"hyperf/guzzle": "~2.1.0",
"hyperf/http-server": "~2.1.0",
"hyperf/logger": "~2.1.0",
"hyperf/memory": "~2.1.0",
"hyperf/metric": "~2.1.0",
"hyperf/nsq": "~2.1.0",
"hyperf/process": "~2.1.0",
"hyperf/redis": "~2.1.0",
"hyperf/snowflake": "~2.1.0",
"hyperf/amqp": "~2.2.0",
"hyperf/cache": "~2.2.0",
"hyperf/command": "~2.2.0",
"hyperf/config": "~2.2.0",
"hyperf/constants": "~2.2.0",
"hyperf/database": "~2.2.0",
"hyperf/db-connection": "~2.2.0",
"hyperf/filesystem": "~2.2.0",
"hyperf/framework": "~2.2.0",
"hyperf/guzzle": "~2.2.0",
"hyperf/http-server": "~2.2.0",
"hyperf/logger": "~2.2.0",
"hyperf/memory": "~2.2.0",
"hyperf/metric": "~2.2.0",
"hyperf/nsq": "~2.2.0",
"hyperf/process": "~2.2.0",
"hyperf/redis": "~2.2.0",
"hyperf/snowflake": "~2.2.0",
"yadakhov/insert-on-duplicate-key": "^1.2"
},
"require-dev": {
"swoft/swoole-ide-helper": "^4.2",
"phpstan/phpstan": "^0.11.2",
"hyperf/devtool": "~2.1.0",
"hyperf/testing": "~2.1.0",
"hyperf/devtool": "~2.2.0",
"hyperf/testing": "~2.2.0",
"daodao97/hyperf-watch": "dev-master",
"symfony/var-dumper": "^5.0"
},

View File

@@ -6,14 +6,8 @@ namespace HyperfAdmin\BaseUtils;
*/
class AKSK
{
private $access_key;
private $secret_key;
public function __construct($access_key, $secret_key)
public function __construct(private $access_key, private $secret_key)
{
$this->access_key = $access_key;
$this->secret_key = $secret_key;
}
public function token($method, $path, $host, $query, $content_type, $body)
@@ -40,7 +34,7 @@ class AKSK
private function digest($secret, $data)
{
return hash_hmac('sha1', $data, $secret, true);
return hash_hmac('sha1', (string) $data, (string) $secret, true);
}
private function sign($secret, $data)

View File

@@ -128,14 +128,14 @@ class AliyunOSS
$conditions[] = [
'starts-with',
'$key',
rtrim($dir, '/') . '/',
rtrim((string) $dir, '/') . '/',
];
}
$base64_policy = base64_encode(json_encode([
'expiration' => $expiration,
'conditions' => $conditions,
]));
$signature = base64_encode(hash_hmac('sha1', $base64_policy, $this->access_key_secret, true));
$signature = base64_encode(hash_hmac('sha1', $base64_policy, (string) $this->access_key_secret, true));
return [
'OSSAccessKeyId' => $this->access_key,
@@ -144,7 +144,7 @@ class AliyunOSS
'Signature' => $signature,
'expire' => $end,
'dir' => $dir, //这个参数是设置用户上传指定的前缀
'cdn' => $this->cdn ? rtrim($this->cdn, '/') : '',
'cdn' => $this->cdn ? rtrim((string) $this->cdn, '/') : '',
];
}
}

View File

@@ -2,6 +2,7 @@
namespace HyperfAdmin\BaseUtils;
use Monolog\Formatter\LineFormatter;
use Monolog\LogRecord;
class ColorLineFormatter extends LineFormatter
{
@@ -11,7 +12,7 @@ class ColorLineFormatter extends LineFormatter
'WARNING' => "\e[0;33m{log_str}\e[0m",
];
public function format(array $record): string
public function format(LogRecord $record): string
{
$log = parent::format($record);

View File

@@ -88,6 +88,9 @@ class ConfigProvider
HttpLogMiddleware::class,
],
],
'metric' => [
'use_standalone_process' => false
],
'redis' => [
'metric' => [
'host' => env('REDIS_ALERT_MANAGER_HOST', 'localhost'),

View File

@@ -9,8 +9,6 @@ use GuzzleHttp\Cookie\CookieJar;
class Guzzle
{
/**
* @param array $config
*
* @return Client
*/
public static function create(array $config = [])
@@ -39,9 +37,9 @@ class Guzzle
$client = self::create([
'timeout' => $headers['timeout'] ?? 10.0,
]);
$method = strtoupper($method);
$method = strtoupper((string) $method);
$options = [];
$headers['charset'] = $headers['charset'] ?? 'UTF-8';
$headers['charset'] ??= 'UTF-8';
$options['headers'] = $headers;
if($method == 'GET' && $params) {
$options['query'] = $params;
@@ -49,7 +47,7 @@ class Guzzle
if($method == 'POST') {
$options['headers']['Content-Type'] = $headers['Content-Type'] ?? 'application/json';
if($options['headers']['Content-Type'] == 'application/json' && $params) {
$options['body'] = \GuzzleHttp\json_encode($params ? $params : (object)[]);
$options['body'] = \GuzzleHttp\json_encode($params ?: (object)[]);
}
if($options['headers']['Content-Type'] == 'application/x-www-form-urlencoded' && $params) {
$options['form_params'] = $params;
@@ -92,9 +90,7 @@ class Guzzle
try {
$options['headers']['X-No-Proxy'] = true;
$options['headers'] = array_merge($options['headers'], array_map(function ($item) {
return $item[0];
}, request_header()));
$options['headers'] = array_merge($options['headers'], array_map(fn($item) => $item[0], request_header()));
foreach ($options['headers'] as $key => $val) {
$new_key = implode('-', array_map('ucfirst', explode('-', $key)));
@@ -102,7 +98,7 @@ class Guzzle
unset($options['headers'][$key]);
}
$parse =parse_url($url);
$parse =parse_url((string) $url);
$domain = isset($parse['port']) ? $parse['host'] . ':' . $parse['port'] : $parse['host'];
$options['cookies'] = CookieJar::fromArray(cookie(), $domain);
@@ -116,16 +112,11 @@ class Guzzle
$options['form_params'] = $form_data;
}
$request = retry(3, function () use ($client, $request, $url, $options) {
return $client->request($request->getMethod(), $url, $options);
}, 50);
$request = retry(3, fn() => $client->request($request->getMethod(), $url, $options), 50);
$content = $request->getBody()->getContents();
$logger->info('proxy_success', compact('url', 'options'));
return my_json_decode($content);
} catch (\GuzzleHttp\Exception\GuzzleException $e) {
$logger->error('proxy_fail', compact('url', 'options', 'e'));
throw new \Exception("proxy exception {$e}", 500);
} catch (\Throwable $e) {
} catch (\GuzzleHttp\Exception\GuzzleException|\Throwable $e) {
$logger->error('proxy_fail', compact('url', 'options', 'e'));
throw new \Exception("proxy exception {$e}", 500);
}

View File

@@ -268,7 +268,7 @@ if(!function_exists('array_remove')) {
if(!function_exists('array_get_node')) {
function array_get_node($key, $arr = [])
{
$path = explode('.', $key);
$path = explode('.', (string) $key);
foreach($path as $key) {
$key = trim($key);
if(empty($arr) || !isset($arr[$key])) {

View File

@@ -183,11 +183,11 @@ if (!function_exists('encrypt')) {
{
$chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-=+";
//$nh = rand(0,64);
$nh = strlen($txt) % 65;
$nh = strlen((string) $txt) % 65;
$ch = $chars[$nh];
$mdKey = md5($key . $ch);
$mdKey = substr($mdKey, $nh % 8, $nh % 8 + 7);
$txt = base64_encode($txt);
$txt = base64_encode((string) $txt);
$tmp = '';
$i = 0;
$j = 0;
@@ -239,7 +239,7 @@ if (!function_exists('is_valid_date')) {
if (!function_exists('str_var_replace')) {
function str_var_replace($str, $data)
{
preg_match_all('/{([\s\S]*?)}/', $str, $match);
preg_match_all('/{([\s\S]*?)}/', (string) $str, $match);
$values = [];
$vars = [];
foreach (($match && $match[1] ? $match[1] : []) as $item) {
@@ -256,7 +256,7 @@ if (!function_exists('convert_memory')) {
{
$unit = ['b', 'kb', 'mb', 'gb', 'tb', 'pb'];
return @round($size / pow(1024, ($i = floor(log($size, 1024)))), 2) . ' ' . $unit[$i];
return @round($size / 1024 ** $i = floor(log($size, 1024)), 2) . ' ' . $unit[$i];
}
}
@@ -277,7 +277,7 @@ if (!function_exists('read_file')) {
if (!function_exists('get_extension')) {
function get_extension($file)
{
return substr(strrchr($file, '.'), 1);
return substr(strrchr((string) $file, '.'), 1);
}
}
@@ -363,7 +363,7 @@ if (!function_exists('http_build_url')) {
if (!function_exists('replace_url_query')) {
function replace_url_query($url, array $query)
{
$parse = parse_url($url);
$parse = parse_url((string) $url);
parse_str($parse['query'], $p);
$parse['query'] = urldecode(http_build_query(array_merge($p, $query)));
@@ -425,7 +425,7 @@ if (!function_exists('my_json_decode')) {
if (!$json) {
return $default;
}
$json = preg_replace('@//[^"]+?$@mui', '', $json);
$json = preg_replace('@//[^"]+?$@mui', '', (string) $json);
$json = preg_replace('@^\s*//.*?$@mui', '', $json);
$json = $json ? @json_decode($json, true) : $default;
if (is_null($json)) {
@@ -524,10 +524,10 @@ if (!function_exists('generate_random_str')) {
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$random_str = '';
for ($i = 0; $i < $length; $i++) {
$random_str .= $characters[rand(0, strlen($characters) - 1)];
$random_str .= $characters[random_int(0, strlen($characters) - 1)];
}
return trim($prefix) . $random_str;
return trim((string) $prefix) . $random_str;
}
}
@@ -537,7 +537,7 @@ if (!function_exists('get_dir_filename')) {
$handler = opendir($dir);
$files = [];
while (($filename = readdir($handler)) !== false) {
$filter_extension = $extension === '' ? true : strpos($filename, $extension);
$filter_extension = $extension === '' ? true : strpos($filename, (string) $extension);
if (!($filename !== "." && $filename !== ".." && $filter_extension)) {
continue;
}
@@ -556,34 +556,33 @@ if (!function_exists('get_dir_filename')) {
if (!function_exists('sp_encrypt')) {
function sp_encrypt($plaintext, $key)
{
$key = substr(sha1($key, true), 0, 16);
$key = substr(sha1((string) $key, true), 0, 16);
$iv = openssl_random_pseudo_bytes(16);
$ciphertext = openssl_encrypt($plaintext, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
$ciphertext_base64 = urlsafe_b64encode($iv . $ciphertext);
return $ciphertext_base64;
return urlsafe_b64encode($iv . $ciphertext);
}
}
if (!function_exists('sp_decrypt')) {
function sp_decrypt($ciphertext_base64, $key)
{
$key = substr(sha1($key, true), 0, 16);
$key = substr(sha1((string) $key, true), 0, 16);
$ciphertext_dec = urlsafe_b64decode($ciphertext_base64);
$iv_size = 16;
$iv_dec = substr($ciphertext_dec, 0, $iv_size);
$ciphertext_dec = substr($ciphertext_dec, $iv_size);
$plaintext_dec = openssl_decrypt($ciphertext_dec, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv_dec);
$iv_dec = substr((string) $ciphertext_dec, 0, $iv_size);
$ciphertext_dec = substr((string) $ciphertext_dec, $iv_size);
return $plaintext_dec;
return openssl_decrypt($ciphertext_dec, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv_dec);
}
}
if (!function_exists('urlsafe_b64encode')) {
function urlsafe_b64encode($string)
{
$data = base64_encode($string);
$data = str_replace([
$data = base64_encode((string) $string);
return str_replace([
'+',
'/',
// '=',
@@ -592,8 +591,6 @@ if (!function_exists('urlsafe_b64encode')) {
'_',
// '',
], $data);
return $data;
}
}
@@ -662,7 +659,6 @@ if (!function_exists('cookie')) {
/**
* 快捷方式,返回 request 相关 cookie
*
* @param string $key
*
* @return mixed
*/
@@ -684,7 +680,6 @@ if (!function_exists('request_header')) {
/**
* 快捷方式,返回 request 相关 header
*
* @param string $key
*
* @return mixed
*/
@@ -754,10 +749,10 @@ if (!function_exists('is_cli')) {
if (!function_exists('xml2array')) {
function xml2array($xml_string, $key = '')
{
if (strpos($xml_string, '<') === false) {
if (!str_contains((string) $xml_string, '<')) {
return [];
}
$array = (array)@simplexml_load_string($xml_string, 'SimpleXMLElement', LIBXML_NOCDATA);
$array = (array)@simplexml_load_string((string) $xml_string, 'SimpleXMLElement', LIBXML_NOCDATA);
if (!$key) {
return $array;
}
@@ -804,7 +799,7 @@ if (!function_exists('now')) {
if (!function_exists('is_json_str')) {
function is_json_str($string)
{
json_decode($string);
json_decode((string) $string);
return (json_last_error() == JSON_ERROR_NONE);
}
}

View File

@@ -85,6 +85,9 @@ if (!function_exists('move_local_file_to_filesystem')) {
if ($private) {
$filesystem->setVisibility($save_file_path, AdapterInterface::VISIBILITY_PRIVATE);
}
if (method_exists($filesystem, 'chmod')) {
$filesystem->chmod($save_file_path, 0644);
}
$meta = $filesystem->getMetadata($save_file_path);
switch (config("file.storage.{$bucket}.driver")) {
case \Hyperf\Filesystem\Adapter\LocalAdapterFactory::class:
@@ -120,17 +123,16 @@ if (!function_exists('filesystem_private_url')) {
}
$filesystem = make(FilesystemFactory::class)->get($bucket);
if (Str::startsWith($save_file_path, 'http')) {
$save_file_path = parse_url($save_file_path)['path'];
$save_file_path = parse_url((string) $save_file_path)['path'];
$parts = explode('/', $save_file_path);
unset($parts[0]);
$save_file_path = implode('/', $parts);
}
$save_file_path = preg_replace('@^oss/@', '', $save_file_path);
$save_file_path = preg_replace('@^oss/@', '', (string) $save_file_path);
try {
switch (config("file.storage.{$bucket}.driver")) {
case \Hyperf\Filesystem\Adapter\LocalAdapterFactory::class:
return config("file.storage.{$bucket}.cdn") . $save_file_path;
break;
case \Hyperf\Filesystem\Adapter\AliyunOssAdapterFactory::class;
$adapter = $filesystem->getAdapter();
if (method_exists($adapter, 'signUrl')) {
@@ -185,11 +187,7 @@ if (!function_exists('process_list_filter')) {
$processes = [];
}
if (is_array($ignore)) {
$processes = array_filter($processes, function ($item) use ($ignore) {
return !Str::startsWith($item, array_map(function ($each) {
return Str::replaceLast('*', '', $each);
}, $ignore));
});
$processes = array_filter($processes, fn($item) => !Str::startsWith($item, array_map(fn($each) => Str::replaceLast('*', '', $each), $ignore)));
}
}
if ($active = $rule['active'] ?? []) {

View File

@@ -37,9 +37,8 @@ class JWT
}
$base64_header = self::base64UrlEncode(json_encode(self::$header, JSON_UNESCAPED_UNICODE));
$base64_payload = self::base64UrlEncode(json_encode($payload, JSON_UNESCAPED_UNICODE));
$token = $base64_header . '.' . $base64_payload . '.' . self::signature($base64_header . '.' . $base64_payload, self::$key, self::$header['alg']);
return $token;
return $base64_header . '.' . $base64_payload . '.' . self::signature($base64_header . '.' . $base64_payload, self::$key, self::$header['alg']);
}
/**
@@ -116,9 +115,7 @@ class JWT
* HMACSHA256签名 https://jwt.io/ 中HMACSHA256签名实现
*
* @param string $input 为base64UrlEncode(header).".".base64UrlEncode(payload)
* @param string $key
* @param string $alg 算法方式
*
* @return mixed
*/
private static function signature(string $input, string $key, string $alg = 'HS256')

View File

@@ -15,7 +15,7 @@ class BootAppConfListener implements ListenerInterface
];
}
public function process(object $event)
public function process(object $event): void
{
Builder::macro('getAsArray', function () {
/** @var \Hyperf\Database\Query\Builder $this */

View File

@@ -15,10 +15,7 @@ class DbQueryExecutedListener implements ListenerInterface
];
}
/**
* @param object $event
*/
public function process(object $event)
public function process(object $event): void
{
if($event instanceof QueryExecuted) {
if(is_production()) {
@@ -73,9 +70,7 @@ class DbQueryExecutedListener implements ListenerInterface
/**
* 获取sql类型
*
* @param string $sql
*
* @return string
*/
protected function getSqlType(string $sql): string
{

View File

@@ -15,7 +15,7 @@ class FetchModeListener implements ListenerInterface
];
}
public function process(object $event)
public function process(object $event): void
{
if($event instanceof StatementPrepared) {
$event->statement->setFetchMode(PDO::FETCH_ASSOC);

View File

@@ -35,7 +35,7 @@ class BaseModel extends Model
*/
public function where2query($where, $query = null)
{
$query = $query ?? $this->newQuery();
$query ??= $this->newQuery();
if (!$where) {
return $query;
}
@@ -43,9 +43,7 @@ class BaseModel extends Model
unset($where['__logic']);
foreach ($where as $key => $item) {
if (is_numeric($key) && is_array($item)) {
$query->where(function ($query) use ($item) {
return $this->where2query($item, $query);
}, null, null, $boolean);
$query->where(fn($query) => $this->where2query($item, $query), null, null, $boolean);
continue;
}
if (!is_array($item)) {
@@ -105,20 +103,20 @@ class BaseModel extends Model
$where = [];
$kw = request()->input('kw');
if ($kw) {
if (preg_match_all('/^\d+$/', $kw)) {
if (preg_match_all('/^\d+$/', (string) $kw)) {
$where[$id_key] = $kw;
} elseif (preg_match_all('/^\d+?,/', $kw)) {
$where[$id_key] = explode(',', $kw);
} elseif (preg_match_all('/^\d+?,/', (string) $kw)) {
$where[$id_key] = explode(',', (string) $kw);
} else {
$where[$name_key] = ['like' => "%{$kw}%"];
}
}
$id = request()->input('id');
if ($id) {
if (preg_match_all('/^\d+$/', $id)) {
if (preg_match_all('/^\d+$/', (string) $id)) {
$where[$id_key] = $id;
} elseif (preg_match_all('/^\d+?,/', $id)) {
$where[$id_key] = explode(',', $id);
} elseif (preg_match_all('/^\d+?,/', (string) $id)) {
$where[$id_key] = explode(',', (string) $id);
}
}
if (!$default_query && !$where) {
@@ -126,7 +124,7 @@ class BaseModel extends Model
}
$where['__logic'] = $logic;
$where = array_merge($where, $extra_where);
$attr['limit'] = $attr['limit'] ?? 100;
$attr['limit'] ??= 100;
return $this->list($where, $attr)->toArray();
}

View File

@@ -276,7 +276,7 @@ class EsBaseModel
];
try {
return $this->client->get($params);
} catch (\Exception $e) {
} catch (\Exception) {
return false;
}
}
@@ -491,7 +491,7 @@ class EsBaseModel
foreach ($val_tile as $operator => $each) {
if (isset($this->operator_map[$operator])) {
$mapping_date_format = $this->mapping['properties'][$key]['format'] ?? null;
$query['bool']['filter']['bool']['must'][$index]['range'][$key][$this->operator_map[$operator]] = $is_time_field ? date($mapping_date_format ? $this->transDateFormat($mapping_date_format) : "Y-m-d\TH:i:s", strtotime($each)) : $each;
$query['bool']['filter']['bool']['must'][$index]['range'][$key][$this->operator_map[$operator]] = $is_time_field ? date($mapping_date_format ? $this->transDateFormat($mapping_date_format) : "Y-m-d\TH:i:s", strtotime((string) $each)) : $each;
}
}
continue;
@@ -500,9 +500,7 @@ class EsBaseModel
if (is_array($val)) {
$query['bool']['filter']['bool']['must'][] = [
'terms' => [
$key => array_map(function ($each) {
return is_numeric($each) ? $each * 1 : $each;
}, $val),
$key => array_map(fn($each) => is_numeric($each) ? $each * 1 : $each, $val),
],
];
continue;

View File

@@ -12,17 +12,14 @@ use HyperfAdmin\BaseUtils\Log;
*/
class Redis
{
private $pool;
/**
* @var \Redis
*/
private $redis;
public function __construct($pool = 'default')
public function __construct(private $pool = 'default')
{
$this->pool = $pool;
$this->redis = container(RedisFactory::class)->get($pool);
$this->redis = container(RedisFactory::class)->get($this->pool);
}
public static function conn($pool = 'default')
@@ -73,8 +70,6 @@ class Redis
}
/**
* @param string $name
* @param int $expired
* @param mixed $callable
*
* @return array|null
@@ -84,7 +79,7 @@ class Redis
if ($this->redis->exists($name)) {
Log::get('redis')->info(sprintf('get %s from cache', $name));
return json_decode($this->redis->get($name), true);
return json_decode((string) $this->redis->get($name), true);
}
$data = call($callable);
if ($data) {

View File

@@ -16,29 +16,29 @@ class RedisArray implements \ArrayAccess
$this->redis = container(RedisFactory::class)->get($cluster);
}
public function offsetExists($offset)
public function offsetExists($offset): bool
{
return $this->redis->hExists($this->name, (string)$offset);
}
public function offsetGet($offset)
public function offsetGet($offset): mixed
{
$val = $this->redis->hGet($this->name, (string)$offset);
if(is_json_str($val)) {
return json_decode($val, true);
return json_decode((string) $val, true);
}
return $val;
}
public function offsetSet($offset, $value)
public function offsetSet($offset, $value): void
{
return $this->redis->hSet($this->name, (string)$offset, is_array($value) ? json_encode($value) : $value);
$this->redis->hSet($this->name, (string)$offset, is_array($value) ? json_encode($value) : $value);
}
public function offsetUnset($offset)
public function offsetUnset($offset): void
{
return $this->redis->hDel($this->name, (string)$offset);
$this->redis->hDel($this->name, (string)$offset);
}
public function __destruct()

View File

@@ -5,10 +5,6 @@ use Hyperf\Redis\RedisFactory;
class RedisQueue
{
protected $cluster = 'default';
protected $queue_name = 'redis_queue_default';
protected $queue_wait_ack_suffix = 'wait_ack';
protected $wait_timeout = 10;
@@ -20,11 +16,9 @@ class RedisQueue
*/
protected $redis;
public function __construct($queue_name, $cluster = 'default')
public function __construct(protected $queue_name, protected $cluster = 'default')
{
$this->queue_name = $queue_name;
$this->cluster = $cluster;
$this->redis = container(RedisFactory::class)->get($cluster);
$this->redis = container(RedisFactory::class)->get($this->cluster);
}
public function length($name = '')
@@ -65,8 +59,8 @@ class RedisQueue
}
$msg = $this->getOne($this->queue_name, $filter);
if($msg) {
$msg_id = md5($msg);
$data = json_decode($msg, true);
$msg_id = md5((string) $msg);
$data = json_decode((string) $msg, true);
$ret = array_merge($data, ['_queue_msg_id' => $msg_id]);
$this->redis->rPush($this->getAckQueueName(), json_encode(array_merge($ret, ['_time' => time()])));
@@ -87,7 +81,7 @@ class RedisQueue
}
foreach(range(0, $len - 1) as $index) {
$ele = $this->redis->lIndex($queue_name, $index);
$ele_array = json_decode($ele, true);
$ele_array = json_decode((string) $ele, true);
if(array_intersect($ele_array, $filter)) {
$this->redis->lRem($queue_name, $ele, 1);
@@ -132,7 +126,7 @@ class RedisQueue
{
$list = $this->redis->lRange($this->getAckQueueName(), 0, -1);
foreach($list as &$item) {
$item = json_decode($item, true);
$item = json_decode((string) $item, true);
}
unset($item);

View File

@@ -77,9 +77,7 @@ abstract class AbstractController extends Controller
*/
public function info()
{
$tableHeader = array_values(array_filter($this->getListHeader(), function ($item) {
return !($item['hidden'] ?? false);
}));
$tableHeader = array_values(array_filter($this->getListHeader(), fn($item) => !($item['hidden'] ?? false)));
$filter = $this->getListFilters();
$actions = $this->options['table']['rowActions'] ?? [];
$actions = $this->buttonConfigConvert($actions);
@@ -125,7 +123,7 @@ abstract class AbstractController extends Controller
foreach ($config as $key => $item) {
$buttons[$key]['text'] = $item['text'] ?? '';
$buttons[$key]['type'] = isset($item['target']) ? $item['type'] ?? 'jump' : (isset($item['rules']) ? 'form' : (isset($item['api']) ? 'api' : 'jump'));
$buttons[$key]['target'] = isset($item['target']) ? $item['target'] : (isset($item['api']) ? $item['api'] : ($item['action'] ?? ''));
$buttons[$key]['target'] = $item['target'] ?? $item['api'] ?? $item['action'] ?? '';
$buttons[$key]['props'] = isset($item['target']) ? $item['props'] ?? [] : [
'icon' => $item['icon'] ?? '',
'circle' => $item['circle'] ?? false,
@@ -148,7 +146,10 @@ abstract class AbstractController extends Controller
if (isset($item[0])) {
$buttons[$key] = $this->buttonConfigConvert($item);
}
}
if (isset($item['method'])) {
$buttons[$key]['method'] = $item['method'];
}
}
return $buttons;
}
@@ -157,11 +158,7 @@ abstract class AbstractController extends Controller
$page = $this->request->input('_page', 1);
$size = $this->request->input('_size', 20);
$table_options = $this->getListHeader();
$columns = array_unique(array_values(array_map(function ($item) {
return explode('.', $item)[0];
}, array_column(array_filter($table_options, function ($each) {
return isset($each['virtual_field']) ? !$each['virtual_field'] : true;
}), 'field'))));
$columns = array_unique(array_values(array_map(fn($item) => explode('.', (string) $item)[0], array_column(array_filter($table_options, fn($each) => isset($each['virtual_field']) ? !$each['virtual_field'] : true), 'field'))));
$filter_options = $this->getListFilters();
$filters = [];
if (!empty($filter_options)) {
@@ -181,27 +178,21 @@ abstract class AbstractController extends Controller
}
$conditions = $this->options['where'] ?? [];
foreach ($filters as $field => $value) {
switch ($filter_options[$field]['search_type'] ?? '') {
case 'between':
$conditions[$field] = ['between' => $value];
break;
case 'full_like':
$conditions[$field] = ['like' => "%{$value}%"];
break;
case 'suffix_like':
$conditions[$field] = ['like' => "{$value}%"];
break;
case 'prefix_like':
$conditions[$field] = ['like' => "%{$value}"];
break;
default:
$conditions[$field] = $value;
break;
}
$conditions[$field] = match ($filter_options[$field]['search_type'] ?? '') {
'between' => ['between' => $value],
'full_like' => ['like' => "%{$value}%"],
'suffix_like' => ['like' => "{$value}%"],
'prefix_like' => ['like' => "%{$value}"],
default => $value,
};
}
$order_by = $this->options['order_by'] ?? '';
$group_by = $this->options['group_by'] ?? '';
if ($sortColumn = $this->request->input('_sort_column') && $sortType = $this->request->input('_sort_type')) {
$order_by = $sortColumn . ' ' . $sortType;
}
if (empty($conditions) && !($this->options['defaultList'] ?? true)) {
return compact('page', 'size', 'conditions', 'order_by', 'columns', 'table_options');
return compact('page', 'size', 'conditions', 'order_by', 'columns', 'table_options', 'group_by');
}
if (method_exists($this, 'beforeListQuery')) {
$hook_params = get_class_method_params_name($this, 'beforeListQuery');
@@ -209,9 +200,11 @@ abstract class AbstractController extends Controller
$this->beforeListQuery($conditions, $order_by);
} elseif (count($hook_params) === 1) {
$this->beforeListQuery($conditions);
} elseif (count($hook_params) === 4) {
$this->beforeListQuery($conditions, $order_by, $group_by, $columns);
}
}
return compact('page', 'size', 'conditions', 'order_by', 'columns', 'table_options');
return compact('page', 'size', 'conditions', 'order_by', 'columns', 'table_options', 'group_by');
}
/**
@@ -226,13 +219,25 @@ abstract class AbstractController extends Controller
$order_by,
$columns,
$tableOptions,
$group_by
] = array_values($this->makeWhere());
$entity = $this->getEntity();
$count = $entity->count($conditions);
if ($group_by) {
$entity->getModel()->groupBy($group_by);
$count_query = clone $entity;
$count = $count_query->getModel()->select(
gettype($group_by) == 'array' ? $group_by[0] : $group_by,
DB::raw('count(*) as total')
)->get()->count();
} else {
$count = $entity->count($conditions);
}
$list = [];
if ($count) {
$attr['select'] = $columns;
$order_by && $attr['order_by'] = $order_by;
$group_by && $attr['group_by'] = $group_by;
$list = $entity->list($conditions, $attr, $page, $size);
}
$list = $this->listFilter($list, $tableOptions);
@@ -301,7 +306,7 @@ abstract class AbstractController extends Controller
$ret = Db::connection($pool)->table("{$db}.{$table}")->whereIn($foreign_key, $where)->get($columns)->toArray();
array_change_v2k($ret, $foreign_key);
foreach ($list as &$item) {
$append = isset($ret[$item[$local_key]]) ? $ret[$item[$local_key]] : $default;
$append = $ret[$item[$local_key]] ?? $default;
unset($append[$foreign_key]);
$item = array_merge($item, $append);
}
@@ -331,7 +336,7 @@ abstract class AbstractController extends Controller
$ret = Db::connection($pool)->table("{$db}.{$table}")->whereIn($foreign_key, $where)->get($columns)->toArray();
$ret = array_group_by($ret, $foreign_key);
foreach ($list as &$item) {
$group = isset($ret[$item[$local_key]]) ? $ret[$item[$local_key]] : $default;
$group = $ret[$item[$local_key]] ?? $default;
$append = [];
foreach (array_keys($default) as $field) {
$append[$field] = array_values(array_unique(array_filter(array_column($group, $field))));
@@ -345,7 +350,7 @@ abstract class AbstractController extends Controller
public function explodeHasStr($has_str)
{
$check = preg_match('/([a-zA-Z_0-9]+\.)?([a-zA-Z_0-9]+)\.([a-zA-Z_0-9]+):([a-zA-Z_0-9]+->)?([a-zA-Z_,0-9 ]+)/', $has_str, $match);
$check = preg_match('/([a-zA-Z_0-9]+\.)?([a-zA-Z_0-9]+)\.([a-zA-Z_0-9]+):([a-zA-Z_0-9]+->)?([a-zA-Z_,0-9 ]+)/', (string) $has_str, $match);
if ($check === 0) {
return false;
}
@@ -356,11 +361,9 @@ abstract class AbstractController extends Controller
$table,
$local_key,
$foreign_key,
] = array_map(function ($item) {
return str_replace(['.', '->'], '', $item);
}, $match);
$pool = $pool ? $pool : 'default';
$local_key = $local_key ? $local_key : 'id';
] = array_map(fn($item) => str_replace(['.', '->'], '', $item), $match);
$pool = $pool ?: 'default';
$local_key = $local_key ?: 'id';
$columns = explode(',', $foreign_key);
if (!$columns) {
return false;
@@ -368,7 +371,7 @@ abstract class AbstractController extends Controller
$foreign_key = $columns[0];
$default = [];
foreach ($columns as $each) {
$default[trim(preg_replace('/[\w ]+as +/i', '', trim($each)))] = null;
$default[trim((string) preg_replace('/[\w ]+as +/i', '', trim($each)))] = null;
}
return array_values(compact('pool', 'db', 'table', 'local_key', 'foreign_key', 'columns', 'default'));
}
@@ -376,15 +379,12 @@ abstract class AbstractController extends Controller
public function getTreeNodeChilds($id)
{
$tableOptions = $this->getListHeader();
$columns = array_unique(array_values(array_map(function ($item) {
return explode('.', $item)[0];
}, array_column(array_filter($tableOptions, function ($each) {
return isset($each['virtual_field']) ? !$each['virtual_field'] : true;
}), 'field'))));
$columns = array_unique(array_values(array_map(fn($item) => explode('.', (string) $item)[0], array_column(array_filter($tableOptions, fn($each) => isset($each['virtual_field']) ? !$each['virtual_field'] : true), 'field'))));
$order_by = $this->options['order_by'] ?? '';
$attr['select'] = $columns;
$order_by && $attr['order_by'] = $order_by;
$childs = $this->getEntity()->list(['pid' => $id], $attr);
$parent_key = $this->options['table']['tree']['pid'] ?? 'pid';
$childs = $this->getEntity()->list([$parent_key => $id], $attr);
foreach ($tableOptions as $item) {
if (!isset($item['render'])) {
continue;
@@ -655,13 +655,9 @@ abstract class AbstractController extends Controller
public function getFields()
{
$form = $this->formOptionsConvert();
$form = array_filter($form, function ($item) {
return !($item['virtual_field'] ?? false);
});
$form = array_filter($form, fn($item) => !($item['virtual_field'] ?? false));
$fields = array_column($form, 'field');
$fields = array_map(function ($item) {
return explode('.', $item)[0];
}, $fields);
$fields = array_map(fn($item) => explode('.', (string) $item)[0], $fields);
return array_unique($fields);
}
@@ -678,7 +674,7 @@ abstract class AbstractController extends Controller
$filter_options = [];
foreach ($this->options['filter'] as $key => $item) {
$filter_option_key = is_array($item) ? $key : str_replace('%', '', $item);
$field_extra = explode('|', $filter_option_key);
$field_extra = explode('|', (string) $filter_option_key);
$field = $field_extra[0];
$form_option = [];
if (isset($form_fields[$field]) && isset($form_options[$form_fields[$field]])) {
@@ -704,7 +700,7 @@ abstract class AbstractController extends Controller
if (Str::startsWith($item, '%') !== false && Str::endsWith($item, '%') !== false) {
$search_type = 'full_like';
}
if (strpos(($filter_options[$filter_option_key]['type'] ?? ''), 'range') !== false) {
if (str_contains(($filter_options[$filter_option_key]['type'] ?? ''), 'range')) {
$search_type = 'between';
}
$filter_options[$filter_option_key]['search_type'] = $search_type;
@@ -781,7 +777,7 @@ abstract class AbstractController extends Controller
}
$form = [];
foreach ($formOption as $key => $val) {
$field_extra = explode('|', $key);
$field_extra = explode('|', (string) $key);
$field = $field_extra[0];
$title = $field_extra[1] ?? $field_extra[0];
$biz = [];
@@ -795,7 +791,7 @@ abstract class AbstractController extends Controller
continue;
}
$rule = $biz['rule'] ?? '';
$rules = is_array($rule) ? $rule : explode('|', $rule);
$rules = is_array($rule) ? $rule : explode('|', (string) $rule);
$_form = [
'title' => $title,
'field' => $field,
@@ -805,18 +801,19 @@ abstract class AbstractController extends Controller
switch ($_form['type']) {
case 'checkbox':
case 'cascader':
$_form['value'] = array_map('intval', is_array($_form['value']) ? $_form['value'] : (array)$_form['value']);
// $_form['value'] = array_map('intval', is_array($_form['value']) ? $_form['value'] : (array)$_form['value']);
$_form['value'] = (array)$_form['value'];
break;
case 'image':
$biz['props']['limit'] = $biz['props']['limit'] ?? 1;
$biz['props']['limit'] ??= 1;
break;
case 'select':
if (isset($biz['props']['selectApi']) && $_form['value']) {
$biz['options'] = select_options($biz['props']['selectApi'], is_array($_form['value']) ? $_form['value'] : explode(',', $_form['value']));
$biz['options'] = select_options($biz['props']['selectApi'], is_array($_form['value']) ? $_form['value'] : explode(',', (string) $_form['value']));
}
// fixme sub-form value 不好取, 先默认查一次
if (isset($biz['props']['selectApi']) && $depth) {
$biz['options'] = select_options($biz['props']['selectApi'], is_array($_form['value']) ? $_form['value'] : explode(',', $_form['value']));
$biz['options'] = select_options($biz['props']['selectApi'], is_array($_form['value']) ? $_form['value'] : explode(',', (string) $_form['value']));
}
break;
default:
@@ -893,7 +890,6 @@ abstract class AbstractController extends Controller
])
&& isset($_form['options'])) {
$_form['type'] = 'select';
unset($_form['value']);
$options_labels = array_column($_form['options'], 'label');
if (!isset($_form['props']['selectApi']) && !in_array('全部', $options_labels)) {
array_unshift($_form['options'], [
@@ -921,7 +917,7 @@ abstract class AbstractController extends Controller
*/
public function getFormRules($options = null)
{
$formOptions = $options ? $options : ($this->options['form'] ?? []);
$formOptions = $options ?: $this->options['form'] ?? [];
$rules = [];
foreach ($formOptions as $key => $val) {
if (is_array($val) && ($val['form'] ?? true) === false) {
@@ -956,7 +952,7 @@ abstract class AbstractController extends Controller
$data_source = $this->request->all();
$pk_val = $data_source[$pk] ?? null;
foreach ($rules as &$val) {
$rule_parts = is_array($val) ? $val : explode('|', $val);
$rule_parts = is_array($val) ? $val : explode('|', (string) $val);
foreach ($rule_parts as &$rule) {
if ($pk_val && is_string($rule) && Str::startsWith($rule, 'unique')) {
// unique rule without itself in update
@@ -1066,7 +1062,7 @@ abstract class AbstractController extends Controller
$up = $this->request->all();
$up_fields = array_keys($up);
foreach ($rules as $key => $val) {
$field_extra = explode('|', $key);
$field_extra = explode('|', (string) $key);
$field = $field_extra[0];
if (!in_array($field, $up_fields)) {
unset($rules[$key]);
@@ -1095,7 +1091,6 @@ abstract class AbstractController extends Controller
*
* @param string $message
* @param int $code
* @param \Throwable $previous
*
* @return void
* @throws \Exception
@@ -1119,7 +1114,7 @@ abstract class AbstractController extends Controller
{
$validates = [];
foreach ($rules as $item) {
$parts = explode(':', $item);
$parts = explode(':', (string) $item);
$rule = array_shift($parts);
switch ($rule) {
case 'required':
@@ -1210,9 +1205,7 @@ abstract class AbstractController extends Controller
$filters = [];
if ($filter_options = $this->getListFilters()) {
array_change_v2k($filter_options, 'field');
$filters = array_filter($this->request->inputs(array_keys($filter_options)), function ($item) {
return !in_array($item, [null, '']);
});
$filters = array_filter($this->request->inputs(array_keys($filter_options)), fn($item) => !in_array($item, [null, '']));
}
$list = [];
if (is_array($notices)) {
@@ -1267,7 +1260,7 @@ abstract class AbstractController extends Controller
{
$const = [];
if ($model = $this->model_class ?? '') {
$const = constant($model . '::' . strtoupper($field)) ?? [];
$const = constant($model . '::' . strtoupper((string) $field)) ?? [];
}
$options = [];
foreach ($const as $k => $v) {
@@ -1283,8 +1276,6 @@ abstract class AbstractController extends Controller
/**
* 新版本检查
*
* @param int $id
* @param int $last_ver_id
*
* @return array
*/

View File

@@ -82,7 +82,7 @@ abstract class Controller
protected function getCalledSource($get_arr = false)
{
$uri = $this->getRequestUri();
$parts = array_filter(explode('/', $uri));
$parts = array_filter(explode('/', (string) $uri));
if ($get_arr) {
return array_values($parts);
}
@@ -98,9 +98,7 @@ abstract class Controller
/**
* 返回成功的请求
*
* @param array $data
* @param string $message
*
* @return array
*/
public function success(array $data = [], $message = '操作成功')
@@ -115,8 +113,6 @@ abstract class Controller
}
/**
* @param int $code
* @param string|null $message
*
* @return array
*/

View File

@@ -47,26 +47,21 @@ class OssAdapter extends AbstractAdapter
$isCName = false;
$token = null;
$this->supports = new Supports();
try {
$this->bucket = $config['bucket'];
empty($config['endpoint']) ? null : $this->endpoint = $config['endpoint'];
empty($config['timeout']) ? $config['timeout'] = 3600 : null;
empty($config['connectTimeout']) ? $config['connectTimeout'] = 10 : null;
if (!empty($config['isCName'])) {
$isCName = true;
}
if (!empty($config['token'])) {
$token = $config['token'];
}
$this->oss = new OssClient(
$config['accessId'], $config['accessSecret'], $this->endpoint, $isCName, $token
);
$this->oss->setTimeout($config['timeout']);
$this->oss->setConnectTimeout($config['connectTimeout']);
} catch (Exception $e) {
throw $e;
$this->bucket = $config['bucket'];
empty($config['endpoint']) ? null : $this->endpoint = $config['endpoint'];
empty($config['timeout']) ? $config['timeout'] = 3600 : null;
empty($config['connectTimeout']) ? $config['connectTimeout'] = 10 : null;
if (!empty($config['isCName'])) {
$isCName = true;
}
if (!empty($config['token'])) {
$token = $config['token'];
}
$this->oss = new OssClient(
$config['accessId'], $config['accessSecret'], $this->endpoint, $isCName, $token
);
$this->oss->setTimeout($config['timeout']);
$this->oss->setConnectTimeout($config['connectTimeout']);
}
/**
@@ -214,7 +209,6 @@ class OssAdapter extends AbstractAdapter
* Create a directory.
*
* @param string $dirname directory name
* @param Config $config
*
* @return array|false
*/
@@ -422,7 +416,6 @@ class OssAdapter extends AbstractAdapter
/**
* Get OSS Options
* @param Config $config
* @return array
*/
private function getOssOptions(Config $config)

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