登录
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
namespace app\common\traits;
|
||||
|
||||
use app\common\audit\AuditBuffer;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
trait Auditable
|
||||
{
|
||||
// 可在模型中设置:
|
||||
// public $auditDisabled = true; // 关闭该模型的日志
|
||||
// public $auditScene = 'alias'; // 覆盖默认场景(默认=表名)
|
||||
|
||||
public static function bootAuditable()
|
||||
{
|
||||
static::created(function (Model $m) {
|
||||
if (self::isMuted($m)) return;
|
||||
AuditBuffer::instance()->pushModelChange(self::scene($m), $m->getKey(), [], $m->getAttributes());
|
||||
});
|
||||
|
||||
static::updated(function (Model $m) {
|
||||
if (self::isMuted($m)) return;
|
||||
$dirty = $m->getDirty();
|
||||
if (!$dirty) return;
|
||||
$old = []; $new = [];
|
||||
foreach (array_keys($dirty) as $k) {
|
||||
$old[$k] = $m->getOriginal($k);
|
||||
$new[$k] = $m->getAttribute($k);
|
||||
}
|
||||
AuditBuffer::instance()->pushModelChange(self::scene($m), $m->getKey(), $old, $new);
|
||||
});
|
||||
|
||||
static::deleted(function (Model $m) {
|
||||
if (self::isMuted($m)) return;
|
||||
AuditBuffer::instance()->pushModelChange(self::scene($m), $m->getKey(), $m->getAttributes(), []);
|
||||
});
|
||||
}
|
||||
|
||||
protected static function scene(Model $m): string
|
||||
{
|
||||
return property_exists($m, 'auditScene') && $m->auditScene ? $m->auditScene : $m->getTable();
|
||||
}
|
||||
|
||||
protected static function isMuted(Model $m): bool
|
||||
{
|
||||
if (property_exists($m, 'auditDisabled') && $m->auditDisabled) return true;
|
||||
return $m->getTable() === 'sys_op_log'; // 避免自记录
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
<?php
|
||||
namespace app\common\traits;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use support\Db;
|
||||
use support\exception\BusinessException;
|
||||
|
||||
class DataPermission
|
||||
{
|
||||
|
||||
/*
|
||||
ALTER TABLE `user` ADD COLUMN `report_to` bigint(20) unsigned DEFAULT '0' COMMENT '上级用户ID';
|
||||
|
||||
ALTER TABLE `roles`
|
||||
ADD COLUMN `parent_id` bigint(20) unsigned DEFAULT '0' COMMENT '父角色ID,0表示顶级角色',
|
||||
ADD INDEX `idx_parent_id` (`parent_id`) USING BTREE;
|
||||
*/
|
||||
|
||||
/**
|
||||
* 应用数据权限过滤到查询
|
||||
*
|
||||
* @param Builder $query 查询构造器
|
||||
* @param string $type 权限类型:superadmin, personal, same_role, role_tree, relation
|
||||
* @param int $currentUserId 当前用户ID
|
||||
* @param int $currentRoleId 当前角色ID
|
||||
* @param string $userField 用户ID字段名
|
||||
* @param string $roleField 角色ID字段名
|
||||
*/
|
||||
public static function applyQueryPermission(
|
||||
Builder $query,
|
||||
string $type,
|
||||
int $currentUserId,
|
||||
int $currentRoleId,
|
||||
string $userField = 'user_id',
|
||||
string $roleField = 'role_id'
|
||||
): void {
|
||||
// 超管不需要过滤
|
||||
if (self::isSuperAdmin($currentUserId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'personal':
|
||||
// 个人权限:只能访问自己的数据
|
||||
$query->where($userField, $currentUserId);
|
||||
break;
|
||||
|
||||
case 'same_role':
|
||||
// 同角色权限:访问同角色的数据
|
||||
$userIds = self::getSameRoleUserIds($currentRoleId);
|
||||
$query->whereIn($userField, $userIds);
|
||||
break;
|
||||
|
||||
case 'role_tree':
|
||||
// 角色树权限:访问自己角色及子角色的数据
|
||||
$userIds = self::getRoleTreeUserIds($currentRoleId);
|
||||
$query->whereIn($userField, $userIds);
|
||||
break;
|
||||
|
||||
case 'relation':
|
||||
// 从属关系权限:访问自己及下属的数据
|
||||
$userIds = self::getRelationUserIds($currentUserId);
|
||||
$query->whereIn($userField, $userIds);
|
||||
break;
|
||||
|
||||
default:
|
||||
// 默认个人权限
|
||||
$query->where($userField, $currentUserId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用数据权限到创建/更新操作
|
||||
*
|
||||
* @param mixed $target 创建时为数组,更新时为Model对象
|
||||
* @param string $type 权限类型
|
||||
* @param int $currentUserId 当前用户ID
|
||||
* @param int $currentRoleId 当前角色ID
|
||||
* @param array $newData 更新时的数据
|
||||
* @param string $userField 用户ID字段名
|
||||
* @param string $roleField 角色ID字段名
|
||||
*/
|
||||
public static function applyMutationPermission(
|
||||
&$target,
|
||||
string $type,
|
||||
int $currentUserId,
|
||||
int $currentRoleId,
|
||||
array $newData = [],
|
||||
string $userField = 'admin_id',
|
||||
string $roleField = 'role_id'
|
||||
): void {
|
||||
// 超管不需要检查
|
||||
if (self::isSuperAdmin($currentUserId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'personal':
|
||||
self::handlePersonalPermission($target, $newData, $userField, $currentUserId);
|
||||
break;
|
||||
|
||||
case 'same_role':
|
||||
self::handleSameRolePermission($target, $newData, $userField, $roleField, $currentRoleId);
|
||||
break;
|
||||
|
||||
case 'role_tree':
|
||||
self::handleRoleTreePermission($target, $newData, $userField, $roleField, $currentRoleId);
|
||||
break;
|
||||
|
||||
case 'relation':
|
||||
self::handleRelationPermission($target, $newData, $userField, $currentUserId);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new BusinessException("未定义的数据权限类型: $type", 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取同角色的用户ID列表
|
||||
*
|
||||
* @param int $roleId 角色ID
|
||||
* @return array 用户ID列表
|
||||
*/
|
||||
public static function getSameRoleUserIds(int $roleId): array
|
||||
{
|
||||
return Db::table('user_roles')
|
||||
->where('role_id', $roleId)
|
||||
->pluck('user_id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色树下的所有用户ID列表
|
||||
*
|
||||
* @param int $roleId 当前角色ID
|
||||
* @return array 用户ID列表
|
||||
*/
|
||||
public static function getRoleTreeUserIds(int $roleId): array
|
||||
{
|
||||
// 获取角色树中的所有角色ID
|
||||
$roleIds = self::getRoleTreeIds($roleId);
|
||||
|
||||
// 获取这些角色下的所有用户ID
|
||||
return Db::table('user_roles')
|
||||
->whereIn('role_id', $roleIds)
|
||||
->pluck('user_id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取从属关系下的用户ID列表(包括自己和下属)
|
||||
*
|
||||
* @param int $userId 当前用户ID
|
||||
* @return array 用户ID列表
|
||||
*/
|
||||
public static function getRelationUserIds(int $userId): array
|
||||
{
|
||||
// 获取所有下属用户ID(递归)
|
||||
$subordinateIds = self::getSubordinateIds($userId);
|
||||
|
||||
// 返回自己和所有下属的ID
|
||||
return array_merge([$userId], $subordinateIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取直接下属的用户ID列表
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 直接下属用户ID列表
|
||||
*/
|
||||
public static function getDirectSubordinateIds(int $userId): array
|
||||
{
|
||||
return Db::table('user')
|
||||
->where('report_to', $userId)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归获取所有下属用户ID列表
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 所有下属用户ID列表
|
||||
*/
|
||||
private static function getSubordinateIds(int $userId): array
|
||||
{
|
||||
$subordinates = self::getDirectSubordinateIds($userId);
|
||||
$allSubordinates = $subordinates;
|
||||
|
||||
foreach ($subordinates as $subordinateId) {
|
||||
$allSubordinates = array_merge($allSubordinates, self::getSubordinateIds($subordinateId));
|
||||
}
|
||||
|
||||
return $allSubordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理个人权限
|
||||
*/
|
||||
private static function handlePersonalPermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
int $currentUserId
|
||||
): void {
|
||||
if (is_array($target)) {
|
||||
// 创建时强制设置用户ID
|
||||
$target[$userField] = $currentUserId;
|
||||
} else {
|
||||
// 更新时校验原始记录
|
||||
if ($target->{$userField} !== $currentUserId) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 禁止变更归属字段
|
||||
if (isset($newData[$userField]) && $newData[$userField] !== $currentUserId) {
|
||||
throw new BusinessException("无权变更归属字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理同角色权限
|
||||
*/
|
||||
private static function handleSameRolePermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
string $roleField,
|
||||
int $currentRoleId
|
||||
): void {
|
||||
// 获取同角色用户ID列表
|
||||
$sameRoleUserIds = self::getSameRoleUserIds($currentRoleId);
|
||||
|
||||
if (is_array($target)) {
|
||||
// 创建时设置归属用户并校验
|
||||
if (isset($target[$userField])) {
|
||||
if (!in_array($target[$userField], $sameRoleUserIds)) {
|
||||
throw new BusinessException("无权为指定用户创建数据", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$userField] = $currentRoleId;
|
||||
}
|
||||
} else {
|
||||
// 更新时校验原始记录
|
||||
if (!in_array($target->{$userField}, $sameRoleUserIds)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 禁止变更归属字段到无权限的用户
|
||||
if (isset($newData[$userField]) && !in_array($newData[$userField], $sameRoleUserIds)) {
|
||||
throw new BusinessException("无权变更归属字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理角色树权限
|
||||
*/
|
||||
private static function handleRoleTreePermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
string $roleField,
|
||||
int $currentRoleId
|
||||
): void {
|
||||
// 获取角色树用户ID列表
|
||||
$roleTreeUserIds = self::getRoleTreeUserIds($currentRoleId);
|
||||
|
||||
if (is_array($target)) {
|
||||
// 创建时校验用户是否在允许范围内
|
||||
if (isset($target[$userField])) {
|
||||
if (!in_array($target[$userField], $roleTreeUserIds)) {
|
||||
throw new BusinessException("无权为指定用户创建数据", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$userField] = $currentRoleId;
|
||||
}
|
||||
} else {
|
||||
// 更新时校验原始记录用户
|
||||
if (!in_array($target->{$userField}, $roleTreeUserIds)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 校验新用户是否在允许范围内
|
||||
if (isset($newData[$userField])) {
|
||||
if (!in_array($newData[$userField], $roleTreeUserIds)) {
|
||||
throw new BusinessException("无权变更用户字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理从属关系权限
|
||||
*/
|
||||
private static function handleRelationPermission(
|
||||
&$target,
|
||||
array $newData,
|
||||
string $userField,
|
||||
int $currentUserId
|
||||
): void {
|
||||
// 获取从属关系用户ID列表
|
||||
$relationUserIds = self::getRelationUserIds($currentUserId);
|
||||
|
||||
if (is_array($target)) {
|
||||
// 创建时校验用户是否在允许范围内
|
||||
if (isset($target[$userField])) {
|
||||
if (!in_array($target[$userField], $relationUserIds)) {
|
||||
throw new BusinessException("无权为指定用户创建数据", 403);
|
||||
}
|
||||
} else {
|
||||
$target[$userField] = $currentUserId;
|
||||
}
|
||||
} else {
|
||||
// 更新时校验原始记录用户
|
||||
if (!in_array($target->{$userField}, $relationUserIds)) {
|
||||
throw new BusinessException("无权操作该记录", 403);
|
||||
}
|
||||
|
||||
// 校验新用户是否在允许范围内
|
||||
if (isset($newData[$userField])) {
|
||||
if (!in_array($newData[$userField], $relationUserIds)) {
|
||||
throw new BusinessException("无权变更用户字段", 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色树ID(当前角色及其所有子角色)
|
||||
* 从数据库中动态获取角色树关系
|
||||
*/
|
||||
private static function getRoleTreeIds(int $roleId): array
|
||||
{
|
||||
static $roleTreeCache = [];
|
||||
|
||||
if (isset($roleTreeCache[$roleId])) {
|
||||
return $roleTreeCache[$roleId];
|
||||
}
|
||||
|
||||
// 从数据库中递归获取角色树
|
||||
$ids = [$roleId];
|
||||
$queue = [$roleId];
|
||||
|
||||
while (!empty($queue)) {
|
||||
$current = array_shift($queue);
|
||||
// 查找当前角色的所有子角色
|
||||
$childRoles = Db::table('roles')
|
||||
->where('parent_id', $current)
|
||||
->where('status', 1) // 只获取启用的角色
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
foreach ($childRoles as $childId) {
|
||||
if (!in_array($childId, $ids)) {
|
||||
$ids[] = $childId;
|
||||
$queue[] = $childId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$roleTreeCache[$roleId] = $ids;
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为超管
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return bool 是否为超管
|
||||
*/
|
||||
public static function isSuperAdmin(int $userId): bool
|
||||
{
|
||||
// 检查用户是否关联了超级管理员角色
|
||||
// 假设角色ID为1的是超级管理员角色
|
||||
$superAdminRoleId = 1;
|
||||
|
||||
return Db::table('user_roles')
|
||||
->where('user_id', $userId)
|
||||
->where('role_id', $superAdminRoleId)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的角色ID
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return int 角色ID
|
||||
*/
|
||||
public static function getUserIdRoleId(int $userId): int
|
||||
{
|
||||
// 从 user_roles 表中获取当前用户的角色ID
|
||||
$roleId = Db::table('user_roles')
|
||||
->where('user_id', $userId)
|
||||
->value('role_id');
|
||||
|
||||
return $roleId ?: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的所有角色ID(包括继承的角色)
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 角色ID列表
|
||||
*/
|
||||
public static function getUserAllRoleIds(int $userId): array
|
||||
{
|
||||
// 获取用户直接关联的角色
|
||||
$directRoleIds = Db::table('user_roles')
|
||||
->where('user_id', $userId)
|
||||
->pluck('role_id')
|
||||
->toArray();
|
||||
|
||||
// 获取所有继承的角色
|
||||
$allRoleIds = [];
|
||||
foreach ($directRoleIds as $roleId) {
|
||||
$allRoleIds = array_merge($allRoleIds, self::getRoleTreeIds($roleId));
|
||||
}
|
||||
|
||||
// 去重并返回
|
||||
return array_unique($allRoleIds);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
namespace app\common\traits;
|
||||
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* 数据转换器 Trait
|
||||
* 用于将原始数据(如模型、集合、分页等)统一转换为数组格式,
|
||||
* 并支持字段级与结果级的灵活后置加工。
|
||||
*/
|
||||
trait DataTransformer
|
||||
{
|
||||
/**
|
||||
* 核心转换方法
|
||||
*
|
||||
* @param mixed $rawData 原始数据:Model、Collection、Paginator、数组等
|
||||
* @param array $opts 可选配置:
|
||||
* - 'fieldFormatter' => callable 字段名格式化回调,如 fn($f)=>snake_case($f)
|
||||
* - 'postFormatter' => callable 结果级加工回调,接收整个数组并返回
|
||||
*
|
||||
* @return array 转换并加工后的纯 PHP 数组
|
||||
*/
|
||||
protected function transformData(
|
||||
mixed $rawData,
|
||||
?callable $postFormatter = null,
|
||||
?callable $fieldFormatter = null
|
||||
): array {
|
||||
try {
|
||||
$fieldFormatter = $fieldFormatter ?: fn($field) => $field;
|
||||
if ($rawData instanceof LengthAwarePaginator) {
|
||||
$arr = $this->formatPaginated($rawData, $postFormatter, $fieldFormatter);
|
||||
} elseif ($rawData instanceof Model) {
|
||||
$arr = $this->formatModel($rawData, $fieldFormatter);
|
||||
} elseif ($rawData instanceof Collection) {
|
||||
$arr = $this->formatCollection($rawData, $fieldFormatter);
|
||||
} elseif (is_array($rawData)) {
|
||||
$arr = $this->formatArray($rawData, $fieldFormatter);
|
||||
} else {
|
||||
$arr = (array)$rawData;
|
||||
}
|
||||
|
||||
if (is_callable($postFormatter)) {
|
||||
$arr = $postFormatter($arr);
|
||||
}
|
||||
|
||||
return $arr;
|
||||
} catch (\Throwable $e) {
|
||||
return [
|
||||
'error' => true,
|
||||
'message' => $e->getMessage(),
|
||||
// 'message' => $e->getMessage()."\n".$e->getLine()."\n".$e->getFile(),
|
||||
'code' => $e->getCode(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化分页数据:转换每条记录
|
||||
*
|
||||
* @param LengthAwarePaginator $p
|
||||
* @param callable $fmt
|
||||
* @return array{records: array, total: int, current: int, size: int}
|
||||
*/
|
||||
protected function formatPaginated(
|
||||
LengthAwarePaginator $p,
|
||||
?callable $postFormatter,
|
||||
callable $fmt
|
||||
): array {
|
||||
$records = $p->getCollection()
|
||||
->map(fn($item) => $this->transformData($item, $postFormatter, $fmt))
|
||||
->toArray();
|
||||
|
||||
return [
|
||||
'records' => $records,
|
||||
'total' => $p->total(),
|
||||
'current' => $p->currentPage(),
|
||||
'size' => $p->perPage(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 Eloquent 模型:主表属性 + 关联递归
|
||||
*
|
||||
* @param Model $model
|
||||
* @param callable $fmt
|
||||
* @return array
|
||||
*/
|
||||
protected function formatModel(
|
||||
Model $model,
|
||||
callable $fmt
|
||||
): array {
|
||||
$out = [];
|
||||
// 1) 主表字段
|
||||
foreach ($model->getAttributes() as $key => $value) {
|
||||
$out[$fmt($key)] = $value;
|
||||
}
|
||||
// 2) 关联递归
|
||||
foreach ($model->getRelations() as $rel => $relData) {
|
||||
if ($relData instanceof Model) {
|
||||
$out[$rel] = $this->formatModel($relData, $fmt);
|
||||
} elseif ($relData instanceof Collection) {
|
||||
$out[$rel] = $relData
|
||||
->map(fn($item) => $this->transformData($item, null, $fmt))
|
||||
->toArray();
|
||||
} else {
|
||||
$out[$rel] = $relData;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化集合数据:对每个元素递归调用 transformData
|
||||
*
|
||||
* @param Collection $c
|
||||
* @param callable $fmt
|
||||
* @return array
|
||||
*/
|
||||
protected function formatCollection(
|
||||
Collection $c,
|
||||
callable $fmt
|
||||
): array {
|
||||
return $c
|
||||
->map(fn($item) => $this->transformData($item, null, $fmt))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化普通数组:递归处理可能出现的模型或集合
|
||||
*
|
||||
* @param array $arr
|
||||
* @param callable $fmt
|
||||
* @return array
|
||||
*/
|
||||
protected function formatArray(
|
||||
array $arr,
|
||||
callable $fmt
|
||||
): array {
|
||||
return array_map(function($item) use ($fmt) {
|
||||
if (is_array($item)
|
||||
|| $item instanceof Model
|
||||
|| $item instanceof Collection
|
||||
|| $item instanceof LengthAwarePaginator
|
||||
) {
|
||||
return $this->transformData($item, null, $fmt);
|
||||
}
|
||||
return $item;
|
||||
}, $arr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
namespace app\common\traits;
|
||||
|
||||
use support\Request;
|
||||
|
||||
trait FormatKvTrait
|
||||
{
|
||||
/**
|
||||
* 主入口:一行把前端扁平化参数变成完整的查询配置
|
||||
*
|
||||
* @param Request $request
|
||||
* @param string $modelClass Eloquent 模型类名
|
||||
* @return array{
|
||||
* model: string,
|
||||
* where: array,
|
||||
* order: array<string,string>,
|
||||
* fields: string[],
|
||||
* include: string[]
|
||||
* }
|
||||
*/
|
||||
protected function buildQueryConfig(Request $request, string $modelClass): array
|
||||
{
|
||||
$all = $request->all();
|
||||
|
||||
return [
|
||||
'model' => $modelClass,
|
||||
'where' => $this->parseFilters($all),
|
||||
'order' => $this->parseSort($all['sort'] ?? ''),
|
||||
'fields' => $this->parseCsv($all['fields'] ?? ''),
|
||||
'include' => $this->parseCsv($all['include'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 把除 sort/fields/include/page/limit 以外的所有参数
|
||||
* 智能转换为 where 条件
|
||||
*
|
||||
* 支持:
|
||||
* - 等值:foo=bar
|
||||
* - NOT:foo=!bar 或 foo=!bar1,bar2
|
||||
* - 范围:foo=2025-07-01,2025-07-15 → between
|
||||
* - 列表:foo=a,b,c → in
|
||||
* - NULL:foo=null
|
||||
* - NOT NULL:foo=!null
|
||||
* - 通配符:foo=xia* → like 'xia%'
|
||||
*
|
||||
* 关联字段直接写 relation.field=xxx
|
||||
*/
|
||||
protected function parseFilters(array $params): array
|
||||
{
|
||||
$where = [];
|
||||
$reserved = ['sort','fields','include','page','limit'];
|
||||
|
||||
foreach ($params as $key => $val) {
|
||||
if (in_array($key, $reserved, true)) {
|
||||
continue;
|
||||
}
|
||||
// NULL / NOT NULL
|
||||
if ($val === 'null') {
|
||||
$where[$key] = ['is null'];
|
||||
continue;
|
||||
}
|
||||
if ($val === '!null') {
|
||||
$where[$key] = ['is not null'];
|
||||
continue;
|
||||
}
|
||||
// NOT equal or NOT IN
|
||||
if (str_starts_with($val, '!')) {
|
||||
$v = substr($val, 1);
|
||||
if (str_contains($v, ',')) {
|
||||
$list = array_map('trim', explode(',', $v));
|
||||
$where[$key] = ['not in', $list];
|
||||
} else {
|
||||
$where[$key] = ['<>', $this->cast($v)];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// BETWEEN or IN
|
||||
if (str_contains($val, ',')) {
|
||||
$parts = array_map('trim', explode(',', $val));
|
||||
if (count($parts) === 2 && $this->isRangeValue($parts[0], $parts[1])) {
|
||||
$where[$key] = ['between', $parts];
|
||||
} else {
|
||||
$where[$key] = ['in', $this->castArray($parts)];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// LIKE
|
||||
if (str_contains($val, '*') || str_contains($val, '%')) {
|
||||
$pattern = str_replace('*', '%', $val);
|
||||
$where[$key] = ['like', $pattern];
|
||||
continue;
|
||||
}
|
||||
// 默认 EQ
|
||||
$where[$key] = $this->cast($val);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 sort=id,-created_at → ['id'=>'asc','created_at'=>'desc']
|
||||
*/
|
||||
protected function parseSort(string $sort): array
|
||||
{
|
||||
$order = [];
|
||||
foreach (explode(',', $sort) as $piece) {
|
||||
$f = trim($piece);
|
||||
if ($f === '') {
|
||||
continue;
|
||||
}
|
||||
if ($f[0] === '-') {
|
||||
$order[substr($f, 1)] = 'desc';
|
||||
} else {
|
||||
$order[$f] = 'asc';
|
||||
}
|
||||
}
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* 逗号分割成数组
|
||||
*/
|
||||
protected function parseCsv(string $csv): array
|
||||
{
|
||||
if ($csv === '') {
|
||||
return [];
|
||||
}
|
||||
return array_filter(array_map('trim', explode(',', $csv)));
|
||||
}
|
||||
|
||||
/** 简单类型转换:全数字→int/float,其余保留原字符串 */
|
||||
protected function cast(string $v): mixed
|
||||
{
|
||||
if (is_numeric($v)) {
|
||||
return strpos($v, '.') !== false ? (float)$v : (int)$v;
|
||||
}
|
||||
return $v;
|
||||
}
|
||||
|
||||
/** 批量类型转换 */
|
||||
protected function castArray(array $arr): array
|
||||
{
|
||||
return array_map(fn($v) => $this->cast($v), $arr);
|
||||
}
|
||||
|
||||
/** 判断两端是否可做 BETWEEN(数字或 YYYY-MM-DD 日期) */
|
||||
protected function isRangeValue(string $a, string $b): bool
|
||||
{
|
||||
return (is_numeric($a) && is_numeric($b))
|
||||
|| (preg_match('/^\d{4}-\d{2}-\d{2}$/', $a)
|
||||
&& preg_match('/^\d{4}-\d{2}-\d{2}$/', $b));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace app\common\traits;
|
||||
|
||||
use app\common\model\VehMoneyValue;
|
||||
use support\Db;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
trait HasMoneyJournal
|
||||
{
|
||||
/**
|
||||
* 【抽象方法】必须由使用该 trait 的模型实现
|
||||
* 返回当前模型对应的金额“业务场景”标识
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected static function moneyObjectScene(): string;
|
||||
|
||||
/** 所有关联金额记录(append-only 全部历史) */
|
||||
public function moneyJournal(): HasMany
|
||||
{
|
||||
return $this->hasMany(VehMoneyValue::class, 'object_id')
|
||||
->where('object_scene', static::moneyObjectScene());
|
||||
}
|
||||
|
||||
/** 取某费项的“最新记录”(按时间/id 倒序) */
|
||||
public function moneyLatest(string $field): ?VehMoneyValue
|
||||
{
|
||||
return $this->moneyJournal()
|
||||
->where('field', $field)
|
||||
->orderByDesc('created_at')
|
||||
->orderByDesc('id')
|
||||
->first();
|
||||
}
|
||||
|
||||
/** 追加一行金额 */
|
||||
public function addMoney(string $field, string $currency, $amount, ?string $rate = null, ?string $remark = null): VehMoneyValue
|
||||
{
|
||||
$now = time();
|
||||
/** @var VehMoneyValue $row */
|
||||
$row = $this->moneyJournal()->create([
|
||||
'object_scene' => static::moneyObjectScene(),
|
||||
'object_id' => $this->id,
|
||||
'field' => $field,
|
||||
'currency' => strtoupper($currency),
|
||||
'amount' => (string)$amount,
|
||||
'exchange_rate' => $rate,
|
||||
'remark' => $remark,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
return $row;
|
||||
}
|
||||
|
||||
/** 批量追加:['unit_price'=>['currency'=>'USD','amount'=>'2199.00','rate'=>'7.2','remark'=>'...'], ...] */
|
||||
public function addMoneyBatch(array $payload): array
|
||||
{
|
||||
return Db::transaction(function () use ($payload) {
|
||||
$out = [];
|
||||
foreach ($payload as $field => $cfg) {
|
||||
$out[$field] = $this->addMoney(
|
||||
$field,
|
||||
$cfg['currency'] ?? 'CNY',
|
||||
$cfg['amount'] ?? '0.00',
|
||||
$cfg['rate'] ?? null,
|
||||
$cfg['remark'] ?? null
|
||||
);
|
||||
}
|
||||
return $out;
|
||||
});
|
||||
}
|
||||
|
||||
/** 把若干费项按各自汇率换到结算币并求和(无 rate 的跳过) */
|
||||
public function sumToSettlement(array $fields, int $scale = 2): string
|
||||
{
|
||||
$sum = '0.00';
|
||||
foreach ($fields as $f) {
|
||||
$row = $this->moneyLatest($f);
|
||||
if ($row) {
|
||||
$v = $row->amountInSettlement($scale);
|
||||
if ($v !== null) $sum = bcadd($sum, $v, $scale);
|
||||
}
|
||||
}
|
||||
return $sum;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\traits;
|
||||
|
||||
use think\Validate;
|
||||
use support\Request;
|
||||
use support\exception\BusinessException;
|
||||
|
||||
trait RequestTrait
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* 验证数据
|
||||
*
|
||||
* @param Validate $validator 验证器实例
|
||||
* @param array $data 待验证的数据
|
||||
* @param string $scene 验证场景
|
||||
* @return array 验证后的数据
|
||||
* @throws BusinessException 验证失败时抛出业务异常
|
||||
*/
|
||||
public function validate(Validate $validator, array $data, string $scene = ''): array
|
||||
{
|
||||
// 如果指定了验证场景,并且验证器支持场景方法,则设置验证场景
|
||||
if ($scene && method_exists($validator, 'scene')) {
|
||||
$validator->scene($scene);
|
||||
}
|
||||
// 执行数据验证
|
||||
if (!$validator->check($data)) {
|
||||
// 验证失败,抛出业务异常,异常信息为验证器返回的错误信息
|
||||
throw new BusinessException($validator->getError());
|
||||
}
|
||||
// 验证成功,返回原始数据
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\traits;
|
||||
|
||||
use support\Response;
|
||||
|
||||
trait ResponseTrait
|
||||
{
|
||||
/**
|
||||
* 返回成功响应
|
||||
*
|
||||
* @param mixed $data 响应数据
|
||||
* @param string $message 响应消息,默认“请求成功”
|
||||
* @param int $code 内部状态码,默认 0 表示成功
|
||||
* @param int $httpStatus HTTP 状态码,默认 200
|
||||
* @param array $header 响应头,默认 JSON 格式
|
||||
* @return Response
|
||||
*/
|
||||
public function success(
|
||||
$data = [],
|
||||
string $message = '请求成功',
|
||||
string $status = 'success',
|
||||
int $code = 200,
|
||||
int $httpStatus = 200,
|
||||
array $header = ['Content-Type' => 'application/json']
|
||||
): Response {
|
||||
return $this->result($status, $code, $httpStatus, $message, $data, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回错误响应
|
||||
*
|
||||
* @param string $message 响应错误消息,默认“请求失败”
|
||||
* @param int $code 内部状态码,默认 1 表示错误
|
||||
* @param int $httpStatus HTTP 状态码,默认 400
|
||||
* @param mixed $data 可选的错误数据
|
||||
* @param array $header 响应头,默认 JSON 格式
|
||||
* @return Response
|
||||
*/
|
||||
public function error(
|
||||
string $message = '请求失败',
|
||||
int $code = 400,
|
||||
int $httpStatus = 400,
|
||||
$data = [],
|
||||
array $header = ['Content-Type' => 'application/json']
|
||||
): Response {
|
||||
return $this->result('fail', $code, $httpStatus, $message, $data, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一响应格式方法
|
||||
*
|
||||
* @param string $status 响应状态,例如 "success" 或 "fail"
|
||||
* @param int $code 内部状态码
|
||||
* @param int $httpStatus HTTP 状态码
|
||||
* @param string $message 响应消息
|
||||
* @param mixed $data 响应数据
|
||||
* @param array $header 响应头
|
||||
* @return Response
|
||||
*/
|
||||
protected function result(
|
||||
string $status,
|
||||
int $code,
|
||||
int $httpStatus,
|
||||
string $message,
|
||||
$data,
|
||||
array $header
|
||||
): Response {
|
||||
$body = [
|
||||
'status' => $status,
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
];
|
||||
return response(json_encode($body, JSON_UNESCAPED_UNICODE), $httpStatus, $header);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\traits\crud;
|
||||
|
||||
use support\Request;
|
||||
|
||||
/**
|
||||
* CRUD 操作控制器 Trait
|
||||
* 提供通用的增删改查控制器方法,通过调用 service 层实现业务逻辑解耦。
|
||||
*/
|
||||
trait CrudControllerTrait
|
||||
{
|
||||
/**
|
||||
* 获取列表
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @return mixed 返回分页数据及总数
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
try {
|
||||
$raw = $this->service->serviceList($request);
|
||||
$data = $this->transformData($raw, method_exists($this, 'indexFormat')
|
||||
? [$this, 'indexFormat']
|
||||
: null);
|
||||
return $this->success($data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取详情
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @param int|string $id 资源 ID
|
||||
* @return mixed 返回资源详情数据
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
try {
|
||||
$raw = $this->service->serviceShow($request);
|
||||
$data = $this->transformData($raw, method_exists($this, 'showFormat')
|
||||
? [$this, 'showFormat']
|
||||
: null);
|
||||
return $this->success($data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @return mixed 返回创建成功的数据或信息
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate($this->service->getValidator(), $request->post(), 'store');
|
||||
$res = $this->service->serviceStore($request, $data);
|
||||
return $this->success($res, '创建成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @return mixed 返回更新成功的数据或信息
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate($this->service->getValidator(), $request->post(), 'update');
|
||||
$res = $this->service->serviceUpdate($request, $data);
|
||||
return $this->success([], '更新成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @return mixed 返回删除操作结果
|
||||
*/
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
try {
|
||||
$count = $this->service->serviceDestroy($request);
|
||||
return $this->success([], "成功删除了 {$count} 条");
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 以下三个钩子方法,**在使用此 Trait 的 Controller** 中实现
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 字段名格式化:例如全部下划线
|
||||
*
|
||||
* @param string $field
|
||||
* @return string
|
||||
*/
|
||||
// protected function fieldFormatter(string $field): string
|
||||
// {
|
||||
// return snake_case($field);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 字段级后置加工规则
|
||||
*
|
||||
* @return array<string,callable>
|
||||
* 键为 “点号嵌套”或 “通配符” 字段路径,
|
||||
* 值为 fn($value, $row)=>mixed
|
||||
*/
|
||||
// protected function getFieldTransforms(): array
|
||||
// {
|
||||
// return [
|
||||
// 'name' => fn($v,$row)=>strtoupper($v),
|
||||
// 'orders.*.total' => fn($v,$row)=>number_format($v,2),
|
||||
// ];
|
||||
// }
|
||||
|
||||
/**
|
||||
* 结果级后置加工:接收整个数组,可任意增删改
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function postFormat(array $data): array
|
||||
// {
|
||||
// // 例如重命名 records 为 items
|
||||
// if (isset($data['records'])) {
|
||||
// $data['items'] = $data['records'];
|
||||
// unset($data['records']);
|
||||
// }
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
namespace app\common\traits\crud;
|
||||
|
||||
use app\common\utils\CrudUtil;
|
||||
|
||||
/**
|
||||
* Trait CrudLogicTrait
|
||||
*
|
||||
* 提供最基础的 CRUD 和复杂查询方法,基于 CrudUtil。
|
||||
* 使用此 Trait 的类,须定义:
|
||||
* protected string $modelClass = \App\common\model\YourModel::class;
|
||||
*/
|
||||
trait CrudLogicTrait
|
||||
{
|
||||
/**
|
||||
* 分页列表
|
||||
*/
|
||||
public function logicList(array $config, int $limit, int $page)
|
||||
{
|
||||
$config['model'] = $this->modelClass;
|
||||
return CrudUtil::paginate(CrudUtil::buildQuery($config), $limit, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 全量列表
|
||||
*/
|
||||
public function logicListAll(array $config)
|
||||
{
|
||||
$config['model'] = $this->modelClass;
|
||||
return CrudUtil::getAll(CrudUtil::buildQuery($config));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单条
|
||||
*/
|
||||
public function logicShow(array $config)
|
||||
{
|
||||
return CrudUtil::findOneBy(CrudUtil::buildQuery($config));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建(可带 dataLimit)
|
||||
*/
|
||||
public function logicCreate(array $data, array $dataLimit = [])
|
||||
{
|
||||
return CrudUtil::create($this->modelClass, $data, $dataLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任意条件更新(可带 dataLimit)
|
||||
*/
|
||||
public function logicUpdateBy(array $config, array $data)
|
||||
{
|
||||
return CrudUtil::updateBy(CrudUtil::buildQuery($config), $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任意条件删除(可带 dataLimit)
|
||||
*/
|
||||
public function logicDeleteBy(array $config)
|
||||
{
|
||||
return CrudUtil::deleteBy(CrudUtil::buildQuery($config));
|
||||
}
|
||||
|
||||
/**
|
||||
* 复杂查询回调
|
||||
*/
|
||||
public function logicComplex(callable $callback)
|
||||
{
|
||||
return CrudUtil::executeComplex($this->modelClass, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主键查询
|
||||
*/
|
||||
public function findByPk(int $id, array $with = [])
|
||||
{
|
||||
return CrudUtil::findByPk($this->modelClass, $id, $with);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
namespace app\common\traits\crud;
|
||||
|
||||
use support\exception\BusinessException;
|
||||
use support\Request;
|
||||
|
||||
/**
|
||||
* Trait CrudServiceTrait
|
||||
*
|
||||
* 提供标准的增删改查 Service 接口,依赖 CrudLogicTrait。
|
||||
* 使用此 Trait 的 Service 类,须:
|
||||
* use CrudLogicTrait, CrudServiceTrait;
|
||||
* protected string $modelClass = \App\common\model\YourModel::class;
|
||||
*/
|
||||
trait CrudServiceTrait
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/** 具体 Service 里定义:protected string $modelClass = YourModel::class; */
|
||||
|
||||
/**
|
||||
* 获取验证器
|
||||
*/
|
||||
public function getValidator()
|
||||
{
|
||||
return $this->validator;
|
||||
}
|
||||
|
||||
/** 分页列表 */
|
||||
public function serviceList(Request $request, string $scene = 'index')
|
||||
{
|
||||
$result = $this->cqyProcess(ucfirst($scene), $request);
|
||||
if ($result === false) {
|
||||
$config = $this->buildQueryConfig($request, $scene);
|
||||
$limit = (int)$request->get('size', 15);
|
||||
$page = (int)$request->get('current', 1);
|
||||
$result = $this->logicList($config, $limit, $page);
|
||||
}
|
||||
return $this->postProcess(ucfirst($scene), $result);
|
||||
}
|
||||
|
||||
/** 全量列表 */
|
||||
public function serviceListAll(Request $request, string $scene = 'default')
|
||||
{
|
||||
$result = $this->cqyProcess(ucfirst($scene), $request);
|
||||
if ($result === false) {
|
||||
$config = $this->buildQueryConfig($request, $scene);
|
||||
$result = $this->logicListAll($config);
|
||||
}
|
||||
return $this->postProcess(ucfirst($scene), $result);
|
||||
}
|
||||
|
||||
/** 查看单条 */
|
||||
public function serviceShow(Request $request, string $scene = 'show')
|
||||
{
|
||||
$config = $this->buildQueryConfig($request, $scene);
|
||||
$item = $this->logicShow($config);
|
||||
return $this->postProcess(ucfirst($scene), $item);
|
||||
}
|
||||
|
||||
/** 创建 */
|
||||
public function serviceStore(Request $request, array $data = [], string $scene = 'store')
|
||||
{
|
||||
$data = $this->preProcess(ucfirst($scene), $data, $request);
|
||||
$dl = $this->getDataLimitFromRequest($request);
|
||||
$created = $this->logicCreate($data, $dl);
|
||||
return $this->postProcess(ucfirst($scene), $created);
|
||||
}
|
||||
|
||||
/** 更新 */
|
||||
public function serviceUpdate(Request $request, array $data = [], string $scene = 'update')
|
||||
{
|
||||
$data = $this->preProcess(ucfirst($scene), $data, $request);
|
||||
$config = $this->buildQueryConfig($request, $scene);
|
||||
$updated = $this->logicUpdateBy($config, $data);
|
||||
return $this->postProcess(ucfirst($scene), $data);
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
public function serviceDestroy(Request $request, string $scene = 'destroy')
|
||||
{
|
||||
$config = $this->buildQueryConfig($request, $scene);
|
||||
$deleted = $this->logicDeleteBy($config);
|
||||
return $this->postProcess(ucfirst($scene), $deleted);
|
||||
}
|
||||
|
||||
/** ----------------- 公共辅助方法 ----------------- */
|
||||
|
||||
/**
|
||||
* 构造 CrudUtil 查询配置
|
||||
*/
|
||||
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
|
||||
{
|
||||
$p = $request->all();
|
||||
|
||||
$config = [];
|
||||
$config['model'] = $this->modelClass;
|
||||
switch ($scene) {
|
||||
case 'default':
|
||||
$config['where']['status'] = 1;
|
||||
$config['order']['sort'] = 'desc';
|
||||
$config['order']['id'] = 'desc';
|
||||
break;
|
||||
case 'index':
|
||||
$config['where']['status'] = 1;
|
||||
$config['order']['sort'] = 'desc';
|
||||
$config['order']['id'] = 'desc';
|
||||
break;
|
||||
case 'show':
|
||||
$config['where']['id'] = $p['id'];
|
||||
$config['where']['status'] = 1;
|
||||
break;
|
||||
case 'store':
|
||||
break;
|
||||
case 'update':
|
||||
$config['where']['id'] = $p['id'];
|
||||
break;
|
||||
case 'destroy':
|
||||
$config['where']['id'] = $p['id'];
|
||||
break;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/** 解析 withFields 字符串 "rel:field,field;..." */
|
||||
protected function parseWithFields(string $str): array
|
||||
{
|
||||
if (!$str) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach (explode(';', $str) as $pair) {
|
||||
[$rel, $flds] = explode(':', $pair);
|
||||
$out[$rel] = explode(',', $flds);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** 从请求读取 dataLimitType, dataLimitField */
|
||||
protected function getDataLimitFromRequest(Request $req): array
|
||||
{
|
||||
$type = $req->get('dataLimitType');
|
||||
if (!$type) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
'type' => $type,
|
||||
'field' => $req->get('dataLimitField', 'admin_id'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* $data 前置处理
|
||||
*
|
||||
* @param array $data
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
protected function preProcess(string $op, mixed $data, Request $request): mixed
|
||||
{
|
||||
$hook = "beforeService{$op}";
|
||||
return method_exists($this, $hook)
|
||||
? $this->{$hook}($data, $request)
|
||||
: $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据操作类型,调用对应的后置钩子
|
||||
*
|
||||
* @param string $op 操作:List, Show, Store, Update, Destroy
|
||||
* @param mixed $data 原始返回值
|
||||
* @return mixed 处理后的返回值
|
||||
*/
|
||||
protected function postProcess(string $op, mixed $data): mixed
|
||||
{
|
||||
$hook = "afterService{$op}";
|
||||
if (method_exists($this, $hook)) {
|
||||
$args = func_get_args();
|
||||
array_shift($args);
|
||||
array_shift($args);
|
||||
$params = array_merge([$data], $args);
|
||||
return call_user_func_array([$this, $hook], $params);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复杂/自定义查询
|
||||
*
|
||||
* @param string $op 场景
|
||||
* @param mixed $data 原始返回值
|
||||
* @return mixed 处理后的返回值
|
||||
*/
|
||||
protected function cqyProcess(string $op, Request $request): mixed
|
||||
{
|
||||
$hook = "complexQuery{$op}";
|
||||
return method_exists($this, $hook)
|
||||
? $this->{$hook}($request)
|
||||
: false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
namespace app\common\traits\crud;
|
||||
|
||||
use support\exception\BusinessException;
|
||||
use support\Request;
|
||||
|
||||
/**
|
||||
* Trait CrudServiceTrait
|
||||
*
|
||||
* 提供标准的增删改查 Service 接口,依赖 CrudLogicTrait。
|
||||
* 使用此 Trait 的 Service 类,须:
|
||||
* use CrudLogicTrait, CrudServiceTrait;
|
||||
* protected string $modelClass = \App\common\model\YourModel::class;
|
||||
*/
|
||||
trait CrudServiceTrait_bak
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/** 具体 Service 里定义:protected string $modelClass = YourModel::class; */
|
||||
|
||||
/**
|
||||
* 获取验证器
|
||||
*/
|
||||
public function getValidator()
|
||||
{
|
||||
return $this->validator;
|
||||
}
|
||||
|
||||
/** 分页列表 */
|
||||
public function serviceList(Request $request)
|
||||
{
|
||||
if (method_exists($this, 'complexQuery')) {
|
||||
$result = $this->complexQuery($request->get());
|
||||
} else {
|
||||
$config = $this->buildQueryConfig($request, 'index');
|
||||
$limit = (int)$request->get('size', 15);
|
||||
$page = (int)$request->get('current', 1);
|
||||
$result = $this->logicList($config, $limit, $page);
|
||||
}
|
||||
return $this->postProcess('List', $result);
|
||||
}
|
||||
|
||||
/** 全量列表 */
|
||||
public function serviceListAll(Request $request)
|
||||
{
|
||||
if (method_exists($this, 'complexQuery')) {
|
||||
$result = $this->complexQuery($request->get());
|
||||
} else {
|
||||
$config = $this->buildQueryConfig($request, 'default');
|
||||
$result = $this->logicListAll($config);
|
||||
}
|
||||
return $this->postProcess('ListAll', $result);
|
||||
}
|
||||
|
||||
/** 查看单条 */
|
||||
public function serviceShow(Request $request)
|
||||
{
|
||||
$config = $this->buildQueryConfig($request, 'show');
|
||||
$item = $this->logicShow($config);
|
||||
return $this->postProcess('Show', $item);
|
||||
}
|
||||
|
||||
/** 创建 */
|
||||
public function serviceStore(Request $request, array $data = [])
|
||||
{
|
||||
$data = $this->preProcess('Store', $data, $request);
|
||||
$dl = $this->getDataLimitFromRequest($request);
|
||||
$created = $this->logicCreate($data, $dl);
|
||||
return $this->postProcess('Store', $created);
|
||||
}
|
||||
|
||||
/** 更新 */
|
||||
public function serviceUpdate(Request $request, array $data = [])
|
||||
{
|
||||
$data = $this->preProcess('Update', $data, $request);
|
||||
$config = $this->buildQueryConfig($request, 'update');
|
||||
$updated = $this->logicUpdateBy($config, $data);
|
||||
return $this->postProcess('Update', $data);
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
public function serviceDestroy(Request $request)
|
||||
{
|
||||
$config = $this->buildQueryConfig($request, 'destroy');
|
||||
$deleted = $this->logicDeleteBy($config);
|
||||
return $this->postProcess('Destroy', $deleted);
|
||||
}
|
||||
|
||||
/** ----------------- 公共辅助方法 ----------------- */
|
||||
|
||||
/**
|
||||
* 构造 CrudUtil 查询配置
|
||||
*/
|
||||
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
|
||||
{
|
||||
$p = $request->all();
|
||||
|
||||
$config = [];
|
||||
$config['model'] = $this->modelClass;
|
||||
switch ($scene) {
|
||||
case 'default':
|
||||
$config['where']['status'] = 1;
|
||||
$config['order']['sort'] = 'desc';
|
||||
$config['order']['id'] = 'desc';
|
||||
break;
|
||||
case 'index':
|
||||
$config['where']['status'] = 1;
|
||||
$config['order']['sort'] = 'desc';
|
||||
$config['order']['id'] = 'desc';
|
||||
break;
|
||||
case 'show':
|
||||
$config['where']['id'] = $p['id'];
|
||||
$config['where']['status'] = 1;
|
||||
break;
|
||||
case 'store':
|
||||
break;
|
||||
case 'update':
|
||||
$config['where']['id'] = $p['id'];
|
||||
break;
|
||||
case 'destroy':
|
||||
$config['where']['id'] = $p['id'];
|
||||
break;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/** 解析 withFields 字符串 "rel:field,field;..." */
|
||||
protected function parseWithFields(string $str): array
|
||||
{
|
||||
if (!$str) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach (explode(';', $str) as $pair) {
|
||||
[$rel, $flds] = explode(':', $pair);
|
||||
$out[$rel] = explode(',', $flds);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** 从请求读取 dataLimitType, dataLimitField */
|
||||
protected function getDataLimitFromRequest(Request $req): array
|
||||
{
|
||||
$type = $req->get('dataLimitType');
|
||||
if (!$type) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
'type' => $type,
|
||||
'field' => $req->get('dataLimitField', 'admin_id'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* $data 前置处理
|
||||
*
|
||||
* @param array $data
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
protected function preProcess(string $op, mixed $data, Request $request): mixed
|
||||
{
|
||||
$hook = "beforeService{$op}";
|
||||
return method_exists($this, $hook)
|
||||
? $this->{$hook}($data, $request)
|
||||
: $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据操作类型,调用对应的后置钩子
|
||||
*
|
||||
* @param string $op 操作:List, Show, Store, Update, Destroy
|
||||
* @param mixed $data 原始返回值
|
||||
* @return mixed 处理后的返回值
|
||||
*/
|
||||
protected function postProcess(string $op, mixed $data): mixed
|
||||
{
|
||||
$hook = "afterService{$op}";
|
||||
return method_exists($this, $hook)
|
||||
? $this->{$hook}($data)
|
||||
: $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理复杂查询
|
||||
* 覆盖此方法,实现复杂查询逻辑
|
||||
*/
|
||||
// protected function complexQuery(array $params): array
|
||||
// {
|
||||
// throw new BusinessException('请在子类实现 complexQuery');
|
||||
// }
|
||||
}
|
||||
Reference in New Issue
Block a user