diff --git a/README.md b/README.md deleted file mode 100644 index 4031784..0000000 --- a/README.md +++ /dev/null @@ -1,70 +0,0 @@ -
-

webman

- -基于workerman开发的超高性能PHP框架 - - -

学习

- - - -
- -

赞助商

- -

特别赞助

- - - - -

铂金赞助

- - - - -
- - -
- -

请作者喝咖啡

- - - -
-如果您觉得webman对您有所帮助,欢迎捐赠。 - - -
- - -
-

LICENSE

-The webman is open-sourced software licensed under the MIT. -
- -
- - diff --git a/app/common/base/BaseController.php b/app/common/base/BaseController.php new file mode 100644 index 0000000..14b2716 --- /dev/null +++ b/app/common/base/BaseController.php @@ -0,0 +1,42 @@ +request->* 获取请求数据 + * - 为避免每个控制器构造函数重复注入 Request,本类通过 __get('request') 返回当前请求对象 + */ +abstract class BaseController +{ + + /** + * 成功响应 + */ + protected function success(mixed $data = null, string $message = 'success', int $code = 200): Response + { + return json([ + 'code' => $code, + 'message' => $message, + 'data' => $data, + ]); + } + + /** + * 失败响应 + */ + protected function fail(string $message = 'fail', int $code = 500, mixed $data = null): Response + { + return json([ + 'code' => $code, + 'message' => $message, + 'data' => $data, + ]); + } +} diff --git a/app/common/base/BaseDao.php b/app/common/base/BaseDao.php new file mode 100644 index 0000000..1abdb68 --- /dev/null +++ b/app/common/base/BaseDao.php @@ -0,0 +1,164 @@ +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 new file mode 100644 index 0000000..9d4a17d --- /dev/null +++ b/app/common/base/BaseModel.php @@ -0,0 +1,101 @@ +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 new file mode 100644 index 0000000..ee661e1 --- /dev/null +++ b/app/common/base/BaseRepository.php @@ -0,0 +1,58 @@ +dao = $dao; + } + + /** + * 魔术方法:代理 DAO 的方法调用 + * 如果仓库自身没有该方法,且存在 DAO 实例,则调用 DAO 的对应方法 + */ + public function __call(string $method, array $arguments) + { + if ($this->dao && method_exists($this->dao, $method)) { + return $this->dao->{$method}(...$arguments); + } + + 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; + } +} + + diff --git a/app/common/base/BaseService.php b/app/common/base/BaseService.php new file mode 100644 index 0000000..5be496e --- /dev/null +++ b/app/common/base/BaseService.php @@ -0,0 +1,56 @@ +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); + } +} + + diff --git a/app/common/middleware/Cors.php b/app/common/middleware/Cors.php new file mode 100644 index 0000000..320f4bc --- /dev/null +++ b/app/common/middleware/Cors.php @@ -0,0 +1,34 @@ +method()) === 'OPTIONS' ? response('', 204) : $handler($request); + + $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/middleware/StaticFile.php b/app/common/middleware/StaticFile.php similarity index 97% rename from app/middleware/StaticFile.php rename to app/common/middleware/StaticFile.php index fa8dbf7..c9605bd 100644 --- a/app/middleware/StaticFile.php +++ b/app/common/middleware/StaticFile.php @@ -12,7 +12,7 @@ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ -namespace app\middleware; +namespace app\common\middleware; use Webman\MiddlewareInterface; use Webman\Http\Response; diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php deleted file mode 100644 index b9b0da7..0000000 --- a/app/controller/IndexController.php +++ /dev/null @@ -1,42 +0,0 @@ - - * { - padding: 0; - margin: 0; - } - iframe { - border: none; - overflow: scroll; - } - - -EOF; - } - - public function view(Request $request) - { - return view('index/view', ['name' => 'webman']); - } - - public function json(Request $request) - { - return json(['code' => 0, 'msg' => 'ok']); - } - -} diff --git a/app/exceptions/AuthFailedException.php b/app/exceptions/AuthFailedException.php new file mode 100644 index 0000000..1d49c13 --- /dev/null +++ b/app/exceptions/AuthFailedException.php @@ -0,0 +1,16 @@ +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 new file mode 100644 index 0000000..1a00f84 --- /dev/null +++ b/app/exceptions/ForbiddenException.php @@ -0,0 +1,16 @@ +post('username', ''); + $password = (string)$request->post('password', ''); + // 前端有本地验证码,这里暂不做服务端校验,仅预留字段 + $verifyCode = $request->post('verifyCode'); + + if ($username === '' || $password === '') { + 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); + } +} diff --git a/app/model/Test.php b/app/model/Test.php deleted file mode 100644 index 92d70e3..0000000 --- a/app/model/Test.php +++ /dev/null @@ -1,29 +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/routes/admin.php b/app/routes/admin.php new file mode 100644 index 0000000..6ca4915 --- /dev/null +++ b/app/routes/admin.php @@ -0,0 +1,14 @@ +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/services/auth/JwtService.php b/app/services/auth/JwtService.php new file mode 100644 index 0000000..e7c2fb2 --- /dev/null +++ b/app/services/auth/JwtService.php @@ -0,0 +1,52 @@ + $now, + 'exp' => $now + $ttl, + ]); + + return JWT::encode($payload, $secret, $alg); + } + + /** + * 解析 JWT + */ + public static function parseToken(string $token): array + { + $config = config('jwt', []); + $secret = $config['secret'] ?? 'mpay-secret'; + $alg = $config['alg'] ?? 'HS256'; + + $decoded = JWT::decode($token, new Key($secret, $alg)); + return json_decode(json_encode($decoded, JSON_UNESCAPED_UNICODE), true) ?: []; + } + + /** + * 获取 ttl(秒) + */ + public static function getTtl(): int + { + $config = config('jwt', []); + return (int)($config['ttl'] ?? 7200); + } +} + + diff --git a/composer.json b/composer.json index ad7370d..0a372d2 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,7 @@ "guzzlehttp/guzzle": "^7.0", "ramsey/uuid": "^4.0", "nesbot/carbon": "^3.0", - "webman/admin": "~2.0" + "webman/database": "^2.1" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index c389fd2..0e5426f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "39121444a6b84a7e26f3813db45c8cb1", + "content-hash": "abca1c99f7511639d3548201fd459bd5", "packages": [ { "name": "brick/math", @@ -1384,56 +1384,6 @@ }, "time": "2024-06-28T20:10:30+00:00" }, - { - "name": "illuminate/pagination", - "version": "v11.47.0", - "source": { - "type": "git", - "url": "https://github.com/illuminate/pagination.git", - "reference": "2c4970463f7ede55be207fbdc27608b4c4178380" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/illuminate/pagination/zipball/2c4970463f7ede55be207fbdc27608b4c4178380", - "reference": "2c4970463f7ede55be207fbdc27608b4c4178380", - "shasum": "" - }, - "require": { - "ext-filter": "*", - "illuminate/collections": "^11.0", - "illuminate/contracts": "^11.0", - "illuminate/support": "^11.0", - "php": "^8.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "11.x-dev" - } - }, - "autoload": { - "psr-4": { - "Illuminate\\Pagination\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "The Illuminate Pagination package.", - "homepage": "https://laravel.com", - "support": { - "issues": "https://github.com/laravel/framework/issues", - "source": "https://github.com/laravel/framework" - }, - "time": "2025-02-14T15:33:00+00:00" - }, { "name": "illuminate/pipeline", "version": "v11.47.0", @@ -1670,90 +1620,6 @@ }, "time": "2025-11-27T16:16:32+00:00" }, - { - "name": "intervention/image", - "version": "2.7.2", - "source": { - "type": "git", - "url": "https://github.com/Intervention/image.git", - "reference": "04be355f8d6734c826045d02a1079ad658322dad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/04be355f8d6734c826045d02a1079ad658322dad", - "reference": "04be355f8d6734c826045d02a1079ad658322dad", - "shasum": "" - }, - "require": { - "ext-fileinfo": "*", - "guzzlehttp/psr7": "~1.1 || ^2.0", - "php": ">=5.4.0" - }, - "require-dev": { - "mockery/mockery": "~0.9.2", - "phpunit/phpunit": "^4.8 || ^5.7 || ^7.5.15" - }, - "suggest": { - "ext-gd": "to use GD library based image processing.", - "ext-imagick": "to use Imagick based image processing.", - "intervention/imagecache": "Caching extension for the Intervention Image library" - }, - "type": "library", - "extra": { - "laravel": { - "aliases": { - "Image": "Intervention\\Image\\Facades\\Image" - }, - "providers": [ - "Intervention\\Image\\ImageServiceProvider" - ] - }, - "branch-alias": { - "dev-master": "2.4-dev" - } - }, - "autoload": { - "psr-4": { - "Intervention\\Image\\": "src/Intervention/Image" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Oliver Vogel", - "email": "oliver@intervention.io", - "homepage": "https://intervention.io/" - } - ], - "description": "Image handling and manipulation library with support for Laravel integration", - "homepage": "http://image.intervention.io/", - "keywords": [ - "gd", - "image", - "imagick", - "laravel", - "thumbnail", - "watermark" - ], - "support": { - "issues": "https://github.com/Intervention/image/issues", - "source": "https://github.com/Intervention/image/tree/2.7.2" - }, - "funding": [ - { - "url": "https://paypal.me/interventionio", - "type": "custom" - }, - { - "url": "https://github.com/Intervention", - "type": "github" - } - ], - "time": "2022-05-21T17:30:32+00:00" - }, { "name": "laravel/serializable-closure", "version": "v1.3.7", @@ -3359,16 +3225,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2" + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/48be2b0653594eea32dcef130cca1c811dcf25c2", - "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8da531f364ddfee53e36092a7eebbbd0b775f6b8", + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8", "shasum": "" }, "require": { @@ -3417,7 +3283,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.4.0" + "source": "https://github.com/symfony/error-handler/tree/v7.4.4" }, "funding": [ { @@ -3437,20 +3303,20 @@ "type": "tidelift" } ], - "time": "2025-11-05T14:29:59+00:00" + "time": "2026-01-20T16:42:42+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d" + "reference": "dc2c0eba1af673e736bb851d747d266108aea746" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9dddcddff1ef974ad87b3708e4b442dc38b2261d", - "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dc2c0eba1af673e736bb851d747d266108aea746", + "reference": "dc2c0eba1af673e736bb851d747d266108aea746", "shasum": "" }, "require": { @@ -3502,7 +3368,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.4" }, "funding": [ { @@ -3522,7 +3388,7 @@ "type": "tidelift" } ], - "time": "2025-10-28T09:38:46+00:00" + "time": "2026-01-05T11:45:34+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -3602,16 +3468,16 @@ }, { "name": "symfony/finder", - "version": "v7.4.3", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "fffe05569336549b20a1be64250b40516d6e8d06" + "reference": "01b24a145bbeaa7141e75887ec904c34a6728a5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/fffe05569336549b20a1be64250b40516d6e8d06", - "reference": "fffe05569336549b20a1be64250b40516d6e8d06", + "url": "https://api.github.com/repos/symfony/finder/zipball/01b24a145bbeaa7141e75887ec904c34a6728a5f", + "reference": "01b24a145bbeaa7141e75887ec904c34a6728a5f", "shasum": "" }, "require": { @@ -3646,7 +3512,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.3" + "source": "https://github.com/symfony/finder/tree/v7.4.4" }, "funding": [ { @@ -3666,20 +3532,20 @@ "type": "tidelift" } ], - "time": "2025-12-23T14:50:43+00:00" + "time": "2026-01-12T12:19:02+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.3", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "a70c745d4cea48dbd609f4075e5f5cbce453bd52" + "reference": "977a554a34cf8edc95ca351fbecb1bb1ad05cc94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a70c745d4cea48dbd609f4075e5f5cbce453bd52", - "reference": "a70c745d4cea48dbd609f4075e5f5cbce453bd52", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/977a554a34cf8edc95ca351fbecb1bb1ad05cc94", + "reference": "977a554a34cf8edc95ca351fbecb1bb1ad05cc94", "shasum": "" }, "require": { @@ -3728,7 +3594,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.3" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.4" }, "funding": [ { @@ -3748,20 +3614,20 @@ "type": "tidelift" } ], - "time": "2025-12-23T14:23:49+00:00" + "time": "2026-01-09T12:14:21+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.3", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "885211d4bed3f857b8c964011923528a55702aa5" + "reference": "48b067768859f7b68acf41dfb857a5a4be00acdd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/885211d4bed3f857b8c964011923528a55702aa5", - "reference": "885211d4bed3f857b8c964011923528a55702aa5", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/48b067768859f7b68acf41dfb857a5a4be00acdd", + "reference": "48b067768859f7b68acf41dfb857a5a4be00acdd", "shasum": "" }, "require": { @@ -3847,7 +3713,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.3" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.4" }, "funding": [ { @@ -3867,20 +3733,20 @@ "type": "tidelift" } ], - "time": "2025-12-31T08:43:57+00:00" + "time": "2026-01-24T22:13:01+00:00" }, { "name": "symfony/mime", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a" + "reference": "40945014c0a9471ccfe19673c54738fa19367a3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/bdb02729471be5d047a3ac4a69068748f1a6be7a", - "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a", + "url": "https://api.github.com/repos/symfony/mime/zipball/40945014c0a9471ccfe19673c54738fa19367a3c", + "reference": "40945014c0a9471ccfe19673c54738fa19367a3c", "shasum": "" }, "require": { @@ -3936,7 +3802,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.0" + "source": "https://github.com/symfony/mime/tree/v7.4.4" }, "funding": [ { @@ -3956,7 +3822,7 @@ "type": "tidelift" } ], - "time": "2025-11-16T10:14:42+00:00" + "time": "2026-01-08T16:12:55+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4713,16 +4579,16 @@ }, { "name": "symfony/string", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003" + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d50e862cb0a0e0886f73ca1f31b865efbb795003", - "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003", + "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", "shasum": "" }, "require": { @@ -4780,7 +4646,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.0" + "source": "https://github.com/symfony/string/tree/v7.4.4" }, "funding": [ { @@ -4800,20 +4666,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-01-12T10:54:30+00:00" }, { "name": "symfony/translation", - "version": "v7.4.3", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "7ef27c65d78886f7599fdd5c93d12c9243ecf44d" + "reference": "bfde13711f53f549e73b06d27b35a55207528877" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/7ef27c65d78886f7599fdd5c93d12c9243ecf44d", - "reference": "7ef27c65d78886f7599fdd5c93d12c9243ecf44d", + "url": "https://api.github.com/repos/symfony/translation/zipball/bfde13711f53f549e73b06d27b35a55207528877", + "reference": "bfde13711f53f549e73b06d27b35a55207528877", "shasum": "" }, "require": { @@ -4880,7 +4746,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.3" + "source": "https://github.com/symfony/translation/tree/v7.4.4" }, "funding": [ { @@ -4900,7 +4766,7 @@ "type": "tidelift" } ], - "time": "2025-12-29T09:31:36+00:00" + "time": "2026-01-13T10:40:19+00:00" }, { "name": "symfony/translation-contracts", @@ -4986,16 +4852,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.4.3", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "7e99bebcb3f90d8721890f2963463280848cba92" + "reference": "0e4769b46a0c3c62390d124635ce59f66874b282" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7e99bebcb3f90d8721890f2963463280848cba92", - "reference": "7e99bebcb3f90d8721890f2963463280848cba92", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0e4769b46a0c3c62390d124635ce59f66874b282", + "reference": "0e4769b46a0c3c62390d124635ce59f66874b282", "shasum": "" }, "require": { @@ -5049,7 +4915,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.3" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.4" }, "funding": [ { @@ -5069,7 +4935,7 @@ "type": "tidelift" } ], - "time": "2025-12-18T07:04:31+00:00" + "time": "2026-01-01T22:13:48+00:00" }, { "name": "symfony/var-exporter", @@ -5446,49 +5312,6 @@ ], "time": "2024-11-21T01:49:47+00:00" }, - { - "name": "webman/admin", - "version": "v2.1.7", - "source": { - "type": "git", - "url": "https://github.com/webman-php/admin.git", - "reference": "44a6158fbac2475edca92d3fc0108221a93373b5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webman-php/admin/zipball/44a6158fbac2475edca92d3fc0108221a93373b5", - "reference": "44a6158fbac2475edca92d3fc0108221a93373b5", - "shasum": "" - }, - "require": { - "ext-json": "*", - "guzzlehttp/guzzle": "^7.5", - "illuminate/events": ">=7.30", - "illuminate/pagination": ">=7.30", - "intervention/image": "^2.7", - "laravel/serializable-closure": "^1.0", - "webman/captcha": "^1.0.0", - "webman/database": ">=2.1", - "webman/event": "^1.0", - "workerman/webman-framework": ">=2.1" - }, - "type": "project", - "autoload": { - "psr-4": { - "Webman\\Admin\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Webman Admin", - "support": { - "issues": "https://github.com/webman-php/admin/issues", - "source": "https://github.com/webman-php/admin/tree/v2.1.7" - }, - "time": "2025-05-07T14:58:24+00:00" - }, { "name": "webman/cache", "version": "v2.1.3", diff --git a/config/app.php b/config/app.php index f26e358..5398819 100644 --- a/config/app.php +++ b/config/app.php @@ -15,7 +15,7 @@ use support\Request; return [ - 'debug' => true, + 'debug' => getenv('APP_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 69a8135..5c76d03 100644 --- a/config/autoload.php +++ b/config/autoload.php @@ -14,7 +14,7 @@ return [ 'files' => [ - base_path() . '/app/functions.php', + base_path() . '/support/functions.php', base_path() . '/support/Request.php', base_path() . '/support/Response.php', ] diff --git a/config/cache.php b/config/cache.php index 74775dc..499a785 100644 --- a/config/cache.php +++ b/config/cache.php @@ -14,7 +14,7 @@ */ return [ - 'default' => 'file', + 'default' => getenv('CACHE_DRIVER') ?? 'file', 'stores' => [ 'file' => [ 'driver' => 'file', diff --git a/config/database.php b/config/database.php index 429e599..a790446 100644 --- a/config/database.php +++ b/config/database.php @@ -1,84 +1,25 @@ env('DB_CONNECTION', 'mysql'), - - // 数据库连接配置 +return [ + 'default' => getenv('DB_CONNECTION') ?? 'mysql', 'connections' => [ - // 主数据库配置 'mysql' => [ - 'driver' => 'mysql', - 'host' => env('DB_HOST', '192.168.31.200'), - 'port' => env('DB_PORT', 3306), - 'database' => env('DB_DATABASE', 'mpay_v2'), - 'username' => env('DB_USERNAME', 'mpay_v2'), - 'password' => env('DB_PASSWORD', 'pXfNWELALrwAAt88'), - 'unix_socket' => '', - 'charset' => 'utf8mb4', - 'collation' => 'utf8mb4_unicode_ci', - 'prefix' => env('DB_PREFIX', ''), - 'strict' => true, - 'engine' => 'InnoDB', - 'pool' => [ - 'max_connections' => env('DB_POOL_MAX_CONNECTIONS', 20), - 'min_connections' => env('DB_POOL_MIN_CONNECTIONS', 3), - 'wait_timeout' => env('DB_POOL_WAIT_TIMEOUT', 3), - 'idle_timeout' => env('DB_POOL_IDLE_TIMEOUT', 60), - 'heartbeat_interval' => env('DB_POOL_HEARTBEAT_INTERVAL', 50), + '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') ?? '', + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_general_ci', + 'prefix' => getenv('DB_PREFIX') ?? '', + 'strict' => true, + 'engine' => null, + 'options' => [ + PDO::ATTR_EMULATE_PREPARES => false, // Must be false for Swoole and Swow drivers. ], - ], - - // 读库配置(主从分离) - 'mysql_read' => [ - 'driver' => 'mysql', - 'host' => env('DB_READ_HOST', env('DB_HOST', '192.168.31.200')), - 'port' => env('DB_READ_PORT', env('DB_PORT', 3306)), - 'database' => env('DB_READ_DATABASE', env('DB_DATABASE', 'mpay_v2')), - 'username' => env('DB_READ_USERNAME', env('DB_USERNAME', 'mpay_v2')), - 'password' => env('DB_READ_PASSWORD', env('DB_PASSWORD', 'pXfNWELALrwAAt88')), - 'unix_socket' => '', - 'charset' => 'utf8mb4', - 'collation' => 'utf8mb4_unicode_ci', - 'prefix' => env('DB_PREFIX', ''), - 'strict' => true, - 'engine' => 'InnoDB', 'pool' => [ - 'max_connections' => 15, - 'min_connections' => 2, - 'wait_timeout' => 3, - 'idle_timeout' => 60, - 'heartbeat_interval' => 50, - ], - ], - - // 写库配置(主从分离) - 'mysql_write' => [ - 'driver' => 'mysql', - 'host' => env('DB_WRITE_HOST', env('DB_HOST', '192.168.31.200')), - 'port' => env('DB_WRITE_PORT', env('DB_PORT', 3306)), - 'database' => env('DB_WRITE_DATABASE', env('DB_DATABASE', 'mpay_v2')), - 'username' => env('DB_WRITE_USERNAME', env('DB_USERNAME', 'mpay_v2')), - 'password' => env('DB_WRITE_PASSWORD', env('DB_PASSWORD', 'pXfNWELALrwAAt88')), - 'unix_socket' => '', - 'charset' => 'utf8mb4', - 'collation' => 'utf8mb4_unicode_ci', - 'prefix' => env('DB_PREFIX', ''), - 'strict' => true, - 'engine' => 'InnoDB', - 'pool' => [ - 'max_connections' => 10, - 'min_connections' => 2, + 'max_connections' => 5, + 'min_connections' => 1, 'wait_timeout' => 3, 'idle_timeout' => 60, 'heartbeat_interval' => 50, diff --git a/config/jwt.php b/config/jwt.php new file mode 100644 index 0000000..bcb2906 --- /dev/null +++ b/config/jwt.php @@ -0,0 +1,12 @@ + getenv('JWT_SECRET') ?: 'mpay-secret', + // 过期时间(秒) + 'ttl' => (int)(getenv('JWT_TTL') ?: 7200), + // 签名算法 + 'alg' => getenv('JWT_ALG') ?: 'HS256', +]; + + diff --git a/config/middleware.php b/config/middleware.php index 8e964ed..4669a1d 100644 --- a/config/middleware.php +++ b/config/middleware.php @@ -1,4 +1,5 @@ [ + app\common\middleware\Cors::class, //跨域中间件 + ], +]; diff --git a/config/redis.php b/config/redis.php index 6b25c69..5722090 100644 --- a/config/redis.php +++ b/config/redis.php @@ -1,4 +1,5 @@ [ - 'host' => env('REDIS_HOST', '127.0.0.1'), - 'password' => env('REDIS_PASSWORD', ''), - 'port' => env('REDIS_PORT', 6379), - 'database' => env('REDIS_DATABASE', 0), + 'host' => getenv('REDIS_HOST') ?? '127.0.0.1', + 'password' => getenv('REDIS_PASSWORD') ?? '', + 'port' => getenv('REDIS_PORT') ?? 6379, + 'database' => getenv('REDIS_DATABASE') ?? 0, 'pool' => [ - 'max_connections' => env('REDIS_POOL_MAX_CONNECTIONS', 20), - 'min_connections' => env('REDIS_POOL_MIN_CONNECTIONS', 5), - 'wait_timeout' => env('REDIS_POOL_WAIT_TIMEOUT', 3), - 'idle_timeout' => env('REDIS_POOL_IDLE_TIMEOUT', 60), - 'heartbeat_interval' => env('REDIS_POOL_HEARTBEAT_INTERVAL', 50), - ], - 'options' => [ - 'prefix' => env('CACHE_PREFIX', 'mpay_v2_'), - ], - ], - - // 缓存专用Redis连接 - 'cache' => [ - 'host' => env('REDIS_CACHE_HOST', env('REDIS_HOST', '127.0.0.1')), - 'password' => env('REDIS_CACHE_PASSWORD', env('REDIS_PASSWORD', '')), - 'port' => env('REDIS_CACHE_PORT', env('REDIS_PORT', 6379)), - 'database' => env('REDIS_CACHE_DATABASE', 1), - 'pool' => [ - 'max_connections' => 15, - 'min_connections' => 3, + 'max_connections' => 20, + 'min_connections' => 5, 'wait_timeout' => 3, 'idle_timeout' => 60, 'heartbeat_interval' => 50, ], - 'options' => [ - 'prefix' => env('CACHE_PREFIX', 'mpay_v2_cache_'), - ], - ], - - // 队列专用Redis连接 - 'queue' => [ - 'host' => env('REDIS_QUEUE_HOST', env('REDIS_HOST', '127.0.0.1')), - 'password' => env('REDIS_QUEUE_PASSWORD', env('REDIS_PASSWORD', '')), - 'port' => env('REDIS_QUEUE_PORT', env('REDIS_PORT', 6379)), - 'database' => env('REDIS_QUEUE_DATABASE', 2), - 'pool' => [ - 'max_connections' => 10, - 'min_connections' => 2, - 'wait_timeout' => 3, - 'idle_timeout' => 60, - 'heartbeat_interval' => 50, - ], - 'options' => [ - 'prefix' => env('QUEUE_PREFIX', 'mpay_v2_queue_'), - ], - ], - - // 会话存储Redis连接 - 'session' => [ - 'host' => env('REDIS_SESSION_HOST', env('REDIS_HOST', '127.0.0.1')), - 'password' => env('REDIS_SESSION_PASSWORD', env('REDIS_PASSWORD', '')), - 'port' => env('REDIS_SESSION_PORT', env('REDIS_PORT', 6379)), - 'database' => env('REDIS_SESSION_DATABASE', 3), - 'pool' => [ - 'max_connections' => 10, - 'min_connections' => 2, - 'wait_timeout' => 3, - 'idle_timeout' => 60, - 'heartbeat_interval' => 50, - ], - 'options' => [ - 'prefix' => 'mpay_v2_session_', - ], - ], - - // JWT黑名单Redis连接 - 'jwt_blacklist' => [ - 'host' => env('REDIS_JWT_HOST', env('REDIS_HOST', '127.0.0.1')), - 'password' => env('REDIS_JWT_PASSWORD', env('REDIS_PASSWORD', '')), - 'port' => env('REDIS_JWT_PORT', env('REDIS_PORT', 6379)), - 'database' => env('REDIS_JWT_DATABASE', 4), - 'pool' => [ - 'max_connections' => 5, - 'min_connections' => 1, - 'wait_timeout' => 3, - 'idle_timeout' => 60, - 'heartbeat_interval' => 50, - ], - 'options' => [ - 'prefix' => 'mpay_v2_jwt_blacklist_', - ], ], ]; diff --git a/config/route.php b/config/route.php index a5064fc..230aba8 100644 --- a/config/route.php +++ b/config/route.php @@ -1,4 +1,5 @@ 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; + } +}); - - +/** + * 关闭默认路由 + */ +Route::disableDefaultRoute(); diff --git a/config/static.php b/config/static.php index 6313679..40f7da8 100644 --- a/config/static.php +++ b/config/static.php @@ -1,4 +1,5 @@ true, 'middleware' => [ // Static file Middleware - //app\middleware\StaticFile::class, + app\common\middleware\StaticFile::class, ], -]; \ No newline at end of file +]; diff --git a/doc/异常处理.txt b/doc/异常处理.txt new file mode 100644 index 0000000..af5a1fe --- /dev/null +++ b/doc/异常处理.txt @@ -0,0 +1,114 @@ +异常处理 +配置 +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 new file mode 100644 index 0000000..54f155f --- /dev/null +++ b/support/exception/Handler.php @@ -0,0 +1,51 @@ +render($request); + if ($response instanceof Response) { + return $response; + } + } + } + + // 其他异常使用父类默认处理(debug=true 时带 trace,字段沿用 webman 默认) + return parent::render($request, $exception); + } +} + diff --git a/app/functions.php b/support/functions.php similarity index 100% rename from app/functions.php rename to support/functions.php