This commit is contained in:
34
app/Amqp/Consumer/AliSlsConsumer.php
Normal file
34
app/Amqp/Consumer/AliSlsConsumer.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Amqp\Consumer;
|
||||
|
||||
use App\Service\AliLogsSignService;
|
||||
use Hyperf\Amqp\Result;
|
||||
use Hyperf\Amqp\Annotation\Consumer;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午7:18
|
||||
* Description: 阿里日志消费.
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
#[Consumer(exchange: 'wh_ali_sls', routingKey: 'wh_ali_sls_key', queue: 'wh_ali_sls_queue', name: "AliSlsConsumer", nums: 5)]
|
||||
class AliSlsConsumer extends BaseConsumer
|
||||
{
|
||||
#[Inject]
|
||||
protected AliLogsSignService $aliLogsSignService;
|
||||
public function handle($data): Result
|
||||
{
|
||||
$this->aliLogsSignService->putWebTracking($data);
|
||||
|
||||
return Result::ACK;
|
||||
}
|
||||
}
|
89
app/Amqp/Consumer/BaseConsumer.php
Normal file
89
app/Amqp/Consumer/BaseConsumer.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午6:37
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Amqp\Consumer;
|
||||
|
||||
use Hyperf\Amqp\Message\ConsumerMessage;
|
||||
use App\Context\QueueContext;
|
||||
use Hyperf\Amqp\Result;
|
||||
use Hyperf\DbConnection\Db;
|
||||
use PhpAmqpLib\Exception\AMQPChannelClosedException;
|
||||
use PhpAmqpLib\Message\AMQPMessage;
|
||||
use App\Log\Log;
|
||||
use Exception;
|
||||
use PhpAmqpLib\Exception\AMQPConnectionClosedException;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午7:18
|
||||
* Description: 消费者抽象基类.
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
abstract class BaseConsumer extends ConsumerMessage
|
||||
{
|
||||
public function consumeMessage($data, AMQPMessage $message): Result
|
||||
{
|
||||
$consumerClass = get_class($this);
|
||||
|
||||
// 设置用户信息上下文信息
|
||||
if (!empty($data['user'])) {
|
||||
QueueContext::setUser($data['user']);
|
||||
}
|
||||
if (!empty($company = $data['company'])) {
|
||||
QueueContext::setCompanyInfo($company);
|
||||
}
|
||||
|
||||
Db::beginTransaction();
|
||||
|
||||
try {
|
||||
$handle = $this->handle($data);
|
||||
|
||||
Db::commit();
|
||||
|
||||
return $handle;
|
||||
} catch (AMQPChannelClosedException|AMQPConnectionClosedException $e) {
|
||||
Log::get('queue', 'queue')->error("AMQP通道关闭异常 ($consumerClass): " . $e->getMessage(), [
|
||||
'data' => $data,
|
||||
'exception' => $e,
|
||||
]);
|
||||
|
||||
Db::rollBack();
|
||||
|
||||
// 可选:重连逻辑 or 丢弃
|
||||
return Result::ACK; // 或 NACK
|
||||
} catch (Exception $e) {
|
||||
Log::get('queue', 'queue')->error("AMQP消费者异常 ($consumerClass): " . $e->getMessage(), [
|
||||
'data' => $data,
|
||||
'exception' => $e,
|
||||
]);
|
||||
|
||||
Db::rollBack();
|
||||
|
||||
return Result::ACK;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 子类实现的核心处理逻辑
|
||||
* @param mixed $data
|
||||
* @return Result
|
||||
*/
|
||||
abstract protected function handle(mixed $data): Result;
|
||||
}
|
19
app/Amqp/Consumer/UserImportConsumer.php
Normal file
19
app/Amqp/Consumer/UserImportConsumer.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Amqp\Consumer;
|
||||
|
||||
use Hyperf\Amqp\Result;
|
||||
use Hyperf\Amqp\Annotation\Consumer;
|
||||
use Hyperf\Amqp\Message\ConsumerMessage;
|
||||
use PhpAmqpLib\Message\AMQPMessage;
|
||||
|
||||
#[Consumer(exchange: 'wh_user_import', routingKey: 'wh_user_import_key', queue: 'wh_user_import_queue', name: "UserImportConsumer", nums: 5)]
|
||||
class UserImportConsumer extends BaseConsumer
|
||||
{
|
||||
public function handle($data): Result
|
||||
{
|
||||
return Result::ACK;
|
||||
}
|
||||
}
|
12
app/Amqp/Producer/AliSlsProducer.php
Normal file
12
app/Amqp/Producer/AliSlsProducer.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Amqp\Producer;
|
||||
|
||||
use Hyperf\Amqp\Annotation\Producer;
|
||||
|
||||
#[Producer(exchange: 'wh_ali_sls', routingKey: 'wh_ali_sls_key')]
|
||||
class AliSlsProducer extends BaseProducer
|
||||
{
|
||||
}
|
44
app/Amqp/Producer/BaseProducer.php
Normal file
44
app/Amqp/Producer/BaseProducer.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/6
|
||||
* Time: 上午9:53
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Amqp\Producer;
|
||||
|
||||
use App\Context\UserContext;
|
||||
use Hyperf\Amqp\Message\ProducerMessage;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/6
|
||||
* Time: 上午9:57
|
||||
* Description: amqp生产者抽象基类.
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
abstract class BaseProducer extends ProducerMessage
|
||||
{
|
||||
public function __construct(array $data)
|
||||
{
|
||||
// 设置用户信息上下文信息
|
||||
if (UserContext::hasCurrentUser()) {
|
||||
$data['user'] = UserContext::getCurrentUser();
|
||||
}
|
||||
|
||||
$this->payload = $data;
|
||||
$this->properties['delivery_mode'] = 2; // 消息持久化
|
||||
}
|
||||
}
|
13
app/Amqp/Producer/UserImportProducer.php
Normal file
13
app/Amqp/Producer/UserImportProducer.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Amqp\Producer;
|
||||
|
||||
use Hyperf\Amqp\Annotation\Producer;
|
||||
use Hyperf\Amqp\Message\ProducerMessage;
|
||||
|
||||
#[Producer(exchange: 'wh_user_import', routingKey: 'wh_user_import_key')]
|
||||
class UserImportProducer extends BaseProducer
|
||||
{
|
||||
}
|
56
app/Aspect/TransactionalAspect.php
Normal file
56
app/Aspect/TransactionalAspect.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午11:50
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Aspect;
|
||||
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Hyperf\Di\Aop\AbstractAspect;
|
||||
use Hyperf\Di\Annotation\Aspect;
|
||||
use Hyperf\Di\Aop\ProceedingJoinPoint;
|
||||
use Throwable;
|
||||
|
||||
#[Aspect]
|
||||
class TransactionalAspect extends AbstractAspect
|
||||
{
|
||||
public array $classes = [
|
||||
'App\Controller\*',
|
||||
];
|
||||
|
||||
public array $annotations = [
|
||||
];
|
||||
|
||||
/**
|
||||
* 处理方法执行过程,通过AOP的方式对方法执行进行事务控制。
|
||||
*
|
||||
* @param ProceedingJoinPoint $proceedingJoinPoint AOP中的连接点对象,代表正在执行的方法
|
||||
* @return mixed 返回执行方法的结果
|
||||
* @throws Throwable 如果执行过程中发生异常,则抛出
|
||||
*/
|
||||
public function process(ProceedingJoinPoint $proceedingJoinPoint): mixed
|
||||
{
|
||||
Db::beginTransaction(); // 开始事务
|
||||
try {
|
||||
$result = $proceedingJoinPoint->process(); // 执行目标方法
|
||||
|
||||
Db::commit(); // 方法执行成功,提交事务
|
||||
|
||||
return $result;
|
||||
} catch (Throwable $e) {
|
||||
Db::rollback(); // 发生异常,回滚事务
|
||||
|
||||
throw $e; // 重新抛出捕获的异常
|
||||
}
|
||||
}
|
||||
}
|
38
app/Constants/AbsConst.php
Normal file
38
app/Constants/AbsConst.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2024/3/6
|
||||
* Time: 11:08
|
||||
* Description:
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants;
|
||||
|
||||
use Hyperf\Constants\AbstractConstants;
|
||||
use Hyperf\Constants\ConstantsCollector;
|
||||
|
||||
class AbsConst extends AbstractConstants
|
||||
{
|
||||
/**
|
||||
* 获取所有常量的键值对列表.
|
||||
*/
|
||||
public static function getConstantsList(): array
|
||||
{
|
||||
$class = static::class;
|
||||
$constants = ConstantsCollector::list();
|
||||
$list = [];
|
||||
if (isset($constants[$class])) {
|
||||
foreach ($constants[$class] as $name => $value) {
|
||||
// 确保获取的是当前常量对应的消息
|
||||
$message = $value['message'] ?? '';
|
||||
$list[] = [
|
||||
'label' => $message,
|
||||
'value' => $name,
|
||||
];
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
}
|
27
app/Constants/ActiveStatusConst.php
Normal file
27
app/Constants/ActiveStatusConst.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2024/3/15
|
||||
* Time: 11:13
|
||||
* Description:
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants;
|
||||
|
||||
use Hyperf\Constants\Annotation\Constants;
|
||||
|
||||
#[Constants]
|
||||
class ActiveStatusConst extends AbsConst
|
||||
{
|
||||
/**
|
||||
* @Message("禁用")
|
||||
*/
|
||||
public const int ACTIVE_DISABLE = 0;
|
||||
|
||||
/**
|
||||
* @Message("启用")
|
||||
*/
|
||||
public const int ACTIVE_ENABLE = 1;
|
||||
}
|
21
app/Constants/CompanyTypeConst.php
Normal file
21
app/Constants/CompanyTypeConst.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants;
|
||||
|
||||
use Hyperf\Constants\Annotation\Constants;
|
||||
|
||||
#[Constants]
|
||||
class CompanyTypeConst extends AbsConst
|
||||
{
|
||||
/**
|
||||
* @Message("开证公司")
|
||||
*/
|
||||
public const int ISSUING_COMPANY = 1;
|
||||
|
||||
/**
|
||||
* @Message("收货公司")
|
||||
*/
|
||||
public const int RECEIVING_COMPANY = 2;
|
||||
}
|
22
app/Constants/CustomerTypeConst.php
Normal file
22
app/Constants/CustomerTypeConst.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants;
|
||||
|
||||
use Hyperf\Constants\AbstractConstants;
|
||||
use Hyperf\Constants\Annotation\Constants;
|
||||
|
||||
#[Constants]
|
||||
class CustomerTypeConst extends AbstractConstants
|
||||
{
|
||||
/**
|
||||
* @Message("代理")
|
||||
*/
|
||||
public const int AGENT = 1;
|
||||
|
||||
/**
|
||||
* @Message("非代理")
|
||||
*/
|
||||
public const int NON_AGENT = 2;
|
||||
}
|
26
app/Constants/RoleTypeConst.php
Normal file
26
app/Constants/RoleTypeConst.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants;
|
||||
|
||||
use Hyperf\Constants\Annotation\Constants;
|
||||
|
||||
#[Constants]
|
||||
class RoleTypeConst extends AbsConst
|
||||
{
|
||||
/**
|
||||
* @Message("普通用户")
|
||||
*/
|
||||
const int NORMAL_USER = 0;
|
||||
|
||||
/**
|
||||
* @Message("管理员")
|
||||
*/
|
||||
const int ADMIN = 1;
|
||||
|
||||
/**
|
||||
* @Message("超级管理员")
|
||||
*/
|
||||
const int SUPER_ADMIN = 2;
|
||||
}
|
21
app/Constants/SourceConst.php
Executable file
21
app/Constants/SourceConst.php
Executable file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants;
|
||||
|
||||
use Hyperf\Constants\Annotation\Constants;
|
||||
|
||||
#[Constants]
|
||||
class SourceConst extends AbsConst
|
||||
{
|
||||
/**
|
||||
* @Message("管理后台")
|
||||
*/
|
||||
public const int SOURCE_PC = 1;
|
||||
|
||||
/**
|
||||
* @Message("手机端")
|
||||
*/
|
||||
public const int SOURCE_MOBILE = 2;
|
||||
}
|
21
app/Constants/UserTypeConst.php
Normal file
21
app/Constants/UserTypeConst.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Constants;
|
||||
|
||||
use Hyperf\Constants\Annotation\Constants;
|
||||
|
||||
#[Constants]
|
||||
class UserTypeConst extends AbsConst
|
||||
{
|
||||
/**
|
||||
* @Message("系统用户!")
|
||||
*/
|
||||
public const int SYSTEM_USER = 1;
|
||||
|
||||
/**
|
||||
* @Message("注册用户!")
|
||||
*/
|
||||
public const int REGISTER_USER = 2;
|
||||
}
|
70
app/Context/QueueContext.php
Normal file
70
app/Context/QueueContext.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午6:40
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Context;
|
||||
|
||||
use Hyperf\Context\Context;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午7:19
|
||||
* Description: 队列上下文
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class QueueContext
|
||||
{
|
||||
private const string USER_KEY = 'queue.user';
|
||||
|
||||
private const string COMPANY_KEY = 'company';
|
||||
|
||||
public static function setUser(array $user): void
|
||||
{
|
||||
Context::set(self::USER_KEY, $user);
|
||||
}
|
||||
|
||||
public static function getUser(): ?array
|
||||
{
|
||||
return Context::get(self::USER_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前公司信息
|
||||
* @param array $companyInfo
|
||||
* @return void
|
||||
*/
|
||||
public static function setCompanyInfo(array $companyInfo): void
|
||||
{
|
||||
Context::set(self::COMPANY_KEY, $companyInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前公司信息
|
||||
* @return array|null
|
||||
*/
|
||||
public static function getCompanyInfo(): ?array
|
||||
{
|
||||
return Context::get(self::COMPANY_KEY);
|
||||
}
|
||||
|
||||
public static function clear(): void
|
||||
{
|
||||
Context::destroy(self::USER_KEY);
|
||||
}
|
||||
}
|
86
app/Context/UserContext.php
Normal file
86
app/Context/UserContext.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午10:38
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Context;
|
||||
|
||||
use App\Repository\Company\FirstCompanyRepository;
|
||||
use Exception;
|
||||
use Hyperf\Context\Context;
|
||||
use function Hyperf\Support\make;
|
||||
|
||||
class UserContext
|
||||
{
|
||||
private const string USER_KEY = 'user';
|
||||
|
||||
private const string TOKEN_KEY = 'token';
|
||||
|
||||
/**
|
||||
* 设置当前用户信息到 Context
|
||||
*
|
||||
* @param array|string|int $user 用户信息数组
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function setCurrentUser(array|string|int $user): void
|
||||
{
|
||||
$companyRepository = make(FirstCompanyRepository::class);
|
||||
if (!empty($companyInfo = $companyRepository->getCompanyByFullName($user['user']['full_name']))) {
|
||||
$user['company'] = $companyInfo;
|
||||
}
|
||||
|
||||
Context::set(self::USER_KEY, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Context 获取当前用户信息
|
||||
*
|
||||
* @return array|null 返回用户信息数组或 null 如果未找到
|
||||
*/
|
||||
public static function getCurrentUser(): ?array
|
||||
{
|
||||
return Context::get(self::USER_KEY);
|
||||
}
|
||||
|
||||
public static function hasCurrentUser(): bool
|
||||
{
|
||||
return Context::has(self::USER_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前用户令牌
|
||||
* @param string $token
|
||||
* @return void
|
||||
*/
|
||||
public static function setCurrentToken(string $token): void
|
||||
{
|
||||
Context::set(self::TOKEN_KEY, $token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Context 获取当前用户令牌
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getCurrentToken(): ?string
|
||||
{
|
||||
return Context::get(self::TOKEN_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前用户信息
|
||||
*/
|
||||
public static function clearCurrentUser(): void
|
||||
{
|
||||
Context::set(self::USER_KEY, null);
|
||||
}
|
||||
}
|
86
app/Controller/AbstractController.php
Normal file
86
app/Controller/AbstractController.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Context\UserContext;
|
||||
use App\Service\OpLogsService;
|
||||
use App\Utils\ApiResponse;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\HttpServer\Contract\ResponseInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
abstract class AbstractController
|
||||
{
|
||||
#[Inject]
|
||||
protected ContainerInterface $container;
|
||||
|
||||
#[Inject]
|
||||
protected RequestInterface $request;
|
||||
|
||||
#[Inject]
|
||||
protected ResponseInterface $response;
|
||||
|
||||
#[Inject]
|
||||
protected ApiResponse $apiResponse;
|
||||
|
||||
#[Inject]
|
||||
protected OpLogsService $opLogsService;
|
||||
|
||||
/**
|
||||
* 获取默认分页参数.
|
||||
* @return array
|
||||
*/
|
||||
protected function getPage(): array
|
||||
{
|
||||
$params = $this->request->all();
|
||||
return [$params['page'] ?? 1, $params['pageSize'] ?? 100];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户令牌
|
||||
* @return string|null
|
||||
*/
|
||||
public function token(): ?string
|
||||
{
|
||||
return UserContext::getCurrentToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
* @return array
|
||||
*/
|
||||
public function user(): array
|
||||
{
|
||||
return UserContext::getCurrentUser() ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前公司信息
|
||||
* @return array
|
||||
*/
|
||||
public function company(): array
|
||||
{
|
||||
return $this->user()['company'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作日志.
|
||||
* @param string $log
|
||||
* @param int $source
|
||||
*/
|
||||
protected function opLogs(string $log, int $source = 0): void
|
||||
{
|
||||
$this->opLogsService->operatorLogs($log, $source);
|
||||
}
|
||||
}
|
43
app/Controller/CompanyController.php
Normal file
43
app/Controller/CompanyController.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午6:04
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Repository\Company\CompanyRepository;
|
||||
use App\Request\CompanyRequest;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpMessage\Server\Response;
|
||||
use Hyperf\Validation\Annotation\Scene;
|
||||
|
||||
class CompanyController extends AbstractController
|
||||
{
|
||||
#[Inject]
|
||||
protected CompanyRepository $companyRepository;
|
||||
|
||||
/**
|
||||
* 添加公司.
|
||||
* @param CompanyRequest $request
|
||||
* @return Response
|
||||
*/
|
||||
#[Scene(scene: 'addCompany', argument: 'request')]
|
||||
public function addCompany(CompanyRequest $request): Response
|
||||
{
|
||||
$data = $request->all();
|
||||
|
||||
$this->companyRepository->add($data);
|
||||
|
||||
return $this->apiResponse->success();
|
||||
}
|
||||
}
|
45
app/Controller/FirstCompanyController.php
Normal file
45
app/Controller/FirstCompanyController.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午9:29
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Repository\Company\FirstCompanyRepository;
|
||||
use App\Request\FirstCompanyRequest;
|
||||
use Exception;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpMessage\Server\Response;
|
||||
use Hyperf\Validation\Annotation\Scene;
|
||||
|
||||
class FirstCompanyController extends AbstractController
|
||||
{
|
||||
#[Inject]
|
||||
protected FirstCompanyRepository $firstCompanyRepository;
|
||||
|
||||
/**
|
||||
* 添加公司.
|
||||
* @param FirstCompanyRequest $request
|
||||
* @return Response
|
||||
* @throws Exception
|
||||
*/
|
||||
#[Scene(scene: 'addFirstCompany', argument: 'request')]
|
||||
public function addFirstCompany(FirstCompanyRequest $request): Response
|
||||
{
|
||||
$data = $request->all();
|
||||
|
||||
$this->firstCompanyRepository->addCompany($data);
|
||||
|
||||
return $this->apiResponse->success();
|
||||
}
|
||||
}
|
52
app/Controller/PurchaseController.php
Normal file
52
app/Controller/PurchaseController.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午5:35
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Repository\Purchase\PurchaseRepository;
|
||||
use App\Request\PurchaseRequest;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpMessage\Server\Response;
|
||||
use Hyperf\Validation\Annotation\Scene;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午5:42
|
||||
* Description: 采购单入库管理.
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class PurchaseController extends AbstractController
|
||||
{
|
||||
#[Inject]
|
||||
protected PurchaseRepository $purchaseRepository;
|
||||
|
||||
/**
|
||||
* 新增采购单入库单
|
||||
* @param PurchaseRequest $request
|
||||
* @return Response
|
||||
*/
|
||||
#[Scene(scene: 'addPurchase', argument: '')]
|
||||
public function addPurchase(PurchaseRequest $request): Response
|
||||
{
|
||||
$this->purchaseRepository->addPurchase($request->all());
|
||||
|
||||
return $this->apiResponse->success();
|
||||
}
|
||||
}
|
51
app/Controller/RoleController.php
Normal file
51
app/Controller/RoleController.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/4
|
||||
* Time: 下午2:52
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\JsonRpc\UserAuthServiceInterface;
|
||||
use App\Request\RoleRequest;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpMessage\Server\Response;
|
||||
use Hyperf\Validation\Annotation\Scene;
|
||||
|
||||
class RoleController extends AbstractController
|
||||
{
|
||||
#[Inject]
|
||||
protected UserAuthServiceInterface $userAuthServiceInterface;
|
||||
|
||||
/**
|
||||
* 添加角色.
|
||||
* @param RoleRequest $request
|
||||
* @return Response
|
||||
*/
|
||||
#[Scene(scene: 'addRole', argument: 'request')]
|
||||
public function addRole(RoleRequest $request): Response
|
||||
{
|
||||
$params = $request->all();
|
||||
|
||||
$data = [
|
||||
'companyInfo' => $this->company(),
|
||||
'id' => $params['id'] ?? null,
|
||||
'role_name' => $params['role_name'],
|
||||
'active_status' => $params['active_status'],
|
||||
'sort' => $params['sort'],
|
||||
];
|
||||
// 添加角色.
|
||||
$this->userAuthServiceInterface->addRole($data);
|
||||
|
||||
return $this->apiResponse->success();
|
||||
}
|
||||
}
|
110
app/Controller/UserController.php
Normal file
110
app/Controller/UserController.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午8:20
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Amqp\Producer\UserImportProducer;
|
||||
use App\Constants\UserTypeConst;
|
||||
use App\Context\UserContext;
|
||||
use App\JsonRpc\UserAuthServiceInterface;
|
||||
use App\Request\UserRequest;
|
||||
use Exception;
|
||||
use Hyperf\Amqp\Producer;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpMessage\Server\Response;
|
||||
use Hyperf\Validation\Annotation\Scene;
|
||||
|
||||
class UserController extends AbstractController
|
||||
{
|
||||
#[Inject]
|
||||
protected UserAuthServiceInterface $userAuthService;
|
||||
|
||||
#[Inject]
|
||||
protected Producer $producer;
|
||||
|
||||
/**
|
||||
* 用户登录.
|
||||
* @param UserRequest $request
|
||||
* @return Response
|
||||
* @throws Exception
|
||||
*/
|
||||
#[Scene(scene: 'userLogin', argument: 'request')]
|
||||
public function userLogin(UserRequest $request): Response
|
||||
{
|
||||
$rpcUser = $this->userAuthService->userLogin([
|
||||
'login_name' => $request->input('login_name'),
|
||||
'password' => $request->input('password'),
|
||||
]);
|
||||
$user = $rpcUser['result'] ?? [];
|
||||
if (empty($user)) {
|
||||
return $this->apiResponse->error('用户名不存在');
|
||||
}
|
||||
|
||||
// 设置用户信息上下文
|
||||
UserContext::setCurrentUser($user['user']);
|
||||
|
||||
$this->opLogs('[用户登录]登录名 ' . $request->input('login_name'));
|
||||
|
||||
return $this->apiResponse->success($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户.
|
||||
* @param UserRequest $request
|
||||
* @return Response
|
||||
*/
|
||||
#[Scene(scene: 'addUser', argument: 'request')]
|
||||
public function addUser(UserRequest $request): Response
|
||||
{
|
||||
$data = $request->all();
|
||||
$data['token'] = $this->token();
|
||||
$data['user_type'] = UserTypeConst::SYSTEM_USER;
|
||||
$data['companyInfo'] = $this->company();
|
||||
$data['role_ids'] = $request->input('role_ids', []); // 角色ID列表
|
||||
|
||||
$this->userAuthService->addUser($data);
|
||||
|
||||
return $this->apiResponse->success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户列表.
|
||||
* @param UserRequest $request
|
||||
* @return Response
|
||||
*/
|
||||
#[Scene(scene: 'getUserList', argument: 'request')]
|
||||
public function getUserList(UserRequest $request): Response
|
||||
{
|
||||
$data = [
|
||||
'companyInfo' => $this->company(),
|
||||
'userInfo' => UserContext::getCurrentUser(),
|
||||
'getPage' => $this->getPage(),
|
||||
'params' => $request->all()
|
||||
];
|
||||
|
||||
$rpcResult = $this->userAuthService->userList($data);
|
||||
|
||||
return $this->apiResponse->success($rpcResult['result']);
|
||||
}
|
||||
|
||||
public function importUser(): Response
|
||||
{
|
||||
$data = [];
|
||||
|
||||
$this->producer->produce(new UserImportProducer($data));
|
||||
|
||||
return $this->apiResponse->success();
|
||||
}
|
||||
}
|
432
app/Dao/AbstractDao.php
Normal file
432
app/Dao/AbstractDao.php
Normal file
@ -0,0 +1,432 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/1/6
|
||||
* Time: 下午4:07
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Dao;
|
||||
|
||||
use App\Model\Model;
|
||||
use App\Service\Trait\ColumnConfigTrait;
|
||||
use Exception;
|
||||
use Hyperf\Collection\Collection;
|
||||
use Hyperf\Database\Model\Builder;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/2/28
|
||||
* Time: 上午8:32
|
||||
* Description: AbstractDao.
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
abstract class AbstractDao
|
||||
{
|
||||
use ColumnConfigTrait;
|
||||
|
||||
// 支持的表达式 - 白名单操作符
|
||||
protected static array $OPERATORS_THAT_SUPPORT_VALUES = [
|
||||
// 比较运算符
|
||||
'=', '<', '<=', '>', '>=', '<>', '!=',
|
||||
|
||||
// 逻辑运算符
|
||||
'between', 'not between', 'in', 'not in', 'like', 'is null', 'is not null',
|
||||
];
|
||||
|
||||
#[Inject]
|
||||
protected RequestInterface $request;
|
||||
|
||||
private int|string $keyId;
|
||||
|
||||
private array $queryResult;
|
||||
|
||||
/**
|
||||
* @return Model
|
||||
*/
|
||||
abstract protected function getModel(): string;
|
||||
|
||||
/**
|
||||
* 数据操作.
|
||||
*/
|
||||
public function builder(): Builder
|
||||
{
|
||||
return $this->getModel()::query();
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化.
|
||||
*/
|
||||
public function make(): AbstractDao
|
||||
{
|
||||
return new $this();
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录查询.
|
||||
* @throws Exception
|
||||
*/
|
||||
public function qInfo(array $params = []): Builder
|
||||
{
|
||||
$query = $this->builder();
|
||||
$params = $params ?: $this->request->all();
|
||||
if (!empty($params['ids']) && is_array($params['ids'])) {
|
||||
$query->whereIn('id', $params['ids']);
|
||||
} else {
|
||||
$query->where('id', intval($params['id']));
|
||||
}
|
||||
if (!$query->exists()) {
|
||||
throw new Exception('记录不存在');
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用创建&更新.
|
||||
* @param array $data 需要新增更新的数据
|
||||
* @param array $updateCondition 更新条件
|
||||
* @return $this
|
||||
* @throws Exception
|
||||
*/
|
||||
public function commonCreate(array $data = [], array $updateCondition = []): AbstractDao
|
||||
{
|
||||
$ignoreUpdateFields = ['id', 'company_id', 'creator_id', 'creator_name']; // 不允许更新的字段
|
||||
|
||||
// 1:根据条件更新或创建
|
||||
if (!empty($updateCondition)) {
|
||||
$updateData = array_diff_key($data, array_flip($ignoreUpdateFields));
|
||||
$this->builder()->updateOrCreate($updateCondition, $updateData);
|
||||
|
||||
// 为保证查询条件一致,重新构造查询
|
||||
$query = $this->builder()->where($updateCondition);
|
||||
$records = $query->get();
|
||||
|
||||
$this->queryResult = $records->count() > 1
|
||||
? $records->toArray()
|
||||
: $records->first()?->toArray();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// 2:根据主键 ID 更新
|
||||
if (!empty($data['id'])) {
|
||||
$this->keyId = $data['id'];
|
||||
$query = $this->qInfo($data);
|
||||
$query->lockForUpdate()->first(); // 获取锁
|
||||
|
||||
$updateData = array_diff_key($data, array_flip($ignoreUpdateFields));
|
||||
|
||||
$res = $query->update($updateData);
|
||||
if (!$res) {
|
||||
throw new Exception('数据更新失败');
|
||||
}
|
||||
|
||||
// 更新后重新查询,避免条件变化导致查询不到
|
||||
$this->queryResult = $this->builder()->where('id', $this->keyId)->first()?->toArray();
|
||||
return $this;
|
||||
}
|
||||
|
||||
// 3:创建新记录
|
||||
$res = $this->builder()->create($data);
|
||||
if (!$res) {
|
||||
throw new Exception('数据新增失败');
|
||||
}
|
||||
|
||||
$this->keyId = $res->getKey();
|
||||
$this->queryResult = $res->toArray();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回更新&新增主键.
|
||||
*/
|
||||
public function getKey(): int
|
||||
{
|
||||
return $this->keyId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数据库操作结果.
|
||||
*/
|
||||
public function result(): array
|
||||
{
|
||||
return $this->queryResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据验证
|
||||
*/
|
||||
public function verifyData(Builder $builder, array $params = []): Builder
|
||||
{
|
||||
$map = [];
|
||||
$condition = [];
|
||||
if (!empty($params['id']) && is_numeric($params['id'])) {
|
||||
$condition = ['id' => $params['id']];
|
||||
}
|
||||
foreach ($condition as $k => $v) {
|
||||
$map[] = [$k, '!=', $v];
|
||||
}
|
||||
return clone $builder->where($map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新状态
|
||||
* @throws Exception
|
||||
*/
|
||||
public function updateStatus(): null|object
|
||||
{
|
||||
$params = $this->request->all();
|
||||
$query = $this->qInfo()->lockForUpdate();
|
||||
$info = $query->first();
|
||||
if ($info['active_status'] == $params['status']) {
|
||||
throw new Exception('状态不对,请确认');
|
||||
}
|
||||
$query->update(['active_status' => $params['status']]);
|
||||
return $info;
|
||||
}
|
||||
|
||||
/***************************************************************************************
|
||||
* 构建查询数据条件参数.
|
||||
* $params = [
|
||||
* 'mail_sync_id' => 123,
|
||||
* 'sn' => ['operator' => '!=', 'value' => 'ABC123'],
|
||||
* 'aliid' => ['operator' => 'like', 'value' => '%example%'],
|
||||
* 'serial_number' => ['operator' => '>', 'value' => 100],
|
||||
* 'ali_model' => 'ModelX',
|
||||
* 'model' => 'XYZ',
|
||||
* 'date' => ['operator' => 'between', 'value' => ['2023-01-01', '2023-01-31']],
|
||||
* 'id' => [
|
||||
* ['operator' => '>=', 'value' => 10],
|
||||
* ['operator' => '<=', 'value' => 100]
|
||||
* ],
|
||||
* 'status' => ['operator' => 'in', 'value' => ['active', 'pending']],
|
||||
* ];
|
||||
*************************************************************************************
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function daoBuildWhere(Builder $builder, array $params, array $fields): Builder
|
||||
{
|
||||
if (empty($params)) return $builder; // 如果没有参数,直接返回
|
||||
// 用于存储所有 like 条件
|
||||
$likeConditions = [];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
// 跳过空参数
|
||||
if (!isset($params[$field]) || $params[$field] === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$param = $params[$field];
|
||||
// 如果参数是数组
|
||||
if (is_array($param)) {
|
||||
// 处理同一个字段的多个条件
|
||||
if (isset($param[0]) && is_array($param[0])) {
|
||||
self::addMultipleConditions($builder, $field, $param);
|
||||
} else {
|
||||
// 处理单个条件
|
||||
self::handleSingleCondition($builder, $likeConditions, $field, $param);
|
||||
}
|
||||
} else {
|
||||
// 处理字符串条件,字符串条件,强制使用绑定参数
|
||||
$builder->where($field, '=', $param);
|
||||
}
|
||||
}
|
||||
|
||||
// 应用所有 like 条件
|
||||
self::applyLikeConditions($builder, $likeConditions);
|
||||
|
||||
// 添加排序功能
|
||||
if (!empty($params['sort_orders'])) {
|
||||
self::applySortOrders($builder, $params['sort_orders'], $fields);
|
||||
}
|
||||
|
||||
return $builder;
|
||||
}
|
||||
|
||||
private static function addMultipleConditions(Builder $builder, string $field, array $conditions): void
|
||||
{
|
||||
// 处理同一个字段的多个条件
|
||||
$builder->where(/**
|
||||
* @throws Exception
|
||||
*/ function ($query) use ($field, $conditions) {
|
||||
foreach ($conditions as $condition) {
|
||||
// 应用每个条件
|
||||
self::applyCondition($query, $field, $condition);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static function handleSingleCondition(Builder $builder, array &$likeConditions, string $field, array $param): void
|
||||
{
|
||||
// 收集 like 条件或处理其他单个条件
|
||||
if (isset($param['operator']) && strtolower($param['operator']) === 'like') {
|
||||
// 如果是 like 条件,将其添加到 likeConditions 数组中
|
||||
$likeConditions[] = ['field' => $field, 'condition' => $param];
|
||||
} else {
|
||||
// 否则,直接应用条件
|
||||
$builder->where(
|
||||
/**
|
||||
* @throws Exception
|
||||
*/ function ($query) use ($field, $param) {
|
||||
self::applyCondition($query, $field, $param);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static function applyLikeConditions(Builder $builder, array $likeConditions): void
|
||||
{
|
||||
// 应用所有 like 条件
|
||||
if (!empty($likeConditions)) {
|
||||
$builder->where(function ($query) use ($likeConditions) {
|
||||
foreach ($likeConditions as $likeCondition) {
|
||||
if (empty($likeCondition['condition']['value'])) continue;
|
||||
// 使用 orWhere 添加每个 like 条件
|
||||
$query->orWhere($likeCondition['field'], 'like', $likeCondition['condition']['value']);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用排序功能.
|
||||
* @param Builder $builder
|
||||
* @param array $sortOrders
|
||||
* @param array $fields
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function applySortOrders(Builder $builder, array $sortOrders, array $fields): void
|
||||
{
|
||||
foreach ($sortOrders as $field => $sortOrder) {
|
||||
if (!in_array($sortOrder, ['asc', 'desc'])) {
|
||||
throw new Exception('排序方式不正确');
|
||||
}
|
||||
if (!in_array($field, $fields)) {
|
||||
throw new Exception('排序字段不正确');
|
||||
}
|
||||
$builder->orderBy($field, $sortOrder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用单个字段的查询条件.
|
||||
* 调用方式
|
||||
* (builder,'name','='|['operate'=>'bett']).
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function applyCondition(Builder &$builder, string $field, mixed $condition): void
|
||||
{
|
||||
if (is_array($condition) && isset($condition['operator']) && isset($condition['value'])) {
|
||||
if (empty($condition['value'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$operator = strtolower($condition['operator']);
|
||||
if (!in_array($operator, self::$OPERATORS_THAT_SUPPORT_VALUES)) {
|
||||
throw new Exception('不支持的查询表达式: ' . $condition['operator']);
|
||||
}
|
||||
$builder = match ($operator) {
|
||||
'between' => $builder->whereBetween($field, $condition['value']),
|
||||
'not between' => $builder->whereNotBetween($field, $condition['value']),
|
||||
'in' => $builder->whereIn($field, $condition['value']),
|
||||
'not in' => $builder->whereNotIn($field, $condition['value']),
|
||||
'is null' => $builder->whereNull($field),
|
||||
'is not null' => $builder->whereNotNull($field),
|
||||
'like' => $builder->orWhere($field, 'like', $condition['value']),
|
||||
default => $builder->where($field, $operator, $condition['value']),
|
||||
};
|
||||
} else {
|
||||
$builder->where($field, '=', $condition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段列表(公共).
|
||||
* @param Builder $builder
|
||||
* @param array $params
|
||||
* @param array $fields
|
||||
* @param string|array $addSelect
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function selectFields(Builder $builder, array $params, array $fields = [], string|array $addSelect = ''): array
|
||||
{
|
||||
self::daoBuildWhere($builder, $params, $fields);
|
||||
|
||||
$field = $params['field'] ?? '';
|
||||
if (!in_array($field, $fields)) {
|
||||
throw new Exception('字段参数错误');
|
||||
}
|
||||
|
||||
$data = $builder->select(['id', $field]);
|
||||
if (!empty($addSelect)) {
|
||||
$data = $data->addSelect($addSelect);
|
||||
}
|
||||
return $data->get()->filter(function ($item) use ($field) {
|
||||
// 过滤空值但不过滤布尔值
|
||||
return !empty($item[$field]) || is_bool($item[$field]);
|
||||
})->unique($field)->values()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页处理函数.
|
||||
*
|
||||
* 本函数用于对给定的Builder或Collection对象进行分页处理,并返回分页后的数据及其它相关信息。
|
||||
*
|
||||
* @param Builder|Collection $builder 可以是Eloquent Builder对象或Collection对象
|
||||
* @param array $params 包含分页参数的数组,比如页码和每页数量
|
||||
* @return array 返回一个包含分页数据、字段配置、过滤条件、表格设置等信息的数组
|
||||
* @throws Exception
|
||||
*/
|
||||
public function paginate(Builder|Collection $builder, array $params = []): array
|
||||
{
|
||||
try {
|
||||
// 从参数中获取页码和每页数量,未指定则默认值
|
||||
$page = empty($params['page']) ? 1 : (int)$params['page'];
|
||||
$page_size = empty($params['pageSize']) ? 100 : (int)$params['pageSize'];
|
||||
// 计算跳过的记录数
|
||||
$skip = ($page - 1) * $page_size;
|
||||
// 获取总记录数
|
||||
$total = $builder->count();
|
||||
// 计算总页数,若记录数为0,则页数为0
|
||||
$pageSize = $total == 0 ? 0 : (int)ceil($total / $page_size);
|
||||
// 根据Builder类型获取分页后的数据
|
||||
if ($builder instanceof Collection) {
|
||||
// 对Collection对象进行分页
|
||||
$rows = $builder->values()->slice($skip, $page_size)->values();
|
||||
} else {
|
||||
// 对Builder对象进行分页查询
|
||||
$rows = $builder->skip($skip)->take($page_size)->orderBy('id', 'desc')->get();
|
||||
}
|
||||
|
||||
// 获取列配置信息
|
||||
$columnConfig = $this->getColumnConfig();
|
||||
$tableConfig = $this->getTableConfig();
|
||||
|
||||
return compact(
|
||||
'rows',
|
||||
'page',
|
||||
'total',
|
||||
'pageSize',
|
||||
'columnConfig',
|
||||
'tableConfig'
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
38
app/Dao/Company/CompanyDao.php
Normal file
38
app/Dao/Company/CompanyDao.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午5:59
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Dao\Company;
|
||||
|
||||
use App\Dao\AbstractDao;
|
||||
use App\Model\Company;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午6:00
|
||||
* Description: CompanyDao类用于提供公司相关的数据访问和操作。它继承自AbstractDao类,并实现了CompanyDao接口。
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class CompanyDao extends AbstractDao
|
||||
{
|
||||
public function getModel(): string
|
||||
{
|
||||
return Company::class;
|
||||
}
|
||||
}
|
60
app/Dao/Company/FirstCompanyDao.php
Normal file
60
app/Dao/Company/FirstCompanyDao.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午9:24
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Dao\Company;
|
||||
|
||||
use App\Dao\AbstractDao;
|
||||
use App\Model\FirstCompany;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午9:25
|
||||
* Description: 平台公司数据访问层.
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class FirstCompanyDao extends AbstractDao
|
||||
{
|
||||
public function getModel(): string
|
||||
{
|
||||
return FirstCompany::class;
|
||||
}
|
||||
|
||||
public function getFields(): array
|
||||
{
|
||||
return [
|
||||
'id',
|
||||
'domain',
|
||||
'name',
|
||||
'full_name',
|
||||
'company_type',
|
||||
'address',
|
||||
'logo',
|
||||
'owner', // 公司负责人
|
||||
'id_card', // 法人身份证
|
||||
'mobile',
|
||||
'org_code',
|
||||
'remark',
|
||||
'active_status',
|
||||
'activation_date',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
}
|
||||
}
|
38
app/Dao/Purchase/PurchaseDao.php
Normal file
38
app/Dao/Purchase/PurchaseDao.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午5:26
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Dao\Purchase;
|
||||
|
||||
use App\Dao\AbstractDao;
|
||||
use App\Model\Purchase;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午5:27
|
||||
* Description: 采购单入库单数据访问层.
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class PurchaseDao extends AbstractDao
|
||||
{
|
||||
public function getModel(): string
|
||||
{
|
||||
return Purchase::class;
|
||||
}
|
||||
}
|
33
app/Exception/ApiException.php
Normal file
33
app/Exception/ApiException.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午6:44
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Exception;
|
||||
|
||||
use Hyperf\Server\Exception\ServerException;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午7:19
|
||||
* Description: 业务异常
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class ApiException extends ServerException
|
||||
{
|
||||
}
|
29
app/Exception/BusinessException.php
Normal file
29
app/Exception/BusinessException.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Exception;
|
||||
|
||||
use App\Constants\ErrorCode;
|
||||
use Hyperf\Server\Exception\ServerException;
|
||||
use Throwable;
|
||||
|
||||
class BusinessException extends ServerException
|
||||
{
|
||||
public function __construct(int $code = 0, string $message = null, Throwable $previous = null)
|
||||
{
|
||||
if (is_null($message)) {
|
||||
$message = ErrorCode::getMessage($code);
|
||||
}
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
94
app/Exception/Handler/ApiExceptionHandler.php
Normal file
94
app/Exception/Handler/ApiExceptionHandler.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午8:51
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Exception\Handler;
|
||||
|
||||
use App\Exception\ApiException;
|
||||
use App\Log\Log;
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Hyperf\ExceptionHandler\ExceptionHandler;
|
||||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||||
use Hyperf\Validation\ValidationException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午8:54
|
||||
* Description: 统一异常处理
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class ApiExceptionHandler extends ExceptionHandler
|
||||
{
|
||||
public function __construct(protected ApplicationContext $context)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理异常
|
||||
* @param Throwable $throwable
|
||||
* @param ResponseInterface $response
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function handle(Throwable $throwable, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$errorData = $this->getErrorData($throwable);
|
||||
|
||||
// 记录日志
|
||||
Log::get('default', 'default')->error($errorData['logMessage']);
|
||||
|
||||
// 阻止异常冒泡
|
||||
$this->stopPropagation();
|
||||
|
||||
return $response->withStatus(200)
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withBody(new SwooleStream(json_encode($errorData['responseData'], JSON_UNESCAPED_UNICODE)));
|
||||
}
|
||||
|
||||
public function isValid(Throwable $throwable): bool
|
||||
{
|
||||
return $throwable instanceof ApiException || $throwable instanceof ValidationException;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误.
|
||||
*/
|
||||
protected function getErrorData(Throwable $throwable): array
|
||||
{
|
||||
$code = $throwable->getCode();
|
||||
$errorFile = $throwable->getFile();
|
||||
$errorLine = $throwable->getLine();
|
||||
$errorMessage = $throwable->getMessage();
|
||||
|
||||
if ($throwable instanceof ValidationException) {
|
||||
$errors = $throwable->validator->errors()->toArray();
|
||||
$errorMessage = reset($errors)[0];
|
||||
}
|
||||
|
||||
$responseData = [
|
||||
'code' => $code,
|
||||
'message' => $errorMessage,
|
||||
];
|
||||
|
||||
$logMessage = sprintf('%s %s %s %s', $code, $errorFile, $errorLine, $errorMessage);
|
||||
|
||||
return ['responseData' => $responseData, 'logMessage' => $logMessage];
|
||||
}
|
||||
}
|
97
app/Exception/Handler/AppExceptionHandler.php
Normal file
97
app/Exception/Handler/AppExceptionHandler.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Exception\Handler;
|
||||
|
||||
use App\Exception\ApiException;
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\ExceptionHandler\ExceptionHandler;
|
||||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||||
use Hyperf\RateLimit\Exception\RateLimitException;
|
||||
use Hyperf\Validation\ValidationException;
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Throwable;
|
||||
use function Hyperf\Support\env;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午8:57
|
||||
* Description: 默认异常处理
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class AppExceptionHandler extends ExceptionHandler
|
||||
{
|
||||
public function __construct(protected StdoutLoggerInterface $logger)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(Throwable $throwable, ResponseInterface $response): MessageInterface|ResponseInterface
|
||||
{
|
||||
$this->logException($throwable);
|
||||
|
||||
if ($throwable instanceof RateLimitException) {
|
||||
return $this->handleRateLimitException($response);
|
||||
}
|
||||
|
||||
return $this->handleOtherExceptions($throwable, $response);
|
||||
}
|
||||
|
||||
public function isValid(Throwable $throwable): bool
|
||||
{
|
||||
return ! $throwable instanceof ApiException;
|
||||
}
|
||||
|
||||
protected function createResponseBody($message, $code): StreamInterface
|
||||
{
|
||||
$data = [
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
];
|
||||
return new SwooleStream(json_encode($data, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
protected function logException(Throwable $throwable): void
|
||||
{
|
||||
$this->logger->error(sprintf('%s[%s] in %s', $throwable->getMessage(), $throwable->getLine(), $throwable->getFile()));
|
||||
$this->logger->error($throwable->getTraceAsString());
|
||||
}
|
||||
|
||||
protected function handleRateLimitException(ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$message = '触发请求频率限流规则';
|
||||
return $response->withStatus(429)
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withBody($this->createResponseBody($message, 429));
|
||||
}
|
||||
|
||||
protected function handleOtherExceptions(Throwable $throwable, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$message = $throwable->getMessage();
|
||||
|
||||
if (env('APP_ENV') === 'production' && ! $throwable instanceof ValidationException) {
|
||||
return $response->withStatus(200)
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withBody($this->createResponseBody($message, 0));
|
||||
}
|
||||
|
||||
return $response->withHeader('Server', 'mes-auto')
|
||||
->withStatus(200)
|
||||
->withBody($this->createResponseBody($message, 0));
|
||||
}
|
||||
}
|
83
app/Job/BaseJob.php
Normal file
83
app/Job/BaseJob.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午10:55
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Job;
|
||||
|
||||
use App\Context\QueueContext;
|
||||
use App\Log\Log;
|
||||
use Exception;
|
||||
use Hyperf\AsyncQueue\Job;
|
||||
use Hyperf\DbConnection\Db;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午10:57
|
||||
* Description: Job基础任务类.
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
abstract class BaseJob extends Job
|
||||
{
|
||||
public function __construct(public array $data)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
// 设置用户信息上下文信息
|
||||
if (!empty($user = $this->data['user'])) {
|
||||
QueueContext::setUser($user);
|
||||
}
|
||||
|
||||
if (!empty($company = $this->data['company'])) {
|
||||
QueueContext::setCompanyInfo($company);
|
||||
}
|
||||
|
||||
// 运行业务逻辑, 总是在事务中执行,保证事务的原子性
|
||||
Db::beginTransaction();
|
||||
try {
|
||||
$this->process();
|
||||
|
||||
Db::commit();
|
||||
} catch (Exception $e) {
|
||||
Db::rollBack();
|
||||
$this->logError($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 子类必须实现的业务逻辑处理方法
|
||||
*/
|
||||
abstract protected function process(): void;
|
||||
|
||||
/**
|
||||
* 日志记录,可按需扩展日志通道
|
||||
*/
|
||||
protected function logError(Exception $e): void
|
||||
{
|
||||
Log::get('queue', 'queue')->error(sprintf(
|
||||
"[%s] %s in %s:%d\n%s",
|
||||
static::class,
|
||||
$e->getMessage(),
|
||||
$e->getFile(),
|
||||
$e->getLine(),
|
||||
$e->getTraceAsString()
|
||||
));
|
||||
}
|
||||
}
|
141
app/Job/ColumnConfigJob.php
Executable file
141
app/Job/ColumnConfigJob.php
Executable file
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Job;
|
||||
|
||||
use App\Model\ColumnConfig;
|
||||
use App\Scope\CompanyScope;
|
||||
|
||||
use Hyperf\Coroutine\Parallel;
|
||||
use function Hyperf\Collection\collect;
|
||||
use function Hyperf\Config\config;
|
||||
|
||||
class ColumnConfigJob extends BaseJob
|
||||
{
|
||||
protected function process(): void
|
||||
{
|
||||
['user' => $user, 'method' => $method, 'params' => $params] = $this->data;
|
||||
|
||||
// 提交了保存列配置
|
||||
if (!isset($params['save_column'])) return;
|
||||
if ($params['save_column'] === false) {
|
||||
$this->deleteColumnConfig($user, $method);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取默认配置, 没有配置则不处理
|
||||
$configList = config('column_config.' . $method) ?? [];
|
||||
if (empty($configList)) return;
|
||||
|
||||
$submitConfig = $params['column_config'];
|
||||
$submittedMap = collect($submitConfig)->keyBy('prop')->toArray();
|
||||
|
||||
$parallel = new Parallel();
|
||||
|
||||
foreach ($configList as $key => $value) {
|
||||
$parallel->add(function () use ($key, $value, $submittedMap, $user) {
|
||||
$value['sort'] = $key + 1;
|
||||
|
||||
// 存在前端提交的列
|
||||
if (isset($submittedMap[$value['prop']])) {
|
||||
$merged = array_merge([
|
||||
'condition' => 'like',
|
||||
'search_type' => 'text',
|
||||
'is_search' => 1,
|
||||
'sortable' => 0,
|
||||
], $value, $submittedMap[$value['prop']]);
|
||||
|
||||
return $this->prepareConfig($merged, $user);
|
||||
}
|
||||
|
||||
// 否则删除该列配置
|
||||
ColumnConfig::query()->withoutGlobalScope(CompanyScope::class)
|
||||
->where([
|
||||
'prop' => $value['prop'],
|
||||
'method' => $this->data['method'],
|
||||
'creator_id' => $user['id'],
|
||||
])
|
||||
->forceDelete();
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
// 执行并获取所有结果
|
||||
$results = $parallel->wait();
|
||||
|
||||
// 过滤非 null 的更新数据
|
||||
$updates = array_filter($results);
|
||||
|
||||
if (!empty($updates)) {
|
||||
$this->updateOrCreateColumnConfigs($updates, $method, $user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新或创建列配置.
|
||||
*/
|
||||
private function updateOrCreateColumnConfigs(array $configs, string $method, array $creator): void
|
||||
{
|
||||
// 默认配置key值
|
||||
$defaultKeys = (new ColumnConfig())->getFillable();
|
||||
|
||||
// 删除$configs里不存在$defaultKeys里的字段,防止前端传递的额外字段导致更新失败
|
||||
$configs = array_map(function ($config) use ($defaultKeys) {
|
||||
$newConfig = array_intersect_key($config, array_flip($defaultKeys));
|
||||
$newConfig['sort'] = $config['sort'] ?? 0;
|
||||
return $newConfig;
|
||||
}, $configs);
|
||||
|
||||
// 添加公共字段
|
||||
foreach ($configs as &$config) {
|
||||
$config['company_id'] = $creator['company_id'] ?? 0;
|
||||
$config['method'] = $method;
|
||||
$config['creator_id'] = $creator['id'];
|
||||
$config['creator_name'] = $creator['name'];
|
||||
$config['created_at'] = $config['updated_at'] = time();
|
||||
}
|
||||
unset($config);
|
||||
|
||||
$updateKeys = array_keys($configs[0]);
|
||||
// 删除不需要更新的字段
|
||||
unset(
|
||||
$updateKeys[array_search('creator_name', $updateKeys, true)],
|
||||
$updateKeys[array_search('prop', $updateKeys, true)],
|
||||
$updateKeys[array_search('method', $updateKeys, true)],
|
||||
$updateKeys[array_search('creator_id', $updateKeys, true)],
|
||||
);
|
||||
|
||||
// 批量插入或更新
|
||||
ColumnConfig::query()->withoutGlobalScope(CompanyScope::class)
|
||||
->upsert($configs, ['prop', 'method', 'creator_id'], array_values($updateKeys));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除列配置.
|
||||
*/
|
||||
private function deleteColumnConfig(array $user, string $method): void
|
||||
{
|
||||
ColumnConfig::query()->withoutGlobalScope(CompanyScope::class)
|
||||
->where(['method' => $method, 'creator_id' => $user['id']])
|
||||
->forceDelete();
|
||||
}
|
||||
|
||||
private function prepareConfig(array $config, array $user): array
|
||||
{
|
||||
return array_merge($config, [
|
||||
'company_id' => $user['company_id'] ?? 0,
|
||||
'creator_id' => $user['id'],
|
||||
'creator_name' => $user['name'],
|
||||
]);
|
||||
}
|
||||
}
|
87
app/Job/OpLogsJob.php
Executable file
87
app/Job/OpLogsJob.php
Executable file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Job;
|
||||
|
||||
use App\Constants\SourceConst;
|
||||
use App\JsonRpc\EasyAppServiceInterface;
|
||||
use App\Model\OperatorLogs;
|
||||
use Exception;
|
||||
use Hyperf\Collection\Collection;
|
||||
use function Hyperf\Collection\collect;
|
||||
use function Hyperf\Config\config;
|
||||
use function Hyperf\Support\make;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2024/12/25
|
||||
* Time: 上午9:36
|
||||
* Description: 操作日志记录任务.
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class OpLogsJob extends BaseJob
|
||||
{
|
||||
/**
|
||||
* 处理日志记录逻辑。
|
||||
* 该方法首先尝试获取日志操作信息,如果信息不为空,则根据这些信息填充操作日志数组,
|
||||
* 包括日志类型、标题、计时器以及来源信息。随后,它将尝试获取当前用户信息,并最后创建操作日志记录。
|
||||
*
|
||||
* @return void 该方法没有返回值。
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function process(): void
|
||||
{
|
||||
// 尝试获取日志操作信息
|
||||
$logAction = $this->getLogAction();
|
||||
// 如果日志操作信息为空,则直接返回,不进行后续操作
|
||||
if ($logAction->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rpcResult = make(EasyAppServiceInterface::class)->getClientIPInfo(['ip' => $this->data['ip']]);
|
||||
|
||||
// 构建请求日志消息
|
||||
$clientIPInfo = $rpcResult['result']['ip_info'] ?? '';
|
||||
|
||||
// 从日志操作信息中提取日志类型,并保存到操作日志数组中
|
||||
$type = $logAction->first()['id'] ?? 0;
|
||||
$this->data['type'] = $type;
|
||||
// 同样从日志操作信息中提取日志标题,并保存
|
||||
$this->data['log_title'] = $logAction->first()['name'] ?? '';
|
||||
// 初始化计时器
|
||||
$this->data['timer'] = 0;
|
||||
// 设置日志来源,优先使用已存在的来源信息,如果不存在,则尝试从路由前缀获取来源信息
|
||||
$this->data['source'] = $this->data['source'] ?: $this->getRoutePrefix();
|
||||
// 查询IP属地,并保存到操作日志数组中
|
||||
$this->data['location'] = $clientIPInfo;
|
||||
|
||||
// 创建操作日志记录
|
||||
OperatorLogs::query()->create($this->data);
|
||||
}
|
||||
|
||||
|
||||
private function getLogAction(): Collection
|
||||
{
|
||||
$defineLogs = config('op_logs');
|
||||
return collect($defineLogs)->where('action', '=', $this->data['action']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取路由前缀.
|
||||
*/
|
||||
private function getRoutePrefix(): int
|
||||
{
|
||||
$arr = explode('/', $this->data['route']);
|
||||
$res = $arr[0] ?? '';
|
||||
return match ($res) {
|
||||
'api' => SourceConst::SOURCE_PC,
|
||||
'mobile' => SourceConst::SOURCE_MOBILE,
|
||||
default => 0,
|
||||
};
|
||||
}
|
||||
}
|
54
app/Job/RequestWriteLogsJob.php
Normal file
54
app/Job/RequestWriteLogsJob.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Job;
|
||||
|
||||
use App\JsonRpc\EasyAppServiceInterface;
|
||||
use App\Log\Log;
|
||||
use function Hyperf\Support\make;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/4
|
||||
* Time: 上午10:23
|
||||
* Description: 请求日志写入任务.
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class RequestWriteLogsJob extends BaseJob
|
||||
{
|
||||
protected function process(): void
|
||||
{
|
||||
$params = $this->data['params']; // 提取请求参数
|
||||
|
||||
// 从参数中删除敏感信息
|
||||
foreach ($params as $key => $value) {
|
||||
if (in_array($key, ['password', 'pwd', 'pwd_conf', 'original_pwd'])) {
|
||||
unset($params[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$rpcResult = make(EasyAppServiceInterface::class)->getClientIPInfo([
|
||||
'ip' => $this->data['client_ip']
|
||||
]);
|
||||
|
||||
$clientIPInfo = $rpcResult['result']['ip_info'] ?? '';
|
||||
|
||||
$logMessage = sprintf('%s %s %s',
|
||||
$this->data['client_ip'] . ' ' . $clientIPInfo,
|
||||
$this->data['method'],
|
||||
$this->data['uri']
|
||||
);
|
||||
if (!empty($params)) {
|
||||
$logMessage .= ' params: ' . json_encode($params, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
// 记录日志
|
||||
$log = Log::get('request', 'request');
|
||||
$log->info($logMessage);
|
||||
}
|
||||
}
|
37
app/JsonRpc/EasyAppServiceConsumer.php
Normal file
37
app/JsonRpc/EasyAppServiceConsumer.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/4
|
||||
* Time: 上午10:11
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\JsonRpc;
|
||||
|
||||
use Hyperf\RpcClient\AbstractServiceClient;
|
||||
|
||||
class EasyAppServiceConsumer extends AbstractServiceClient implements EasyAppServiceInterface
|
||||
{
|
||||
// 定义对应服务提供者的服务名
|
||||
protected string $serviceName = 'EasyAppService';
|
||||
|
||||
// 定义对应服务提供者的服务协议
|
||||
protected string $protocol = 'jsonrpc-http';
|
||||
|
||||
/**
|
||||
* 获取客户端IP信息
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
public function getClientIpInfo(array $params): array
|
||||
{
|
||||
return $this->__request(__FUNCTION__, $params);
|
||||
}
|
||||
}
|
26
app/JsonRpc/EasyAppServiceInterface.php
Normal file
26
app/JsonRpc/EasyAppServiceInterface.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/4
|
||||
* Time: 上午10:10
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\JsonRpc;
|
||||
|
||||
interface EasyAppServiceInterface
|
||||
{
|
||||
/**
|
||||
* 获取客户端IP信息
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
public function getClientIpInfo(array $params): array;
|
||||
}
|
33
app/JsonRpc/InventoryServiceConsumer.php
Normal file
33
app/JsonRpc/InventoryServiceConsumer.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午1:58
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\JsonRpc;
|
||||
|
||||
use Hyperf\RpcClient\AbstractServiceClient;
|
||||
|
||||
class InventoryServiceConsumer extends AbstractServiceClient implements InventoryServiceInterface
|
||||
{
|
||||
protected string $serviceName = 'InventoryService';
|
||||
|
||||
protected string $protocol = 'jsonrpc-http';
|
||||
|
||||
/**
|
||||
* 新增库存-手动单条.
|
||||
*/
|
||||
public function addInventory(array $data): void
|
||||
{
|
||||
$this->__request(__FUNCTION__, $data);
|
||||
}
|
||||
}
|
26
app/JsonRpc/InventoryServiceInterface.php
Normal file
26
app/JsonRpc/InventoryServiceInterface.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午1:58
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\JsonRpc;
|
||||
|
||||
interface InventoryServiceInterface
|
||||
{
|
||||
/**
|
||||
* 新增库存-手动单条.
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function addInventory(array $data): void;
|
||||
}
|
86
app/JsonRpc/UserAuthServiceConsumer.php
Normal file
86
app/JsonRpc/UserAuthServiceConsumer.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午8:36
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\JsonRpc;
|
||||
|
||||
use Hyperf\RpcClient\AbstractServiceClient;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午8:37
|
||||
* Description: 用户服务。
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class UserAuthServiceConsumer extends AbstractServiceClient implements UserAuthServiceInterface
|
||||
{
|
||||
protected string $serviceName = 'UserAuthService';
|
||||
|
||||
protected string $protocol = 'jsonrpc-http';
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function userLogin(array $data): array
|
||||
{
|
||||
return $this->__request(__FUNCTION__, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户.
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function addUser(array $data): void
|
||||
{
|
||||
$this->__request(__FUNCTION__, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function userInfoByToken(array $data): array
|
||||
{
|
||||
return $this->__request(__FUNCTION__, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加角色.
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function addRole(array $data): void
|
||||
{
|
||||
$this->__request(__FUNCTION__, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户列表.
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function userList(array $data): array
|
||||
{
|
||||
return $this->__request(__FUNCTION__, $data);
|
||||
}
|
||||
}
|
52
app/JsonRpc/UserAuthServiceInterface.php
Normal file
52
app/JsonRpc/UserAuthServiceInterface.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午8:35
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\JsonRpc;
|
||||
|
||||
interface UserAuthServiceInterface
|
||||
{
|
||||
/**
|
||||
* 用户登录
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function userLogin(array $data): array;
|
||||
|
||||
/**
|
||||
* 新增用户.
|
||||
*/
|
||||
public function addUser(array $data): void;
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function userInfoByToken(array $data): array;
|
||||
|
||||
/**
|
||||
* 添加角色.
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function addRole(array $data): void;
|
||||
|
||||
/**
|
||||
* 获取用户列表.
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function userList(array $data): array;
|
||||
}
|
72
app/Listener/AutoMainFieldsListener.php
Executable file
72
app/Listener/AutoMainFieldsListener.php
Executable file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Listener;
|
||||
|
||||
use App\Context\QueueContext;
|
||||
use App\Context\UserContext;
|
||||
use Hyperf\Database\Model\Events\Creating;
|
||||
use Hyperf\Database\Model\Events\Saving;
|
||||
use Hyperf\Event\Annotation\Listener;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/05/24
|
||||
* Time: 下午4:27
|
||||
* Description: 自动填充模型字段监听器,用于自动填充模型字段。
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
#[Listener]
|
||||
class AutoMainFieldsListener implements ListenerInterface
|
||||
{
|
||||
public function __construct(protected ContainerInterface $container)
|
||||
{
|
||||
}
|
||||
|
||||
public function listen(): array
|
||||
{
|
||||
return [
|
||||
Creating::class,
|
||||
Saving::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function process(object $event): void
|
||||
{
|
||||
/**
|
||||
* 在模型创建或保存事件中,自动设置模型的company_id和creator_id等相关字段。
|
||||
* 这个逻辑只在用户上下文存在,且事件为Creating或Saving时触发。
|
||||
*
|
||||
* @param object $event 事件对象,预期为Creating或Saving事件之一。
|
||||
* @return void
|
||||
*/
|
||||
|
||||
$user = QueueContext::getUser() ?? UserContext::getCurrentUser();
|
||||
$company = $user['company'] ?? [];
|
||||
|
||||
$model = $event->getModel();
|
||||
// 判断事件类型是否符合条件,以及用户上下文是否存在
|
||||
if (!($event instanceof Creating || $event instanceof Saving) || !$user) {
|
||||
return;
|
||||
}
|
||||
$schema = $model->getConnection()->getSchemaBuilder();
|
||||
|
||||
// 检查模型表是否有company_id字段,若有,则设置company_id
|
||||
if ($schema->hasColumn($model->getTable(), 'company_id')) {
|
||||
$model->company_id = $model->company_id ?? $company['id'] ?? 0;
|
||||
}
|
||||
|
||||
// 检查模型表是否有creator_id和creator_name字段,若有,则设置对应的值
|
||||
if ($schema->hasColumn($model->getTable(), 'creator_id')) {
|
||||
$model->creator_id ??= $user['id'];
|
||||
$model->creator_name ??= $user['name'] ?? '';
|
||||
}
|
||||
}
|
||||
}
|
80
app/Listener/DbQueryExecutedListener.php
Normal file
80
app/Listener/DbQueryExecutedListener.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Listener;
|
||||
|
||||
use Hyperf\Collection\Arr;
|
||||
use Hyperf\Database\Events\QueryExecuted;
|
||||
use Hyperf\Event\Annotation\Listener;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
#[Listener]
|
||||
class DbQueryExecutedListener implements ListenerInterface
|
||||
{
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private LoggerInterface $logger;
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->logger = $container->get(LoggerFactory::class)->get('sql', 'sql');
|
||||
}
|
||||
|
||||
public function listen(): array
|
||||
{
|
||||
return [
|
||||
QueryExecuted::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $event
|
||||
*/
|
||||
public function process(object $event): void
|
||||
{
|
||||
/**
|
||||
* 处理QueryExecuted事件,记录查询的SQL和执行时间。
|
||||
*
|
||||
* @param QueryExecuted $event 该事件对象包含执行的SQL语句、绑定参数和执行时间
|
||||
*/
|
||||
if ($event instanceof QueryExecuted) {
|
||||
$sql = $event->sql; // 获取执行的SQL语句
|
||||
|
||||
// 检查绑定参数是否为关联数组,如果不是,将 "?" 替换为实际的参数值
|
||||
if (!Arr::isAssoc($event->bindings)) {
|
||||
$position = 0; // 初始化绑定参数的搜索位置
|
||||
foreach ($event->bindings as $value) { // 遍历绑定参数
|
||||
$position = strpos($sql, '?', $position); // 查找下一个 "?" 位置
|
||||
if ($position === false) { // 如果找不到 "?",则退出循环
|
||||
break;
|
||||
}
|
||||
$value = "'$value'"; // 将参数值包裹在单引号中
|
||||
$sql = substr_replace($sql, $value, $position, 1); // 替换 "?" 为实际参数值
|
||||
$position += strlen($value); // 更新搜索位置
|
||||
}
|
||||
}
|
||||
|
||||
// 使用logger记录SQL语句和执行时间
|
||||
$this->logger->info(sprintf('[%s] %s', $event->time, $sql));
|
||||
}
|
||||
}
|
||||
}
|
74
app/Listener/QueueHandleListener.php
Normal file
74
app/Listener/QueueHandleListener.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Listener;
|
||||
|
||||
use Hyperf\AsyncQueue\AnnotationJob;
|
||||
use Hyperf\AsyncQueue\Event\AfterHandle;
|
||||
use Hyperf\AsyncQueue\Event\BeforeHandle;
|
||||
use Hyperf\AsyncQueue\Event\Event;
|
||||
use Hyperf\AsyncQueue\Event\FailedHandle;
|
||||
use Hyperf\AsyncQueue\Event\RetryHandle;
|
||||
use Hyperf\Event\Annotation\Listener;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
#[Listener]
|
||||
class QueueHandleListener implements ListenerInterface
|
||||
{
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->logger = $container->get(LoggerFactory::class)->get('queue');
|
||||
}
|
||||
|
||||
public function listen(): array
|
||||
{
|
||||
return [
|
||||
AfterHandle::class,
|
||||
BeforeHandle::class,
|
||||
FailedHandle::class,
|
||||
RetryHandle::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function process(object $event): void
|
||||
{
|
||||
if ($event instanceof Event && $event->getMessage()->job()) {
|
||||
$job = $event->getMessage()->job();
|
||||
$jobClass = get_class($job);
|
||||
if ($job instanceof AnnotationJob) {
|
||||
$jobClass = sprintf('Job[%s@%s]', $job->class, $job->method);
|
||||
}
|
||||
$date = date('Y-m-d H:i:s');
|
||||
|
||||
switch (true) {
|
||||
case $event instanceof BeforeHandle:
|
||||
$this->logger->info(sprintf('[%s] Processing %s.', $date, $jobClass));
|
||||
break;
|
||||
case $event instanceof AfterHandle:
|
||||
$this->logger->info(sprintf('[%s] Processed %s.', $date, $jobClass));
|
||||
break;
|
||||
case $event instanceof FailedHandle:
|
||||
$this->logger->error(sprintf('[%s] Failed %s.', $date, $jobClass));
|
||||
$this->logger->error((string) $event->getThrowable());
|
||||
break;
|
||||
case $event instanceof RetryHandle:
|
||||
$this->logger->warning(sprintf('[%s] Retried %s.', $date, $jobClass));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
app/Listener/ResumeExitCoordinatorListener.php
Normal file
35
app/Listener/ResumeExitCoordinatorListener.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Listener;
|
||||
|
||||
use Hyperf\Command\Event\AfterExecute;
|
||||
use Hyperf\Coordinator\Constants;
|
||||
use Hyperf\Coordinator\CoordinatorManager;
|
||||
use Hyperf\Event\Annotation\Listener;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
|
||||
#[Listener]
|
||||
class ResumeExitCoordinatorListener implements ListenerInterface
|
||||
{
|
||||
public function listen(): array
|
||||
{
|
||||
return [
|
||||
AfterExecute::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function process(object $event): void
|
||||
{
|
||||
CoordinatorManager::until(Constants::WORKER_EXIT)->resume();
|
||||
}
|
||||
}
|
46
app/Log/AliSlsHandler.php
Normal file
46
app/Log/AliSlsHandler.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午6:33
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Log;
|
||||
|
||||
use App\Amqp\Producer\AliSlsProducer;
|
||||
use Hyperf\Amqp\Producer;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Monolog\Handler\AbstractProcessingHandler;
|
||||
use Monolog\LogRecord;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午7:19
|
||||
* Description: 阿里云日志服务处理器.
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class AliSlsHandler extends AbstractProcessingHandler
|
||||
{
|
||||
#[Inject]
|
||||
protected Producer $producer;
|
||||
|
||||
protected function write(LogRecord $record): void
|
||||
{
|
||||
$logs = ['channel' => $record['channel'] ?? '', 'formatted' => $record['formatted'] ?? ''];
|
||||
$message = new AliSlsProducer($logs);
|
||||
$this->producer->produce($message, true);
|
||||
}
|
||||
}
|
44
app/Log/AppendRequestIdProcessor.php
Normal file
44
app/Log/AppendRequestIdProcessor.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午6:33
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Log;
|
||||
|
||||
use Hyperf\Context\Context;
|
||||
use Hyperf\Coroutine\Coroutine;
|
||||
use Monolog\LogRecord;
|
||||
use Monolog\Processor\ProcessorInterface;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午7:19
|
||||
* Description: 添加请求ID和协程ID
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class AppendRequestIdProcessor implements ProcessorInterface
|
||||
{
|
||||
public const string REQUEST_ID = 'log.request.id';
|
||||
|
||||
public function __invoke(LogRecord $record): array|LogRecord
|
||||
{
|
||||
$record['extra']['request_id'] = Context::getOrSet(self::REQUEST_ID, uniqid('xw_cloud'));
|
||||
$record['extra']['coroutine_id'] = Coroutine::id();
|
||||
return $record;
|
||||
}
|
||||
}
|
62
app/Log/Log.php
Normal file
62
app/Log/Log.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午6:42
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Log;
|
||||
|
||||
use App\Exception\ApiException;
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use function Hyperf\Support\make;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午7:19
|
||||
* Description: 日志记录器类。
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class Log
|
||||
{
|
||||
#[Inject]
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
/**
|
||||
* 根据提供的名称和分组获取日志记录器实例。
|
||||
*
|
||||
* @param string $name 日志记录器的名称,默认为'app'。
|
||||
* @param string $group 日志记录器的分组,默认为'job'。
|
||||
* @return LoggerInterface 返回一个日志记录器实例。
|
||||
*/
|
||||
public static function get(string $name = 'app', string $group = 'job'): LoggerInterface
|
||||
{
|
||||
try {
|
||||
// 尝试从应用上下文容器中获取LoggerFactory实例,并进一步获取指定名称和分组的日志记录器
|
||||
return ApplicationContext::getContainer()->get(LoggerFactory::class)->get($name, $group);
|
||||
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
|
||||
// 如果在获取过程中发生异常,使用默认的日志记录器记录错误信息,并抛出ApiException
|
||||
$logs = make(LoggerFactory::class)->get('default');
|
||||
$logs->error($e->getMessage());
|
||||
throw new ApiException($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
48
app/Log/StdoutLoggerFactory.php
Normal file
48
app/Log/StdoutLoggerFactory.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午6:03
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Log;
|
||||
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午6:04
|
||||
* Description: 创建一个日志记录器
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class StdoutLoggerFactory
|
||||
{
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
* @return LoggerInterface
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container): LoggerInterface
|
||||
{
|
||||
return ApplicationContext::getContainer()->get(LoggerFactory::class)->get();
|
||||
}
|
||||
}
|
77
app/Middleware/CheckTokenMiddleware.php
Normal file
77
app/Middleware/CheckTokenMiddleware.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/4
|
||||
* Time: 下午2:40
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Middleware;
|
||||
|
||||
use App\Context\UserContext;
|
||||
use App\Exception\ApiException;
|
||||
use App\JsonRpc\UserAuthServiceInterface;
|
||||
use App\Repository\Company\FirstCompanyRepository;
|
||||
use Exception;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/4
|
||||
* Time: 下午2:43
|
||||
* Description: 验证Token中间件.
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class CheckTokenMiddleware implements MiddlewareInterface
|
||||
{
|
||||
#[Inject]
|
||||
protected UserAuthServiceInterface $userAuthServiceInterface;
|
||||
|
||||
public function __construct(protected ContainerInterface $container)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$token = $this->extractToken($request);
|
||||
|
||||
$rpcResult = $this->userAuthServiceInterface->userInfoByToken(['token' => $token]);
|
||||
|
||||
$userInfo = $rpcResult['result']['user'] ?? [];
|
||||
if (!$userInfo) {
|
||||
throw new ApiException('用户信息不存在', 401);
|
||||
}
|
||||
|
||||
UserContext::setCurrentUser($userInfo);
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
protected function extractToken(ServerRequestInterface $request): string
|
||||
{
|
||||
$authorizationHeader = $request->getHeaderLine('Authorization') ?? '';
|
||||
if (!$authorizationHeader) {
|
||||
throw new ApiException('授权标头无效', 401);
|
||||
}
|
||||
return str_starts_with($authorizationHeader, 'Bearer ') ? substr($authorizationHeader, 7) : '';
|
||||
}
|
||||
}
|
70
app/Middleware/RequestLogsMiddleware.php
Normal file
70
app/Middleware/RequestLogsMiddleware.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午10:43
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Middleware;
|
||||
|
||||
use App\Service\QueueService;
|
||||
use App\Service\SysService;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午10:44
|
||||
* Description: 请求日志中间件
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class RequestLogsMiddleware implements MiddlewareInterface
|
||||
{
|
||||
#[Inject]
|
||||
protected QueueService $queueService;
|
||||
|
||||
#[Inject]
|
||||
protected SysService $sysService;
|
||||
|
||||
public function __construct(protected ContainerInterface $container)
|
||||
{
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$method = $request->getMethod(); // 请求方法
|
||||
$uri = $request->getUri()->getPath(); // 请求的URI路径
|
||||
$params = $request->getMethod() === 'POST' ? $request->getParsedBody() : $request->getQueryParams();
|
||||
|
||||
$clientIp = $this->sysService->getClientIpInfo($request);
|
||||
|
||||
$data = [
|
||||
'params' => $params,
|
||||
'method' => $method,
|
||||
'uri' => $uri,
|
||||
'client_ip' => $clientIp,
|
||||
];
|
||||
|
||||
// 添加请求日志,存储到队列中
|
||||
$this->queueService->make($data)->writeRequestLogs();
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
49
app/Model/ColumnConfig.php
Executable file
49
app/Model/ColumnConfig.php
Executable file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2024/12/24
|
||||
* Time: 下午3:26
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
class ColumnConfig extends Model
|
||||
{
|
||||
protected ?string $table = 'column_config';
|
||||
|
||||
protected array $fillable = [
|
||||
'company_id',
|
||||
'method',
|
||||
'prop',
|
||||
'label',
|
||||
'sortable',
|
||||
'sort',
|
||||
'width',
|
||||
'hide',
|
||||
'fix',
|
||||
'filter',
|
||||
'is_search',
|
||||
'search_type',
|
||||
'condition',
|
||||
'creator_id',
|
||||
'creator_name',
|
||||
];
|
||||
|
||||
protected array $casts = [
|
||||
'created_at' => 'datetime:Y-m-d H:i:s',
|
||||
'updated_at' => 'datetime:Y-m-d H:i:s',
|
||||
'sortable' => 'boolean',
|
||||
'hide' => 'boolean',
|
||||
'fix' => 'boolean',
|
||||
'filter' => 'boolean',
|
||||
'is_search' => 'boolean',
|
||||
];
|
||||
}
|
27
app/Model/Company.php
Normal file
27
app/Model/Company.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午5:58
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
class Company extends Model
|
||||
{
|
||||
protected ?string $table = 'company';
|
||||
|
||||
protected array $fillable = [
|
||||
'company_type',
|
||||
'name',
|
||||
'status',
|
||||
];
|
||||
}
|
37
app/Model/FirstCompany.php
Normal file
37
app/Model/FirstCompany.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午9:18
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
class FirstCompany extends Model
|
||||
{
|
||||
protected ?string $table = 'first_company';
|
||||
|
||||
protected array $fillable = [
|
||||
'domain',
|
||||
'name',
|
||||
'full_name',
|
||||
'company_type',
|
||||
'address',
|
||||
'logo',
|
||||
'owner', // 公司负责人
|
||||
'id_card', // 法人身份证
|
||||
'mobile',
|
||||
'org_code',
|
||||
'remark',
|
||||
'active_status',
|
||||
'activation_date',
|
||||
];
|
||||
}
|
27
app/Model/Model.php
Normal file
27
app/Model/Model.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use Hyperf\Database\Model\SoftDeletes;
|
||||
use Hyperf\DbConnection\Model\Model as BaseModel;
|
||||
use Hyperf\ModelCache\Cacheable;
|
||||
use Hyperf\ModelCache\CacheableInterface;
|
||||
|
||||
abstract class Model extends BaseModel implements CacheableInterface
|
||||
{
|
||||
use Cacheable, SoftDeletes;
|
||||
|
||||
protected ?string $dateFormat = 'U';
|
||||
|
||||
protected array $hidden = ['deleted_at', 'password'];
|
||||
}
|
63
app/Model/OperatorLogs.php
Normal file
63
app/Model/OperatorLogs.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午7:33
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use Hyperf\Database\Model\Builder;
|
||||
use Hyperf\Database\Model\SoftDeletingScope;
|
||||
|
||||
class OperatorLogs extends Model
|
||||
{
|
||||
protected ?string $table = 'operator_logs';
|
||||
|
||||
protected array $fillable = [
|
||||
'company_id',
|
||||
'location',
|
||||
'type',
|
||||
'log_title',
|
||||
'route',
|
||||
'params',
|
||||
'ip',
|
||||
'source',
|
||||
'timer',
|
||||
'agent',
|
||||
'remark',
|
||||
'creator_id',
|
||||
'creator_name',
|
||||
];
|
||||
|
||||
protected array $casts = [
|
||||
'created_at' => 'datetime:Y-m-d H:i:s',
|
||||
'updated_at' => 'datetime:Y-m-d H:i:s',
|
||||
'params' => 'json',
|
||||
];
|
||||
|
||||
public function setIpAttribute(mixed $value): void
|
||||
{
|
||||
$this->attributes['ip'] = sprintf('%u', ip2long($value));
|
||||
}
|
||||
|
||||
public function getIpAttribute(mixed $value): string
|
||||
{
|
||||
return long2ip($value);
|
||||
}
|
||||
|
||||
// 忽略软删除
|
||||
public function newQuery(bool $cache = false): Builder
|
||||
{
|
||||
$builder = parent::newQuery();
|
||||
return $builder->withoutGlobalScope(SoftDeletingScope::class);
|
||||
}
|
||||
}
|
43
app/Model/Purchase.php
Normal file
43
app/Model/Purchase.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午4:42
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
class Purchase extends Model
|
||||
{
|
||||
protected ?string $table = 'purchase';
|
||||
|
||||
protected array $fillable = [
|
||||
'purchase_sn',
|
||||
'warehouse_ids',
|
||||
'contract_sn',
|
||||
'purchase_time',
|
||||
'customer_type',
|
||||
'container_type',
|
||||
'container_count',
|
||||
'original_count',
|
||||
'original_cube',
|
||||
'receiving_container',
|
||||
'receiving_count',
|
||||
'receiving_cube',
|
||||
'kz_company_id',
|
||||
'kz_company_name',
|
||||
'sh_company_id',
|
||||
'sh_company_name',
|
||||
'remark',
|
||||
'is_from_erp',
|
||||
'erp_return_id',
|
||||
];
|
||||
}
|
34
app/Model/TableConfig.php
Executable file
34
app/Model/TableConfig.php
Executable file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2024/12/24
|
||||
* Time: 下午3:34
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
class TableConfig extends Model
|
||||
{
|
||||
protected ?string $table = 'table_config';
|
||||
|
||||
protected array $fillable = [
|
||||
'company_id',
|
||||
'method',
|
||||
'size',
|
||||
'config',
|
||||
];
|
||||
|
||||
protected array $casts = [
|
||||
'created_at' => 'datetime:Y-m-d H:i:s',
|
||||
'updated_at' => 'datetime:Y-m-d H:i:s',
|
||||
'config' => 'json',
|
||||
];
|
||||
}
|
21
app/Process/AsyncQueueConsumer.php
Normal file
21
app/Process/AsyncQueueConsumer.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Process;
|
||||
|
||||
use Hyperf\AsyncQueue\Process\ConsumerProcess;
|
||||
use Hyperf\Process\Annotation\Process;
|
||||
|
||||
#[Process]
|
||||
class AsyncQueueConsumer extends ConsumerProcess
|
||||
{
|
||||
}
|
72
app/Repository/AbstractRepository.php
Executable file
72
app/Repository/AbstractRepository.php
Executable file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2024/12/24
|
||||
* Time: 下午3:04
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Service\Trait\ColumnConfigTrait;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2024/12/24
|
||||
* Time: 下午3:06
|
||||
* Description: BaseRepository类用于提供一个基本的仓库模式实现。它包含一个DAO(数据访问对象)属性,并允许通过魔术方法动态调用DAO上的方法。
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class AbstractRepository
|
||||
{
|
||||
use ColumnConfigTrait;
|
||||
|
||||
/**
|
||||
* 存储数据访问对象的属性。
|
||||
* @var string
|
||||
*/
|
||||
protected mixed $dao;
|
||||
|
||||
/**
|
||||
* 当尝试调用不存在的实例方法时,自动调用此方法。
|
||||
* 允许通过对象的动态方法调用DAO上的相应方法。
|
||||
* @param string $name 被调用的方法名
|
||||
* @param array $arguments 被调用方法的参数数组
|
||||
* @return mixed 返回DAO方法的执行结果
|
||||
*/
|
||||
public function __call(string $name, array $arguments)
|
||||
{
|
||||
// 动态调用DAO上的方法。
|
||||
return call_user_func_array([$this->dao, $name], $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置DAO对象。
|
||||
* 用于设置存储数据访问对象的属性。
|
||||
* @param string $dao 数据访问对象的实例
|
||||
*/
|
||||
public function setDao(string $dao): void
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
}
|
87
app/Repository/Company/CompanyRepository.php
Normal file
87
app/Repository/Company/CompanyRepository.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午6:00
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository\Company;
|
||||
|
||||
use App\Dao\Company\CompanyDao;
|
||||
use App\Repository\AbstractRepository;
|
||||
use Exception;
|
||||
use function Hyperf\Collection\collect;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午6:01
|
||||
* Description: CompanyRepository类用于提供公司相关的数据访问和操作。它继承自AbstractRepository类,并实现了CompanyDao接口。
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class CompanyRepository extends AbstractRepository
|
||||
{
|
||||
public function __construct(CompanyDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加公司。
|
||||
* @param array $data
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function add(array $data): void
|
||||
{
|
||||
$count = $this->dao->builder();
|
||||
if (!empty($data['id'])) {
|
||||
$count->where('id', '!=', $data['id']);
|
||||
}
|
||||
|
||||
// 重复验证邮箱和手机号码
|
||||
$make = $this->dao->make();
|
||||
$verify = $make->verifyData(clone $count);
|
||||
$collect = clone collect($verify->get());
|
||||
|
||||
$errors = [];
|
||||
if ($collect->where('name', '=', $data['name'])->isNotEmpty()) {
|
||||
$errors[] = '公司名称已存在';
|
||||
}
|
||||
if (!empty($errors)) {
|
||||
throw new Exception(implode(',', $errors));
|
||||
}
|
||||
|
||||
$this->dao->commonCreate($this->paramsData($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数数据。
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
private function paramsData(array $params): array
|
||||
{
|
||||
if (empty($params['id'])) {
|
||||
return $params;
|
||||
}
|
||||
return [
|
||||
'id' => $params['id'],
|
||||
'company_type' => $params['company_type'], // 公司类别:1开证 2收货
|
||||
'name' => $params['name'], // 公司名称
|
||||
'status' => $params['status'] // 状态:0禁用 1启用
|
||||
];
|
||||
}
|
||||
}
|
111
app/Repository/Company/FirstCompanyRepository.php
Normal file
111
app/Repository/Company/FirstCompanyRepository.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午9:25
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository\Company;
|
||||
|
||||
use App\Dao\Company\FirstCompanyDao;
|
||||
use App\Repository\AbstractRepository;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use function Hyperf\Collection\collect;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午9:26
|
||||
* Description: 平台公司数据仓库类.
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class FirstCompanyRepository extends AbstractRepository
|
||||
{
|
||||
public function __construct(FirstCompanyDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增公司.
|
||||
* @throws Exception
|
||||
*/
|
||||
public function addCompany(array $data): void
|
||||
{
|
||||
$count = $this->dao->builder();
|
||||
|
||||
// 重复字段验证
|
||||
$make = $this->dao->make();
|
||||
$verify = $make->verifyData(clone $count, $data);
|
||||
$collect = clone collect($verify->get());
|
||||
|
||||
$errors = [];
|
||||
if ($collect->where('name', '=', $data['name'])->isNotEmpty()) {
|
||||
$errors[] = '公司简称已存在';
|
||||
}
|
||||
if ($collect->where('full_name', '=', $data['full_name'])->isNotEmpty()) {
|
||||
$errors[] = '公司全称已存在';
|
||||
}
|
||||
if ($collect->where('mobile', '=', $data['mobile'])->isNotEmpty()) {
|
||||
$errors[] = '负责人手机已存在';
|
||||
}
|
||||
if (!empty($errors)) {
|
||||
throw new Exception(implode(',', $errors));
|
||||
}
|
||||
|
||||
$this->dao->commonCreate($this->paramsData($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数处理.
|
||||
*/
|
||||
private function paramsData(array $params): array
|
||||
{
|
||||
if (empty($params['id'])) {
|
||||
return $params;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $params['id'],
|
||||
'domain' => $params['domain'],
|
||||
'name' => $params['name'],
|
||||
'full_name' => $params['full_name'],
|
||||
'company_type' => $params['company_type'],
|
||||
'address' => $params['address'] ?? '',
|
||||
'logo' => $params['logo'] ?? '',
|
||||
'owner' => $params['owner'] ?? '', // 公司负责人
|
||||
'id_card' => $params['id_card'] ?? '', // 法人身份证
|
||||
'mobile' => $params['mobile'],
|
||||
'org_code' => $params['org_code'] ?? '',
|
||||
'remark' => $params['remark'] ?? '',
|
||||
'active_status' => $params['active_status'],
|
||||
'activation_date' => Carbon::parse($params['activation_date'])->timestamp,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据公司全称获取公司信息.
|
||||
* @param string $fullName
|
||||
* @return array|null
|
||||
*/
|
||||
public function getCompanyByFullName(string $fullName): ?array
|
||||
{
|
||||
return $this->dao->builder()
|
||||
->select($this->dao->getFields())
|
||||
->where('full_name', '=', $fullName)
|
||||
->first()?->toArray();
|
||||
}
|
||||
}
|
43
app/Repository/Purchase/PurchaseRepository.php
Normal file
43
app/Repository/Purchase/PurchaseRepository.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午5:31
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository\Purchase;
|
||||
|
||||
use App\Dao\Purchase\PurchaseDao;
|
||||
use App\Repository\AbstractRepository;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午5:32
|
||||
* Description: PurchaseRepository类用于提供采购相关的数据访问和操作。它继承自AbstractRepository类,并实现了PurchaseDao接口。
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class PurchaseRepository extends AbstractRepository
|
||||
{
|
||||
public function __construct(PurchaseDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
public function addPurchase(array $data): void
|
||||
{
|
||||
|
||||
}
|
||||
}
|
37
app/Request/AbstractRequest.php
Normal file
37
app/Request/AbstractRequest.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午8:22
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Request;
|
||||
|
||||
use Hyperf\Validation\Request\FormRequest;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午8:24
|
||||
* Description: 请求基类。
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
abstract class AbstractRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
51
app/Request/CompanyRequest.php
Normal file
51
app/Request/CompanyRequest.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/4
|
||||
* Time: 下午2:54
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Request;
|
||||
|
||||
use App\Constants\ActiveStatusConst;
|
||||
use App\Constants\CompanyTypeConst;
|
||||
|
||||
class CompanyRequest extends AbstractRequest
|
||||
{
|
||||
public array $scenes = [
|
||||
'addCompany' => [
|
||||
'id',
|
||||
'company_type',
|
||||
'name',
|
||||
'status',
|
||||
],
|
||||
];
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'id' => 'integer',
|
||||
'company_type' => 'required|in:' . implode(',', array_column(CompanyTypeConst::getConstantsList(), 'value')),
|
||||
'name' => 'required|string|max:128',
|
||||
'status' => 'integer|in:0,1',
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'id' => '公司ID',
|
||||
'company_type' => '公司类型',
|
||||
'name' => '公司名称',
|
||||
'status' => '状态',
|
||||
];
|
||||
}
|
||||
}
|
89
app/Request/FirstCompanyRequest.php
Normal file
89
app/Request/FirstCompanyRequest.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/5
|
||||
* Time: 下午9:28
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Request;
|
||||
|
||||
class FirstCompanyRequest extends AbstractRequest
|
||||
{
|
||||
public array $scenes = [
|
||||
'addFirstCompany' => [
|
||||
'domain',
|
||||
'name',
|
||||
'full_name',
|
||||
'company_type',
|
||||
'address',
|
||||
'logo',
|
||||
'owner', // 公司负责人
|
||||
'id_card', // 法人身份证
|
||||
'mobile',
|
||||
'org_code',
|
||||
'remark',
|
||||
'active_status',
|
||||
'activation_date',
|
||||
],
|
||||
];
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'id' => 'integer',
|
||||
'name' => 'required|string|max:60',
|
||||
// 域名只能是英文、数字、下划线、短横线
|
||||
'domain' => 'required|string|max:100|regex:/^[a-zA-Z0-9_-]+$/',
|
||||
'full_name' => 'required|string|max:255',
|
||||
'company_type' => 'required|in:1,2',
|
||||
'address' => 'required|string|max:255',
|
||||
'logo' => 'string|max:255',
|
||||
'owner' => 'required|string|max:45',
|
||||
'id_card' => 'string|max:18',
|
||||
// 验证手机号,加正则验证
|
||||
'mobile' => 'required|string|max:11|regex:/^1[3-9]\d{9}$/',
|
||||
'org_code' => 'string|max:64',
|
||||
'remark' => 'string|max:255',
|
||||
'active_status' => 'required|integer|in:0,1',
|
||||
// 激活日期:不能小于当前时间
|
||||
'activation_date' => 'date|date_format:Y-m-d|after_or_equal:today',
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'id' => '公司ID',
|
||||
'name' => '公司名称',
|
||||
'domain' => '公司域名',
|
||||
'full_name' => '公司全称',
|
||||
'company_type' => '公司类型',
|
||||
'address' => '公司地址',
|
||||
'logo' => '公司logo',
|
||||
'owner' => '公司负责人',
|
||||
'id_card' => '法人身份证',
|
||||
'mobile' => '手机号码',
|
||||
'org_code' => '组织机构代码',
|
||||
'remark' => '备注',
|
||||
'active_status' => '激活状态',
|
||||
'activation_date' => '激活日期',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'domain.regex' => '只能是英文、数字、下划线、短横线',
|
||||
'mobile.regex' => '手机号码格式不正确',
|
||||
'activation_date.after_or_equal' => '激活日期不能小于当前时间',
|
||||
];
|
||||
}
|
||||
}
|
84
app/Request/PurchaseRequest.php
Normal file
84
app/Request/PurchaseRequest.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午8:25
|
||||
* Description: 用户验证类。
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Request;
|
||||
|
||||
class PurchaseRequest extends AbstractRequest
|
||||
{
|
||||
public array $scenes = [
|
||||
'userLogin' => ['login_name', 'password'],
|
||||
'addUser' => [
|
||||
'login_name', // 登录用户名
|
||||
'password' => 'string|between:6,15', // 密码
|
||||
'dept_id', // 部门ID
|
||||
'department', // 部门名称
|
||||
'emp_id', // 员工工号
|
||||
'name', // 姓名
|
||||
'mobile', // 手机号
|
||||
'email', // 邮箱地址
|
||||
'avatar', // 头像
|
||||
'active_status', // 状态 0禁用 1启用
|
||||
'remark', // 备注
|
||||
'role_ids', // 角色ID列表
|
||||
'role_ids.*' // 角色ID
|
||||
],
|
||||
'getUserList' => [
|
||||
'page', // 页码
|
||||
'pageSize', // 页大小
|
||||
]
|
||||
];
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'login_name' => 'required|string|regex:/^[a-zA-Z0-9_]+$/|between:3,20',
|
||||
'password' => 'required|string|between:6,15',
|
||||
'dept_id' => 'integer|min:1',
|
||||
'department' => 'string|between:2,20',
|
||||
'emp_id' => 'integer|min:1',
|
||||
'name' => 'string|between:2,20',
|
||||
'mobile' => 'string|between:11,11',
|
||||
'email' => 'string|between:6,30',
|
||||
'avatar' => 'string|between:1,255',
|
||||
'active_status' => 'in:0,1',
|
||||
'remark' => 'string|between:1,255',
|
||||
'role_ids' => 'array',
|
||||
'role_ids.*' => 'integer|min:1|distinct',
|
||||
'page' => 'integer|min:1',
|
||||
'pageSize' => 'integer|min:1',
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'login_name' => '登录账号',
|
||||
'password' => '登录密码',
|
||||
'dept_id' => '部门ID',
|
||||
'department' => '部门名称',
|
||||
'emp_id' => '员工工号',
|
||||
'name' => '姓名',
|
||||
'mobile' => '手机号',
|
||||
'email' => '邮箱地址',
|
||||
'avatar' => '头像',
|
||||
'active_status' => '状态',
|
||||
'remark' => '备注',
|
||||
'role_ids' => '角色ID列表',
|
||||
'role_ids.*' => '角色ID',
|
||||
'page' => '页码',
|
||||
'pageSize' => '页大小',
|
||||
];
|
||||
}
|
||||
}
|
48
app/Request/RoleRequest.php
Normal file
48
app/Request/RoleRequest.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/4
|
||||
* Time: 下午2:54
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Request;
|
||||
|
||||
class RoleRequest extends AbstractRequest
|
||||
{
|
||||
public array $scenes = [
|
||||
'addRole' => [
|
||||
'id',
|
||||
'role_name',
|
||||
'active_status',
|
||||
'sort',
|
||||
],
|
||||
];
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'id' => 'integer',
|
||||
'role_name' => 'required|string',
|
||||
'active_status' => 'required|integer',
|
||||
'sort' => 'required|integer',
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'id' => '角色ID',
|
||||
'role_name' => '角色名称',
|
||||
'active_status' => '角色状态',
|
||||
'sort' => '排序',
|
||||
];
|
||||
}
|
||||
}
|
84
app/Request/UserRequest.php
Normal file
84
app/Request/UserRequest.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午8:25
|
||||
* Description: 用户验证类。
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Request;
|
||||
|
||||
class UserRequest extends AbstractRequest
|
||||
{
|
||||
public array $scenes = [
|
||||
'userLogin' => ['login_name', 'password'],
|
||||
'addUser' => [
|
||||
'login_name', // 登录用户名
|
||||
'password' => 'string|between:6,15', // 密码
|
||||
'dept_id', // 部门ID
|
||||
'department', // 部门名称
|
||||
'emp_id', // 员工工号
|
||||
'name', // 姓名
|
||||
'mobile', // 手机号
|
||||
'email', // 邮箱地址
|
||||
'avatar', // 头像
|
||||
'active_status', // 状态 0禁用 1启用
|
||||
'remark', // 备注
|
||||
'role_ids', // 角色ID列表
|
||||
'role_ids.*' // 角色ID
|
||||
],
|
||||
'getUserList' => [
|
||||
'page', // 页码
|
||||
'pageSize', // 页大小
|
||||
]
|
||||
];
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'login_name' => 'required|string|regex:/^[a-zA-Z0-9_]+$/|between:3,20',
|
||||
'password' => 'required|string|between:6,15',
|
||||
'dept_id' => 'integer|min:1',
|
||||
'department' => 'string|between:2,20',
|
||||
'emp_id' => 'integer|min:1',
|
||||
'name' => 'string|between:2,20',
|
||||
'mobile' => 'string|between:11,11',
|
||||
'email' => 'string|between:6,30',
|
||||
'avatar' => 'string|between:1,255',
|
||||
'active_status' => 'in:0,1',
|
||||
'remark' => 'string|between:1,255',
|
||||
'role_ids' => 'array',
|
||||
'role_ids.*' => 'integer|min:1|distinct',
|
||||
'page' => 'integer|min:1',
|
||||
'pageSize' => 'integer|min:1',
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'login_name' => '登录账号',
|
||||
'password' => '登录密码',
|
||||
'dept_id' => '部门ID',
|
||||
'department' => '部门名称',
|
||||
'emp_id' => '员工工号',
|
||||
'name' => '姓名',
|
||||
'mobile' => '手机号',
|
||||
'email' => '邮箱地址',
|
||||
'avatar' => '头像',
|
||||
'active_status' => '状态',
|
||||
'remark' => '备注',
|
||||
'role_ids' => '角色ID列表',
|
||||
'role_ids.*' => '角色ID',
|
||||
'page' => '页码',
|
||||
'pageSize' => '页大小',
|
||||
];
|
||||
}
|
||||
}
|
73
app/Scope/CompanyScope.php
Executable file
73
app/Scope/CompanyScope.php
Executable file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2024/12/24
|
||||
* Time: 下午3:19
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scope;
|
||||
|
||||
use App\Context\QueueContext;
|
||||
use App\Context\UserContext;
|
||||
use App\Utils\TableUtils;
|
||||
use Exception;
|
||||
use Hyperf\Database\Model\Builder;
|
||||
use Hyperf\Database\Model\Model;
|
||||
use Hyperf\Database\Model\Scope;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\Redis\Redis;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/5/7
|
||||
* Time: 下午4:38
|
||||
* Description: 作用域类,用于限制查询结果为当前公司、供应商的记录。
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class CompanyScope implements Scope
|
||||
{
|
||||
use TableUtils;
|
||||
|
||||
#[Inject]
|
||||
protected Redis $redis;
|
||||
|
||||
/**
|
||||
* 将当前用户所属公司的条件应用于查询构建器。
|
||||
*
|
||||
* @param Builder $builder 查询构建器实例。
|
||||
* @param Model $model 模型实例,通常是想要应用作用域的模型。
|
||||
* @return Builder 应用了作用域条件的查询构建器实例。
|
||||
* @throws Exception
|
||||
*/
|
||||
public function apply(Builder $builder, Model $model): Builder
|
||||
{
|
||||
// 尝试从上下文中获取当前用户信息
|
||||
$user = QueueContext::getUser() ?? UserContext::getCurrentUser();
|
||||
$company = $user['company'];
|
||||
// 如果没有找到用户信息,则不应用任何条件,直接返回查询构建器
|
||||
if (!$user || !$company) {
|
||||
return $builder;
|
||||
}
|
||||
|
||||
// 如果存在 company_id,应用企业作用域
|
||||
if (!empty($companyId = $company['id'] ?? null)) {
|
||||
return self::hasColumn($model, 'company_id')
|
||||
? $builder->where($model->getTable() . '.company_id', $companyId)
|
||||
: $builder;
|
||||
}
|
||||
|
||||
return $builder;
|
||||
}
|
||||
}
|
158
app/Service/AliLogsSignService.php
Normal file
158
app/Service/AliLogsSignService.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午7:13
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use Hyperf\Config\Annotation\Value;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\Guzzle\ClientFactory;
|
||||
use function Hyperf\Coroutine\co;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午7:18
|
||||
* Description: 阿里云日志服务.
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class AliLogsSignService
|
||||
{
|
||||
protected const string LOG_SIGNATURE_METHOD = 'hmac-sha1';
|
||||
|
||||
protected const string LOG_API_VERSION = '0.6.0';
|
||||
|
||||
#[Value('alibaba.accessKeyId')]
|
||||
protected string $accessKeyId;
|
||||
|
||||
#[Value('alibaba.accessKeySecret')]
|
||||
protected string $accessKeySecret;
|
||||
|
||||
#[Value('alibaba.logs')]
|
||||
protected array $logs;
|
||||
|
||||
#[Inject]
|
||||
protected ClientFactory $clientFactory;
|
||||
|
||||
/**
|
||||
* 签名及请求头拼接.
|
||||
* @param mixed $method
|
||||
* @param mixed $uri
|
||||
* @param mixed $params
|
||||
* @param mixed $body
|
||||
* @param mixed $logProject
|
||||
* @param mixed $logEndpoint
|
||||
*/
|
||||
public function buildHeaders(
|
||||
string $method,
|
||||
string $uri,
|
||||
array $params,
|
||||
string $body,
|
||||
string|object $logProject,
|
||||
string $logEndpoint
|
||||
): array
|
||||
{
|
||||
$headers = [
|
||||
'x-log-signaturemethod' => self::LOG_SIGNATURE_METHOD,
|
||||
'x-log-apiversion' => self::LOG_API_VERSION,
|
||||
'Host' => sprintf('%s.%s', $logProject, $logEndpoint),
|
||||
'Content-Type' => 'application/json',
|
||||
];
|
||||
$contentLength = 0;
|
||||
$contentMd5 = '';
|
||||
if (!empty($body) && strlen($body) > 0) {
|
||||
$contentLength = strlen($body);
|
||||
$contentMd5 = strtoupper(md5($body));
|
||||
$headers['Content-MD5'] = $contentMd5;
|
||||
}
|
||||
// date
|
||||
setlocale(LC_TIME, 'en_US');
|
||||
$date = gmdate('D, d M Y H:i:s \G\M\T', time());
|
||||
$headers['Date'] = $date;
|
||||
$headers['Content-Length'] = (string)$contentLength;
|
||||
$contentType = $headers['Content-Type'];
|
||||
$message = $method . "\n" . $contentMd5 . "\n" . $contentType . "\n" . $date . "\n";
|
||||
// header
|
||||
$filterHeaders = [];
|
||||
foreach ($headers as $key => $val) {
|
||||
if (str_starts_with($key, 'x-log-') || str_starts_with($key, 'x-acs-')) {
|
||||
$filterHeaders[$key] = $val;
|
||||
}
|
||||
}
|
||||
ksort($filterHeaders);
|
||||
foreach ($filterHeaders as $key => $val) {
|
||||
$message .= $key . ':' . $val . "\n";
|
||||
}
|
||||
// uri and params
|
||||
$message .= $uri;
|
||||
if (sizeof($params) > 0) {
|
||||
$message .= '?';
|
||||
}
|
||||
ksort($params);
|
||||
$sep = '';
|
||||
foreach ($params as $key => $val) {
|
||||
$message .= $sep . $key . '=' . $val;
|
||||
$sep = '&';
|
||||
}
|
||||
// signature & authorization
|
||||
$signature = $this->generateSignature($message);
|
||||
$auth = 'LOG ' . $this->accessKeyId . ':' . $signature;
|
||||
$headers['Authorization'] = $auth;
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现调用PutWebTracking接口将多条日志合并进行采集.
|
||||
*/
|
||||
public function putWebTracking(array $record): void
|
||||
{
|
||||
$logEndpoint = $this->logs['log_endpoint'];
|
||||
$logProject = $this->logs['log_project'];
|
||||
$logStores = $this->logs['log_store'];
|
||||
$params = [];
|
||||
$body = [
|
||||
'__topic__' => 'mes api logs',
|
||||
'__source__' => 'admin api',
|
||||
'__logs__' => [
|
||||
['Logs' => $record['formatted']],
|
||||
],
|
||||
'__tags__' => [
|
||||
'mes' => $record['channel'],
|
||||
],
|
||||
];
|
||||
$body = json_encode($body);
|
||||
|
||||
$sign_url = sprintf('/logstores/%s/track', $logStores);
|
||||
$headers = $this->buildHeaders('POST', $sign_url, $params, $body, $logProject, $logEndpoint);
|
||||
$options = [
|
||||
'headers' => $headers,
|
||||
'body' => $body,
|
||||
'query' => $params,
|
||||
];
|
||||
$url = sprintf('https://%s.%s/logstores/%s/track', $logProject, $logEndpoint, $logStores);
|
||||
$client = $this->clientFactory->create();
|
||||
co(function () use ($client, $url, $options) {
|
||||
$client->request('POST', $url, $options);
|
||||
});
|
||||
}
|
||||
|
||||
protected function generateSignature(string $message): string
|
||||
{
|
||||
return base64_encode(hash_hmac('sha1', $message, $this->accessKeySecret, true));
|
||||
}
|
||||
}
|
44
app/Service/MigrateService.php
Normal file
44
app/Service/MigrateService.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/4
|
||||
* Time: 下午1:57
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use Hyperf\Database\Schema\Blueprint;
|
||||
|
||||
class MigrateService
|
||||
{
|
||||
/***
|
||||
* 在创建的时候自动添加如下字段
|
||||
* @param Blueprint $blueprint
|
||||
* @return Blueprint
|
||||
*/
|
||||
public static function migrateCreateInfo(Blueprint $blueprint): Blueprint
|
||||
{
|
||||
$blueprint->integer('creator_id')->default(0)->unsigned()->comment('创建人ID');
|
||||
$blueprint->string('creator_name', 45)->default('')->comment('创建人姓名');
|
||||
$blueprint->integer('created_at')->default(0)->unsigned()->comment('创建时间');
|
||||
$blueprint->integer('updated_at')->default(0)->unsigned()->comment('更新时间');
|
||||
$blueprint->integer('deleted_at')->nullable()->unsigned()->comment('软删除时间');
|
||||
return $blueprint;
|
||||
}
|
||||
|
||||
public static function migrateTime(Blueprint $blueprint): Blueprint
|
||||
{
|
||||
$blueprint->integer('created_at')->default(0)->unsigned()->comment('创建时间');
|
||||
$blueprint->integer('updated_at')->default(0)->unsigned()->comment('更新时间');
|
||||
$blueprint->integer('deleted_at')->nullable()->unsigned()->comment('软删除时间');
|
||||
return $blueprint;
|
||||
}
|
||||
}
|
121
app/Service/OpLogsService.php
Executable file
121
app/Service/OpLogsService.php
Executable file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2024/12/25
|
||||
* Time: 上午9:15
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Context\QueueContext;
|
||||
use App\Context\UserContext;
|
||||
use Hyperf\Collection\Arr;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\HttpServer\Router\Dispatched;
|
||||
|
||||
use function Hyperf\Support\make;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2024/12/25
|
||||
* Time: 上午9:37
|
||||
* Description: 操作日志服务类。
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class OpLogsService
|
||||
{
|
||||
/**
|
||||
* 记录操作日志。
|
||||
*
|
||||
* 该方法用于记录与请求相关联的操作日志,包括操作动作、路由、参数、IP地址、用户代理等信息。
|
||||
* 允许通过$logStream参数传递额外的备注信息,并可选择指定日志的来源。
|
||||
*
|
||||
* @param string $logStream 日志流或额外的备注信息,默认为空字符串
|
||||
* @param null|int $source 日志来源的标识,默认为0
|
||||
*/
|
||||
public function operatorLogs(string $logStream = '', ?int $source = 0): void
|
||||
{
|
||||
// 创建请求对象
|
||||
$request = make(RequestInterface::class);
|
||||
// 尝试从请求中获取调度信息
|
||||
$dispatched = $request->getAttribute(Dispatched::class);
|
||||
|
||||
// 如果不存在调度信息,则不记录日志
|
||||
if (! $dispatched instanceof Dispatched) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析路由处理器信息
|
||||
$handler = $dispatched->handler->callback ?? null;
|
||||
[$routeControllerName, $routeActionName] = $handler;
|
||||
// 提取控制器名称
|
||||
$controllerName = Arr::last(explode('\\', $routeControllerName));
|
||||
// 组装当前执行的动作字符串
|
||||
$currAction = $controllerName . '@' . $routeActionName;
|
||||
|
||||
// 过滤敏感参数后收集请求信息
|
||||
$params = $this->filterSensitiveParams($request->all());
|
||||
$clientIP = $this->getClientIP($request);
|
||||
$agent = $request->getHeaderLine('User-Agent');
|
||||
|
||||
// 准备日志数据
|
||||
$data = [
|
||||
'action' => $currAction,
|
||||
'route' => $request->path(),
|
||||
'params' => $params,
|
||||
'ip' => $clientIP,
|
||||
'agent' => $agent,
|
||||
'remark' => $logStream,
|
||||
'source' => $source,
|
||||
];
|
||||
// 将日志数据加入队列,以便后续处理
|
||||
make(QueueService::class)->make($data)->opLogs();
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤敏感参数.
|
||||
*/
|
||||
protected function filterSensitiveParams(array $params): array
|
||||
{
|
||||
$sensitiveKeys = ['password', 'pwd', 'pwd_conf', 'original_pwd', 'old_pwd', 'new_pwd', 'conf_pwd'];
|
||||
|
||||
foreach ($sensitiveKeys as $key) {
|
||||
Arr::forget($params, $key);
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端 IP.
|
||||
*/
|
||||
protected function getClientIP(RequestInterface $request): string
|
||||
{
|
||||
$realIP = $request->getHeaderLine('x-real-ip');
|
||||
$forwardedFor = $request->getHeaderLine('x-forwarded-for');
|
||||
|
||||
$clientIP = $realIP ?: $forwardedFor;
|
||||
return $clientIP ?: '127.0.0.1';
|
||||
}
|
||||
}
|
92
app/Service/QueueService.php
Normal file
92
app/Service/QueueService.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午10:53
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Context\UserContext;
|
||||
use App\Job\ColumnConfigJob;
|
||||
use App\Job\OpLogsJob;
|
||||
use App\Job\RequestWriteLogsJob;
|
||||
use Hyperf\Amqp\Producer;
|
||||
use Hyperf\AsyncQueue\Driver\DriverFactory;
|
||||
use Hyperf\AsyncQueue\Driver\DriverInterface;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
|
||||
class QueueService
|
||||
{
|
||||
// 存储配置参数
|
||||
protected array $params = [];
|
||||
|
||||
// 当前操作的函数名
|
||||
protected string $function = '';
|
||||
|
||||
// 队列驱动实例
|
||||
protected DriverInterface $driver;
|
||||
|
||||
// 生产者实例,用于发送消息
|
||||
#[Inject]
|
||||
protected Producer $producer;
|
||||
|
||||
/**
|
||||
* 构造函数,初始化队列服务。
|
||||
*
|
||||
* @param DriverFactory $driverFactory 驱动工厂,用于获取具体的队列驱动实例
|
||||
*/
|
||||
public function __construct(DriverFactory $driverFactory)
|
||||
{
|
||||
$this->driver = $driverFactory->get('whf');
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建队列任务并执行。
|
||||
*
|
||||
* @param array $params
|
||||
* @return QueueService
|
||||
*/
|
||||
public function make(array $params): static
|
||||
{
|
||||
// 将当前用户信息添加到参数中
|
||||
$params['user'] = UserContext::getCurrentUser();
|
||||
|
||||
$this->params = $params;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将请求日志写入文件。
|
||||
*
|
||||
*/
|
||||
public function writeRequestLogs(): void
|
||||
{
|
||||
$this->driver->push(new RequestWriteLogsJob($this->params));
|
||||
}
|
||||
|
||||
/**
|
||||
* 列配置保存.
|
||||
*/
|
||||
public function saveColumnConfig(): void
|
||||
{
|
||||
$this->driver->push(new ColumnConfigJob($this->params));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将操作日志推送到队列中.
|
||||
*/
|
||||
public function opLogs(): void
|
||||
{
|
||||
// 将操作日志封装为OpLogsJob任务,并推送到驱动器对应的队列中
|
||||
$this->driver->push(new OpLogsJob($this->params));
|
||||
}
|
||||
}
|
48
app/Service/SysService.php
Normal file
48
app/Service/SysService.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/4
|
||||
* Time: 上午9:51
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/4
|
||||
* Time: 上午10:19
|
||||
* Description: 系统服务
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class SysService
|
||||
{
|
||||
/**
|
||||
* 获取客户端IP信息
|
||||
* @param ServerRequestInterface $request
|
||||
* @return string
|
||||
*/
|
||||
public function getClientIpInfo(ServerRequestInterface $request): string
|
||||
{
|
||||
// 尝试从请求头获取客户端真实IP或转发的IP
|
||||
$realIP = $request->getHeaderLine('x-real-ip');
|
||||
$forwardedFor = $request->getHeaderLine('x-forwarded-for');
|
||||
// 确定客户端IP,优先使用 x-real-ip,其次是 x-forwarded-for,最后是默认IP
|
||||
$clientIP = $realIP ?: $forwardedFor;
|
||||
|
||||
return $clientIP ?: '127.0.0.1';
|
||||
}
|
||||
}
|
147
app/Service/Trait/ColumnConfigTrait.php
Executable file
147
app/Service/Trait/ColumnConfigTrait.php
Executable file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2024/12/24
|
||||
* Time: 下午3:05
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Service\Trait;
|
||||
|
||||
use App\Context\UserContext;
|
||||
use App\Model\ColumnConfig;
|
||||
use App\Model\TableConfig;
|
||||
use App\Service\QueueService;
|
||||
use Exception;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpServer\Router\Dispatched;
|
||||
use Hyperf\Stringable\Str;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
use function Hyperf\Collection\collect;
|
||||
use function Hyperf\Config\config;
|
||||
|
||||
trait ColumnConfigTrait
|
||||
{
|
||||
#[Inject]
|
||||
protected QueueService $queueService;
|
||||
|
||||
#[Inject]
|
||||
protected ServerRequestInterface $clientRequest;
|
||||
|
||||
/**
|
||||
* 获取列配置.
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getColumnConfig(): array
|
||||
{
|
||||
$user = UserContext::getCurrentUser();
|
||||
if (! $user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$method = Str::snake($this->getCurrentAction());
|
||||
$this->saveColumnConfig($user, $method, $this->clientRequest->getParsedBody());
|
||||
|
||||
$this->saveTableConfig($user);
|
||||
// 从数据库中查询对应的列配置详情
|
||||
$fields = ColumnConfig::query()
|
||||
->where(['creator_id' => $user['id'], 'method' => $method])
|
||||
->select(['prop', 'label', 'sortable', 'sort', 'width', 'hide', 'fix', 'filter', 'is_search', 'search_type', 'condition'])
|
||||
->orderBy('sort')
|
||||
->get();
|
||||
return $fields->isNotEmpty() ? $fields->toArray() : $this->getDefaultConfig($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表格配置.
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getTableConfig(): array
|
||||
{
|
||||
$user = UserContext::getCurrentUser();
|
||||
if (! $user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$config = TableConfig::query()
|
||||
->where(['creator_id' => $user['id'], 'method' => Str::snake($this->getCurrentAction())])
|
||||
->select(['size', 'config'])
|
||||
->first();
|
||||
|
||||
return $config ? $config->toArray() : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认配置.
|
||||
*/
|
||||
private function getDefaultConfig(string $method): array
|
||||
{
|
||||
$configList = config('column_config.' . $method);
|
||||
if (empty($configList)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return collect($configList)->map(function ($item, $key) {
|
||||
$item['sort'] = $key + 1;
|
||||
return $item;
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求所匹配的方法.
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getCurrentAction(): string
|
||||
{
|
||||
$dispatched = $this->clientRequest->getAttribute(Dispatched::class);
|
||||
return $dispatched->handler->callback[1] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存列配置.
|
||||
* @throws Exception
|
||||
*/
|
||||
private function saveColumnConfig(array $user, string $method, array $params): void
|
||||
{
|
||||
$this->queueService->make(compact('user', 'method', 'params'))->saveColumnConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function saveTableConfig(array $user): void
|
||||
{
|
||||
$body = $this->clientRequest->getParsedBody();
|
||||
if (empty($body['table_config'])) {
|
||||
return;
|
||||
}
|
||||
$query = TableConfig::query();
|
||||
$params = $body['table_config'];
|
||||
$method = Str::snake($this->getCurrentAction());
|
||||
$query->updateOrCreate(
|
||||
['creator_id' => $user['id'], 'method' => $method],
|
||||
[
|
||||
'method' => $method,
|
||||
'size' => $params['size'] ?? 'medium',
|
||||
'config' => $params['config'] ?? [],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
119
app/Utils/ApiResponse.php
Normal file
119
app/Utils/ApiResponse.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午8:30
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
use Hyperf\HttpMessage\Server\Response;
|
||||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午8:31
|
||||
* Description: A utility class for creating API responses.
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
class ApiResponse
|
||||
{
|
||||
protected string $token = ''; // Stores the authorization token
|
||||
|
||||
protected Response $response; // Holds the response object
|
||||
|
||||
/**
|
||||
* Constructor to initialize ApiResponse object with a response object.
|
||||
*
|
||||
* @param Response $response the response object to be used for sending API responses
|
||||
*/
|
||||
public function __construct(Response $response)
|
||||
{
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a successful API response with optional data, message, and status code.
|
||||
*
|
||||
* @param array $data the data to be returned in the response
|
||||
* @param string $message a success message to be returned
|
||||
* @param int $code the HTTP status code for the response
|
||||
* @return Response the constructed response with success data
|
||||
*/
|
||||
public function success(array $data = [], string $message = 'Success', int $code = 200): Response
|
||||
{
|
||||
$result = [
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
];
|
||||
|
||||
return self::jsonResponse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an error API response with an error message, optional data, and status code.
|
||||
*
|
||||
* @param string $message the error message to be returned
|
||||
* @param int $code the HTTP status code for the error response
|
||||
* @param null $data optional additional data to be returned with the error
|
||||
* @return Response the constructed response with error data
|
||||
*/
|
||||
public function error(string $message, int $code = 400, $data = null): Response
|
||||
{
|
||||
$result = [
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
];
|
||||
|
||||
return self::jsonResponse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an authorization token to be included in the response headers.
|
||||
*
|
||||
* @param string $token the authorization token
|
||||
* @return static the current instance of ApiResponse with the token set
|
||||
*/
|
||||
public function setToken(string $token): static
|
||||
{
|
||||
$this->token = $token;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts data into a JSON response with an optional status code.
|
||||
* If a token has been set, it includes the token in the authorization header.
|
||||
*
|
||||
* @param array $data the data to encode into JSON and send in the response
|
||||
* @param int $statusCode the HTTP status code for the response
|
||||
* @return Response the constructed JSON response
|
||||
*/
|
||||
public function jsonResponse(array $data, int $statusCode = 200): Response
|
||||
{
|
||||
$response = $this->response;
|
||||
if ($this->token) {
|
||||
// Adds authorization header to the response if a token is set
|
||||
$response->withHeader('Authorization', 'Bearer ' . $this->token);
|
||||
}
|
||||
// Constructs and returns the response with the specified data and headers
|
||||
return $response
|
||||
->withStatus($statusCode)
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withBody(new SwooleStream(json_encode($data)));
|
||||
}
|
||||
}
|
81
app/Utils/TableUtils.php
Normal file
81
app/Utils/TableUtils.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/5/6
|
||||
* Time: 下午10:42
|
||||
* Description:
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
use App\Exception\ApiException;
|
||||
use App\Model\Model;
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Psr\SimpleCache\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/5/6
|
||||
* Time: 下午10:44
|
||||
* Description: 表工具类, 用于判断表是否存在某个字段
|
||||
*
|
||||
* (c) ykxiao <yk_9001@hotmail.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
trait TableUtils
|
||||
{
|
||||
/**
|
||||
* 检查数据表是否包含指定列。
|
||||
*
|
||||
* @param Model|string $modelOrTable
|
||||
* @param string $column 列名称。
|
||||
* @return bool 如果数据表包含指定列,则返回 true;否则返回 false。
|
||||
*/
|
||||
public static function hasColumn(Model|string $modelOrTable, string $column): bool
|
||||
{
|
||||
try {
|
||||
if ($modelOrTable instanceof Model) {
|
||||
$connection = $modelOrTable->getConnection();
|
||||
$tableName = $modelOrTable->getTable();
|
||||
} else {
|
||||
// 如果是字符串表名,默认使用全局连接
|
||||
$connection = Db::connection();
|
||||
$tableName = $modelOrTable;
|
||||
}
|
||||
|
||||
// 获取前缀(手动拼接)
|
||||
$prefix = $connection->getConfig('prefix') ?? '';
|
||||
$fullTable = $prefix . $tableName;
|
||||
|
||||
// 使用缓存避免频繁查询
|
||||
$cacheKey = "table_column_exists:$fullTable:$column";
|
||||
/** @var CacheInterface $cache */
|
||||
$cache = ApplicationContext::getContainer()->get(CacheInterface::class);
|
||||
if (($exists = $cache->get($cacheKey)) !== null) {
|
||||
return $exists;
|
||||
}
|
||||
|
||||
// 执行 SHOW COLUMNS
|
||||
$result = $connection->select("SHOW COLUMNS FROM `$fullTable` LIKE '" . addslashes($column) . "'");
|
||||
$exists = !empty($result);
|
||||
|
||||
$cache->set($cacheKey, $exists, 3600);
|
||||
return $exists;
|
||||
} catch (ContainerExceptionInterface|NotFoundExceptionInterface|InvalidArgumentException $e) {
|
||||
throw new ApiException($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user