样机小程序
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\utils;
|
||||
|
||||
use Firebase\JWT\JWT as FirebaseJWT;
|
||||
use Firebase\JWT\Key;
|
||||
use support\exception\BusinessException;
|
||||
|
||||
class Jwt
|
||||
{
|
||||
protected $secretKey;
|
||||
protected $algorithm;
|
||||
protected $issuer;
|
||||
protected $expiration;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->secretKey = config('jwt.jwt_secret_key');
|
||||
$this->algorithm = config('jwt.jwt_algorithm');
|
||||
$this->issuer = config('jwt.jwt_issuer');
|
||||
$this->expiration = config('jwt.jwt_expiration');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 JWT 令牌
|
||||
*
|
||||
* @param array $payload
|
||||
* @return string
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public function generateToken(array $payload): string
|
||||
{
|
||||
$defaultPayload = [
|
||||
// // —— 注册 Claim ——
|
||||
'iss' => $this->issuer, // 签发者
|
||||
// 'sub' => 'user_1001', // 面向的用户
|
||||
// 'aud' => 'api.gateway.example.com', // 接收方
|
||||
// 'iat' => time(), // 签发时间
|
||||
// 'nbf' => time(), // 生效时间
|
||||
// 'exp' => time() + $this->expiration, // 1 小时后过期
|
||||
'jti' => bin2hex(random_bytes(16)), // 唯一 ID
|
||||
// // —— 自定义 Claim ——
|
||||
// 'uid' => 1001, // 业务用户 ID
|
||||
// 'roles' => ['admin', 'editor'], // 用户角色
|
||||
// 'meta' => ['region' => 'CN', 'tier' => 5], // 任意自定义数据
|
||||
];
|
||||
|
||||
$payload = array_merge($defaultPayload, $payload);
|
||||
|
||||
try {
|
||||
return FirebaseJWT::encode($payload, $this->secretKey, $this->algorithm);
|
||||
} catch (\Exception $e) {
|
||||
throw new BusinessException("令牌生成失败:" . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 JWT 令牌
|
||||
*
|
||||
* @param string $token
|
||||
* @return object
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public function verifyToken(string $token)
|
||||
{
|
||||
try {
|
||||
return FirebaseJWT::decode($token, new Key($this->secretKey, $this->algorithm));
|
||||
} catch (\Firebase\JWT\ExpiredException $e) {
|
||||
throw new BusinessException('令牌已过期', 401);
|
||||
} catch (\Firebase\JWT\SignatureInvalidException $e) {
|
||||
throw new BusinessException('无效的令牌签名', 401);
|
||||
} catch (\Firebase\JWT\BeforeValidException $e) {
|
||||
throw new BusinessException('令牌尚未生效', 401);
|
||||
} catch (\Exception $e) {
|
||||
throw new BusinessException('令牌验证失败:' . $e->getMessage(), 400);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function describe()
|
||||
{
|
||||
/* payload
|
||||
1. 注册 Claim 详解
|
||||
|
||||
Claim 名称 含义 类型 建议取值
|
||||
iss Issuer,发行者,标识该 JWT 的签发主体 string or URI 你的服务域名或项目标识,比如 https://api.example.com
|
||||
sub Subject,主题,标识该 JWT 所面向的用户 string or int 用户在你系统里的唯一 ID
|
||||
aud Audience,受众,标识该 JWT 的接收方 string or array 接收该令牌的服务标识,比如 ["service-a","service-b"]
|
||||
exp Expiration Time,过期时间,Unix 时间戳,到达此时间后不可用 int time() + 3600(1 小时后过期)
|
||||
nbf Not Before,生效时间,Unix 时间戳,表示在此时间之前不可用 int time() 或者 time() + 60(1 分钟后生效)
|
||||
iat Issued At,签发时间,Unix 时间戳 int time()
|
||||
jti JWT ID,JWT 的唯一标识符,用于去重或一次性令牌 string uniqid('', true) 或者 UUID
|
||||
|
||||
注意:exp、nbf、iat 必须是整数(秒级时间戳),否则库会抛类型异常或验证失败。
|
||||
|
||||
2. 自定义 Claim
|
||||
|
||||
除了上面这些“注册”字段,你可以任意添加,比如:
|
||||
|
||||
php
|
||||
$payload['uid'] = 123; // 业务用户 ID
|
||||
$payload['roles'] = ['admin','user']; // 权限数组
|
||||
$payload['feature'] = 'beta'; // 标记某个功能开关
|
||||
|
||||
这些字段在你的应用程序里可以自由取用。
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\utils;
|
||||
|
||||
use support\Db;
|
||||
|
||||
class TableInspector
|
||||
{
|
||||
/**
|
||||
* 获取某个表的所有字段信息
|
||||
*/
|
||||
public static function getColumns(string $table, ?string $database = null): array
|
||||
{
|
||||
$database = $database ?: Db::connection()->getDatabaseName();
|
||||
|
||||
$columns = Db::select("
|
||||
SELECT COLUMN_NAME, DATA_TYPE, COLUMN_TYPE, IS_NULLABLE,
|
||||
COLUMN_DEFAULT, COLUMN_COMMENT, EXTRA
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
||||
ORDER BY ORDINAL_POSITION
|
||||
", [$database, $table]);
|
||||
|
||||
return array_map(function ($col) {
|
||||
return [
|
||||
'name' => $col->COLUMN_NAME,
|
||||
'type' => $col->DATA_TYPE,
|
||||
'column_type' => $col->COLUMN_TYPE,
|
||||
'nullable' => $col->IS_NULLABLE === 'YES',
|
||||
'default' => $col->COLUMN_DEFAULT,
|
||||
'comment' => $col->COLUMN_COMMENT,
|
||||
'extra' => $col->EXTRA,
|
||||
];
|
||||
}, $columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段名列表
|
||||
*/
|
||||
public static function getColumnNames(string $table, ?string $database = null): array
|
||||
{
|
||||
return array_column(self::getColumns($table, $database), 'name');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段注释 map:字段名 => 注释
|
||||
*/
|
||||
public static function getColumnComments(string $table, ?string $database = null): array
|
||||
{
|
||||
$columns = self::getColumns($table, $database);
|
||||
$map = [];
|
||||
foreach ($columns as $col) {
|
||||
$map[$col['name']] = $col['comment'];
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,505 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\utils;
|
||||
|
||||
class Tree
|
||||
{
|
||||
// /**
|
||||
// * 递归构建树形结构
|
||||
// *
|
||||
// * @param array $data 数据列表
|
||||
// * @param int $parentId 父级ID
|
||||
// * @param string $pidField 父级字段名
|
||||
// * @param string $idField ID字段名
|
||||
// * @param int $level 当前层级
|
||||
// * @return array
|
||||
// */
|
||||
// public static function build(array $data, int $parentId = 0, string $pidField = 'parent_id', string $idField = 'id', int $level = 0): array
|
||||
// {
|
||||
// $tree = [];
|
||||
|
||||
// foreach ($data as $item) {
|
||||
// if ($item[$pidField] == $parentId) {
|
||||
// $item['level'] = $level;
|
||||
// $children = static::build($data, $item[$idField], $pidField, $idField, $level + 1);
|
||||
|
||||
// if (!empty($children)) {
|
||||
// $item['children'] = $children;
|
||||
// }
|
||||
|
||||
// $tree[] = $item;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return $tree;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * 使用引用方式构建树形结构(性能更好)
|
||||
// *
|
||||
// * @param array $data 数据列表
|
||||
// * @param string $pidField 父级字段名
|
||||
// * @param string $idField ID字段名
|
||||
// * @return array
|
||||
// */
|
||||
// public static function buildWithReference(array $data, string $pidField = 'parent_id', string $idField = 'id'): array
|
||||
// {
|
||||
// $items = [];
|
||||
// $tree = [];
|
||||
|
||||
// // 建立以id为索引的数组
|
||||
// foreach ($data as $item) {
|
||||
// $item['level'] = 0;
|
||||
// $items[$item[$idField]] = $item;
|
||||
// }
|
||||
|
||||
// // 构建树形结构
|
||||
// foreach ($items as $id => $item) {
|
||||
// if ($item[$pidField] == 0) {
|
||||
// $tree[] = &$items[$id];
|
||||
// } else {
|
||||
// if (isset($items[$item[$pidField]])) {
|
||||
// $items[$id]['level'] = $items[$item[$pidField]]['level'] + 1;
|
||||
|
||||
// if (!isset($items[$item[$pidField]]['children'])) {
|
||||
// $items[$item[$pidField]]['children'] = [];
|
||||
// }
|
||||
|
||||
// $items[$item[$pidField]]['children'][] = &$items[$id];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return $tree;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * 将树形结构扁平化为列表(带层级前缀)
|
||||
// *
|
||||
// * @param array $tree 树形结构数据
|
||||
// * @param string $prefix 前缀字符
|
||||
// * @param string $field 显示字段名
|
||||
// * @param int $level 层级
|
||||
// * @return array
|
||||
// */
|
||||
// public static function flattenWithPrefix(array $tree, string $prefix = '--', string $field = 'name', int $level = 0): array
|
||||
// {
|
||||
// $result = [];
|
||||
|
||||
// foreach ($tree as $item) {
|
||||
// $item[$field . '_with_prefix'] = str_repeat($prefix, $item['level']) . $item[$field];
|
||||
// $result[] = $item;
|
||||
|
||||
// if (isset($item['children']) && !empty($item['children'])) {
|
||||
// $children = static::flattenWithPrefix($item['children'], $prefix, $field, $item['level'] + 1);
|
||||
// $result = array_merge($result, $children);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return $result;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * 根据父ID查找所有子节点(包括子孙节点)
|
||||
// *
|
||||
// * @param array $data 数据列表
|
||||
// * @param int $parentId 父级ID
|
||||
// * @param string $pidField 父级字段名
|
||||
// * @param string $idField ID字段名
|
||||
// * @return array
|
||||
// */
|
||||
// public static function findChildren(array $data, int $parentId, string $pidField = 'parent_id', string $idField = 'id'): array
|
||||
// {
|
||||
// $children = [];
|
||||
|
||||
// foreach ($data as $item) {
|
||||
// if ($item[$pidField] == $parentId) {
|
||||
// $children[] = $item;
|
||||
// $grandChildren = static::findChildren($data, $item[$idField], $pidField, $idField);
|
||||
// $children = array_merge($children, $grandChildren);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return $children;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * 获取指定节点的所有父级节点
|
||||
// *
|
||||
// * @param array $data 数据列表
|
||||
// * @param int $id 当前节点ID
|
||||
// * @param string $pidField 父级字段名
|
||||
// * @param string $idField ID字段名
|
||||
// * @return array
|
||||
// */
|
||||
// public static function getParents(array $data, int $id, string $pidField = 'parent_id', string $idField = 'id'): array
|
||||
// {
|
||||
// $parents = [];
|
||||
|
||||
// foreach ($data as $item) {
|
||||
// if ($item[$idField] == $id) {
|
||||
// if ($item[$pidField] != 0) {
|
||||
// $parents[] = $item;
|
||||
// $parentParents = static::getParents($data, $item[$pidField], $pidField, $idField);
|
||||
// $parents = array_merge($parents, $parentParents);
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return $parents;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * 获取指定节点的子孙节点ID列表
|
||||
// *
|
||||
// * @param array $data 数据列表
|
||||
// * @param int $parentId 父级ID
|
||||
// * @param string $pidField 父级字段名
|
||||
// * @param string $idField ID字段名
|
||||
// * @return array
|
||||
// */
|
||||
// public static function getChildrenIds(array $data, int $parentId, string $pidField = 'parent_id', string $idField = 'id'): array
|
||||
// {
|
||||
// $ids = [];
|
||||
// $children = static::findChildren($data, $parentId, $pidField, $idField);
|
||||
|
||||
// foreach ($children as $child) {
|
||||
// $ids[] = $child[$idField];
|
||||
// }
|
||||
|
||||
// return $ids;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * 检查是否有子节点
|
||||
// *
|
||||
// * @param array $data 数据列表
|
||||
// * @param int $parentId 父级ID
|
||||
// * @param string $pidField 父级字段名
|
||||
// * @return bool
|
||||
// */
|
||||
// public static function hasChildren(array $data, int $parentId, string $pidField = 'parent_id'): bool
|
||||
// {
|
||||
// foreach ($data as $item) {
|
||||
// if ($item[$pidField] == $parentId) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return false;
|
||||
// }
|
||||
|
||||
|
||||
// /**
|
||||
// * 构建带有权限列表的资源树(权限不显示在树结构中)
|
||||
// *
|
||||
// * @param array $data 资源数据列表
|
||||
// * @param int $parentId 父级ID
|
||||
// * @param string $pidField 父级字段名
|
||||
// * @param string $idField ID字段名
|
||||
// * @param string $typeField 类型字段名
|
||||
// * @param string $permissionType 权限类型标识
|
||||
// * @param int $level 当前层级
|
||||
// * @return array
|
||||
// */
|
||||
// public static function buildWithAuthList(
|
||||
// array $data,
|
||||
// int $parentId = 0,
|
||||
// string $pidField = 'parent_id',
|
||||
// string $idField = 'id',
|
||||
// string $typeField = 'type',
|
||||
// string $permissionType = 'permission',
|
||||
// int $level = 0
|
||||
// ): array {
|
||||
// $tree = [];
|
||||
|
||||
// foreach ($data as $item) {
|
||||
// // 只处理指定父级的非权限节点
|
||||
// if ($item[$pidField] == $parentId && $item[$typeField] != $permissionType) {
|
||||
// $item['level'] = $level;
|
||||
|
||||
// // 递归处理子节点(非权限类型)
|
||||
// $children = static::buildWithAuthList($data, $item[$idField], $pidField, $idField, $typeField, $permissionType, $level + 1);
|
||||
|
||||
// if (!empty($children)) {
|
||||
// $item['children'] = $children;
|
||||
// }
|
||||
|
||||
// // 收集当前节点下的所有权限
|
||||
// $item['auth_list'] = static::collectDirectPermissions($data, $item[$idField], $pidField, $typeField, $permissionType);
|
||||
|
||||
// $tree[] = $item;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return $tree;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * 收集指定节点下的直接权限项(只收集直接子节点中的权限)
|
||||
// *
|
||||
// * @param array $data 数据列表
|
||||
// * @param int $parentId 父级ID
|
||||
// * @param string $pidField 父级字段名
|
||||
// * @param string $typeField 类型字段名
|
||||
// * @param string $permissionType 权限类型标识
|
||||
// * @return array
|
||||
// */
|
||||
// public static function collectDirectPermissions1(
|
||||
// array $data,
|
||||
// int $parentId,
|
||||
// string $pidField = 'parent_id',
|
||||
// string $typeField = 'type',
|
||||
// string $permissionType = 'permission'
|
||||
// ): array {
|
||||
// $permissions = [];
|
||||
|
||||
// foreach ($data as $item) {
|
||||
// // 收集直接子节点中的权限类型
|
||||
// if ($item[$pidField] == $parentId && $item[$typeField] == $permissionType) {
|
||||
// $permissions[] = $item;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return $permissions;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * 使用引用方式构建带有权限列表的资源树(权限不显示在树结构中)
|
||||
// *
|
||||
// * @param array $data 资源数据列表
|
||||
// * @param string $pidField 父级字段名
|
||||
// * @param string $idField ID字段名
|
||||
// * @param string $typeField 类型字段名
|
||||
// * @param string $permissionType 权限类型标识
|
||||
// * @return array
|
||||
// */
|
||||
// public static function buildWithAuthListAndReference(
|
||||
// array $data,
|
||||
// string $pidField = 'parent_id',
|
||||
// string $idField = 'id',
|
||||
// string $typeField = 'type',
|
||||
// string $permissionType = 'permission'
|
||||
// ): array {
|
||||
// $items = [];
|
||||
// $tree = [];
|
||||
|
||||
// // 建立以id为索引的数组(只包含非权限节点)
|
||||
// foreach ($data as $item) {
|
||||
// if ($item[$typeField] != $permissionType) {
|
||||
// $item['level'] = 0;
|
||||
// $items[$item[$idField]] = $item;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 构建树形结构
|
||||
// foreach ($items as $id => $item) {
|
||||
// if ($item[$pidField] == 0) {
|
||||
// $tree[] = &$items[$id];
|
||||
// } else {
|
||||
// if (isset($items[$item[$pidField]])) {
|
||||
// $items[$id]['level'] = $items[$item[$pidField]]['level'] + 1;
|
||||
|
||||
// if (!isset($items[$item[$pidField]]['children'])) {
|
||||
// $items[$item[$pidField]]['children'] = [];
|
||||
// }
|
||||
|
||||
// $items[$item[$pidField]]['children'][] = &$items[$id];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 为每个节点添加权限列表
|
||||
// foreach ($items as &$item) {
|
||||
// $item['auth_list'] = static::collectDirectPermissions($data, $item[$idField], $pidField, $typeField, $permissionType);
|
||||
// }
|
||||
|
||||
// return $tree;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// 8.13 add
|
||||
/**
|
||||
* 构建前端需要的菜单树结构
|
||||
*
|
||||
* @param array $data 服务端菜单数据
|
||||
* @param int $parentId 父级ID
|
||||
* @return array
|
||||
*/
|
||||
public static function buildMenuTree(array $data, int $parentId = 0): array
|
||||
{
|
||||
$tree = [];
|
||||
|
||||
foreach ($data as $item) {
|
||||
// 只处理非权限类型的节点
|
||||
if ($item['parent_id'] == $parentId && $item['type'] != 'permission') {
|
||||
// 转换数据结构以匹配前端需要的格式
|
||||
$menuItem = [
|
||||
'id' => $item['id'],
|
||||
'path' => $item['url'] ?? '',
|
||||
'name' => $item['name'],
|
||||
'component' => $item['url'] ?? '',
|
||||
'meta' => [
|
||||
'title' => $item['name'],
|
||||
'icon' => $item['icon'] ?? '',
|
||||
'isHide' => !empty($item['is_hide']),
|
||||
'isHideTab' => !empty($item['is_hide_tab']),
|
||||
'keepAlive' => !empty($item['keep_alive']),
|
||||
]
|
||||
];
|
||||
|
||||
// 收集当前节点下的所有权限
|
||||
$permissions = static::collectDirectPermissions($data, $item['id']);
|
||||
if (!empty($permissions)) {
|
||||
$authList = [];
|
||||
foreach ($permissions as $permission) {
|
||||
$authList[] = [
|
||||
'id' => $permission['id'],
|
||||
'title' => $permission['name'] ?? '',
|
||||
'authMark' => $permission['code'] ?? '',
|
||||
'selected' => isset($permission['selected']) && $permission['selected'] ? true : false
|
||||
];
|
||||
}
|
||||
$menuItem['meta']['authList'] = $authList;
|
||||
}
|
||||
|
||||
// 将服务端额外字段放入meta中
|
||||
$extraFields = static::getExtraFields($item);
|
||||
if (!empty($extraFields)) {
|
||||
$menuItem['meta'] = array_merge($menuItem['meta'], $extraFields);
|
||||
}
|
||||
|
||||
// 递归处理子菜单
|
||||
$children = static::buildMenuTree($data, $item['id']);
|
||||
if (!empty($children)) {
|
||||
$menuItem['children'] = $children;
|
||||
}
|
||||
|
||||
$tree[] = $menuItem;
|
||||
}
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用引用方式构建前端需要的菜单树结构(性能更好)
|
||||
*
|
||||
* @param array $data 服务端菜单数据
|
||||
* @return array
|
||||
*/
|
||||
public static function buildMenuTreeWithReference(array $data): array
|
||||
{
|
||||
$items = [];
|
||||
$tree = [];
|
||||
|
||||
// 过滤出菜单类型的节点并转换数据结构
|
||||
$menuData = array_filter($data, function($item) {
|
||||
return ($item['type'] ?? '') != 'permission';
|
||||
});
|
||||
|
||||
// 建立以id为索引的数组并转换数据结构
|
||||
foreach ($menuData as $item) {
|
||||
$items[$item['id']] = [
|
||||
'id' => $item['id'],
|
||||
'path' => $item['url'] ?? '',
|
||||
'name' => $item['name'],
|
||||
'component' => $item['url'] ?? '',
|
||||
'meta' => [
|
||||
'title' => $item['name'],
|
||||
'icon' => $item['icon'] ?? '',
|
||||
'isHide' => !empty($item['is_hide']),
|
||||
'isHideTab' => !empty($item['is_hide_tab']),
|
||||
'keepAlive' => !empty($item['keep_alive']),
|
||||
]
|
||||
];
|
||||
|
||||
// 将服务端额外字段放入meta中
|
||||
$extraFields = static::getExtraFields($item);
|
||||
if (!empty($extraFields)) {
|
||||
$items[$item['id']]['meta'] = array_merge($items[$item['id']]['meta'], $extraFields);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建树形结构
|
||||
foreach ($menuData as $item) {
|
||||
if ($item['parent_id'] == 0) {
|
||||
$tree[] = &$items[$item['id']];
|
||||
} else {
|
||||
if (isset($items[$item['parent_id']])) {
|
||||
if (!isset($items[$item['parent_id']]['children'])) {
|
||||
$items[$item['parent_id']]['children'] = [];
|
||||
}
|
||||
$items[$item['parent_id']]['children'][] = &$items[$item['id']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个节点添加权限列表
|
||||
foreach ($items as &$item) {
|
||||
$parentId = $item['id'];
|
||||
$permissions = static::collectDirectPermissions($data, $parentId);
|
||||
if (!empty($permissions)) {
|
||||
$authList = [];
|
||||
foreach ($permissions as $permission) {
|
||||
$authList[] = [
|
||||
'id' => $permission['id'],
|
||||
'title' => $permission['name'] ?? '',
|
||||
'authMark' => $permission['code'] ?? ''
|
||||
];
|
||||
}
|
||||
$item['meta']['authList'] = $authList;
|
||||
}
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集指定节点下的直接权限项(只收集直接子节点中的权限)
|
||||
*
|
||||
* @param array $data 数据列表
|
||||
* @param int $parentId 父级ID
|
||||
* @return array
|
||||
*/
|
||||
public static function collectDirectPermissions(array $data, int $parentId): array
|
||||
{
|
||||
$permissions = [];
|
||||
|
||||
foreach ($data as $item) {
|
||||
// 收集直接子节点中的权限类型
|
||||
if ($item['parent_id'] == $parentId && ($item['type'] ?? '') == 'permission') {
|
||||
$permissions[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需要放入meta的额外字段
|
||||
*
|
||||
* @param array $item 菜单项
|
||||
* @return array
|
||||
*/
|
||||
public static function getExtraFields(array $item): array
|
||||
{
|
||||
$extraFields = [];
|
||||
$standardFields = [
|
||||
'id', 'name', 'url', 'icon', 'is_hide', 'is_hide_tab', 'keep_alive', 'level'
|
||||
];
|
||||
|
||||
foreach ($item as $key => $value) {
|
||||
// 如果不是标准字段且不是auth_list,则放入extraFields
|
||||
if (!in_array($key, $standardFields) && $key !== 'auth_list') {
|
||||
// 转换下划线命名到驼峰命名
|
||||
$camelKey = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $key))));
|
||||
$extraFields[$camelKey] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $extraFields;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\utils\scaffold;
|
||||
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use app\common\utils\TableInspector;
|
||||
|
||||
class Generate
|
||||
{
|
||||
|
||||
// 配置项
|
||||
protected $options = [];
|
||||
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
if (empty($options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
public function doGenerate()
|
||||
{
|
||||
$fs = new Filesystem();
|
||||
|
||||
$templateDir = __DIR__ . '/templates/';
|
||||
|
||||
$resultStr = '';
|
||||
|
||||
if (!empty($this->options['controller'])) {
|
||||
$controllerPath = __DIR__ . "/../../../{$this->options['controller']['module']}/controller/".$this->rName($this->options['controller']['name'])."Controller.php";
|
||||
$fs->mkdir(__DIR__ . "/../../../{$this->options['controller']['module']}/controller");
|
||||
$this->deleteFileIfExists($controllerPath);
|
||||
$ControllerStub = $this->generateFile($templateDir, 'ControllerStub.stub', $controllerPath, 'controller');
|
||||
$resultStr .= $ControllerStub;
|
||||
}
|
||||
|
||||
if (!empty($this->options['service'])) {
|
||||
$servicePath = __DIR__ . "/../../../{$this->options['service']['module']}/service/".$this->rName($this->options['service']['name'])."Service.php";
|
||||
$fs->mkdir(__DIR__ . "/../../../{$this->options['service']['module']}/service");
|
||||
$this->deleteFileIfExists($servicePath);
|
||||
$ServiceStub = $this->generateFile($templateDir, 'ServiceStub.stub', $servicePath, 'service');
|
||||
$resultStr .= $ServiceStub;
|
||||
}
|
||||
|
||||
if (!empty($this->options['logic'])) {
|
||||
$logicPath = __DIR__ . "/../../../{$this->options['logic']['module']}/logic/".$this->rName($this->options['logic']['name'])."Logic.php";
|
||||
$fs->mkdir(__DIR__ . "/../../../{$this->options['logic']['module']}/logic");
|
||||
$this->deleteFileIfExists($logicPath);
|
||||
$LogicStub = $this->generateFile($templateDir, 'LogicStub.stub', $logicPath, 'logic');
|
||||
$resultStr .= $LogicStub;
|
||||
}
|
||||
|
||||
if (!empty($this->options['model'])) {
|
||||
$modelPath = __DIR__ . "/../../../{$this->options['model']['module']}/model/".$this->rName($this->options['model']['name']).".php";
|
||||
$fs->mkdir(__DIR__ . "/../../../{$this->options['model']['module']}/model");
|
||||
$this->deleteFileIfExists($modelPath);
|
||||
$ModelStub = $this->generateFile($templateDir, 'ModelStub.stub', $modelPath, 'model');
|
||||
$resultStr .= $ModelStub;
|
||||
}
|
||||
|
||||
if (!empty($this->options['validate'])) {
|
||||
$validatePath = __DIR__ . "/../../../{$this->options['validate']['module']}/validate/".$this->rName($this->options['validate']['name'])."Validate.php";
|
||||
$fs->mkdir(__DIR__ . "/../../../{$this->options['validate']['module']}/validate");
|
||||
$this->deleteFileIfExists($validatePath);
|
||||
$ValidatorStub = $this->generateFile($templateDir, 'ValidateStub.stub', $validatePath, 'validate');
|
||||
$resultStr .= $ValidatorStub;
|
||||
}
|
||||
|
||||
return $resultStr ? $resultStr . "<info>文件生成成功!</info> <br/>" : '未生成任何文件';
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模板生成文件
|
||||
*
|
||||
* @param string $templateDir 模板目录
|
||||
* @param string $templateFile 模板文件名
|
||||
* @param string $outputPath 输出文件路径
|
||||
*/
|
||||
protected function generateFile($templateDir, $templateFile, $outputPath, $fileType)
|
||||
{
|
||||
$templatePath = $templateDir . $templateFile;
|
||||
|
||||
if (!file_exists($templatePath)) {
|
||||
echo "<error>模板文件 {$templateFile} 不存在!</error> \n";
|
||||
return;
|
||||
}
|
||||
|
||||
$content = file_get_contents($templatePath);
|
||||
|
||||
switch ($fileType) {
|
||||
case 'controller':
|
||||
$content = str_replace('{{controllerModule}}', $this->options['controller']['module'], $content);
|
||||
$content = str_replace('{{serviceModule}}', $this->options['service']['module'], $content);
|
||||
$content = str_replace('{{serviceName}}', $this->rName($this->options['service']['name']), $content);
|
||||
$content = str_replace('{{controllerName}}', $this->rName($this->options['controller']['name']), $content);
|
||||
break;
|
||||
case 'service':
|
||||
$content = str_replace('{{serviceModule}}', $this->options['service']['module'], $content);
|
||||
$content = str_replace('{{validateModule}}', $this->options['validate']['module'], $content);
|
||||
$content = str_replace('{{validateName}}', $this->rName($this->options['validate']['name']), $content);
|
||||
$content = str_replace('{{modelModule}}', $this->options['model']['module'], $content);
|
||||
$content = str_replace('{{modelName}}', $this->rName($this->options['model']['name']), $content);
|
||||
$content = str_replace('{{serviceName}}', $this->rName($this->options['service']['name']), $content);
|
||||
break;
|
||||
case 'logic':
|
||||
$content = str_replace('{{logicModule}}', $this->options['logic']['module'], $content);
|
||||
$content = str_replace('{{modelModule}}', $this->options['model']['module'], $content);
|
||||
$content = str_replace('{{modelName}}', $this->rName($this->options['model']['name']), $content);
|
||||
$content = str_replace('{{logicName}}', $this->rName($this->options['logic']['name']), $content);
|
||||
break;
|
||||
case 'model':
|
||||
$content = str_replace('{{modelModule}}', $this->options['model']['module'], $content);
|
||||
$content = str_replace('{{modelName}}', $this->rName($this->options['model']['name']), $content);
|
||||
$content = str_replace('{{table}}', $this->options['table'], $content);
|
||||
$content = str_replace('{{fillable}}', $this->fillableFields(), $content);
|
||||
break;
|
||||
case 'validate':
|
||||
$content = str_replace('{{validateModule}}', $this->options['validate']['module'], $content);
|
||||
$content = str_replace('{{validateName}}', $this->rName($this->options['validate']['name']), $content);
|
||||
break;
|
||||
}
|
||||
|
||||
file_put_contents($outputPath, $content);
|
||||
|
||||
return "生成文件: <info>{$outputPath}</info> <br/>";
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段
|
||||
*
|
||||
* @param array $fields 字段数组
|
||||
*/
|
||||
public function fillableFields()
|
||||
{
|
||||
$tableName = $this->options['table'];
|
||||
$fields = TableInspector::getColumnNames($tableName);
|
||||
$str = '';
|
||||
foreach ($fields as $key => $val) {
|
||||
if ($key < (count($fields)-1)) {
|
||||
$str .= "'{$val}', ";
|
||||
} else {
|
||||
$str .= "'{$val}'";
|
||||
}
|
||||
}
|
||||
$fields = trim($str, '"');
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果文件存在,删除它
|
||||
*
|
||||
* @param string $path 文件路径
|
||||
*/
|
||||
protected function deleteFileIfExists($path)
|
||||
{
|
||||
if (file_exists($path)) {
|
||||
// "文件: <comment>{$path}</comment> 已存在,核对后手动删除。\n";
|
||||
// return;
|
||||
|
||||
unlink($path);
|
||||
echo "删除已存在文件: <comment>{$path}</comment> \n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置名称
|
||||
*/
|
||||
public function rName($name = '')
|
||||
{
|
||||
return ucfirst(camelize($name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下划线转驼峰
|
||||
* 思路:
|
||||
* step1.原字符串转小写,原字符串中的分隔符用空格替换,在字符串开头加上分隔符
|
||||
* step2.将字符串中每个单词的首字母转换为大写,再去空格,去字符串首部附加的分隔符.
|
||||
*/
|
||||
function camelize($uncamelized_words, $separator='_') {
|
||||
$uncamelized_words = $separator . str_replace($separator, " ", strtolower($uncamelized_words));
|
||||
return ltrim(str_replace(" ", "", ucwords($uncamelized_words)), $separator );
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰命名转下划线命名
|
||||
* 思路:
|
||||
* 小写和大写紧挨一起的地方,加上分隔符,然后全部转小写
|
||||
*/
|
||||
function uncamelize($camelCaps, $separator='_') {
|
||||
return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . $separator . "$2", $camelCaps));
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\{{controllerModule}}\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\{{serviceModule}}\service\{{serviceName}}Service;
|
||||
|
||||
class {{controllerName}}Controller extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new {{serviceName}}Service();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\{{logicModule}}\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\{{modelModule}}\model\{{modelName}};
|
||||
|
||||
class {{logicName}}Logic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = {{modelName}}::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace app\{{modelModule}}\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
class {{modelName}} extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = '{{table}}';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
|
||||
/**
|
||||
* 允许自动填充模型字段
|
||||
*/
|
||||
protected $fillable = [{{fillable}}];
|
||||
|
||||
/**
|
||||
* 不允许自动填充模型字段
|
||||
*/
|
||||
protected $guarded = [];
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace app\{{serviceModule}}\service;
|
||||
|
||||
use app\{{validateModule}}\validate\{{validateName}}Validate;
|
||||
use app\common\traits\crud\CrudServiceTrait;
|
||||
use app\{{modelModule}}\model\{{modelName}};
|
||||
|
||||
class {{serviceName}}Service
|
||||
{
|
||||
|
||||
use CrudServiceTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = {{modelName}}::class;
|
||||
|
||||
/**
|
||||
* 验证器
|
||||
*/
|
||||
protected $validator = {{validateName}}Validate::class;
|
||||
|
||||
/**
|
||||
* 获取验证器
|
||||
*/
|
||||
public function getValidator()
|
||||
{
|
||||
return new $this->validator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\{{validateModule}}\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class {{validateName}}Validate extends Validate
|
||||
{
|
||||
// 规则
|
||||
protected $rule = [
|
||||
|
||||
];
|
||||
|
||||
// 提示
|
||||
protected $message = [
|
||||
|
||||
];
|
||||
|
||||
// 场景
|
||||
protected $scene = [
|
||||
'store' => [
|
||||
|
||||
],
|
||||
|
||||
'update' => [
|
||||
|
||||
]
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user