diff --git a/.gitignore b/.gitignore index 6159566..8c952c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,9 @@ -/runtime +/.cursor /.idea /.vscode -/vendor -*.log -.env /tests/tmp /tests/.phpunit.result.cache -.kiro -plugin - -# 部署相关文件 -deploy.bat +/vendor +/runtime +*.log +.env diff --git a/LICENSE b/LICENSE index 2c66292..f519b37 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 walkor and contributors (see https://github.com/walkor/webman/contributors) +Copyright (c) 2026 技术老胡 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/app/common/base/BaseController.php b/app/common/base/BaseController.php index 14b2716..a859440 100644 --- a/app/common/base/BaseController.php +++ b/app/common/base/BaseController.php @@ -2,41 +2,62 @@ namespace app\common\base; -use support\Response; use support\Request; +use support\Response; /** - * 控制器基础类 - * - 提供统一的 success/fail 响应封装 + * 控制器基础父类 * - * 约定: - * - 控制器统一通过 $this->request->* 获取请求数据 - * - 为避免每个控制器构造函数重复注入 Request,本类通过 __get('request') 返回当前请求对象 + * 约定统一的 JSON 返回结构: + * { + * "code": 200, + * "message": "success", + * "data": ... + * } */ -abstract class BaseController +class BaseController { - /** - * 成功响应 + * 成功返回 */ protected function success(mixed $data = null, string $message = 'success', int $code = 200): Response { return json([ - 'code' => $code, + 'code' => $code, 'message' => $message, - 'data' => $data, + 'data' => $data, ]); } /** - * 失败响应 + * 失败返回 */ - protected function fail(string $message = 'fail', int $code = 500, mixed $data = null): Response + protected function fail(string $message = 'error', int $code = 500, mixed $data = null): Response { return json([ - 'code' => $code, + 'code' => $code, 'message' => $message, - 'data' => $data, + 'data' => $data, ]); } + + /** + * 获取当前登录用户的 token 载荷 + * + * 从 AuthMiddleware 注入的用户信息中获取 + */ + protected function currentUser(Request $request): ?array + { + return $request->user ?? null; + } + + /** + * 获取当前登录用户ID + */ + protected function currentUserId(Request $request): int + { + return (int) ($request->userId ?? 0); + } } + + diff --git a/app/common/base/BaseDao.php b/app/common/base/BaseDao.php deleted file mode 100644 index 1abdb68..0000000 --- a/app/common/base/BaseDao.php +++ /dev/null @@ -1,164 +0,0 @@ -connection); - } - - /** - * 获取查询构造器 - */ - protected function query(): Builder - { - return Db::connection($this->connection)->table($this->table); - } - - /** - * 根据 ID 查找单条记录 - */ - public function findById(int $id, array $columns = ['*']): ?array - { - $result = $this->query()->where('id', $id)->first($columns); - return $result ? (array)$result : null; - } - - /** - * 根据条件查找单条记录 - */ - public function findOne(array $where, array $columns = ['*']): ?array - { - $query = $this->query(); - foreach ($where as $key => $value) { - $query->where($key, $value); - } - $result = $query->first($columns); - return $result ? (array)$result : null; - } - - /** - * 根据条件查找多条记录 - */ - public function findMany(array $where = [], array $columns = ['*'], array $orderBy = [], int $limit = 0): array - { - $query = $this->query(); - foreach ($where as $key => $value) { - if (is_array($value)) { - $query->whereIn($key, $value); - } else { - $query->where($key, $value); - } - } - foreach ($orderBy as $column => $direction) { - $query->orderBy($column, $direction); - } - if ($limit > 0) { - $query->limit($limit); - } - $results = $query->get($columns); - return array_map(fn($item) => (array)$item, $results->toArray()); - } - - /** - * 插入单条记录 - */ - public function insert(array $data): int - { - return $this->query()->insertGetId($data); - } - - /** - * 批量插入 - */ - public function insertBatch(array $data): bool - { - return $this->query()->insert($data); - } - - /** - * 根据 ID 更新记录 - */ - public function updateById(int $id, array $data): int - { - return $this->query()->where('id', $id)->update($data); - } - - /** - * 根据条件更新记录 - */ - public function update(array $where, array $data): int - { - $query = $this->query(); - foreach ($where as $key => $value) { - $query->where($key, $value); - } - return $query->update($data); - } - - /** - * 根据 ID 删除记录 - */ - public function deleteById(int $id): int - { - return $this->query()->where('id', $id)->delete(); - } - - /** - * 根据条件删除记录 - */ - public function delete(array $where): int - { - $query = $this->query(); - foreach ($where as $key => $value) { - $query->where($key, $value); - } - return $query->delete(); - } - - /** - * 统计记录数 - */ - public function count(array $where = []): int - { - $query = $this->query(); - foreach ($where as $key => $value) { - $query->where($key, $value); - } - return $query->count(); - } - - /** - * 判断记录是否存在 - */ - public function exists(array $where): bool - { - return $this->count($where) > 0; - } -} - - diff --git a/app/common/base/BaseModel.php b/app/common/base/BaseModel.php index 9d4a17d..e50ea75 100644 --- a/app/common/base/BaseModel.php +++ b/app/common/base/BaseModel.php @@ -2,100 +2,35 @@ namespace app\common\base; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Builder; +use support\Model; /** - * 模型基础类 - * - 统一禁用时间戳(如需要可在子类开启) - * - 提供常用查询作用域和便捷方法 + * 所有业务模型的基础父类 */ -abstract class BaseModel extends Model +class BaseModel extends Model { /** - * 禁用时间戳(默认) + * 约定所有主键字段名 + * + * @var string + */ + protected $primaryKey = 'id'; + + /** + * 是否自动维护 created_at / updated_at + * + * 大部分业务表都有这两个字段,如不需要可在子类里覆盖为 false。 + * + * @var bool */ public $timestamps = false; /** - * 允许批量赋值的字段(子类可覆盖) + * 默认不禁止任何字段的批量赋值 + * + * 建议在具体模型中按需设置 $fillable 或 $guarded。 + * + * @var array */ protected $guarded = []; - - /** - * 连接名称(默认使用配置的 default) - */ - protected $connection = 'default'; - - /** - * 根据 ID 查找(返回数组格式) - */ - public static function findById(int $id): ?array - { - $model = static::find($id); - return $model ? $model->toArray() : null; - } - - /** - * 根据条件查找单条(返回数组格式) - */ - public static function findOne(array $where): ?array - { - $query = static::query(); - foreach ($where as $key => $value) { - $query->where($key, $value); - } - $model = $query->first(); - return $model ? $model->toArray() : null; - } - - /** - * 根据条件查找多条(返回数组格式) - */ - public static function findMany(array $where = [], array $orderBy = [], int $limit = 0): array - { - $query = static::query(); - foreach ($where as $key => $value) { - if (is_array($value)) { - $query->whereIn($key, $value); - } else { - $query->where($key, $value); - } - } - foreach ($orderBy as $column => $direction) { - $query->orderBy($column, $direction); - } - if ($limit > 0) { - $query->limit($limit); - } - return $query->get()->map(fn($item) => $item->toArray())->toArray(); - } - - /** - * 启用状态作用域 - */ - public function scopeEnabled(Builder $query): Builder - { - return $query->where('status', 1); - } - - /** - * 禁用状态作用域 - */ - public function scopeDisabled(Builder $query): Builder - { - return $query->where('status', 0); - } - - /** - * 转换为数组(统一处理 null 值) - */ - public function toArray(): array - { - $array = parent::toArray(); - // 将 null 值转换为空字符串(可选,根据业务需求调整) - return array_map(fn($value) => $value === null ? '' : $value, $array); - } } - - diff --git a/app/common/base/BaseRepository.php b/app/common/base/BaseRepository.php index ee661e1..88fc3ef 100644 --- a/app/common/base/BaseRepository.php +++ b/app/common/base/BaseRepository.php @@ -2,57 +2,73 @@ namespace app\common\base; +use support\Model; + /** - * 仓库基础类 - * - 支持注入 DAO 依赖 - * - 通过魔术方法代理 DAO 的方法调用 - * - 提供通用的数据访问封装 + * 仓储层基础父类 + * + * 封装单表常用的 CRUD / 分页操作,具体仓储继承后可扩展业务查询。 */ abstract class BaseRepository { /** - * DAO 实例(可选,子类通过构造函数注入) + * @var Model */ - protected ?BaseDao $dao = null; + protected Model $model; - /** - * 构造函数,子类可注入 DAO - */ - public function __construct(?BaseDao $dao = null) + public function __construct(Model $model) { - $this->dao = $dao; + $this->model = $model; } /** - * 魔术方法:代理 DAO 的方法调用 - * 如果仓库自身没有该方法,且存在 DAO 实例,则调用 DAO 的对应方法 + * 根据主键查询 */ - public function __call(string $method, array $arguments) + public function find(int $id, array $columns = ['*']): ?Model { - if ($this->dao && method_exists($this->dao, $method)) { - return $this->dao->{$method}(...$arguments); + return $this->model->newQuery()->find($id, $columns); + } + + /** + * 新建记录 + */ + public function create(array $data): Model + { + return $this->model->newQuery()->create($data); + } + + /** + * 按主键更新 + */ + public function updateById(int $id, array $data): bool + { + return (bool) $this->model->newQuery()->whereKey($id)->update($data); + } + + /** + * 按主键删除 + */ + public function deleteById(int $id): bool + { + return (bool) $this->model->newQuery()->whereKey($id)->delete(); + } + + /** + * 简单分页查询示例 + * + * @param array $where ['字段' => 值],值为 null / '' 时会被忽略 + */ + public function paginate(array $where = [], int $page = 1, int $pageSize = 10, array $columns = ['*']) + { + $query = $this->model->newQuery(); + + foreach ($where as $field => $value) { + if ($value === null || $value === '') { + continue; + } + $query->where($field, $value); } - throw new \BadMethodCallException( - sprintf('Method %s::%s does not exist', static::class, $method) - ); - } - - /** - * 检查 DAO 是否已注入 - */ - protected function hasDao(): bool - { - return $this->dao !== null; - } - - /** - * 获取 DAO 实例 - */ - protected function getDao(): ?BaseDao - { - return $this->dao; + return $query->paginate($pageSize, $columns, 'page', $page); } } - - diff --git a/app/common/base/BaseService.php b/app/common/base/BaseService.php index 5be496e..6089baa 100644 --- a/app/common/base/BaseService.php +++ b/app/common/base/BaseService.php @@ -2,55 +2,23 @@ namespace app\common\base; -use support\Log; +use support\Db; /** - * 服务基础类 - * - 提供日志记录能力 - * - 预留事务、事件发布等扩展点 + * 业务服务层基础父类 */ -abstract class BaseService +class BaseService { /** - * 记录日志 + * 事务封装 + * + * 使用方式: + * $this->transaction(function () { ... }); */ - protected function log(string $level, string $message, array $context = []): void + protected function transaction(callable $callback) { - $logMessage = sprintf('[%s] %s', static::class, $message); - Log::log($level, $logMessage, $context); - } - - /** - * 记录信息日志 - */ - protected function info(string $message, array $context = []): void - { - $this->log('info', $message, $context); - } - - /** - * 记录警告日志 - */ - protected function warning(string $message, array $context = []): void - { - $this->log('warning', $message, $context); - } - - /** - * 记录错误日志 - */ - protected function error(string $message, array $context = []): void - { - $this->log('error', $message, $context); - } - - /** - * 记录调试日志 - */ - protected function debug(string $message, array $context = []): void - { - $this->log('debug', $message, $context); + return Db::connection()->transaction(function () use ($callback) { + return $callback(); + }); } } - - diff --git a/app/common/constants/CommonStatus.php b/app/common/constants/CommonStatus.php new file mode 100644 index 0000000..91ed3ab --- /dev/null +++ b/app/common/constants/CommonStatus.php @@ -0,0 +1,22 @@ +method()) === 'OPTIONS' ? response('', 204) : $handler($request); - $response->withHeaders([ + return $response->withHeaders([ 'Access-Control-Allow-Credentials' => 'true', 'Access-Control-Allow-Origin' => $request->header('origin', '*'), 'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'), 'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'), ]); - - return $response; } } diff --git a/app/services/auth/JwtService.php b/app/common/utils/JwtUtil.php similarity index 82% rename from app/services/auth/JwtService.php rename to app/common/utils/JwtUtil.php index e7c2fb2..443321d 100644 --- a/app/services/auth/JwtService.php +++ b/app/common/utils/JwtUtil.php @@ -1,11 +1,11 @@ data($data); - } - - public function getBizCode(): int - { - return (int)$this->getCode(); - } - - // 保持与 webman BusinessException 方法签名兼容 - public function getData(): array - { - return parent::getData(); - } - - /** - * 自定义渲染 - * - json 请求:返回 {code,message,data} - * - 非 json:返回文本 - */ - public function render(\Webman\Http\Request $request): ?\Webman\Http\Response - { - if ($request->expectsJson()) { - return json([ - 'code' => $this->getBizCode() ?: 500, - 'message' => $this->getMessage(), - 'data' => $this->getData(), - ]); - } - return new \Webman\Http\Response(200, [], $this->getMessage()); - } -} - - diff --git a/app/exceptions/ForbiddenException.php b/app/exceptions/ForbiddenException.php deleted file mode 100644 index 1a00f84..0000000 --- a/app/exceptions/ForbiddenException.php +++ /dev/null @@ -1,16 +0,0 @@ -post('username', ''); - $password = (string)$request->post('password', ''); - // 前端有本地验证码,这里暂不做服务端校验,仅预留字段 - $verifyCode = $request->post('verifyCode'); + try { + $data = $this->captchaService->generate(); + return $this->success($data); + } catch (\Throwable $e) { + return $this->fail('验证码生成失败:' . $e->getMessage(), 500); + } + } - if ($username === '' || $password === '') { - return $this->fail('账号或密码不能为空', 400); + /** + * POST /login + * + * 用户登录 + */ + public function login(Request $request) + { + $username = $request->post('username', ''); + $password = $request->post('password', ''); + $verifyCode = $request->post('verifyCode', ''); + $captchaId = $request->post('captchaId', ''); + + // 参数校验 + if (empty($username) || empty($password) || empty($verifyCode) || empty($captchaId)) { + return $this->fail('请填写完整登录信息', 400); } - $token = $this->authService->login($username, $password, $verifyCode); - - return $this->success(['token' => $token]); - } - - /** - * 获取当前登录用户信息 - */ - public function getUserInfo(Request $request): Response - { - // 前端在 Authorization 中直接传 token - $token = (string)$request->header('authorization', ''); - $id = $request->get('id'); - - $data = $this->authService->getUserInfo($token, $id); - - return $this->success($data); + try { + $data = $this->authService->login($username, $password, $verifyCode, $captchaId); + return $this->success($data); + } catch (\RuntimeException $e) { + return $this->fail($e->getMessage(), $e->getCode() ?: 500); + } catch (\Throwable $e) { + return $this->fail('登录失败:' . $e->getMessage(), 500); + } } } + diff --git a/app/http/admin/controller/MenuController.php b/app/http/admin/controller/MenuController.php new file mode 100644 index 0000000..30bcb7d --- /dev/null +++ b/app/http/admin/controller/MenuController.php @@ -0,0 +1,53 @@ +buildMenuTree($this->getSystemMenu()); + + return $this->success($routers); + } + + /** + * 获取系统菜单数据 + * 从配置文件读取 + */ + private function getSystemMenu(): array + { + return config('menu', []); + } + + /** + * 构建菜单树形结构 + */ + private function buildMenuTree(array $menus, string $parentId = '0'): array + { + $tree = []; + + foreach ($menus as $menu) { + if (($menu['parentId'] ?? '0') === $parentId) { + $children = $this->buildMenuTree($menus, $menu['id']); + $menu['children'] = !empty($children) ? $children : null; + $tree[] = $menu; + } + } + + // 按 sort 排序 + usort($tree, function ($a, $b) { + return ($a['meta']['sort'] ?? 0) <=> ($b['meta']['sort'] ?? 0); + }); + + return $tree; + } +} + diff --git a/app/http/admin/controller/SystemController.php b/app/http/admin/controller/SystemController.php new file mode 100644 index 0000000..a5bb0c5 --- /dev/null +++ b/app/http/admin/controller/SystemController.php @@ -0,0 +1,46 @@ +fail('未找到指定的字典:' . $code, 404); + } + return $this->success($dict); + } + + // 返回所有字典 + return $this->success($allDicts); + } +} + diff --git a/app/http/admin/controller/UserController.php b/app/http/admin/controller/UserController.php new file mode 100644 index 0000000..7255a81 --- /dev/null +++ b/app/http/admin/controller/UserController.php @@ -0,0 +1,44 @@ +currentUserId($request); + + if ($userId <= 0) { + return $this->fail('未获取到用户信息,请先登录', 401); + } + + try { + $data = $this->userService->getUserInfoById($userId); + return $this->success($data); + } catch (\RuntimeException $e) { + return $this->fail($e->getMessage(), $e->getCode() ?: 500); + } + } +} + diff --git a/app/http/admin/middleware/AuthMiddleware.php b/app/http/admin/middleware/AuthMiddleware.php new file mode 100644 index 0000000..f337539 --- /dev/null +++ b/app/http/admin/middleware/AuthMiddleware.php @@ -0,0 +1,81 @@ +header('Authorization', ''); + if (!$auth) { + return $this->unauthorized('缺少认证令牌'); + } + + // 兼容 "Bearer xxx" 或直接 "xxx" + if (str_starts_with($auth, 'Bearer ')) { + $token = substr($auth, 7); + } else { + $token = $auth; + } + + if (!$token) { + return $this->unauthorized('认证令牌格式错误'); + } + + try { + // 解析 JWT token + $payload = JwtUtil::parseToken($token); + + if (empty($payload) || !isset($payload['user_id'])) { + return $this->unauthorized('认证令牌无效'); + } + + // 将用户信息存储到请求对象中,供控制器使用 + $request->user = $payload; + $request->userId = (int) ($payload['user_id'] ?? 0); + + // 继续处理请求 + return $handler($request); + } catch (\Throwable $e) { + // 根据异常类型返回不同的错误信息 + $message = $e->getMessage(); + if (str_contains($message, 'expired') || str_contains($message, 'Expired')) { + return $this->unauthorized('认证令牌已过期', 401); + } elseif (str_contains($message, 'signature') || str_contains($message, 'Signature')) { + return $this->unauthorized('认证令牌签名无效', 401); + } else { + return $this->unauthorized('认证令牌验证失败:' . $message, 401); + } + } + } + + /** + * 返回未授权响应 + */ + private function unauthorized(string $message, int $code = 401): Response + { + return json([ + 'code' => $code, + 'message' => $message, + 'data' => null, + ], $code); + } +} + diff --git a/app/models/User.php b/app/models/User.php new file mode 100644 index 0000000..2f1e837 --- /dev/null +++ b/app/models/User.php @@ -0,0 +1,30 @@ +belongsToMany(Role::class, 'role_user', 'user_id', 'role_id'); + } +} + + diff --git a/app/repositories/AdminUserRepository.php b/app/repositories/AdminUserRepository.php deleted file mode 100644 index 212d3c6..0000000 --- a/app/repositories/AdminUserRepository.php +++ /dev/null @@ -1,207 +0,0 @@ -dao 的方法 - */ -class AdminUserRepository extends BaseRepository -{ - /** - * 构造函数:支持注入 DAO(当前阶段为可选) - */ - public function __construct(?BaseDao $dao = null) - { - parent::__construct($dao); - } - /** - * 模拟账户数据(对齐前端 mock accountData) - */ - protected function accounts(): array - { - return [ - [ - 'id' => 1, - 'deptId' => '100', - 'deptName' => '研发部门', - 'userName' => 'admin', - 'nickName' => '超级管理员', - 'email' => '2547096351@qq.com', - 'phone' => '15888888888', - 'sex' => 1, - 'avatar' => 'https://ooo.0x0.ooo/2025/04/10/O0dG7r.jpg', - 'status' => 1, - 'description' => '系统初始用户', - 'roles' => ['admin'], - 'loginIp' => '0:0:0:0:0:0:0:1', - 'loginDate' => '2025-03-31 10:30:59', - 'createBy' => 'admin', - 'createTime' => '2024-03-19 11:21:01', - 'updateBy' => null, - 'updateTime' => null, - 'admin' => true, - ], - [ - 'id' => 2, - 'deptId' => '100010101', - 'deptName' => '研发部门', - 'userName' => 'common', - 'nickName' => '普通用户', - 'email' => '2547096351@qq.com', - 'phone' => '15222222222', - 'sex' => 0, - 'avatar' => 'https://ooo.0x0.ooo/2025/04/10/O0ddJI.jpg', - 'status' => 1, - 'description' => 'UI组用户', - 'roles' => ['common'], - 'loginIp' => '0:0:0:0:0:0:0:1', - 'loginDate' => '2025-03-31 10:30:59', - 'createBy' => 'admin', - 'createTime' => '2024-03-19 11:21:01', - 'updateBy' => null, - 'updateTime' => null, - 'admin' => false, - ], - ]; - } - - /** - * 模拟角色数据(对齐前端 mock roleData) - */ - protected function roles(): array - { - return [ - [ - 'id' => 1, - 'name' => '超级管理员', - 'code' => 'admin', - 'sort' => 1, - 'status' => 1, - 'admin' => true, - 'description' => '默认角色,超级管理员,上帝角色', - ], - [ - 'id' => 2, - 'name' => '普通员工', - 'code' => 'common', - 'sort' => 2, - 'status' => 1, - 'admin' => false, - 'description' => '负责一些基础功能', - ], - ]; - } - - /** - * 模拟权限数据(对齐前端 mock permissionData) - */ - protected function permissions(): array - { - return [ - [ - 'meta' => [ - 'roles' => ['admin'], - 'permission' => 'sys:btn:add', - ], - ], - [ - 'meta' => [ - 'roles' => ['admin'], - 'permission' => 'sys:btn:edit', - ], - ], - [ - 'meta' => [ - 'roles' => ['admin'], - 'permission' => 'sys:btn:delete', - ], - ], - [ - 'meta' => [ - 'roles' => ['admin', 'common'], - 'permission' => 'common:btn:add', - ], - ], - [ - 'meta' => [ - 'roles' => ['admin', 'common'], - 'permission' => 'common:btn:edit', - ], - ], - [ - 'meta' => [ - 'roles' => ['admin', 'common'], - 'permission' => 'common:btn:delete', - ], - ], - ]; - } - - public function findByUsername(string $username): ?array - { - foreach ($this->accounts() as $account) { - if ($account['userName'] === $username) { - return $account; - } - } - return null; - } - - public function findById(int $id): ?array - { - foreach ($this->accounts() as $account) { - if ($account['id'] === $id) { - return $account; - } - } - return null; - } - - public function getRoleInfoByCodes(array $codes): array - { - if (!$codes) { - return []; - } - $roles = []; - foreach ($this->roles() as $role) { - if (in_array($role['code'], $codes, true)) { - $roles[] = $role; - } - } - return $roles; - } - - public function getPermissionsByRoleCodes(array $codes): array - { - if (!$codes) { - return []; - } - $permissions = []; - foreach ($this->permissions() as $item) { - $meta = $item['meta'] ?? []; - $roles = $meta['roles'] ?? []; - $permission = $meta['permission'] ?? null; - if (!$permission) { - continue; - } - foreach ($codes as $code) { - if (in_array($code, $roles, true)) { - $permissions[] = $permission; - break; - } - } - } - // 去重 - return array_values(array_unique($permissions)); - } -} - - diff --git a/app/repositories/CronJobRepository.php b/app/repositories/CronJobRepository.php new file mode 100644 index 0000000..d7bd98d --- /dev/null +++ b/app/repositories/CronJobRepository.php @@ -0,0 +1,19 @@ +model + ->newQuery() + ->whereIn('type', [1, 2]) // 1目录 2菜单,排除3按钮 + ->where('status', 1) // 只获取启用的菜单 + ->orderBy('sort', 'asc') + ->orderBy('id', 'asc') + ->get() + ->toArray(); + } + + /** + * 根据菜单ID列表获取启用的菜单(仅目录和菜单类型,排除按钮) + */ + public function getMenusByIds(array $menuIds): array + { + if (empty($menuIds)) { + return []; + } + + return $this->model + ->newQuery() + ->whereIn('id', $menuIds) + ->whereIn('type', [1, 2]) // 1目录 2菜单,排除3按钮 + ->where('status', 1) // 只获取启用的菜单 + ->orderBy('sort', 'asc') + ->orderBy('id', 'asc') + ->get() + ->toArray(); + } +} + + diff --git a/app/repositories/RoleRepository.php b/app/repositories/RoleRepository.php new file mode 100644 index 0000000..7b5a721 --- /dev/null +++ b/app/repositories/RoleRepository.php @@ -0,0 +1,19 @@ +model + ->newQuery() + ->where('user_name', $userName) + ->first(); + + return $user; + } + + /** + * 根据主键查询并预加载角色 + */ + public function findWithRoles(int $id): ?User + { + /** @var User|null $user */ + $user = $this->model + ->newQuery() + ->with('roles') + ->find($id); + + return $user; + } +} + + diff --git a/app/routes/admin.php b/app/routes/admin.php index 6ca4915..7dc71a2 100644 --- a/app/routes/admin.php +++ b/app/routes/admin.php @@ -2,13 +2,33 @@ /** * 管理后台路由定义 + * + * 接口前缀:/adminapi + * 跨域中间件:Cors */ use Webman\Route; use app\http\admin\controller\AuthController; +use app\http\admin\controller\UserController; +use app\http\admin\controller\MenuController; +use app\http\admin\controller\SystemController; +use app\common\middleware\Cors; +use app\http\admin\middleware\AuthMiddleware; -Route::group('/admin', function () { - // 登录相关 - Route::post('/mock/login', [AuthController::class, 'login']); - Route::get('/mock/user/getUserInfo', [AuthController::class, 'getUserInfo']); -}); +Route::group('/adminapi', function () { + // 认证相关(无需JWT验证) + Route::get('/captcha', [AuthController::class, 'captcha']); + Route::post('/login', [AuthController::class, 'login']); + + // 需要认证的路由组 + Route::group('', function () { + // 用户相关(需要JWT验证) + Route::get('/user/getUserInfo', [UserController::class, 'getUserInfo']); + + // 菜单相关(需要JWT验证) + Route::get('/menu/getRouters', [MenuController::class, 'getRouters']); + + // 系统相关(需要JWT验证) + Route::get('/system/getDict[/{code}]', [SystemController::class, 'getDict']); + })->middleware([AuthMiddleware::class]); +})->middleware([Cors::class]); \ No newline at end of file diff --git a/app/routes/api.php b/app/routes/api.php index e69de29..67b84ef 100644 --- a/app/routes/api.php +++ b/app/routes/api.php @@ -0,0 +1,8 @@ + string] + * @throws \RuntimeException + */ + public function login(string $username, string $password, string $verifyCode, string $captchaId): array + { + // 1. 校验验证码 + if (!$this->captchaService->validate($captchaId, $verifyCode)) { + throw new \RuntimeException('验证码错误或已失效', 400); + } + + // 2. 查询用户 + $user = $this->userRepository->findByUserName($username); + if (!$user) { + throw new \RuntimeException('账号或密码错误', 401); + } + + // 3. 校验密码 + if (!$this->validatePassword($password, $user->password)) { + throw new \RuntimeException('账号或密码错误', 401); + } + + // 4. 检查用户状态 + if ($user->status !== 1) { + throw new \RuntimeException('账号已被禁用', 403); + } + + // 5. 生成 JWT token(包含用户ID、用户名、昵称等信息) + $token = $this->generateToken($user); + + // 6. 将 token 信息存入 Redis(用于后续刷新、黑名单等) + $this->cacheToken($token, $user->id); + + // 7. 更新用户最后登录信息 + $this->updateLoginInfo($user); + + // 返回 token,前端使用该 token 访问需要认证的接口 + return [ + 'token' => $token, + ]; + } + + /** + * 校验密码 + * + * @param string $password 明文密码 + * @param string|null $hash 数据库中的密码hash + * @return bool + */ + private function validatePassword(string $password, ?string $hash): bool + { + // 如果数据库密码为空,允许使用默认密码(仅用于开发/演示) + if ($hash === null || $hash === '') { + // 开发环境:允许 admin/123456 和 common/123456 无密码登录 + // 生产环境应移除此逻辑 + return in_array($password, ['123456'], true); + } + + return password_verify($password, $hash); + } + + /** + * 生成 JWT token + * + * @param \app\models\User $user + * @return string + */ + private function generateToken($user): string + { + $payload = [ + 'user_id' => $user->id, + 'user_name' => $user->user_name, + 'nick_name' => $user->nick_name, + ]; + + return JwtUtil::generateToken($payload); + } + + /** + * 将 token 信息缓存到 Redis + * + * @param string $token + * @param int $userId + */ + private function cacheToken(string $token, int $userId): void + { + $key = JwtUtil::getCachePrefix() . $token; + $data = [ + 'user_id' => $userId, + 'created_at' => time(), + ]; + Cache::set($key, $data, JwtUtil::getTtl()); + } + + /** + * 更新用户登录信息 + * + * @param \app\models\User $user + */ + private function updateLoginInfo($user): void + { + // 获取客户端真实IP(优先使用 x-real-ip,其次 x-forwarded-for,最后 remoteIp) + $request = request(); + $ip = $request->header('x-real-ip', '') + ?: ($request->header('x-forwarded-for', '') ? explode(',', $request->header('x-forwarded-for', ''))[0] : '') + ?: $request->getRemoteIp(); + + $user->login_ip = trim($ip); + $user->login_at = date('Y-m-d H:i:s'); + $user->save(); + } +} + diff --git a/app/services/CaptchaService.php b/app/services/CaptchaService.php new file mode 100644 index 0000000..4c69ecb --- /dev/null +++ b/app/services/CaptchaService.php @@ -0,0 +1,101 @@ + string, 'image' => string] + */ + public function generate(): array + { + // 使用 webman/captcha 生成验证码图片和文本 + $builder = new CaptchaBuilder; + // 适配前端登录表单尺寸:110x30 + $builder->build(110, 30); + + $code = strtolower($builder->getPhrase()); + $id = bin2hex(random_bytes(16)); + + $payload = [ + 'code' => $code, + 'created_at' => time(), + 'error_times' => 0, + 'used' => false, + ]; + + Cache::set($this->buildKey($id), $payload, self::EXPIRE_SECONDS); + + // 获取图片二进制并转为 base64 + $imgContent = $builder->get(); + $base64 = base64_encode($imgContent ?: ''); + + return [ + 'captchaId' => $id, + 'image' => 'data:image/jpeg;base64,' . $base64, + ]; + } + + /** + * 校验验证码(基于 captchaId + code) + * + * @param string|null $id 验证码ID + * @param string|null $code 用户输入的验证码 + * @return bool + */ + public function validate(?string $id, ?string $code): bool + { + if ($id === null || $id === '' || $code === null || $code === '') { + return false; + } + + $key = $this->buildKey($id); + $data = Cache::get($key); + if (!$data || !is_array($data)) { + return false; + } + + // 已使用或错误次数过多 + if (!empty($data['used']) || ($data['error_times'] ?? 0) >= self::MAX_ERROR_TIMES) { + Cache::delete($key); + return false; + } + + $expect = (string)($data['code'] ?? ''); + if ($expect === '' || strtolower($code) !== strtolower($expect)) { + $data['error_times'] = ($data['error_times'] ?? 0) + 1; + Cache::set($key, $data, self::EXPIRE_SECONDS); + return false; + } + + // 标记为已使用,防重放 + $data['used'] = true; + Cache::set($key, $data, self::EXPIRE_SECONDS); + + return true; + } + + /** + * 构建缓存键 + */ + private function buildKey(string $id): string + { + return self::CACHE_PREFIX . $id; + } +} + diff --git a/app/services/UserService.php b/app/services/UserService.php new file mode 100644 index 0000000..e5e04de --- /dev/null +++ b/app/services/UserService.php @@ -0,0 +1,43 @@ +users->find($id); + if (!$user) { + throw new \RuntimeException('用户不存在', 404); + } + + $userArray = $user->toArray(); + + return [ + 'user' => $userArray, + 'roles' => ['admin'], + 'permissions' => ['*:*:*'], + ]; + } +} diff --git a/app/services/auth/AuthService.php b/app/services/auth/AuthService.php deleted file mode 100644 index fb52ee5..0000000 --- a/app/services/auth/AuthService.php +++ /dev/null @@ -1,104 +0,0 @@ -userRepository->findByUsername($username); - if (!$user) { - throw new AuthFailedException(); - } - - // 当前阶段使用明文模拟密码校验 - if ($password !== '123456') { - throw new AuthFailedException(); - } - - $payload = [ - 'uid' => $user['id'], - 'username' => $user['userName'], - 'roles' => $user['roles'] ?? [], - ]; - - $token = JwtService::generateToken($payload); - - // 写入 Redis 会话,key 使用 token,方便快速失效 - $ttl = JwtService::getTtl(); - $key = 'mpay:auth:token:' . $token; - $redis = Redis::connection('default'); - $redis->setex($key, $ttl, json_encode($payload, JSON_UNESCAPED_UNICODE)); - - return $token; - } - - /** - * 根据 token 获取用户信息(对齐前端 mock 返回结构) - */ - public function getUserInfo(string $token, $id = null): array - { - if ($token === '') { - throw new UnauthorizedException(); - } - - $redis = Redis::connection('default'); - $key = 'mpay:auth:token:' . $token; - $session = $redis->get($key); - if (!$session) { - // 尝试从 JWT 解出(例如服务重启后 Redis 丢失的情况) - $payload = JwtService::parseToken($token); - } else { - $payload = json_decode($session, true) ?: []; - } - - if (empty($payload['uid']) && empty($payload['username'])) { - throw new UnauthorizedException(); - } - - // 对齐 mock:如果有 id 参数则按 id 查,否则用 payload 中 uid 查 - if ($id !== null && $id !== '') { - $user = $this->userRepository->findById((int)$id); - } else { - $user = $this->userRepository->findById((int)($payload['uid'] ?? 0)); - } - - if (!$user) { - throw new UnauthorizedException(); - } - - // 角色信息 - $roleInfo = $this->userRepository->getRoleInfoByCodes($user['roles'] ?? []); - $user['roles'] = $roleInfo; - $roleCodes = array_map(static fn($item) => $item['code'], $roleInfo); - - // 权限信息 - if (in_array('admin', $roleCodes, true)) { - $permissions = ['*:*:*']; - } else { - $permissions = $this->userRepository->getPermissionsByRoleCodes($roleCodes); - } - - return [ - 'user' => $user, - 'roles' => $roleCodes, - 'permissions' => $permissions, - ]; - } -} - - diff --git a/app/view/index/view.html b/app/view/index/view.html deleted file mode 100644 index 67ebb26..0000000 --- a/app/view/index/view.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - webman - - - -hello - - diff --git a/composer.json b/composer.json index 0a372d2..2ab515c 100644 --- a/composer.json +++ b/composer.json @@ -28,23 +28,18 @@ "workerman/webman-framework": "^2.1", "monolog/monolog": "^2.0", "php-di/php-di": "7.0", - "webman/cache": "^2.1", + "webman/database": "^2.1", "webman/redis": "^2.1", - "illuminate/events": "^11.0", - "webman/redis-queue": "^1.3", + "illuminate/events": "^12.49", + "webman/cache": "^2.1", + "webman/console": "^2.1", "topthink/think-validate": "^3.0", - "webman/rate-limiter": "^1.1", "webman/captcha": "^1.0", "webman/event": "^1.0", "vlucas/phpdotenv": "^5.6", "workerman/crontab": "^1.0", - "webman/console": "^2.1", - "firebase/php-jwt": "^6.0", - "illuminate/database": "^11.0", - "guzzlehttp/guzzle": "^7.0", - "ramsey/uuid": "^4.0", - "nesbot/carbon": "^3.0", - "webman/database": "^2.1" + "webman/redis-queue": "^2.1", + "firebase/php-jwt": "^7.0" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index 0e5426f..aa3208a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "abca1c99f7511639d3548201fd459bd5", + "content-hash": "ea45057d8c2734266a897db21c27e75c", "packages": [ { "name": "brick/math", - "version": "0.14.1", + "version": "0.14.7", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" + "reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", + "url": "https://api.github.com/repos/brick/math/zipball/07ff363b16ef8aca9692bba3be9e73fe63f34e50", + "reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50", "shasum": "" }, "require": { @@ -56,7 +56,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.1" + "source": "https://github.com/brick/math/tree/0.14.7" }, "funding": [ { @@ -64,7 +64,7 @@ "type": "github" } ], - "time": "2025-11-24T14:40:29+00:00" + "time": "2026-02-07T10:57:35+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -227,16 +227,16 @@ }, { "name": "firebase/php-jwt", - "version": "v6.11.1", + "version": "v7.0.2", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" + "reference": "5645b43af647b6947daac1d0f659dd1fbe8d3b65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/5645b43af647b6947daac1d0f659dd1fbe8d3b65", + "reference": "5645b43af647b6947daac1d0f659dd1fbe8d3b65", "shasum": "" }, "require": { @@ -284,9 +284,9 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" + "source": "https://github.com/firebase/php-jwt/tree/v7.0.2" }, - "time": "2025-04-09T20:32:01+00:00" + "time": "2025-12-16T22:17:28+00:00" }, { "name": "fruitcake/php-cors", @@ -834,32 +834,32 @@ }, { "name": "illuminate/bus", - "version": "v11.47.0", + "version": "v12.49.0", "source": { "type": "git", "url": "https://github.com/illuminate/bus.git", - "reference": "2adf211bada7184410501b37882845230d6bf2f4" + "reference": "34a5617fe40c90000751bd72bcc9ab9b012d9b41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/bus/zipball/2adf211bada7184410501b37882845230d6bf2f4", - "reference": "2adf211bada7184410501b37882845230d6bf2f4", + "url": "https://api.github.com/repos/illuminate/bus/zipball/34a5617fe40c90000751bd72bcc9ab9b012d9b41", + "reference": "34a5617fe40c90000751bd72bcc9ab9b012d9b41", "shasum": "" }, "require": { - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/pipeline": "^11.0", - "illuminate/support": "^11.0", + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/pipeline": "^12.0", + "illuminate/support": "^12.0", "php": "^8.2" }, "suggest": { - "illuminate/queue": "Required to use closures when chaining jobs (^7.0)." + "illuminate/queue": "Required to use closures when chaining jobs (^12.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -883,35 +883,39 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-10-14T19:59:30+00:00" + "time": "2026-01-20T15:12:10+00:00" }, { "name": "illuminate/collections", - "version": "v11.47.0", + "version": "v12.50.0", "source": { "type": "git", "url": "https://github.com/illuminate/collections.git", - "reference": "856b1da953e46281ba61d7c82d337072d3ee1825" + "reference": "b4bbe2a929aaacf0ade3bec535f1f8fac6e6ed38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/collections/zipball/856b1da953e46281ba61d7c82d337072d3ee1825", - "reference": "856b1da953e46281ba61d7c82d337072d3ee1825", + "url": "https://api.github.com/repos/illuminate/collections/zipball/b4bbe2a929aaacf0ade3bec535f1f8fac6e6ed38", + "reference": "b4bbe2a929aaacf0ade3bec535f1f8fac6e6ed38", "shasum": "" }, "require": { - "illuminate/conditionable": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "php": "^8.2" + "illuminate/conditionable": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "php": "^8.2", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33" }, "suggest": { - "symfony/var-dumper": "Required to use the dump method (^7.0)." + "illuminate/http": "Required to convert collections to API resources (^12.0).", + "symfony/var-dumper": "Required to use the dump method (^7.2)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -939,29 +943,29 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-03-24T11:54:20+00:00" + "time": "2026-02-01T16:38:26+00:00" }, { "name": "illuminate/conditionable", - "version": "v11.47.0", + "version": "v12.50.0", "source": { "type": "git", "url": "https://github.com/illuminate/conditionable.git", - "reference": "319b717e0587bd7c8a3b44464f0e27867b4bcda9" + "reference": "ec677967c1f2faf90b8428919124d2184a4c9b49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/conditionable/zipball/319b717e0587bd7c8a3b44464f0e27867b4bcda9", - "reference": "319b717e0587bd7c8a3b44464f0e27867b4bcda9", + "url": "https://api.github.com/repos/illuminate/conditionable/zipball/ec677967c1f2faf90b8428919124d2184a4c9b49", + "reference": "ec677967c1f2faf90b8428919124d2184a4c9b49", "shasum": "" }, "require": { - "php": "^8.0.2" + "php": "^8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -985,34 +989,45 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-03-24T11:54:20+00:00" + "time": "2025-05-13T15:08:45+00:00" }, { "name": "illuminate/container", - "version": "v11.47.0", + "version": "v12.50.0", "source": { "type": "git", "url": "https://github.com/illuminate/container.git", - "reference": "79bf9149ad7ddd7e14326ebcdd41197d2c4ee36a" + "reference": "a605f25d0e014b6e521bcb142a4eba578966a24f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/container/zipball/79bf9149ad7ddd7e14326ebcdd41197d2c4ee36a", - "reference": "79bf9149ad7ddd7e14326ebcdd41197d2c4ee36a", + "url": "https://api.github.com/repos/illuminate/container/zipball/a605f25d0e014b6e521bcb142a4eba578966a24f", + "reference": "a605f25d0e014b6e521bcb142a4eba578966a24f", "shasum": "" }, "require": { - "illuminate/contracts": "^11.0", + "illuminate/contracts": "^12.0", + "illuminate/reflection": "^12.0", "php": "^8.2", - "psr/container": "^1.1.1|^2.0.1" + "psr/container": "^1.1.1|^2.0.1", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33" }, "provide": { "psr/container-implementation": "1.1|2.0" }, + "suggest": { + "illuminate/auth": "Required to use the Auth attribute", + "illuminate/cache": "Required to use the Cache attribute", + "illuminate/config": "Required to use the Config attribute", + "illuminate/database": "Required to use the DB attribute", + "illuminate/filesystem": "Required to use the Storage attribute", + "illuminate/log": "Required to use the Log or Context attributes" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -1036,20 +1051,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-03-24T11:54:20+00:00" + "time": "2026-01-29T03:13:58+00:00" }, { "name": "illuminate/contracts", - "version": "v11.47.0", + "version": "v12.50.0", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", - "reference": "4787042340aae19a7ea0fa82f4073c4826204a48" + "reference": "3d4eeab332c04a9eaea90968c19a66f78745e47a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/contracts/zipball/4787042340aae19a7ea0fa82f4073c4826204a48", - "reference": "4787042340aae19a7ea0fa82f4073c4826204a48", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/3d4eeab332c04a9eaea90968c19a66f78745e47a", + "reference": "3d4eeab332c04a9eaea90968c19a66f78745e47a", "shasum": "" }, "require": { @@ -1060,7 +1075,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -1084,46 +1099,49 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-11-27T16:16:07+00:00" + "time": "2026-01-28T15:26:27+00:00" }, { "name": "illuminate/database", - "version": "v11.47.0", + "version": "v12.50.0", "source": { "type": "git", "url": "https://github.com/illuminate/database.git", - "reference": "96abcce13f405701363d916dd312835e04848d04" + "reference": "a20d2278cca2b9052381fbfab301d9bc7b5c47d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/database/zipball/96abcce13f405701363d916dd312835e04848d04", - "reference": "96abcce13f405701363d916dd312835e04848d04", + "url": "https://api.github.com/repos/illuminate/database/zipball/a20d2278cca2b9052381fbfab301d9bc7b5c47d1", + "reference": "a20d2278cca2b9052381fbfab301d9bc7b5c47d1", "shasum": "" }, "require": { - "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12|^0.13|^0.14", + "brick/math": "^0.11|^0.12|^0.13|^0.14", "ext-pdo": "*", - "illuminate/collections": "^11.0", - "illuminate/container": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/support": "^11.0", + "illuminate/collections": "^12.0", + "illuminate/container": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", "laravel/serializable-closure": "^1.3|^2.0", - "php": "^8.2" + "php": "^8.2", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php85": "^1.33" }, "suggest": { "ext-filter": "Required to use the Postgres database driver.", "fakerphp/faker": "Required to use the eloquent factory builder (^1.24).", - "illuminate/console": "Required to use the database commands (^11.0).", - "illuminate/events": "Required to use the observers with Eloquent (^11.0).", - "illuminate/filesystem": "Required to use the migrations (^11.0).", - "illuminate/pagination": "Required to paginate the result set (^11.0).", - "symfony/finder": "Required to use Eloquent model factories (^7.0)." + "illuminate/console": "Required to use the database commands (^12.0).", + "illuminate/events": "Required to use the observers with Eloquent (^12.0).", + "illuminate/filesystem": "Required to use the migrations (^12.0).", + "illuminate/http": "Required to convert Eloquent models to API resources (^12.0).", + "illuminate/pagination": "Required to paginate the result set (^12.0).", + "symfony/finder": "Required to use Eloquent model factories (^7.2)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -1153,35 +1171,35 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-09-29T09:23:31+00:00" + "time": "2026-02-04T15:21:01+00:00" }, { "name": "illuminate/events", - "version": "v11.47.0", + "version": "v12.49.0", "source": { "type": "git", "url": "https://github.com/illuminate/events.git", - "reference": "b72dab66d8e05d22dc5aa949efec150bbc73e827" + "reference": "829cff84e98a840bfcd68299a3ab4611a295d838" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/events/zipball/b72dab66d8e05d22dc5aa949efec150bbc73e827", - "reference": "b72dab66d8e05d22dc5aa949efec150bbc73e827", + "url": "https://api.github.com/repos/illuminate/events/zipball/829cff84e98a840bfcd68299a3ab4611a295d838", + "reference": "829cff84e98a840bfcd68299a3ab4611a295d838", "shasum": "" }, "require": { - "illuminate/bus": "^11.0", - "illuminate/collections": "^11.0", - "illuminate/container": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/support": "^11.0", + "illuminate/bus": "^12.0", + "illuminate/collections": "^12.0", + "illuminate/container": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", "php": "^8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -1208,47 +1226,47 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-03-24T11:54:20+00:00" + "time": "2026-01-08T15:49:00+00:00" }, { "name": "illuminate/filesystem", - "version": "v11.47.0", + "version": "v12.50.0", "source": { "type": "git", "url": "https://github.com/illuminate/filesystem.git", - "reference": "d1f217b75ee193bbe27f31df8a94ff6759f31469" + "reference": "8cad07cf6004a7cd0a876c6ad2136a1511c2bb58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/filesystem/zipball/d1f217b75ee193bbe27f31df8a94ff6759f31469", - "reference": "d1f217b75ee193bbe27f31df8a94ff6759f31469", + "url": "https://api.github.com/repos/illuminate/filesystem/zipball/8cad07cf6004a7cd0a876c6ad2136a1511c2bb58", + "reference": "8cad07cf6004a7cd0a876c6ad2136a1511c2bb58", "shasum": "" }, "require": { - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/support": "^11.0", + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", "php": "^8.2", - "symfony/finder": "^7.0.3" + "symfony/finder": "^7.2.0" }, "suggest": { "ext-fileinfo": "Required to use the Filesystem class.", "ext-ftp": "Required to use the Flysystem FTP driver.", "ext-hash": "Required to use the Filesystem class.", - "illuminate/http": "Required for handling uploaded files (^7.0).", + "illuminate/http": "Required for handling uploaded files (^12.0).", "league/flysystem": "Required to use the Flysystem local driver (^3.25.1).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).", - "symfony/mime": "Required to enable support for guessing extensions (^7.0)." + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).", + "symfony/mime": "Required to enable support for guessing extensions (^7.2)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -1275,20 +1293,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-03-24T11:54:20+00:00" + "time": "2026-01-19T15:15:34+00:00" }, { "name": "illuminate/http", - "version": "v11.47.0", + "version": "v12.50.0", "source": { "type": "git", "url": "https://github.com/illuminate/http.git", - "reference": "b2c744c2dedc1d39b3d5ea6b85232de086f84351" + "reference": "03a49ac6a09bdb5c0420758dec50216abbffcb70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/http/zipball/b2c744c2dedc1d39b3d5ea6b85232de086f84351", - "reference": "b2c744c2dedc1d39b3d5ea6b85232de086f84351", + "url": "https://api.github.com/repos/illuminate/http/zipball/03a49ac6a09bdb5c0420758dec50216abbffcb70", + "reference": "03a49ac6a09bdb5c0420758dec50216abbffcb70", "shasum": "" }, "require": { @@ -1296,15 +1314,16 @@ "fruitcake/php-cors": "^1.3", "guzzlehttp/guzzle": "^7.8.2", "guzzlehttp/uri-template": "^1.0", - "illuminate/collections": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/session": "^11.0", - "illuminate/support": "^11.0", + "illuminate/collections": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/session": "^12.0", + "illuminate/support": "^12.0", "php": "^8.2", - "symfony/http-foundation": "^7.0.3", - "symfony/http-kernel": "^7.0.3", - "symfony/mime": "^7.0.3", - "symfony/polyfill-php83": "^1.31" + "symfony/http-foundation": "^7.2.0", + "symfony/http-kernel": "^7.2.0", + "symfony/mime": "^7.2.0", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php85": "^1.33" }, "suggest": { "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image()." @@ -1312,7 +1331,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -1336,20 +1355,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-11-17T22:04:40+00:00" + "time": "2026-02-04T15:08:54+00:00" }, { "name": "illuminate/macroable", - "version": "v11.47.0", + "version": "v12.50.0", "source": { "type": "git", "url": "https://github.com/illuminate/macroable.git", - "reference": "e1cb9e51b9ed5d3c9bc1ab431d0a52fe42a990ed" + "reference": "e862e5648ee34004fa56046b746f490dfa86c613" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/macroable/zipball/e1cb9e51b9ed5d3c9bc1ab431d0a52fe42a990ed", - "reference": "e1cb9e51b9ed5d3c9bc1ab431d0a52fe42a990ed", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/e862e5648ee34004fa56046b746f490dfa86c613", + "reference": "e862e5648ee34004fa56046b746f490dfa86c613", "shasum": "" }, "require": { @@ -1358,7 +1377,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -1382,31 +1401,35 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-06-28T20:10:30+00:00" + "time": "2024-07-23T16:31:01+00:00" }, { "name": "illuminate/pipeline", - "version": "v11.47.0", + "version": "v12.49.0", "source": { "type": "git", "url": "https://github.com/illuminate/pipeline.git", - "reference": "f73bb7cab13ac8ef91094dc46976f5e992eea127" + "reference": "b6a14c20d69a44bf0a6fba664a00d23ca71770ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/pipeline/zipball/f73bb7cab13ac8ef91094dc46976f5e992eea127", - "reference": "f73bb7cab13ac8ef91094dc46976f5e992eea127", + "url": "https://api.github.com/repos/illuminate/pipeline/zipball/b6a14c20d69a44bf0a6fba664a00d23ca71770ee", + "reference": "b6a14c20d69a44bf0a6fba664a00d23ca71770ee", "shasum": "" }, "require": { - "illuminate/contracts": "^11.0", - "illuminate/support": "^11.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", "php": "^8.2" }, + "suggest": { + "illuminate/database": "Required to use database transactions (^12.0)." + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -1430,37 +1453,37 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-03-24T11:54:20+00:00" + "time": "2025-08-20T13:36:50+00:00" }, { "name": "illuminate/redis", - "version": "v11.47.0", + "version": "v12.49.0", "source": { "type": "git", "url": "https://github.com/illuminate/redis.git", - "reference": "d39281045e514cf9c2f3621eac158479f4178197" + "reference": "26f41f22f285295f8eeb1e00ba3ff3f24021a9eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/redis/zipball/d39281045e514cf9c2f3621eac158479f4178197", - "reference": "d39281045e514cf9c2f3621eac158479f4178197", + "url": "https://api.github.com/repos/illuminate/redis/zipball/26f41f22f285295f8eeb1e00ba3ff3f24021a9eb", + "reference": "26f41f22f285295f8eeb1e00ba3ff3f24021a9eb", "shasum": "" }, "require": { - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "illuminate/support": "^11.0", + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", "php": "^8.2" }, "suggest": { "ext-redis": "Required to use the phpredis connector (^4.0|^5.0|^6.0).", - "predis/predis": "Required to use the predis connector (^2.3)." + "predis/predis": "Required to use the predis connector (^2.3|^3.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -1484,40 +1507,91 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-03-24T11:54:20+00:00" + "time": "2026-01-02T17:50:54+00:00" }, { - "name": "illuminate/session", - "version": "v11.47.0", + "name": "illuminate/reflection", + "version": "v12.50.0", "source": { "type": "git", - "url": "https://github.com/illuminate/session.git", - "reference": "00a36b354c12a414236218c1ad193c445b36a191" + "url": "https://github.com/illuminate/reflection.git", + "reference": "6188e97a587371b9951c2a7e337cd760308c17d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/session/zipball/00a36b354c12a414236218c1ad193c445b36a191", - "reference": "00a36b354c12a414236218c1ad193c445b36a191", + "url": "https://api.github.com/repos/illuminate/reflection/zipball/6188e97a587371b9951c2a7e337cd760308c17d7", + "reference": "6188e97a587371b9951c2a7e337cd760308c17d7", + "shasum": "" + }, + "require": { + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Reflection package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-02-04T15:21:22+00:00" + }, + { + "name": "illuminate/session", + "version": "v12.50.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/session.git", + "reference": "ddd0fce2f49889eede824db81c283b4e82aa77d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/session/zipball/ddd0fce2f49889eede824db81c283b4e82aa77d0", + "reference": "ddd0fce2f49889eede824db81c283b4e82aa77d0", "shasum": "" }, "require": { "ext-ctype": "*", "ext-session": "*", - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/filesystem": "^11.0", - "illuminate/support": "^11.0", + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/filesystem": "^12.0", + "illuminate/support": "^12.0", "php": "^8.2", - "symfony/finder": "^7.0.3", - "symfony/http-foundation": "^7.0.3" + "symfony/finder": "^7.2.0", + "symfony/http-foundation": "^7.2.0" }, "suggest": { - "illuminate/console": "Required to use the session:table command (^11.0)." + "illuminate/console": "Required to use the session:table command (^12.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -1541,20 +1615,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-03-24T11:54:20+00:00" + "time": "2026-01-22T17:27:05+00:00" }, { "name": "illuminate/support", - "version": "v11.47.0", + "version": "v12.50.0", "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "20fbd9f9f502a55de0cbba3f3f81444b7c44af4b" + "reference": "411a11401406e7d542aa67a4b400feed6bedef0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/20fbd9f9f502a55de0cbba3f3f81444b7c44af4b", - "reference": "20fbd9f9f502a55de0cbba3f3f81444b7c44af4b", + "url": "https://api.github.com/repos/illuminate/support/zipball/411a11401406e7d542aa67a4b400feed6bedef0c", + "reference": "411a11401406e7d542aa67a4b400feed6bedef0c", "shasum": "" }, "require": { @@ -1562,12 +1636,15 @@ "ext-ctype": "*", "ext-filter": "*", "ext-mbstring": "*", - "illuminate/collections": "^11.0", - "illuminate/conditionable": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/macroable": "^11.0", - "nesbot/carbon": "^2.72.6|^3.8.4", + "illuminate/collections": "^12.0", + "illuminate/conditionable": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/reflection": "^12.0", + "nesbot/carbon": "^3.8.4", "php": "^8.2", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php85": "^1.33", "voku/portable-ascii": "^2.0.2" }, "conflict": { @@ -1577,20 +1654,20 @@ "spatie/once": "*" }, "suggest": { - "illuminate/filesystem": "Required to use the Composer class (^11.0).", + "illuminate/filesystem": "Required to use the Composer class (^12.0).", "laravel/serializable-closure": "Required to use the once function (^1.3|^2.0).", "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.7).", "league/uri": "Required to use the Uri class (^7.5.1).", "ramsey/uuid": "Required to use Str::uuid() (^4.7).", - "symfony/process": "Required to use the Composer class (^7.0).", - "symfony/uid": "Required to use Str::ulid() (^7.0).", - "symfony/var-dumper": "Required to use the dd function (^7.0).", + "symfony/process": "Required to use the Composer class (^7.2).", + "symfony/uid": "Required to use Str::ulid() (^7.2).", + "symfony/var-dumper": "Required to use the dd function (^7.2).", "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.6.1)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -1618,7 +1695,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-11-27T16:16:32+00:00" + "time": "2026-02-04T15:14:59+00:00" }, { "name": "laravel/serializable-closure", @@ -1785,16 +1862,16 @@ }, { "name": "nesbot/carbon", - "version": "3.11.0", + "version": "3.11.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "bdb375400dcd162624531666db4799b36b64e4a1" + "reference": "f438fcc98f92babee98381d399c65336f3a3827f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/bdb375400dcd162624531666db4799b36b64e4a1", - "reference": "bdb375400dcd162624531666db4799b36b64e4a1", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/f438fcc98f92babee98381d399c65336f3a3827f", + "reference": "f438fcc98f92babee98381d399c65336f3a3827f", "shasum": "" }, "require": { @@ -1818,7 +1895,7 @@ "phpstan/extension-installer": "^1.4.3", "phpstan/phpstan": "^2.1.22", "phpunit/phpunit": "^10.5.53", - "squizlabs/php_codesniffer": "^3.13.4" + "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0" }, "bin": [ "bin/carbon" @@ -1861,14 +1938,14 @@ } ], "description": "An API extension for DateTime that supports 281 different languages.", - "homepage": "https://carbon.nesbot.com", + "homepage": "https://carbonphp.github.io/carbon/", "keywords": [ "date", "datetime", "time" ], "support": { - "docs": "https://carbon.nesbot.com/docs", + "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html", "issues": "https://github.com/CarbonPHP/carbon/issues", "source": "https://github.com/CarbonPHP/carbon" }, @@ -1886,7 +1963,7 @@ "type": "tidelift" } ], - "time": "2025-12-02T21:04:28+00:00" + "time": "2026-01-29T09:26:29+00:00" }, { "name": "nikic/fast-route", @@ -2646,172 +2723,18 @@ }, "time": "2019-03-08T08:55:37+00:00" }, - { - "name": "ramsey/collection", - "version": "2.1.1", - "source": { - "type": "git", - "url": "https://github.com/ramsey/collection.git", - "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", - "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "require-dev": { - "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.45", - "fakerphp/faker": "^1.24", - "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^2.1", - "mockery/mockery": "^1.6", - "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.4", - "phpspec/prophecy-phpunit": "^2.3", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-mockery": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^10.5", - "ramsey/coding-standard": "^2.3", - "ramsey/conventional-commits": "^1.6", - "roave/security-advisories": "dev-latest" - }, - "type": "library", - "extra": { - "captainhook": { - "force-install": true - }, - "ramsey/conventional-commits": { - "configFile": "conventional-commits.json" - } - }, - "autoload": { - "psr-4": { - "Ramsey\\Collection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - } - ], - "description": "A PHP library for representing and manipulating collections.", - "keywords": [ - "array", - "collection", - "hash", - "map", - "queue", - "set" - ], - "support": { - "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.1.1" - }, - "time": "2025-03-22T05:38:12+00:00" - }, - { - "name": "ramsey/uuid", - "version": "4.9.2", - "source": { - "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "8429c78ca35a09f27565311b98101e2826affde0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", - "reference": "8429c78ca35a09f27565311b98101e2826affde0", - "shasum": "" - }, - "require": { - "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", - "php": "^8.0", - "ramsey/collection": "^1.2 || ^2.0" - }, - "replace": { - "rhumsaa/uuid": "self.version" - }, - "require-dev": { - "captainhook/captainhook": "^5.25", - "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "ergebnis/composer-normalize": "^2.47", - "mockery/mockery": "^1.6", - "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.6", - "php-mock/php-mock-mockery": "^1.5", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpbench/phpbench": "^1.2.14", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-mockery": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^9.6", - "slevomat/coding-standard": "^8.18", - "squizlabs/php_codesniffer": "^3.13" - }, - "suggest": { - "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", - "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", - "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", - "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." - }, - "type": "library", - "extra": { - "captainhook": { - "force-install": true - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Ramsey\\Uuid\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", - "keywords": [ - "guid", - "identifier", - "uuid" - ], - "support": { - "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.2" - }, - "time": "2025-12-14T04:43:48+00:00" - }, { "name": "symfony/cache", - "version": "v7.4.3", + "version": "v7.3.11", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "642117d18bc56832e74b68235359ccefab03dd11" + "reference": "e3e76b9ba0dff3dfe08ebda500723976dd9de407" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/642117d18bc56832e74b68235359ccefab03dd11", - "reference": "642117d18bc56832e74b68235359ccefab03dd11", + "url": "https://api.github.com/repos/symfony/cache/zipball/e3e76b9ba0dff3dfe08ebda500723976dd9de407", + "reference": "e3e76b9ba0dff3dfe08ebda500723976dd9de407", "shasum": "" }, "require": { @@ -2819,14 +2742,12 @@ "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", "symfony/cache-contracts": "^3.6", - "symfony/deprecation-contracts": "^2.5|^3", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/service-contracts": "^2.5|^3", - "symfony/var-exporter": "^6.4|^7.0|^8.0" + "symfony/var-exporter": "^6.4|^7.0" }, "conflict": { "doctrine/dbal": "<3.6", - "ext-redis": "<6.1", - "ext-relay": "<0.12.1", "symfony/dependency-injection": "<6.4", "symfony/http-kernel": "<6.4", "symfony/var-dumper": "<6.4" @@ -2841,13 +2762,13 @@ "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/clock": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/filesystem": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -2882,7 +2803,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.4.3" + "source": "https://github.com/symfony/cache/tree/v7.3.11" }, "funding": [ { @@ -2902,7 +2823,7 @@ "type": "tidelift" } ], - "time": "2025-12-28T10:45:24+00:00" + "time": "2026-01-27T16:12:03+00:00" }, { "name": "symfony/cache-contracts", @@ -3060,16 +2981,16 @@ }, { "name": "symfony/console", - "version": "v7.4.3", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6" + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/732a9ca6cd9dfd940c639062d5edbde2f6727fb6", - "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6", + "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", "shasum": "" }, "require": { @@ -3134,7 +3055,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.3" + "source": "https://github.com/symfony/console/tree/v7.4.4" }, "funding": [ { @@ -3154,7 +3075,7 @@ "type": "tidelift" } ], - "time": "2025-12-23T14:50:43+00:00" + "time": "2026-01-13T11:36:38+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3468,16 +3389,16 @@ }, { "name": "symfony/finder", - "version": "v7.4.4", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "01b24a145bbeaa7141e75887ec904c34a6728a5f" + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/01b24a145bbeaa7141e75887ec904c34a6728a5f", - "reference": "01b24a145bbeaa7141e75887ec904c34a6728a5f", + "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", "shasum": "" }, "require": { @@ -3512,7 +3433,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.4" + "source": "https://github.com/symfony/finder/tree/v7.4.5" }, "funding": [ { @@ -3532,20 +3453,20 @@ "type": "tidelift" } ], - "time": "2026-01-12T12:19:02+00:00" + "time": "2026-01-26T15:07:59+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.4", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "977a554a34cf8edc95ca351fbecb1bb1ad05cc94" + "reference": "446d0db2b1f21575f1284b74533e425096abdfb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/977a554a34cf8edc95ca351fbecb1bb1ad05cc94", - "reference": "977a554a34cf8edc95ca351fbecb1bb1ad05cc94", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/446d0db2b1f21575f1284b74533e425096abdfb6", + "reference": "446d0db2b1f21575f1284b74533e425096abdfb6", "shasum": "" }, "require": { @@ -3594,7 +3515,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.4" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.5" }, "funding": [ { @@ -3614,20 +3535,20 @@ "type": "tidelift" } ], - "time": "2026-01-09T12:14:21+00:00" + "time": "2026-01-27T16:16:02+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.4", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "48b067768859f7b68acf41dfb857a5a4be00acdd" + "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/48b067768859f7b68acf41dfb857a5a4be00acdd", - "reference": "48b067768859f7b68acf41dfb857a5a4be00acdd", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/229eda477017f92bd2ce7615d06222ec0c19e82a", + "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a", "shasum": "" }, "require": { @@ -3713,7 +3634,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.4" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.5" }, "funding": [ { @@ -3733,20 +3654,20 @@ "type": "tidelift" } ], - "time": "2026-01-24T22:13:01+00:00" + "time": "2026-01-28T10:33:42+00:00" }, { "name": "symfony/mime", - "version": "v7.4.4", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "40945014c0a9471ccfe19673c54738fa19367a3c" + "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/40945014c0a9471ccfe19673c54738fa19367a3c", - "reference": "40945014c0a9471ccfe19673c54738fa19367a3c", + "url": "https://api.github.com/repos/symfony/mime/zipball/b18c7e6e9eee1e19958138df10412f3c4c316148", + "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148", "shasum": "" }, "require": { @@ -3757,15 +3678,15 @@ }, "conflict": { "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", + "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/type-resolver": "<1.5.1", "symfony/mailer": "<6.4", "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "phpdocumentor/reflection-docblock": "^5.2", "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/process": "^6.4|^7.0|^8.0", "symfony/property-access": "^6.4|^7.0|^8.0", @@ -3802,7 +3723,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.4" + "source": "https://github.com/symfony/mime/tree/v7.4.5" }, "funding": [ { @@ -3822,7 +3743,7 @@ "type": "tidelift" } ], - "time": "2026-01-08T16:12:55+00:00" + "time": "2026-01-27T08:59:58+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4410,6 +4331,86 @@ ], "time": "2025-07-08T02:45:35+00:00" }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" + }, { "name": "symfony/polyfill-php85", "version": "v1.33.0", @@ -5463,16 +5464,16 @@ }, { "name": "webman/database", - "version": "v2.1.8", + "version": "v2.1.9", "source": { "type": "git", "url": "https://github.com/webman-php/database.git", - "reference": "ded41bdd6319af22a5bfa7c46ca2040f747c8f45" + "reference": "5a1463c96c79b35225f1cbd2f3e65830a7b4da0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webman-php/database/zipball/ded41bdd6319af22a5bfa7c46ca2040f747c8f45", - "reference": "ded41bdd6319af22a5bfa7c46ca2040f747c8f45", + "url": "https://api.github.com/repos/webman-php/database/zipball/5a1463c96c79b35225f1cbd2f3e65830a7b4da0e", + "reference": "5a1463c96c79b35225f1cbd2f3e65830a7b4da0e", "shasum": "" }, "require": { @@ -5495,9 +5496,9 @@ "description": "Webman database", "support": { "issues": "https://github.com/webman-php/database/issues", - "source": "https://github.com/webman-php/database/tree/v2.1.8" + "source": "https://github.com/webman-php/database/tree/v2.1.9" }, - "time": "2026-01-06T14:31:27+00:00" + "time": "2026-02-02T10:45:05+00:00" }, { "name": "webman/event", @@ -5530,41 +5531,6 @@ }, "time": "2023-12-04T09:22:12+00:00" }, - { - "name": "webman/rate-limiter", - "version": "v1.1.7", - "source": { - "type": "git", - "url": "https://github.com/webman-php/rate-limiter.git", - "reference": "d8e981a4a1181e5e30e60b73f37665d4d44a354d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webman-php/rate-limiter/zipball/d8e981a4a1181e5e30e60b73f37665d4d44a354d", - "reference": "d8e981a4a1181e5e30e60b73f37665d4d44a354d", - "shasum": "" - }, - "require": { - "php": ">=8.0", - "workerman/webman-framework": ">=1.5.15" - }, - "type": "library", - "autoload": { - "psr-4": { - "Webman\\RateLimiter\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Webman plugin webman/rate-limiter", - "support": { - "issues": "https://github.com/webman-php/rate-limiter/issues", - "source": "https://github.com/webman-php/rate-limiter/tree/v1.1.7" - }, - "time": "2025-12-05T19:05:21+00:00" - }, { "name": "webman/redis", "version": "v2.1.3", @@ -5603,20 +5569,23 @@ }, { "name": "webman/redis-queue", - "version": "v1.3.2", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/webman-php/redis-queue.git", - "reference": "80b9ddca0405bbb6d02e6b368e8036b3b1a13814" + "reference": "ff4791e21f3c324a47e21da7b6f2dae5a7311dcb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webman-php/redis-queue/zipball/80b9ddca0405bbb6d02e6b368e8036b3b1a13814", - "reference": "80b9ddca0405bbb6d02e6b368e8036b3b1a13814", + "url": "https://api.github.com/repos/webman-php/redis-queue/zipball/ff4791e21f3c324a47e21da7b6f2dae5a7311dcb", + "reference": "ff4791e21f3c324a47e21da7b6f2dae5a7311dcb", "shasum": "" }, "require": { - "workerman/redis-queue": "^1.2" + "ext-redis": "*", + "php": ">=8.1", + "workerman/redis-queue": "^1.2", + "workerman/webman-framework": "^2.1 || dev-master" }, "type": "library", "autoload": { @@ -5628,9 +5597,9 @@ "description": "Redis message queue plugin for webman.", "support": { "issues": "https://github.com/webman-php/redis-queue/issues", - "source": "https://github.com/webman-php/redis-queue/tree/v1.3.2" + "source": "https://github.com/webman-php/redis-queue/tree/v2.1.1" }, - "time": "2024-04-03T02:00:20+00:00" + "time": "2025-11-14T07:12:52+00:00" }, { "name": "workerman/coroutine", diff --git a/config/app.php b/config/app.php index 5398819..f26e358 100644 --- a/config/app.php +++ b/config/app.php @@ -15,7 +15,7 @@ use support\Request; return [ - 'debug' => getenv('APP_DEBUG') ?? true, + 'debug' => true, 'error_reporting' => E_ALL, 'default_timezone' => 'Asia/Shanghai', 'request_class' => Request::class, diff --git a/config/autoload.php b/config/autoload.php index 5c76d03..ea2ec01 100644 --- a/config/autoload.php +++ b/config/autoload.php @@ -14,7 +14,7 @@ return [ 'files' => [ - base_path() . '/support/functions.php', + base_path() . '/support/helpers.php', base_path() . '/support/Request.php', base_path() . '/support/Response.php', ] diff --git a/config/bootstrap.php b/config/bootstrap.php index 95d2e87..ec4603f 100644 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -13,5 +13,5 @@ */ return [ - support\bootstrap\Session::class, + // support\bootstrap\Session::class, ]; diff --git a/config/cache.php b/config/cache.php index 499a785..c17d3c6 100644 --- a/config/cache.php +++ b/config/cache.php @@ -14,7 +14,7 @@ */ return [ - 'default' => getenv('CACHE_DRIVER') ?? 'file', + 'default' => env('CACHE_DRIVER', 'file'), 'stores' => [ 'file' => [ 'driver' => 'file', @@ -22,10 +22,10 @@ return [ ], 'redis' => [ 'driver' => 'redis', - 'connection' => 'default' + 'connection' => env('CACHE_REDIS_CONNECTION', 'default') ], 'array' => [ 'driver' => 'array' ] ] -]; \ No newline at end of file +]; diff --git a/config/container.php b/config/container.php index 591a956..7e637a6 100644 --- a/config/container.php +++ b/config/container.php @@ -12,8 +12,8 @@ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ -$builder = new \DI\ContainerBuilder(); -$builder->addDefinitions(config('dependence', [])); -$builder->useAutowiring(true); -$builder->useAttributes(true); -return $builder->build(); \ No newline at end of file + $builder = new \DI\ContainerBuilder(); + $builder->addDefinitions(config('dependence', [])); + $builder->useAutowiring(true); + $builder->useAttributes(true); + return $builder->build(); \ No newline at end of file diff --git a/config/database.php b/config/database.php index a790446..1540ad7 100644 --- a/config/database.php +++ b/config/database.php @@ -1,17 +1,17 @@ getenv('DB_CONNECTION') ?? 'mysql', + 'default' => 'mysql', 'connections' => [ 'mysql' => [ - 'driver' => getenv('DB_DRIVER') ?? 'mysql', - 'host' => getenv('DB_HOST') ?? '127.0.0.1', - 'port' => getenv('DB_PORT') ?? '3306', - 'database' => getenv('DB_DATABASE') ?? '', - 'username' => getenv('DB_USERNAME') ?? '', - 'password' => getenv('DB_PASSWORD') ?? '', + 'driver' => 'mysql', + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', 3306), + 'database' => env('DB_DATABASE', ''), + 'username' => env('DB_USERNAME', ''), + 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_general_ci', - 'prefix' => getenv('DB_PREFIX') ?? '', + 'prefix' => '', 'strict' => true, 'engine' => null, 'options' => [ @@ -26,4 +26,4 @@ return [ ], ], ], -]; \ No newline at end of file +]; diff --git a/config/dict.php b/config/dict.php new file mode 100644 index 0000000..64f2ba2 --- /dev/null +++ b/config/dict.php @@ -0,0 +1,48 @@ + '性别', + 'code' => 'gender', + 'description' => '这是一个性别字典', + 'list' => [ + ['name' => '女', 'value' => 0], + ['name' => '男', 'value' => 1], + ['name' => '其它', 'value' => 2], + ], + ], + [ + 'name' => '状态', + 'code' => 'status', + 'description' => '状态字段可以用这个', + 'list' => [ + ['name' => '禁用', 'value' => 0], + ['name' => '启用', 'value' => 1], + ], + ], + [ + 'name' => '岗位', + 'code' => 'post', + 'description' => '岗位字段', + 'list' => [ + ['name' => '总经理', 'value' => 1], + ['name' => '总监', 'value' => 2], + ['name' => '人事主管', 'value' => 3], + ['name' => '开发部主管', 'value' => 4], + ['name' => '普通职员', 'value' => 5], + ['name' => '其它', 'value' => 999], + ], + ], + [ + 'name' => '任务状态', + 'code' => 'taskStatus', + 'description' => '任务状态字段可以用它', + 'list' => [ + ['name' => '失败', 'value' => 0], + ['name' => '成功', 'value' => 1], + ], + ], +]; diff --git a/config/jwt.php b/config/jwt.php index bcb2906..fefb32a 100644 --- a/config/jwt.php +++ b/config/jwt.php @@ -1,12 +1,19 @@ getenv('JWT_SECRET') ?: 'mpay-secret', - // 过期时间(秒) - 'ttl' => (int)(getenv('JWT_TTL') ?: 7200), - // 签名算法 - 'alg' => getenv('JWT_ALG') ?: 'HS256', + // JWT 密钥(请在生产环境修改为强随机字符串) + 'secret' => env('JWT_SECRET', 'mpay-admin-secret-key-change-in-production'), + + // Token 有效期(秒),默认 2 小时 + 'ttl' => (int)env('JWT_TTL', 7200), + + // 加密算法 + 'alg' => env('JWT_ALG', 'HS256'), + + // Token 缓存前缀(用于 Redis 存储) + 'cache_prefix' => env('JWT_CACHE_PREFIX', 'token_'), ]; - diff --git a/config/menu.php b/config/menu.php new file mode 100644 index 0000000..024ef3c --- /dev/null +++ b/config/menu.php @@ -0,0 +1,1107 @@ + '01', + 'parentId' => '0', + 'path' => '/home', + 'name' => 'home', + 'component' => 'home/home', + 'meta' => [ + 'title' => '平台首页', + 'hide' => false, + 'disable' => false, + 'keepAlive' => false, + 'affix' => true, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin', 'common'], + 'svgIcon' => 'home', + 'icon' => '', + 'sort' => 1, + 'type' => 2, + ], + ], + + // 2. 收款订单(交易全链路管理) + [ + 'id' => '02', + 'parentId' => '0', + 'path' => '/order', + 'name' => 'order', + 'redirect' => '/order/order-list', + 'meta' => [ + 'title' => '收款订单', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin', 'common'], + 'svgIcon' => 'order', + 'icon' => '', + 'sort' => 2, + 'type' => 1, + ], + ], + [ + 'id' => '0201', + 'parentId' => '02', + 'path' => '/order/order-list', + 'name' => 'order-list', + 'component' => 'order/order-list/index', + 'meta' => [ + 'title' => '订单管理', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin', 'common'], + 'icon' => 'icon-file', + 'sort' => 1, + 'type' => 2, + ], + ], + [ + 'id' => '0202', + 'parentId' => '02', + 'path' => '/order/refund', + 'name' => 'refund', + 'component' => 'order/refund/index', + 'meta' => [ + 'title' => '退款管理', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin', 'common'], + 'icon' => 'icon-loop', + 'sort' => 2, + 'type' => 2, + ], + ], + [ + 'id' => '0203', + 'parentId' => '02', + 'path' => '/order/exception', + 'name' => 'exception', + 'component' => 'order/exception/index', + 'meta' => [ + 'title' => '异常订单', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin', 'common'], + 'icon' => 'icon-bug', + 'sort' => 3, + 'type' => 2, + ], + ], + [ + 'id' => '0204', + 'parentId' => '02', + 'path' => '/order/order-log', + 'name' => 'order-log', + 'component' => 'order/order-log/index', + 'meta' => [ + 'title' => '订单日志', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-book', + 'sort' => 4, + 'type' => 2, + ], + ], + [ + 'id' => '0205', + 'parentId' => '02', + 'path' => '/order/export', + 'name' => 'export', + 'component' => 'order/export/index', + 'meta' => [ + 'title' => '导出订单', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-printer', + 'sort' => 5, + 'type' => 2, + ], + ], + [ + 'id' => '0206', + 'parentId' => '02', + 'path' => '/order/user-statistics', + 'name' => 'user-statistics', + 'component' => 'order/user-statistics/index', + 'meta' => [ + 'title' => '支付用户统计', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-user', + 'sort' => 6, + 'type' => 2, + ], + ], + [ + 'id' => '0207', + 'parentId' => '02', + 'path' => '/order/blacklist', + 'name' => 'blacklist', + 'component' => 'order/blacklist/index', + 'meta' => [ + 'title' => '黑名单管理', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-lock', + 'sort' => 7, + 'type' => 2, + ], + ], + + // 3. 商户管理(生命周期全流程) + [ + 'id' => '03', + 'parentId' => '0', + 'path' => '/merchant', + 'name' => 'merchant', + 'redirect' => '/merchant/merchant-list', + 'meta' => [ + 'title' => '商户管理', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin', 'common'], + 'svgIcon' => 'merchant', + 'icon' => '', + 'sort' => 3, + 'type' => 1, + ], + ], + [ + 'id' => '0301', + 'parentId' => '03', + 'path' => '/merchant/merchant-list', + 'name' => 'merchant-list', + 'component' => 'merchant/merchant-list/index', + 'meta' => [ + 'title' => '商户列表', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin', 'common'], + 'icon' => 'icon-user-group', + 'sort' => 1, + 'type' => 2, + ], + ], + [ + 'id' => '0302', + 'parentId' => '03', + 'path' => '/merchant/audit', + 'name' => 'audit', + 'component' => 'merchant/audit/index', + 'meta' => [ + 'title' => '商户入驻审核', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-idcard', + 'sort' => 2, + 'type' => 2, + ], + ], + [ + 'id' => '0303', + 'parentId' => '03', + 'path' => '/merchant/config', + 'name' => 'config', + 'component' => 'merchant/config/index', + 'meta' => [ + 'title' => '商户配置', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-tool', + 'sort' => 3, + 'type' => 2, + ], + ], + [ + 'id' => '0304', + 'parentId' => '03', + 'path' => '/merchant/group', + 'name' => 'group', + 'component' => 'merchant/group/index', + 'meta' => [ + 'title' => '商户分组', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-folder', + 'sort' => 4, + 'type' => 2, + ], + ], + [ + 'id' => '0305', + 'parentId' => '03', + 'path' => '/merchant/funds', + 'name' => 'funds', + 'component' => 'merchant/funds/index', + 'meta' => [ + 'title' => '商户资金管理', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-bar-chart', + 'sort' => 5, + 'type' => 2, + ], + ], + [ + 'id' => '0306', + 'parentId' => '03', + 'path' => '/merchant/package', + 'name' => 'package', + 'component' => 'merchant/package/index', + 'meta' => [ + 'title' => '商户套餐管理', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-gift', + 'sort' => 6, + 'type' => 2, + ], + ], + [ + 'id' => '0307', + 'parentId' => '03', + 'path' => '/merchant/statistics', + 'name' => 'statistics', + 'component' => 'merchant/statistics/index', + 'meta' => [ + 'title' => '商户统计', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-bar-chart', + 'sort' => 7, + 'type' => 2, + ], + ], + + // 4. 财务中心(结算、分账、对账一体化) + [ + 'id' => '04', + 'parentId' => '0', + 'path' => '/finance', + 'name' => 'finance', + 'redirect' => '/finance/settlement', + 'meta' => [ + 'title' => '财务中心', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'svgIcon' => 'financial', + 'icon' => '', + 'sort' => 4, + 'type' => 1, + ], + ], + [ + 'id' => '0401', + 'parentId' => '04', + 'path' => '/finance/settlement', + 'name' => 'settlement', + 'component' => 'finance/settlement/index', + 'meta' => [ + 'title' => '结算管理', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-calendar', + 'sort' => 1, + 'type' => 2, + ], + ], + [ + 'id' => '0402', + 'parentId' => '04', + 'path' => '/finance/batch-settlement', + 'name' => 'batch-settlement', + 'component' => 'finance/batch-settlement/index', + 'meta' => [ + 'title' => '批量结算', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-calendar-clock', + 'sort' => 2, + 'type' => 2, + ], + ], + [ + 'id' => '0403', + 'parentId' => '04', + 'path' => '/finance/settlement-record', + 'name' => 'settlement-record', + 'component' => 'finance/settlement-record/index', + 'meta' => [ + 'title' => '结算记录', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-book', + 'sort' => 3, + 'type' => 2, + ], + ], + [ + 'id' => '0404', + 'parentId' => '04', + 'path' => '/finance/split', + 'name' => 'split', + 'component' => 'finance/split/index', + 'meta' => [ + 'title' => '分账管理', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-branch', + 'sort' => 4, + 'type' => 2, + ], + ], + [ + 'id' => '0405', + 'parentId' => '04', + 'path' => '/finance/fee', + 'name' => 'fee', + 'component' => 'finance/fee/index', + 'meta' => [ + 'title' => '手续费管理', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-tag', + 'sort' => 5, + 'type' => 2, + ], + ], + [ + 'id' => '0406', + 'parentId' => '04', + 'path' => '/finance/reconciliation', + 'name' => 'reconciliation', + 'component' => 'finance/reconciliation/index', + 'meta' => [ + 'title' => '财务对账', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-file', + 'sort' => 6, + 'type' => 2, + ], + ], + [ + 'id' => '0407', + 'parentId' => '04', + 'path' => '/finance/invoice', + 'name' => 'invoice', + 'component' => 'finance/invoice/index', + 'meta' => [ + 'title' => '发票管理', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-file-pdf', + 'sort' => 7, + 'type' => 2, + ], + ], + + // 5. 支付通道(聚合与稳定性保障) + [ + 'id' => '05', + 'parentId' => '0', + 'path' => '/channel', + 'name' => 'channel', + 'redirect' => '/channel/channel-list', + 'meta' => [ + 'title' => '支付通道', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'svgIcon' => 'channel', + 'icon' => '', + 'sort' => 5, + 'type' => 1, + ], + ], + [ + 'id' => '0501', + 'parentId' => '05', + 'path' => '/channel/channel-list', + 'name' => 'channel-list', + 'component' => 'channel/channel-list/index', + 'meta' => [ + 'title' => '通道管理', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-cloud', + 'sort' => 1, + 'type' => 2, + ], + ], + [ + 'id' => '0502', + 'parentId' => '05', + 'path' => '/channel/channel-config', + 'name' => 'channel-config', + 'component' => 'channel/channel-config/index', + 'meta' => [ + 'title' => '通道配置', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-tool', + 'sort' => 2, + 'type' => 2, + ], + ], + [ + 'id' => '0503', + 'parentId' => '05', + 'path' => '/channel/payment-method', + 'name' => 'payment-method', + 'component' => 'channel/payment-method/index', + 'meta' => [ + 'title' => '支付方式', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-relation', + 'sort' => 3, + 'type' => 2, + ], + ], + [ + 'id' => '0504', + 'parentId' => '05', + 'path' => '/channel/plugin', + 'name' => 'plugin', + 'component' => 'channel/plugin/index', + 'meta' => [ + 'title' => '支付插件', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-tool', + 'sort' => 4, + 'type' => 2, + ], + ], + [ + 'id' => '0505', + 'parentId' => '05', + 'path' => '/channel/polling', + 'name' => 'polling', + 'component' => 'channel/polling/index', + 'meta' => [ + 'title' => '通道轮询与容灾', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-loop', + 'sort' => 5, + 'type' => 2, + ], + ], + [ + 'id' => '0506', + 'parentId' => '05', + 'path' => '/channel/monitor', + 'name' => 'monitor', + 'component' => 'channel/monitor/index', + 'meta' => [ + 'title' => '通道监控', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-desktop', + 'sort' => 6, + 'type' => 2, + ], + ], + + // 6. 风控与安全(全链路风险防控) + [ + 'id' => '06', + 'parentId' => '0', + 'path' => '/risk', + 'name' => 'risk', + 'redirect' => '/risk/rule', + 'meta' => [ + 'title' => '风控与安全', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'svgIcon' => 'risk', + 'icon' => '', + 'sort' => 6, + 'type' => 1, + ], + ], + [ + 'id' => '0601', + 'parentId' => '06', + 'path' => '/risk/rule', + 'name' => 'rule', + 'component' => 'risk/rule/index', + 'meta' => [ + 'title' => '风控规则', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-safe', + 'sort' => 1, + 'type' => 2, + ], + ], + [ + 'id' => '0602', + 'parentId' => '06', + 'path' => '/risk/warning', + 'name' => 'warning', + 'component' => 'risk/warning/index', + 'meta' => [ + 'title' => '风控预警', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-notification', + 'sort' => 2, + 'type' => 2, + ], + ], + [ + 'id' => '0603', + 'parentId' => '06', + 'path' => '/risk/disposal', + 'name' => 'disposal', + 'component' => 'risk/disposal/index', + 'meta' => [ + 'title' => '风险处置', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-tool', + 'sort' => 3, + 'type' => 2, + ], + ], + [ + 'id' => '0604', + 'parentId' => '06', + 'path' => '/risk/report', + 'name' => 'report', + 'component' => 'risk/report/index', + 'meta' => [ + 'title' => '风险报告', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-file', + 'sort' => 4, + 'type' => 2, + ], + ], + [ + 'id' => '0605', + 'parentId' => '06', + 'path' => '/risk/security', + 'name' => 'security', + 'component' => 'risk/security/index', + 'meta' => [ + 'title' => '安全配置', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-lock', + 'sort' => 5, + 'type' => 2, + ], + ], + + // 7. 运营分析(数据驱动决策) + [ + 'id' => '08', + 'parentId' => '0', + 'path' => '/analysis', + 'name' => 'analysis', + 'redirect' => '/analysis/transaction', + 'meta' => [ + 'title' => '运营分析', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'svgIcon' => 'analysis', + 'icon' => '', + 'sort' => 7, + 'type' => 1, + ], + ], + [ + 'id' => '0801', + 'parentId' => '08', + 'path' => '/analysis/transaction', + 'name' => 'transaction', + 'component' => 'analysis/transaction/index', + 'meta' => [ + 'title' => '交易分析', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-bar-chart', + 'sort' => 1, + 'type' => 2, + ], + ], + [ + 'id' => '0802', + 'parentId' => '08', + 'path' => '/analysis/merchant-analysis', + 'name' => 'merchant-analysis', + 'component' => 'analysis/merchant-analysis/index', + 'meta' => [ + 'title' => '商户分析', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-bar-chart', + 'sort' => 2, + 'type' => 2, + ], + ], + [ + 'id' => '0803', + 'parentId' => '08', + 'path' => '/analysis/finance-analysis', + 'name' => 'finance-analysis', + 'component' => 'analysis/finance-analysis/index', + 'meta' => [ + 'title' => '财务分析', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-bar-chart', + 'sort' => 3, + 'type' => 2, + ], + ], + [ + 'id' => '0804', + 'parentId' => '08', + 'path' => '/analysis/report', + 'name' => 'report', + 'component' => 'analysis/report/index', + 'meta' => [ + 'title' => '报表中心', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-file', + 'sort' => 4, + 'type' => 2, + ], + ], + + // 8. 系统设置(基础配置与运维) + [ + 'id' => '07', + 'parentId' => '0', + 'path' => '/system', + 'name' => 'system', + 'redirect' => '/system/base-config', + 'meta' => [ + 'title' => '系统设置', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'svgIcon' => 'config', + 'sort' => 8, + 'type' => 1, + ], + ], + [ + 'id' => '0701', + 'parentId' => '07', + 'path' => '/system/base-config', + 'name' => 'base-config', + 'component' => 'system/base-config/index', + 'meta' => [ + 'title' => '基础配置', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-tool', + 'sort' => 1, + 'type' => 2, + ], + ], + [ + 'id' => '0702', + 'parentId' => '07', + 'path' => '/system/notice-config', + 'name' => 'notice-config', + 'component' => 'system/notice-config/index', + 'meta' => [ + 'title' => '通知配置', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-notification', + 'sort' => 2, + 'type' => 2, + ], + ], + [ + 'id' => '0703', + 'parentId' => '07', + 'path' => '/system/log', + 'name' => 'log', + 'component' => 'system/log/index', + 'meta' => [ + 'title' => '日志管理', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-book', + 'sort' => 3, + 'type' => 2, + ], + ], + [ + 'id' => '0704', + 'parentId' => '07', + 'path' => '/system/api', + 'name' => 'api', + 'component' => 'system/api/index', + 'meta' => [ + 'title' => 'API管理', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'icon' => 'icon-tool', + 'sort' => 4, + 'type' => 2, + ], + ], + [ + 'id' => '0705', + 'parentId' => '07', + 'path' => '/system/userinfo', + 'name' => 'userinfo', + 'component' => 'system/userinfo/userinfo', + 'meta' => [ + 'title' => '个人信息', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin', 'common'], + 'icon' => 'icon-user', + 'sort' => 10, + 'type' => 2, + ], + ], + + // 9. 关于项目 + [ + 'id' => '09', + 'parentId' => '0', + 'path' => '/about', + 'name' => 'about', + 'component' => 'about/about', + 'meta' => [ + 'title' => '关于项目', + 'hide' => false, + 'disable' => false, + 'keepAlive' => true, + 'affix' => false, + 'link' => '', + 'iframe' => false, + 'isFull' => false, + 'roles' => ['admin'], + 'svgIcon' => 'about', + 'sort' => 9, + 'type' => 2, + ], + ], +]; diff --git a/config/middleware.php b/config/middleware.php index 4669a1d..8e964ed 100644 --- a/config/middleware.php +++ b/config/middleware.php @@ -1,5 +1,4 @@ [ - app\common\middleware\Cors::class, //跨域中间件 - ], -]; +return []; \ No newline at end of file diff --git a/config/plugin/webman/console/app.php b/config/plugin/webman/console/app.php new file mode 100644 index 0000000..d7bf968 --- /dev/null +++ b/config/plugin/webman/console/app.php @@ -0,0 +1,28 @@ + true, + + 'build_dir' => BASE_PATH . DIRECTORY_SEPARATOR . 'build', + + 'phar_filename' => 'webman.phar', + + 'phar_format' => Phar::PHAR, // Phar archive format: Phar::PHAR, Phar::TAR, Phar::ZIP + + 'phar_compression' => Phar::NONE, // Compression method for Phar archive: Phar::NONE, Phar::GZ, Phar::BZ2 + + 'bin_filename' => 'webman.bin', + + 'signature_algorithm'=> Phar::SHA256, //set the signature algorithm for a phar and apply it. The signature algorithm must be one of Phar::MD5, Phar::SHA1, Phar::SHA256, Phar::SHA512, or Phar::OPENSSL. + + 'private_key_file' => '', // The file path for certificate or OpenSSL private key file. + + 'exclude_pattern' => '#^(?!.*(composer.json|/.github/|/.idea/|/.git/|/.setting/|/runtime/|/vendor-bin/|/build/|/vendor/webman/admin/))(.*)$#', + + 'exclude_files' => [ + '.env', 'LICENSE', 'composer.json', 'composer.lock', 'start.php', 'webman.phar', 'webman.bin' + ], + + 'custom_ini' => ' +memory_limit = 256M + ', +]; diff --git a/config/plugin/webman/event/app.php b/config/plugin/webman/event/app.php new file mode 100644 index 0000000..8f9c426 --- /dev/null +++ b/config/plugin/webman/event/app.php @@ -0,0 +1,4 @@ + true, +]; \ No newline at end of file diff --git a/config/plugin/webman/event/bootstrap.php b/config/plugin/webman/event/bootstrap.php new file mode 100644 index 0000000..e5b09ba --- /dev/null +++ b/config/plugin/webman/event/bootstrap.php @@ -0,0 +1,17 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + Webman\Event\BootStrap::class, +]; diff --git a/config/plugin/webman/event/command.php b/config/plugin/webman/event/command.php new file mode 100644 index 0000000..e860cf7 --- /dev/null +++ b/config/plugin/webman/event/command.php @@ -0,0 +1,7 @@ + true, +]; \ No newline at end of file diff --git a/config/plugin/webman/redis-queue/command.php b/config/plugin/webman/redis-queue/command.php new file mode 100644 index 0000000..8bfe2a1 --- /dev/null +++ b/config/plugin/webman/redis-queue/command.php @@ -0,0 +1,7 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\RotatingFileHandler::class, + 'constructor' => [ + runtime_path() . '/logs/redis-queue/queue.log', + 7, //$maxFiles + Monolog\Logger::DEBUG, + ], + 'formatter' => [ + 'class' => Monolog\Formatter\LineFormatter::class, + 'constructor' => [null, 'Y-m-d H:i:s', true], + ], + ] + ], + ] +]; diff --git a/config/plugin/webman/redis-queue/process.php b/config/plugin/webman/redis-queue/process.php new file mode 100644 index 0000000..07ec9fb --- /dev/null +++ b/config/plugin/webman/redis-queue/process.php @@ -0,0 +1,11 @@ + [ + 'handler' => Webman\RedisQueue\Process\Consumer::class, + 'count' => 8, // 可以设置多进程同时消费 + 'constructor' => [ + // 消费者类目录 + 'consumer_dir' => app_path() . '/jobs' + ] + ] +]; \ No newline at end of file diff --git a/config/plugin/webman/redis-queue/redis.php b/config/plugin/webman/redis-queue/redis.php new file mode 100644 index 0000000..d238b63 --- /dev/null +++ b/config/plugin/webman/redis-queue/redis.php @@ -0,0 +1,21 @@ + [ + 'host' => 'redis://' . env('REDIS_HOST', '127.0.0.1') . ':' . env('REDIS_PORT', 6379), + 'options' => [ + 'auth' => env('REDIS_PASSWORD', ''), + 'db' => env('QUEUE_REDIS_DATABASE', 0), + 'prefix' => env('QUEUE_REDIS_PREFIX', 'ma:queue:'), + 'max_attempts' => 5, + 'retry_seconds' => 5, + ], + // Connection pool, supports only Swoole or Swow drivers. + 'pool' => [ + 'max_connections' => 5, + 'min_connections' => 1, + 'wait_timeout' => 3, + 'idle_timeout' => 60, + 'heartbeat_interval' => 50, + ] + ], +]; diff --git a/config/redis.php b/config/redis.php index 5722090..7994e42 100644 --- a/config/redis.php +++ b/config/redis.php @@ -1,20 +1,41 @@ + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License */ return [ - // 默认Redis连接 'default' => [ - 'host' => getenv('REDIS_HOST') ?? '127.0.0.1', - 'password' => getenv('REDIS_PASSWORD') ?? '', - 'port' => getenv('REDIS_PORT') ?? 6379, - 'database' => getenv('REDIS_DATABASE') ?? 0, + 'password' => env('REDIS_PASSWORD', ''), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'port' => env('REDIS_PORT', 6379), + 'database' => env('REDIS_DATABASE', 0), 'pool' => [ - 'max_connections' => 20, - 'min_connections' => 5, + 'max_connections' => 5, + 'min_connections' => 1, + 'wait_timeout' => 3, + 'idle_timeout' => 60, + 'heartbeat_interval' => 50, + ], + ], + 'cache' => [ + 'password' => env('REDIS_PASSWORD', ''), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'port' => env('REDIS_PORT', 6379), + 'database' => env('CACHE_REDIS_DATABASE', 1), + 'prefix' => 'ma:cache:', + 'pool' => [ + 'max_connections' => 5, + 'min_connections' => 1, 'wait_timeout' => 3, 'idle_timeout' => 60, 'heartbeat_interval' => 50, diff --git a/config/route.php b/config/route.php index 230aba8..ae8fa2b 100644 --- a/config/route.php +++ b/config/route.php @@ -17,26 +17,22 @@ use Webman\Route; use support\Response; use support\Request; +// 匹配所有options路由(CORS 预检请求) +Route::options('[{path:.+}]', function (Request $request){ + $response = response('', 204); + return $response->withHeaders([ + 'Access-Control-Allow-Credentials' => 'true', + 'Access-Control-Allow-Origin' => $request->header('origin', '*'), + 'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'), + 'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'), + ]); +}); + // 管理后台路由 require_once base_path() . '/app/routes/admin.php'; - - -// 默认路由兜底 -Route::fallback(function (Request $request) { - // 处理预检请求 - if (strtoupper($request->method()) === 'OPTIONS') { - $response = response('', 204); - $response->withHeaders([ - 'Access-Control-Allow-Credentials' => 'true', - 'Access-Control-Max-Age' => '86400', - 'Access-Control-Allow-Origin' => $request->header('origin', '*'), - 'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'), - 'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'), - ]); - return $response; - } -}); +// API 路由 +require_once base_path() . '/app/routes/api.php'; /** * 关闭默认路由 diff --git a/config/static.php b/config/static.php index 40f7da8..6313679 100644 --- a/config/static.php +++ b/config/static.php @@ -1,5 +1,4 @@ true, 'middleware' => [ // Static file Middleware - app\common\middleware\StaticFile::class, + //app\middleware\StaticFile::class, ], -]; +]; \ No newline at end of file diff --git a/doc/异常处理.txt b/doc/异常处理.txt deleted file mode 100644 index af5a1fe..0000000 --- a/doc/异常处理.txt +++ /dev/null @@ -1,114 +0,0 @@ -异常处理 -配置 -config/exception.php - -return [ - // 这里配置异常处理类 - '' => support\exception\Handler::class, -]; -多应用模式时,你可以为每个应用单独配置异常处理类,参见多应用 - -默认异常处理类 -webman中异常默认由 support\exception\Handler 类来处理。可修改配置文件config/exception.php来更改默认异常处理类。异常处理类必须实现Webman\Exception\ExceptionHandlerInterface 接口。 - -interface ExceptionHandlerInterface -{ - /** - * 记录日志 - * @param Throwable $e - * @return mixed - */ - public function report(Throwable $e); - - /** - * 渲染返回 - * @param Request $request - * @param Throwable $e - * @return Response - */ - public function render(Request $request, Throwable $e) : Response; -} -渲染响应 -异常处理类中的render方法是用来渲染响应的。 - -如果配置文件config/app.php中debug值为true(以下简称app.debug=true),将返回详细的异常信息,否则将返回简略的异常信息。 - -如果请求期待是json返回,则返回的异常信息将以json格式返回,类似 - -{ - "code": "500", - "msg": "异常信息" -} -如果app.debug=true,json数据里会额外增加一个trace字段返回详细的调用栈。 - -你可以编写自己的异常处理类来更改默认异常处理逻辑。 - -业务异常 BusinessException -有时候我们想在某个嵌套函数里终止请求并返回一个错误信息给客户端,这时可以通过抛出BusinessException来做到这点。 -例如: - -chackInpout($request->post()); - return response('hello index'); - } - - protected function chackInpout($input) - { - if (!isset($input['token'])) { - throw new BusinessException('参数错误', 3000); - } - } -} -以上示例会返回一个 - -{"code": 3000, "msg": "参数错误"} -注意 -业务异常BusinessException不需要业务try捕获,框架会自动捕获并根据请求类型返回合适的输出。 - -自定义业务异常 -如果以上响应不符合你的需求,例如想把msg要改为message,可以自定义一个MyBusinessException - -新建 app/exception/MyBusinessException.php 内容如下 - -expectsJson()) { - return json(['code' => $this->getCode() ?: 500, 'message' => $this->getMessage()]); - } - // 非json请求则返回一个页面 - return new Response(200, [], $this->getMessage()); - } -} -这样当业务调用 - -use app\exception\MyBusinessException; - -throw new MyBusinessException('参数错误', 3000); -json请求将收到一个类似如下的json返回 - -{"code": 3000, "message": "参数错误"} -提示 -因为BusinessException异常属于业务异常(例如用户输入参数错误),它是可预知的,所以框架并不会认为它是致命错误,并不会记录日志。 - -总结 -在任何想中断当前请求并返回信息给客户端的时候可以考虑使用BusinessException异常。 \ No newline at end of file diff --git a/support/exception/Handler.php b/support/exception/Handler.php deleted file mode 100644 index 54f155f..0000000 --- a/support/exception/Handler.php +++ /dev/null @@ -1,51 +0,0 @@ -render($request); - if ($response instanceof Response) { - return $response; - } - } - } - - // 其他异常使用父类默认处理(debug=true 时带 trace,字段沿用 webman 默认) - return parent::render($request, $exception); - } -} - diff --git a/support/functions.php b/support/functions.php deleted file mode 100644 index 5c9c58d..0000000 --- a/support/functions.php +++ /dev/null @@ -1,4 +0,0 @@ -